better version

This commit is contained in:
delli 2025-12-05 18:20:14 +01:00
commit 57e1cac929
3 changed files with 29 additions and 68 deletions

View file

@ -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

View file

@ -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

View file

@ -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,12 +94,13 @@ 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()
@ -113,11 +108,11 @@ class DownloadManager: ObservableObject {
} }
.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,