From 7d737cb12bd7364319c09d7cc1a79276f461c52d Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 18 Dec 2022 00:58:29 +0100 Subject: [PATCH 1/6] Renamed the DocumentPicker component as SelectDocumentPicker for the Browse module. --- ...ocumentPicker.swift => SelectDocumentPicker.swift} | 11 ++++++----- Modules/Sources/Browse/UI/Views/UploadView.swift | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) rename Modules/Sources/Browse/UI/Components/{DocumentPicker.swift => SelectDocumentPicker.swift} (90%) diff --git a/Modules/Sources/Browse/UI/Components/DocumentPicker.swift b/Modules/Sources/Browse/UI/Components/SelectDocumentPicker.swift similarity index 90% rename from Modules/Sources/Browse/UI/Components/DocumentPicker.swift rename to Modules/Sources/Browse/UI/Components/SelectDocumentPicker.swift index b7b1188..3c1023d 100644 --- a/Modules/Sources/Browse/UI/Components/DocumentPicker.swift +++ b/Modules/Sources/Browse/UI/Components/SelectDocumentPicker.swift @@ -1,5 +1,5 @@ // -// DocumentPicker.swift +// SelectDocumentPicker.swift // Browse // // Created by Javier Cicchelli on 17/12/2022. @@ -10,7 +10,7 @@ import Foundation import SwiftUI import UIKit -struct DocumentPicker: UIViewControllerRepresentable { +struct SelectDocumentPicker: UIViewControllerRepresentable { // MARK: Type aliases @@ -55,20 +55,21 @@ struct DocumentPicker: UIViewControllerRepresentable { func makeCoordinator() -> Coordinator { .init(self) } + } // MARK: - Coordinators -extension DocumentPicker { +extension SelectDocumentPicker { class Coordinator: NSObject, UIDocumentPickerDelegate { // MARK: Properties - private var parent: DocumentPicker + private var parent: SelectDocumentPicker // MARK: Initialisers - init(_ parent: DocumentPicker) { + init(_ parent: SelectDocumentPicker) { self.parent = parent } diff --git a/Modules/Sources/Browse/UI/Views/UploadView.swift b/Modules/Sources/Browse/UI/Views/UploadView.swift index 495a43d..12b33e4 100644 --- a/Modules/Sources/Browse/UI/Views/UploadView.swift +++ b/Modules/Sources/Browse/UI/Views/UploadView.swift @@ -32,7 +32,7 @@ struct UploadView: View { // MARK: Body var body: some View { - DocumentPicker { urls in + SelectDocumentPicker { urls in Task { await addFile(from: urls) } } } From 76a2fa89fbf55dee840253b5b78a21e60df82f72 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 18 Dec 2022 01:03:32 +0100 Subject: [PATCH 2/6] Implemented the SaveDocumentPicker component for the Browse module. --- .../UI/Components/SaveDocumentPicker.swift | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Modules/Sources/Browse/UI/Components/SaveDocumentPicker.swift diff --git a/Modules/Sources/Browse/UI/Components/SaveDocumentPicker.swift b/Modules/Sources/Browse/UI/Components/SaveDocumentPicker.swift new file mode 100644 index 0000000..3e1bec5 --- /dev/null +++ b/Modules/Sources/Browse/UI/Components/SaveDocumentPicker.swift @@ -0,0 +1,120 @@ +// +// SaveDocumentPicker.swift +// Browse +// +// Created by Javier Cicchelli on 17/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import SwiftUI +import UIKit + +struct SaveDocumentPicker: UIViewControllerRepresentable { + + // MARK: Type aliases + + typealias DownloadedClosure = (NSError?) -> Void + typealias CancelledClosure = () -> Void + + // MARK: Properties + + private let fileName: String + private let fileData: Data + private let downloaded: DownloadedClosure + private let cancelled: CancelledClosure? + + // MARK: Initialisers + + init( + fileName: String, + fileData: Data, + downloaded: @escaping DownloadedClosure, + cancelled: CancelledClosure? = nil + ) { + self.fileName = fileName + self.fileData = fileData + self.downloaded = downloaded + self.cancelled = cancelled + } + + // MARK: Functions + + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { + let controller = UIDocumentPickerViewController( + forOpeningContentTypes: [.folder] + ) + + controller.allowsMultipleSelection = false + controller.shouldShowFileExtensions = false + controller.delegate = context.coordinator + + return controller + } + + func updateUIViewController( + _ uiViewController: UIDocumentPickerViewController, + context: Context + ) { } + + func makeCoordinator() -> Coordinator { + .init(self) + } +} + +// MARK: - Coordinators + +extension SaveDocumentPicker { + class Coordinator: NSObject, UIDocumentPickerDelegate { + + // MARK: Properties + + private let parent: SaveDocumentPicker + private let fileCoordinator: NSFileCoordinator = .init() + private let fileManager: FileManager = .default + + // MARK: Initialisers + + init(_ parent: SaveDocumentPicker) { + self.parent = parent + } + + // MARK: UIDocumentPickerDelegate + + func documentPicker( + _ controller: UIDocumentPickerViewController, + didPickDocumentsAt urls: [URL] + ) { + guard + let url = urls.first, + url.startAccessingSecurityScopedResource() + else { + // TODO: Handle this error appropriately. + return + } + + var error: NSError? + + fileCoordinator.coordinate( + writingItemAt: url, + error: &error + ) { url in + fileManager.createFile( + atPath: url.appendingPathComponent(parent.fileName).relativePath, + contents: parent.fileData + ) + } + + url.stopAccessingSecurityScopedResource() + + parent.downloaded(error) + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + controller.dismiss(animated: true) { [weak self] in + self?.parent.cancelled?() + } + } + + } +} From 49b9b78f57050aaee5409773433fbdb3feb11c3d Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 18 Dec 2022 01:04:47 +0100 Subject: [PATCH 3/6] Implemented the DownloadView view for the Browse module. --- .../Browse/UI/Views/DownloadView.swift | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 Modules/Sources/Browse/UI/Views/DownloadView.swift diff --git a/Modules/Sources/Browse/UI/Views/DownloadView.swift b/Modules/Sources/Browse/UI/Views/DownloadView.swift new file mode 100644 index 0000000..8a041ab --- /dev/null +++ b/Modules/Sources/Browse/UI/Views/DownloadView.swift @@ -0,0 +1,104 @@ +// +// DownloadView.swift +// Browse +// +// Created by Javier Cicchelli on 17/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import DataModels +import Foundation +import KeychainStorage +import SwiftUI + +struct DownloadView: View { + + // MARK: Storages + + @KeychainStorage(key: .KeychainStorage.account) private var account: Account? + + // MARK: States + + @State private var data: Data? + + // MARK: Properties + + private let id: String + private let name: String + private let downloaded: ActionClosure + + private let getData: GetDataUseCase = .init() + + // MARK: Initialisers + + init( + id: String, + name: String, + data: Data? = nil, + downloaded: @escaping ActionClosure + ) { + self.id = id + self.name = name + self.downloaded = downloaded + + self._data = .init(initialValue: data) + } + + // MARK: Body + + var body: some View { + if let data { + SaveDocumentPicker( + fileName: name, + fileData: data + ) { error in + guard error == nil else { + // TODO: Handle this error case. + return + } + + downloaded() + } + } else { + LoadingView() + .task { + await loadData() + } + } + } + +} + +// MARK: - Helpers + +private extension DownloadView { + func loadData() async { + guard let account else { + // TODO: Handle this error case. + return + } + + do { + data = try await getData( + id: id, + username: account.username, + password: account.password + ) + } catch { + // TODO: Handle this error case. + } + } +} + +// MARK: - Previews + +struct DownloadView_Previews: PreviewProvider { + static var previews: some View { + DownloadView( + id: "1234567890", + name: "some-name.txt" + ) { + // Downloaded closure. + } + } +} From e78a326595e8fafbab969cf47c54817a62925507 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 18 Dec 2022 01:06:11 +0100 Subject: [PATCH 4/6] Integrated the DownloadView view into the BrowseView view for the Browse module. --- .../Sources/Browse/UI/Views/BrowseView.swift | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/Modules/Sources/Browse/UI/Views/BrowseView.swift b/Modules/Sources/Browse/UI/Views/BrowseView.swift index c493659..dcc95a8 100644 --- a/Modules/Sources/Browse/UI/Views/BrowseView.swift +++ b/Modules/Sources/Browse/UI/Views/BrowseView.swift @@ -23,7 +23,7 @@ public struct BrowseView: View { @State private var stack: Stack? @State private var itemToDelete: (any FileSystemItem)? @State private var showCreateFolder: Bool = false - @State private var showUploadFile: Bool = false + @State private var showSheet: SheetView? // MARK: Properties @@ -56,14 +56,24 @@ public struct BrowseView: View { showCreateFolder = true }, uploadFile: { - showUploadFile = true + showSheet = .upload(id: folder.id) }, showProfile: showProfile ) } - .sheet(isPresented: $showUploadFile) { - UploadView(id: folder.id) { - Task { await loadItems() } + .sheet(item: $showSheet) { sheet in + switch sheet { + case let .upload(id): + UploadView(id: id) { + Task { await loadItems() } + } + case let .download(id, name): + DownloadView( + id: id, + name: name + ) { + Task { await loadItems() } + } } } .createFolder( @@ -118,7 +128,7 @@ private extension BrowseView { MessageView( type: .empty, action: { - showUploadFile = true + showSheet = .upload(id: folder.id) } ) case .error: @@ -162,7 +172,10 @@ private extension BrowseView { DocumentItem(item: item) { stack = .open(document) } download: { - // TODO: download the item id from the backend. + showSheet = .download( + id: document.id, + name: document.name + ) } delete: { itemToDelete = item } @@ -211,6 +224,17 @@ private extension BrowseView { } } +// MARK: - Enumerations + +private extension BrowseView { + enum SheetView: Identifiable { + case upload(id: String) + case download(id: String, name: String) + + var id: String { UUID().uuidString } + } +} + // MARK: - Previews struct BrowseView_Previews: PreviewProvider { From d242171c6e580af6f55fdb2135aebe376af0ef05 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 18 Dec 2022 01:07:04 +0100 Subject: [PATCH 5/6] Implemented the DocumentToolbar toolbar content for the Browse module. --- .../Resources/en.lproj/Localizable.strings | 6 ++- .../Browse/UI/Toolbars/BrowseToolbar.swift | 12 ++---- .../Browse/UI/Toolbars/DocumentToolbar.swift | 42 +++++++++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 Modules/Sources/Browse/UI/Toolbars/DocumentToolbar.swift diff --git a/Modules/Sources/Browse/Resources/en.lproj/Localizable.strings b/Modules/Sources/Browse/Resources/en.lproj/Localizable.strings index fd36893..01712b8 100644 --- a/Modules/Sources/Browse/Resources/en.lproj/Localizable.strings +++ b/Modules/Sources/Browse/Resources/en.lproj/Localizable.strings @@ -46,12 +46,16 @@ "delete_item.system_alert.message" = "An unexpected error occurred while trying to delete the indicated folder from the current folder.\n\nPlease check your Internet connection and try this operation at a later time."; "delete_item.system_alert.button.cancel" = "Understood"; -// BrowseView +// BrowseToolbar "browse.toolbar_item.menu.add_actions.text" = "Add file and/or folder"; "browse.toolbar_item.button.add_folder.text" = "Create a new folder"; "browse.toolbar_item.button.add_file.text" = "Upload a file"; "browse.toolbar_item.button.show_profile.text" = "Show profile"; +// DocumentToolbar + +"document.toolbar_item.button.download_file.text" = "Download this file"; + "browse.swipe_action.delete_item.text" = "Delete item"; "browse.swipe_action.download_item.text" = "Download item"; diff --git a/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift b/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift index 5f2d65c..07b913a 100644 --- a/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift +++ b/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift @@ -28,8 +28,7 @@ struct BrowseToolbar: ToolbarContent { Label { Text( "browse.toolbar_item.button.add_folder.text", - bundle: .module, - comment: "Add folder button text." + bundle: .module ) } icon: { Image.newFolder @@ -42,8 +41,7 @@ struct BrowseToolbar: ToolbarContent { Label { Text( "browse.toolbar_item.button.add_file.text", - bundle: .module, - comment: "Add file button text." + bundle: .module ) } icon: { Image.newFile @@ -53,8 +51,7 @@ struct BrowseToolbar: ToolbarContent { Label { Text( "browse.toolbar_item.menu.add_actions.text", - bundle: .module, - comment: "Add actions menu text." + bundle: .module ) } icon: { Image.add @@ -72,8 +69,7 @@ struct BrowseToolbar: ToolbarContent { Label { Text( "browse.toolbar_item.button.show_profile.text", - bundle: .module, - comment: "Show profile button text." + bundle: .module ) } icon: { Image.profile diff --git a/Modules/Sources/Browse/UI/Toolbars/DocumentToolbar.swift b/Modules/Sources/Browse/UI/Toolbars/DocumentToolbar.swift new file mode 100644 index 0000000..90a5aaf --- /dev/null +++ b/Modules/Sources/Browse/UI/Toolbars/DocumentToolbar.swift @@ -0,0 +1,42 @@ +// +// DocumentToolbar.swift +// Browse +// +// Created by Javier Cicchelli on 18/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import DataModels +import SwiftUI + +struct DocumentToolbar: ToolbarContent { + + // MARK: Properties + + let disabled: Bool + let downloadFile: ActionClosure + + // MARK: Body + + var body: some ToolbarContent { + ToolbarItem(placement: .primaryAction) { + Button { + downloadFile() + } label: { + Label { + Text( + "document.toolbar_item.button.download_file.text", + bundle: .module + ) + } icon: { + Image.download + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + } + } + .disabled(disabled) + } + } + +} From 21d6f2fb2a4492f01e2dd433ab59a1e1976b9607 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 18 Dec 2022 01:08:38 +0100 Subject: [PATCH 6/6] Integrated the DocumentView view and the DocumentToolbar toolbar into the DocumentView view for the Browse module. --- .../Sources/Browse/UI/Views/DocumentView.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Modules/Sources/Browse/UI/Views/DocumentView.swift b/Modules/Sources/Browse/UI/Views/DocumentView.swift index 740247a..8a4e5e8 100644 --- a/Modules/Sources/Browse/UI/Views/DocumentView.swift +++ b/Modules/Sources/Browse/UI/Views/DocumentView.swift @@ -24,6 +24,7 @@ struct DocumentView: View { @State private var status: ViewStatus = .loading @State private var loadedData: Data? + @State private var showDownloadFile: Bool = false private let getData = GetDataUseCase() @@ -38,6 +39,20 @@ struct DocumentView: View { content .navigationTitle(document.name) .navigationBarTitleDisplayMode(.inline) + .toolbar { + DocumentToolbar(disabled: isToolbarDisabled) { + showDownloadFile = true + } + } + .sheet(isPresented: $showDownloadFile) { + DownloadView( + id: document.id, + name: document.name, + data: loadedData + ) { + // downloaded closure. + } + } .task { await loadDataIfPossible() } @@ -48,6 +63,8 @@ struct DocumentView: View { // MARK: - UI private extension DocumentView { + var isToolbarDisabled: Bool { loadedData == nil } + @ViewBuilder var content: some View { switch status { case .noCredentials: