266 lines
6.6 KiB
Swift
266 lines
6.6 KiB
Swift
//
|
|
// 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.
|
|
}
|
|
}
|
|
}
|
|
}
|