// // BrowseView.swift // Browse // // Created by Javier Cicchelli on 03/12/2022. // Copyright © 2022 Röck+Cöde. All rights reserved. // import DataModels import KeychainStorage import SwiftUI public struct BrowseView: View { // MARK: Storages @KeychainStorage(key: .KeychainStorage.account) private var account: Account? // MARK: States @State private var status: ViewStatus = .loading @State private var items: [any FileSystemItem] = [] @State private var stack: Stack? @State private var itemToDelete: (any FileSystemItem)? @State private var showCreateFolder: Bool = false @State private var showSheet: SheetView? // MARK: Properties private let folder: Folder private let showProfile: ActionClosure private let login: ActionClosure private let getItems: GetItemsUseCase = .init() // MARK: Initialisers public init( folder: Folder, showProfile: @escaping ActionClosure, login: @escaping ActionClosure ) { self.folder = folder self.showProfile = showProfile self.login = login } // MARK: Body public var body: some View { content .navigationTitle(folder.name) .toolbar { BrowseToolbar( createFolder: { showCreateFolder = true }, uploadFile: { showSheet = .upload(id: folder.id) }, showProfile: showProfile ) } .overlay { if status == .loading { LoadingView() } } .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( isPresenting: $showCreateFolder, id: folder.id ) { Task { await loadItems() } } .delete(item: $itemToDelete) { Task { await loadItems() } } .task { await loadItems() } } } // MARK: - UI private extension BrowseView { // MARK: Properties @ViewBuilder var content: some View { switch status { case .noCredentials: MessageView( type: .noCredentials, action: login ) case .notSupported: EmptyView() case .loading, .loaded: List(items, id: \.id) { item in switch item { case is Folder: makeFolderItem(for: item) case is Document: makeDocumentItem(for: item) default: EmptyView() } } .listStyle(.inset) .refreshable { Task { await loadItems() } } case .empty: MessageView( type: .empty, action: { showSheet = .upload(id: folder.id) } ) case .error: MessageView(type: .error) { Task { await loadItems() } } } } // MARK: Functions @ViewBuilder func makeFolderItem( for item: any FileSystemItem ) -> some View { if let folder = item as? Folder { FolderItem(item: item) { stack = .browse(folder) } delete: { itemToDelete = item } .navigate( to: BrowseView( folder: folder, showProfile: showProfile, login: login ), tagged: .browse(folder), in: $stack ) } else { EmptyView() } } @ViewBuilder func makeDocumentItem( for item: any FileSystemItem ) -> some View { if let document = item as? Document { DocumentItem(item: item) { stack = .open(document) } download: { showSheet = .download( id: document.id, name: document.name ) } delete: { itemToDelete = item } .navigate( to: DocumentView( document: document, login: login ), tagged: .open(document), in: $stack ) } else { EmptyView() } } } // MARK: - Helpers private extension BrowseView { func loadItems() async { guard let account else { status = .noCredentials return } do { status = .loading let loadedItems = try await getItems( id: folder.id, username: account.username, password: account.password ) // Added some throttle (1 second) not to hide the loading indicator right away. try await Task.sleep(nanoseconds: .Constants.secondInNanoseconds) if loadedItems.isEmpty { status = .empty } else { items = loadedItems status = .loaded } } catch { status = .error } } } // MARK: - Enumerations private extension BrowseView { enum SheetView: Identifiable { case upload(id: String) case download(id: String, name: String) var id: String { UUID().uuidString } } } // MARK: - UInt64+Constants private extension UInt64 { enum Constants { static let secondInNanoseconds = UInt64(1 * Double(NSEC_PER_SEC)) } } // MARK: - Previews struct BrowseView_Previews: PreviewProvider { static var previews: some View { NavigationView { BrowseView(folder: .init( id: "1234567890", name: "Some folder name" )) { // show profile closure. } login: { // login closure. } } } }