Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve download manager #3158

Merged
merged 2 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions Sources/App/WebView/DownloadManager/DownloadManagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct DownloadManagerView: View {
VStack(spacing: .zero) {
HStack {
Button(action: {
viewModel.cancelDownload()
viewModel.deleteFile()
dismiss()
}, label: {
Expand All @@ -25,6 +26,7 @@ struct DownloadManagerView: View {
.gray.opacity(0.5)
)
})
.buttonStyle(.plain)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding()
}
Expand All @@ -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 {
Expand All @@ -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()
}
}
}
}
Expand Down
26 changes: 23 additions & 3 deletions Sources/App/WebView/DownloadManager/DownloadManagerViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -28,18 +44,22 @@ 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 {
try FileManager.default.removeItem(at: url)
} 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
Expand Down
19 changes: 19 additions & 0 deletions Sources/Shared/Environment/AppConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading