From 0b1dbc204ebcb93c5d3e70d08cc35f20e0167864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o?= <5808343+bgoncal@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:55:39 +0100 Subject: [PATCH] Improve download manager --- .../DownloadManager/DownloadManagerView.swift | 29 +++++++++++++++---- .../DownloadManagerViewModel.swift | 26 +++++++++++++++-- Sources/Shared/Environment/AppConstants.swift | 19 ++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/Sources/App/WebView/DownloadManager/DownloadManagerView.swift b/Sources/App/WebView/DownloadManager/DownloadManagerView.swift index 8e4651918..79e2d9a19 100644 --- a/Sources/App/WebView/DownloadManager/DownloadManagerView.swift +++ b/Sources/App/WebView/DownloadManager/DownloadManagerView.swift @@ -15,6 +15,7 @@ struct DownloadManagerView: View { VStack(spacing: .zero) { HStack { Button(action: { + viewModel.cancelDownload() viewModel.deleteFile() dismiss() }, label: { @@ -25,6 +26,7 @@ struct DownloadManagerView: View { .gray.opacity(0.5) ) }) + .buttonStyle(.plain) .frame(maxWidth: .infinity, alignment: .trailing) .padding() } @@ -41,9 +43,16 @@ struct DownloadManagerView: View { Text(L10n.DownloadManager.Downloading.title) .font(.title.bold()) fileCard + Text(viewModel.progress) + .animation(.easeInOut(duration: 1), value: viewModel.progress) } Spacer() } + .onChange(of: viewModel.finished) { _, newValue in + if newValue, Current.isCatalyst { + UIApplication.shared.open(AppConstants.DownloadsDirectory) + } + } } private var successView: some View { @@ -58,12 +67,20 @@ struct DownloadManagerView: View { Text(L10n.DownloadManager.Finished.title) .font(.title.bold()) if let url = viewModel.lastURLCreated { - ShareLink(viewModel.fileName, item: url) - .padding() - .foregroundStyle(.white) - .background(Color.asset(Asset.Colors.haPrimary)) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .padding() + if Current.isCatalyst { + Button { + UIApplication.shared.open(AppConstants.DownloadsDirectory) + } label: { + Label(viewModel.fileName, systemSymbol: .folder) + } + } else { + ShareLink(viewModel.fileName, item: url) + .padding() + .foregroundStyle(.white) + .background(Color.asset(Asset.Colors.haPrimary)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .padding() + } } } } diff --git a/Sources/App/WebView/DownloadManager/DownloadManagerViewModel.swift b/Sources/App/WebView/DownloadManager/DownloadManagerViewModel.swift index fdb8a5f14..04d734878 100644 --- a/Sources/App/WebView/DownloadManager/DownloadManagerViewModel.swift +++ b/Sources/App/WebView/DownloadManager/DownloadManagerViewModel.swift @@ -8,8 +8,12 @@ final class DownloadManagerViewModel: NSObject, ObservableObject { @Published var finished: Bool = false @Published var failed: Bool = false @Published var errorMessage: String = "" + @Published var progress: String = "" @Published var lastURLCreated: URL? + private var progressObservation: NSKeyValueObservation? + private var lastDownload: WKDownload? + func deleteFile() { if let url = lastURLCreated { // Guarantee to delete file before leaving screen @@ -20,6 +24,18 @@ final class DownloadManagerViewModel: NSObject, ObservableObject { } } } + + func cancelDownload() { + progressObservation?.invalidate() + lastDownload?.cancel() + } + + private func bytesToMBString(_ bytes: Int64) -> String { + let formatter = ByteCountFormatter() + formatter.allowedUnits = [.useMB] + formatter.countStyle = .file + return formatter.string(fromByteCount: bytes) + } } extension DownloadManagerViewModel: WKDownloadDelegate { @@ -28,10 +44,10 @@ extension DownloadManagerViewModel: WKDownloadDelegate { decideDestinationUsing response: URLResponse, suggestedFilename: String ) async -> URL? { - let urls = FileManager.default.urls(for: .cachesDirectory, in: .allDomainsMask) + lastDownload = download let name = suggestedFilename.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "Unknown" fileName = name - if let url = URL(string: name, relativeTo: urls[0]) { + if let url = URL(string: name, relativeTo: AppConstants.DownloadsDirectory) { lastURLCreated = url // Guarantee file does not exist, otherwise download will fail do { @@ -39,7 +55,11 @@ extension DownloadManagerViewModel: WKDownloadDelegate { } catch { Current.Log.error("Failed to remove file for download manager at \(url), error: \(error)") } - + progressObservation?.invalidate() + progressObservation = download.progress.observe(\.completedUnitCount) { [weak self] progress, _ in + guard let self else { return } + self.progress = bytesToMBString(progress.completedUnitCount) + } return url } else { return nil diff --git a/Sources/Shared/Environment/AppConstants.swift b/Sources/Shared/Environment/AppConstants.swift index 80cb5c722..7e08c45ac 100644 --- a/Sources/Shared/Environment/AppConstants.swift +++ b/Sources/Shared/Environment/AppConstants.swift @@ -111,6 +111,25 @@ public enum AppConstants { return directoryURL } + public static var DownloadsDirectory: URL { + let fileManager = FileManager.default + let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .allDomainsMask).first! + .appendingPathComponent( + "Downloads", + isDirectory: true + ) + + if !fileManager.fileExists(atPath: directoryURL.path) { + do { + try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) + } catch { + fatalError("Error while attempting to create downloads path URL: \(error)") + } + } + + return directoryURL + } + /// An initialized Keychain from KeychainAccess. public static var Keychain: KeychainAccess.Keychain { KeychainAccess.Keychain(service: BundleID)