266 lines
6.6 KiB
Swift
Raw Normal View History

//
// 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.
}
}
}
}