mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
better version
This commit is contained in:
parent
fc82d5fe05
commit
57e1cac929
3 changed files with 29 additions and 68 deletions
|
|
@ -28,7 +28,7 @@ struct ContentView: View {
|
||||||
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
|
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
|
||||||
@ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared
|
@ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared
|
||||||
@ObservedObject var lockScreenManager = LockScreenManager.shared
|
@ObservedObject var lockScreenManager = LockScreenManager.shared
|
||||||
@ObservedObject var downloadManager = DownloadManager.shared
|
@State private var downloadManager = DownloadManager.shared
|
||||||
|
|
||||||
@Default(.enableStatsFeature) var enableStatsFeature
|
@Default(.enableStatsFeature) var enableStatsFeature
|
||||||
@Default(.showCpuGraph) var showCpuGraph
|
@Default(.showCpuGraph) var showCpuGraph
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import Defaults
|
||||||
|
|
||||||
struct DownloadLiveActivity: View {
|
struct DownloadLiveActivity: View {
|
||||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
@ObservedObject var downloadManager = DownloadManager.shared
|
@State private var downloadManager = DownloadManager.shared
|
||||||
|
|
||||||
@State private var isHovering: Bool = false
|
@State private var isHovering: Bool = false
|
||||||
@State private var gestureProgress: CGFloat = 0
|
@State private var gestureProgress: CGFloat = 0
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Defaults
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Observation
|
||||||
|
import Defaults
|
||||||
|
|
||||||
|
@Observable
|
||||||
@MainActor
|
@MainActor
|
||||||
class DownloadManager: ObservableObject {
|
class DownloadManager {
|
||||||
static let shared = DownloadManager()
|
static let shared = DownloadManager()
|
||||||
|
|
||||||
@Published private(set) var isDownloading: Bool = false
|
private(set) var isDownloading: Bool = false
|
||||||
@Published private(set) var isDownloadCompleted: Bool = false
|
private(set) var isDownloadCompleted: Bool = false
|
||||||
|
|
||||||
private let coordinator = DynamicIslandViewCoordinator.shared
|
private let coordinator = DynamicIslandViewCoordinator.shared
|
||||||
private var source: DispatchSourceFileSystemObject?
|
private var source: DispatchSourceFileSystemObject?
|
||||||
|
|
@ -16,19 +18,18 @@ class DownloadManager: ObservableObject {
|
||||||
private var hasPerformedInitialScan: Bool = false
|
private var hasPerformedInitialScan: Bool = false
|
||||||
private var initialCrDownloadFiles: Set<String> = []
|
private var initialCrDownloadFiles: Set<String> = []
|
||||||
private var previousAllFiles: Set<String> = []
|
private var previousAllFiles: Set<String> = []
|
||||||
|
|
||||||
private var ignoredFiles: Set<String> = []
|
private var ignoredFiles: Set<String> = []
|
||||||
|
|
||||||
private var downloadsDirectory: URL? {
|
private var downloadsDirectory: URL? {
|
||||||
FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
|
FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
init() {
|
||||||
requestDownloadsPermissionIfNeeded()
|
requestDownloadsPermissionIfNeeded()
|
||||||
startMonitoringIfNeeded()
|
startMonitoringIfNeeded()
|
||||||
|
|
||||||
Defaults.publisher(.enableDownloadListener)
|
Defaults.publisher(.enableDownloadListener)
|
||||||
.sink { [weak self] change in
|
.sink { [weak self] _ in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self.startMonitoringIfNeeded()
|
self.startMonitoringIfNeeded()
|
||||||
|
|
@ -56,10 +57,7 @@ class DownloadManager: ObservableObject {
|
||||||
|
|
||||||
let path = downloadsDirectory.path
|
let path = downloadsDirectory.path
|
||||||
let fd = open(path, O_EVTONLY)
|
let fd = open(path, O_EVTONLY)
|
||||||
guard fd >= 0 else {
|
guard fd >= 0 else { return }
|
||||||
print("DownloadManager: Failed to open Downloads directory at \(path)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let src = DispatchSource.makeFileSystemObjectSource(
|
let src = DispatchSource.makeFileSystemObjectSource(
|
||||||
fileDescriptor: fd,
|
fileDescriptor: fd,
|
||||||
|
|
@ -78,21 +76,17 @@ class DownloadManager: ObservableObject {
|
||||||
source = src
|
source = src
|
||||||
src.resume()
|
src.resume()
|
||||||
|
|
||||||
// Initial scan
|
|
||||||
scanDownloadsDirectory()
|
scanDownloadsDirectory()
|
||||||
|
|
||||||
print("DownloadManager: Started monitoring Downloads folder at \(path)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopMonitoring() {
|
private func stopMonitoring() {
|
||||||
source?.cancel()
|
source?.cancel()
|
||||||
source = nil
|
source = nil
|
||||||
|
|
||||||
hasPerformedInitialScan = false
|
hasPerformedInitialScan = false
|
||||||
initialCrDownloadFiles.removeAll()
|
initialCrDownloadFiles.removeAll()
|
||||||
previousAllFiles.removeAll()
|
|
||||||
ignoredFiles.removeAll()
|
ignoredFiles.removeAll()
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
print("DownloadManager: Stopped monitoring Downloads folder")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scanDownloadsDirectory() {
|
private func scanDownloadsDirectory() {
|
||||||
|
|
@ -100,24 +94,25 @@ class DownloadManager: ObservableObject {
|
||||||
|
|
||||||
let crDownloadFiles: Set<String>
|
let crDownloadFiles: Set<String>
|
||||||
let allFiles: Set<String>
|
let allFiles: Set<String>
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let contents = try FileManager.default.contentsOfDirectory(
|
let contents = try FileManager.default.contentsOfDirectory(
|
||||||
at: downloadsDirectory,
|
at: downloadsDirectory,
|
||||||
includingPropertiesForKeys: [.creationDateKey],
|
includingPropertiesForKeys: [.creationDateKey]
|
||||||
options: [.skipsHiddenFiles]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
crDownloadFiles = Set(contents
|
crDownloadFiles = Set(contents
|
||||||
.filter {
|
.filter {
|
||||||
let ext = $0.pathExtension.lowercased()
|
let ext = $0.pathExtension.lowercased()
|
||||||
return ext == "crdownload" || ext == "download"
|
return ext == "crdownload" || ext == "download"
|
||||||
}
|
}
|
||||||
.map { $0.lastPathComponent }
|
.map { $0.lastPathComponent }
|
||||||
)
|
)
|
||||||
|
|
||||||
allFiles = Set(contents.map { $0.lastPathComponent })
|
allFiles = Set(contents.map { $0.lastPathComponent })
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
print("DownloadManager: Failed to read Downloads directory: \(error)")
|
return
|
||||||
crDownloadFiles = []
|
|
||||||
allFiles = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
|
|
@ -126,90 +121,58 @@ class DownloadManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processDownloadFiles(_ crDownloadFiles: Set<String>, allFiles: Set<String>) {
|
private func processDownloadFiles(_ crDownloadFiles: Set<String>, allFiles: Set<String>) {
|
||||||
// Initial scan - track and ignore existing files
|
|
||||||
if !hasPerformedInitialScan {
|
if !hasPerformedInitialScan {
|
||||||
hasPerformedInitialScan = true
|
hasPerformedInitialScan = true
|
||||||
initialCrDownloadFiles = crDownloadFiles
|
initialCrDownloadFiles = crDownloadFiles
|
||||||
previousAllFiles = allFiles
|
previousAllFiles = allFiles
|
||||||
ignoredFiles = crDownloadFiles
|
ignoredFiles = crDownloadFiles
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
print("DownloadManager: initial scan - found \(crDownloadFiles.count) existing files, ignoring")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate differences
|
|
||||||
let newFiles = crDownloadFiles.subtracting(initialCrDownloadFiles)
|
let newFiles = crDownloadFiles.subtracting(initialCrDownloadFiles)
|
||||||
let disappearedFiles = initialCrDownloadFiles.subtracting(crDownloadFiles)
|
let disappearedFiles = initialCrDownloadFiles.subtracting(crDownloadFiles)
|
||||||
let newRegularFiles = allFiles.subtracting(previousAllFiles).subtracting(crDownloadFiles)
|
let newRegularFiles = allFiles.subtracting(previousAllFiles).subtracting(crDownloadFiles)
|
||||||
|
|
||||||
// Log disappeared files
|
|
||||||
if !disappearedFiles.isEmpty {
|
|
||||||
print("DownloadManager: files disappeared: \(disappearedFiles.joined(separator: ", "))")
|
|
||||||
ignoredFiles.subtract(disappearedFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log new regular files (potential completed downloads)
|
|
||||||
if !newRegularFiles.isEmpty {
|
|
||||||
print("DownloadManager: new regular files appeared: \(newRegularFiles.joined(separator: ", "))")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update tracked files BEFORE calculating active files
|
|
||||||
initialCrDownloadFiles = crDownloadFiles
|
initialCrDownloadFiles = crDownloadFiles
|
||||||
previousAllFiles = allFiles
|
previousAllFiles = allFiles
|
||||||
|
|
||||||
// Calculate active downloads
|
|
||||||
let activeFiles = crDownloadFiles.subtracting(ignoredFiles)
|
let activeFiles = crDownloadFiles.subtracting(ignoredFiles)
|
||||||
let hasActiveDownloads = !activeFiles.isEmpty
|
let hasActiveDownloads = !activeFiles.isEmpty
|
||||||
|
|
||||||
|
|
||||||
// Handle new downloads
|
|
||||||
if !newFiles.isEmpty {
|
if !newFiles.isEmpty {
|
||||||
let newActiveFiles = newFiles.subtracting(ignoredFiles)
|
let newActiveFiles = newFiles.subtracting(ignoredFiles)
|
||||||
if !newActiveFiles.isEmpty {
|
if !newActiveFiles.isEmpty {
|
||||||
print("DownloadManager: new download detected: \(newActiveFiles.joined(separator: ", "))")
|
|
||||||
|
|
||||||
if !isDownloading {
|
if !isDownloading {
|
||||||
updateDownloadingState(isActive: true)
|
updateDownloadingState(isActive: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle state changes based on active downloads
|
// completion logic
|
||||||
if isDownloading {
|
if isDownloading {
|
||||||
if !hasActiveDownloads {
|
if !hasActiveDownloads {
|
||||||
// No more active downloads
|
|
||||||
// Check if new regular files appeared (indicating successful completion)
|
|
||||||
if !newRegularFiles.isEmpty || disappearedFiles.isEmpty {
|
if !newRegularFiles.isEmpty || disappearedFiles.isEmpty {
|
||||||
// Download completed successfully - show completion state
|
|
||||||
if !isDownloadCompleted {
|
if !isDownloadCompleted {
|
||||||
print("DownloadManager: download completed successfully - showing completion")
|
|
||||||
updateDownloadingState(isActive: false)
|
updateDownloadingState(isActive: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Download files disappeared but no new regular files appeared - cancelled
|
|
||||||
print("DownloadManager: download cancelled - closing immediately")
|
|
||||||
closeDownloadViewImmediately()
|
closeDownloadViewImmediately()
|
||||||
}
|
}
|
||||||
} else if !disappearedFiles.isEmpty {
|
|
||||||
// Some downloads finished but others remain
|
|
||||||
print("DownloadManager: some finished, \(activeFiles.count) still active")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if hasActiveDownloads {
|
} else if hasActiveDownloads {
|
||||||
// We're not showing UI but we have active downloads - this shouldn't happen
|
|
||||||
// but let's handle it gracefully
|
|
||||||
print("DownloadManager: active downloads found but UI closed - reopening")
|
|
||||||
updateDownloadingState(isActive: true)
|
updateDownloadingState(isActive: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestDownloadsPermissionIfNeeded() {
|
private func requestDownloadsPermissionIfNeeded() {
|
||||||
guard let downloadsDirectory else { return }
|
guard let downloadsDirectory else { return }
|
||||||
|
_ = try? FileManager.default.contentsOfDirectory(at: downloadsDirectory, includingPropertiesForKeys: nil)
|
||||||
_ = try? FileManager.default.contentsOfDirectory(
|
|
||||||
at: downloadsDirectory,
|
|
||||||
includingPropertiesForKeys: nil,
|
|
||||||
options: [.skipsHiddenFiles]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateDownloadingState(isActive: Bool) {
|
private func updateDownloadingState(isActive: Bool) {
|
||||||
|
|
@ -223,7 +186,6 @@ class DownloadManager: ObservableObject {
|
||||||
withAnimation(.smooth) {
|
withAnimation(.smooth) {
|
||||||
isDownloading = true
|
isDownloading = true
|
||||||
}
|
}
|
||||||
print("DownloadManager: download started")
|
|
||||||
coordinator.toggleExpandingView(
|
coordinator.toggleExpandingView(
|
||||||
status: true,
|
status: true,
|
||||||
type: .download,
|
type: .download,
|
||||||
|
|
@ -231,14 +193,13 @@ class DownloadManager: ObservableObject {
|
||||||
browser: .chromium
|
browser: .chromium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if isDownloading {
|
if isDownloading {
|
||||||
print("DownloadManager: download finished - showing completion")
|
|
||||||
withAnimation(.smooth) {
|
withAnimation(.smooth) {
|
||||||
isDownloadCompleted = true
|
isDownloadCompleted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close after 2 seconds
|
|
||||||
completionTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
|
completionTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self?.closeDownloadView()
|
self?.closeDownloadView()
|
||||||
|
|
@ -253,7 +214,7 @@ class DownloadManager: ObservableObject {
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
isDownloadCompleted = false
|
isDownloadCompleted = false
|
||||||
}
|
}
|
||||||
print("DownloadManager: closing download view")
|
|
||||||
coordinator.toggleExpandingView(
|
coordinator.toggleExpandingView(
|
||||||
status: false,
|
status: false,
|
||||||
type: .download,
|
type: .download,
|
||||||
|
|
@ -270,7 +231,7 @@ class DownloadManager: ObservableObject {
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
isDownloadCompleted = false
|
isDownloadCompleted = false
|
||||||
}
|
}
|
||||||
print("DownloadManager: closing download view immediately (cancelled)")
|
|
||||||
coordinator.toggleExpandingView(
|
coordinator.toggleExpandingView(
|
||||||
status: false,
|
status: false,
|
||||||
type: .download,
|
type: .download,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue