Merge pull request #15 from rock-n-code/feature/create-folder
Feature: Create folder
This commit is contained in:
commit
fbef849423
@ -85,7 +85,6 @@ private extension ContentView {
|
|||||||
id: user.rootFolder.id,
|
id: user.rootFolder.id,
|
||||||
name: user.rootFolder.name
|
name: user.rootFolder.name
|
||||||
),
|
),
|
||||||
createFolder: createFolder,
|
|
||||||
uploadFile: uploadFile,
|
uploadFile: uploadFile,
|
||||||
showProfile: showProfile,
|
showProfile: showProfile,
|
||||||
login: login
|
login: login
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// CreateFolderUseCase.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import APIService
|
||||||
|
import DependencyInjection
|
||||||
|
import Dependencies
|
||||||
|
|
||||||
|
struct CreateFolderUseCase {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
let apiService: APIService
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func callAsFunction(
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
username: String,
|
||||||
|
password: String
|
||||||
|
) async throws {
|
||||||
|
_ = try await apiService.createFolder(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
credentials: .init(
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Initialisers
|
||||||
|
|
||||||
|
extension CreateFolderUseCase {
|
||||||
|
init() {
|
||||||
|
@Dependency(\.apiService) var apiService
|
||||||
|
|
||||||
|
self.init(apiService: apiService)
|
||||||
|
}
|
||||||
|
}
|
@ -25,15 +25,26 @@
|
|||||||
"message.type_not_supported.text.second" = "Please be patient while the support for this type of document is being built by our development team.";
|
"message.type_not_supported.text.second" = "Please be patient while the support for this type of document is being built by our development team.";
|
||||||
"message.type_not_supported.button.text" = "Go back to folder";
|
"message.type_not_supported.button.text" = "Go back to folder";
|
||||||
|
|
||||||
|
// CreateFolderViewModifier
|
||||||
|
|
||||||
|
"create_folder.input_alert.title" = "Create a new folder";
|
||||||
|
"create_folder.input_alert.message" = "You are about to create a new folder inside the folder you are currently in.";
|
||||||
|
"create_folder.input_alert.text_field.placeholder" = "Name of the folder to create";
|
||||||
|
"create_folder.input_alert.button.cancel" = "Dismiss";
|
||||||
|
"create_folder.input_alert.button.submit" = "Create folder";
|
||||||
|
"create_folder.system_alert.title" = "An error occurred while creating a new folder";
|
||||||
|
"create_folder.system_alert.message" = "An unexpected error occurred while trying to create a new folder into the current folder.\n\nPlease check your Internet connection and try this operation at a later time.";
|
||||||
|
"create_folder.system_alert.button.cancel" = "Understood";
|
||||||
|
|
||||||
// DeleteItemViewModifier
|
// DeleteItemViewModifier
|
||||||
|
|
||||||
"delete_item.action_sheet.title" = "Delete an item";
|
"delete_item.action_sheet.title" = "Delete an item";
|
||||||
"delete_item.action_sheet.message %@" = "You are about to delete an item named \"%@\" from this folder.\n\nAre you sure you wish to proceed?";
|
"delete_item.action_sheet.message %@" = "You are about to delete an item named \"%@\" from this folder.\n\nAre you sure you wish to proceed?";
|
||||||
"delete_item.action_sheet.button.ok" = "Yes, please delete it.";
|
"delete_item.action_sheet.button.ok" = "Yes, please delete it.";
|
||||||
"delete_item.action_sheet.button.cancel" = "No, I reconsidered.";
|
"delete_item.action_sheet.button.cancel" = "No, I reconsidered.";
|
||||||
"delete_item.system_alert.title" = "...";
|
"delete_item.system_alert.title" = "An error occurred when deleting a folder";
|
||||||
"delete_item.system_alert.message" = "...";
|
"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.dismiss" = "...";
|
"delete_item.system_alert.button.cancel" = "Understood";
|
||||||
|
|
||||||
// BrowseView
|
// BrowseView
|
||||||
|
|
||||||
|
134
Modules/Sources/Browse/UI/Components/InputAlertView.swift
Normal file
134
Modules/Sources/Browse/UI/Components/InputAlertView.swift
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//
|
||||||
|
// InputAlertView.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import DataModels
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct InputAlertView: UIViewControllerRepresentable {
|
||||||
|
|
||||||
|
// MARK: Bindings
|
||||||
|
|
||||||
|
@Binding private var isPresenting: Bool
|
||||||
|
@Binding private var textFieldString: String
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
private let title: String
|
||||||
|
private let message: String
|
||||||
|
private let textFieldPlaceholder: String
|
||||||
|
private let actions: [UIAlertAction]
|
||||||
|
|
||||||
|
// MARK: Initialisers
|
||||||
|
|
||||||
|
init(
|
||||||
|
isPresenting: Binding<Bool>,
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
|
textFieldPlaceholder: String,
|
||||||
|
textFieldString: Binding<String>,
|
||||||
|
actions: [UIAlertAction]
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.message = message
|
||||||
|
self.textFieldPlaceholder = textFieldPlaceholder
|
||||||
|
self.actions = actions
|
||||||
|
|
||||||
|
self._isPresenting = isPresenting
|
||||||
|
self._textFieldString = textFieldString
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func makeUIViewController(
|
||||||
|
context: UIViewControllerRepresentableContext<InputAlertView>
|
||||||
|
) -> UIViewController {
|
||||||
|
.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(
|
||||||
|
_ viewController: UIViewController,
|
||||||
|
context: UIViewControllerRepresentableContext<InputAlertView>
|
||||||
|
) {
|
||||||
|
guard
|
||||||
|
context.coordinator.alert == nil,
|
||||||
|
isPresenting
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let alertController = {
|
||||||
|
let alert = UIAlertController(
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
preferredStyle: .alert
|
||||||
|
)
|
||||||
|
|
||||||
|
alert.addTextField { textField in
|
||||||
|
textField.placeholder = textFieldPlaceholder
|
||||||
|
textField.text = textFieldString
|
||||||
|
textField.delegate = context.coordinator
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.forEach { action in
|
||||||
|
alert.addAction(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
return alert
|
||||||
|
}()
|
||||||
|
|
||||||
|
context.coordinator.alert = alertController
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
|
viewController.present(
|
||||||
|
alertController,
|
||||||
|
animated: true
|
||||||
|
) {
|
||||||
|
isPresenting = false
|
||||||
|
context.coordinator.alert = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> InputAlertView.Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Coordinator
|
||||||
|
|
||||||
|
extension InputAlertView {
|
||||||
|
class Coordinator: NSObject, UITextFieldDelegate {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
var alert: UIAlertController?
|
||||||
|
|
||||||
|
private let component: InputAlertView
|
||||||
|
|
||||||
|
// MARK: Initialisers
|
||||||
|
|
||||||
|
init(_ component: InputAlertView) {
|
||||||
|
self.component = component
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: UITextFieldDelegate
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
component.textFieldString = {
|
||||||
|
if let text = textField.text as NSString? {
|
||||||
|
return text.replacingCharacters(in: range, with: string)
|
||||||
|
} else {
|
||||||
|
return .empty
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
11
Modules/Sources/Browse/UI/Extensions/String+Constants.swift
Normal file
11
Modules/Sources/Browse/UI/Extensions/String+Constants.swift
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// String+Constants.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
static let empty = ""
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// String+Localisations.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
static func localise(key: String) -> Self {
|
||||||
|
NSLocalizedString(
|
||||||
|
key,
|
||||||
|
bundle: .module,
|
||||||
|
comment: .empty
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,18 @@ extension View {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createFolder(
|
||||||
|
isPresenting: Binding<Bool>,
|
||||||
|
id: String,
|
||||||
|
submitted: @escaping ActionClosure
|
||||||
|
) -> some View {
|
||||||
|
modifier(CreateFolderViewModifier(
|
||||||
|
isPresenting: isPresenting,
|
||||||
|
id: id,
|
||||||
|
submitted: submitted
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
func delete(
|
func delete(
|
||||||
item: Binding<(any FileSystemItem)?>,
|
item: Binding<(any FileSystemItem)?>,
|
||||||
deleted: @escaping ActionClosure
|
deleted: @escaping ActionClosure
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// CreateFolderViewModifier.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import DataModels
|
||||||
|
import KeychainStorage
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CreateFolderViewModifier: ViewModifier {
|
||||||
|
|
||||||
|
// MARK: Storages
|
||||||
|
|
||||||
|
@KeychainStorage(key: .KeychainStorage.account) private var account: Account?
|
||||||
|
|
||||||
|
// MARK: States
|
||||||
|
|
||||||
|
@State private var folderName: String = ""
|
||||||
|
@State private var showErrorAlert: Bool = false
|
||||||
|
|
||||||
|
// MARK: Bindings
|
||||||
|
|
||||||
|
@Binding var isPresenting: Bool
|
||||||
|
|
||||||
|
let id: String
|
||||||
|
let submitted: ActionClosure
|
||||||
|
|
||||||
|
private let createFolder: CreateFolderUseCase = .init()
|
||||||
|
|
||||||
|
// MARK: Body
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.background(InputAlertView(
|
||||||
|
isPresenting: $isPresenting,
|
||||||
|
title: .localise(key: "create_folder.input_alert.title"),
|
||||||
|
message: .localise(key: "create_folder.input_alert.message"),
|
||||||
|
textFieldPlaceholder: .localise(key: "create_folder.input_alert.text_field.placeholder"),
|
||||||
|
textFieldString: $folderName,
|
||||||
|
actions: [
|
||||||
|
.init(
|
||||||
|
title: .localise(key: "create_folder.input_alert.button.cancel"),
|
||||||
|
style: .cancel,
|
||||||
|
handler: { _ in
|
||||||
|
folderName = .empty
|
||||||
|
}
|
||||||
|
),
|
||||||
|
.init(
|
||||||
|
title: .localise(key: "create_folder.input_alert.button.submit"),
|
||||||
|
style: .default,
|
||||||
|
handler: { _ in
|
||||||
|
Task {
|
||||||
|
await addFolder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
))
|
||||||
|
.alert(isPresented: $showErrorAlert) {
|
||||||
|
Alert(
|
||||||
|
title: Text(
|
||||||
|
"create_folder.system_alert.title",
|
||||||
|
bundle: .module
|
||||||
|
),
|
||||||
|
message: Text(
|
||||||
|
"create_folder.system_alert.message",
|
||||||
|
bundle: .module
|
||||||
|
),
|
||||||
|
dismissButton: .cancel(Text(
|
||||||
|
"create_folder.system_alert.button.cancel",
|
||||||
|
bundle: .module
|
||||||
|
)) {
|
||||||
|
folderName = .empty
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension CreateFolderViewModifier {
|
||||||
|
func addFolder() async {
|
||||||
|
guard let account else {
|
||||||
|
showErrorAlert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
_ = try await createFolder(
|
||||||
|
id: id,
|
||||||
|
name: folderName,
|
||||||
|
username: account.username,
|
||||||
|
password: account.password
|
||||||
|
)
|
||||||
|
|
||||||
|
folderName = .empty
|
||||||
|
|
||||||
|
submitted()
|
||||||
|
} catch {
|
||||||
|
showErrorAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -115,6 +115,8 @@ private extension DeleteItemViewModifier {
|
|||||||
password: account.password
|
password: account.password
|
||||||
)
|
)
|
||||||
|
|
||||||
|
item = nil
|
||||||
|
|
||||||
deleted()
|
deleted()
|
||||||
} catch {
|
} catch {
|
||||||
showErrorAlert = true
|
showErrorAlert = true
|
||||||
|
@ -22,11 +22,11 @@ public struct BrowseView: View {
|
|||||||
@State private var items: [any FileSystemItem] = []
|
@State private var items: [any FileSystemItem] = []
|
||||||
@State private var stack: Stack?
|
@State private var stack: Stack?
|
||||||
@State private var itemToDelete: (any FileSystemItem)?
|
@State private var itemToDelete: (any FileSystemItem)?
|
||||||
|
@State private var showCreateFolder: Bool = false
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
private let folder: Folder
|
private let folder: Folder
|
||||||
private let createFolder: ActionClosure
|
|
||||||
private let uploadFile: ActionClosure
|
private let uploadFile: ActionClosure
|
||||||
private let showProfile: ActionClosure
|
private let showProfile: ActionClosure
|
||||||
private let login: ActionClosure
|
private let login: ActionClosure
|
||||||
@ -37,13 +37,11 @@ public struct BrowseView: View {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
folder: Folder,
|
folder: Folder,
|
||||||
createFolder: @escaping ActionClosure,
|
|
||||||
uploadFile: @escaping ActionClosure,
|
uploadFile: @escaping ActionClosure,
|
||||||
showProfile: @escaping ActionClosure,
|
showProfile: @escaping ActionClosure,
|
||||||
login: @escaping ActionClosure
|
login: @escaping ActionClosure
|
||||||
) {
|
) {
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
self.createFolder = createFolder
|
|
||||||
self.uploadFile = uploadFile
|
self.uploadFile = uploadFile
|
||||||
self.showProfile = showProfile
|
self.showProfile = showProfile
|
||||||
self.login = login
|
self.login = login
|
||||||
@ -56,14 +54,24 @@ public struct BrowseView: View {
|
|||||||
.navigationTitle(folder.name)
|
.navigationTitle(folder.name)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
BrowseToolbar(
|
BrowseToolbar(
|
||||||
createFolder: createFolder,
|
createFolder: {
|
||||||
|
showCreateFolder = true
|
||||||
|
},
|
||||||
uploadFile: uploadFile,
|
uploadFile: uploadFile,
|
||||||
showProfile: showProfile
|
showProfile: showProfile
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.createFolder(
|
||||||
|
isPresenting: $showCreateFolder,
|
||||||
|
id: folder.id
|
||||||
|
) {
|
||||||
|
Task {
|
||||||
|
await loadItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
.delete(item: $itemToDelete) {
|
.delete(item: $itemToDelete) {
|
||||||
Task {
|
Task {
|
||||||
await updateItems()
|
await loadItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task(id: folder) {
|
.task(id: folder) {
|
||||||
@ -129,7 +137,6 @@ private extension BrowseView {
|
|||||||
.navigate(
|
.navigate(
|
||||||
to: BrowseView(
|
to: BrowseView(
|
||||||
folder: folder,
|
folder: folder,
|
||||||
createFolder: createFolder,
|
|
||||||
uploadFile: uploadFile,
|
uploadFile: uploadFile,
|
||||||
showProfile: showProfile,
|
showProfile: showProfile,
|
||||||
login: login
|
login: login
|
||||||
@ -196,11 +203,6 @@ private extension BrowseView {
|
|||||||
status = .error
|
status = .error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateItems() async {
|
|
||||||
items = items.filter { $0.id != itemToDelete?.id }
|
|
||||||
itemToDelete = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
@ -212,8 +214,6 @@ struct BrowseView_Previews: PreviewProvider {
|
|||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
name: "Some folder name"
|
name: "Some folder name"
|
||||||
)) {
|
)) {
|
||||||
// create folder closure.
|
|
||||||
} uploadFile: {
|
|
||||||
// upload file closure.
|
// upload file closure.
|
||||||
} showProfile: {
|
} showProfile: {
|
||||||
// show profile closure.
|
// show profile closure.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user