From e12ae6098cdd93831e63540dbe14fdf7194d35ce Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 23 Jun 2021 14:23:57 +0200 Subject: [PATCH 01/23] Changed layout of "detected masterkey" view when opening existing vault, refactored some constraints along the way [ci skip] --- .../AddVaultSuccessViewController.swift | 8 +- .../AddVault/AddVaultViewController.swift | 7 +- .../AddVault/ChooseCloudViewController.swift | 1 - ...teNewVaultChooseFolderViewController.swift | 38 ++++++-- .../CreateNewVaultCoordinator.swift | 2 +- ...CreateNewVaultPasswordViewController.swift | 1 + Cryptomator/AddVault/DetectedVaultView.swift | 7 +- ...stingVaultChooseFolderViewController.swift | 91 +++++++++++++++---- .../ChooseFolderViewController.swift | 9 +- .../CreateNewFolderViewController.swift | 2 + .../AccountListViewController.swift | 2 +- Cryptomator/VaultList/EmptyListMessage.swift | 1 - Cryptomator/de.lproj/Localizable.strings | 2 +- Cryptomator/en.lproj/Localizable.strings | 2 +- 14 files changed, 123 insertions(+), 50 deletions(-) diff --git a/Cryptomator/AddVault/AddVaultSuccessViewController.swift b/Cryptomator/AddVault/AddVaultSuccessViewController.swift index 6c3f363cb..199a4e637 100644 --- a/Cryptomator/AddVault/AddVaultSuccessViewController.swift +++ b/Cryptomator/AddVault/AddVaultSuccessViewController.swift @@ -87,10 +87,10 @@ private class VaultSuccessHeaderView: UIView { addSubview(stack) NSLayoutConstraint.activate([ - stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - stack.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 12), - stack.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: -12) + stack.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + stack.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), + stack.topAnchor.constraint(equalTo: topAnchor, constant: 20), + stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20) ]) } } diff --git a/Cryptomator/AddVault/AddVaultViewController.swift b/Cryptomator/AddVault/AddVaultViewController.swift index 4de87cc2b..eb3c3c78e 100644 --- a/Cryptomator/AddVault/AddVaultViewController.swift +++ b/Cryptomator/AddVault/AddVaultViewController.swift @@ -71,10 +71,10 @@ class AddVaultViewController: UITableViewController { imageView.contentMode = .scaleAspectFit imageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - imageView.leadingAnchor.constraint(equalTo: headerView.layoutMarginsGuide.leadingAnchor), - imageView.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -20), + imageView.leadingAnchor.constraint(equalTo: headerView.readableContentGuide.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: headerView.readableContentGuide.trailingAnchor), imageView.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 20), - imageView.trailingAnchor.constraint(equalTo: headerView.layoutMarginsGuide.trailingAnchor) + imageView.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -20) ]) return headerView @@ -93,7 +93,6 @@ class AddVaultViewController: UITableViewController { } #if DEBUG -import CryptomatorCloudAccess import SwiftUI struct VaultAddVCPreview: PreviewProvider { diff --git a/Cryptomator/AddVault/ChooseCloudViewController.swift b/Cryptomator/AddVault/ChooseCloudViewController.swift index 1e008152a..a138af59c 100644 --- a/Cryptomator/AddVault/ChooseCloudViewController.swift +++ b/Cryptomator/AddVault/ChooseCloudViewController.swift @@ -57,7 +57,6 @@ class ChooseCloudViewController: SingleSectionHeaderTableViewController { } #if DEBUG -import CryptomatorCloudAccess import SwiftUI struct ChooseCloudVCPreview: PreviewProvider { diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift index b0d2ae0de..cc25cc947 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift @@ -7,11 +7,8 @@ // import UIKit -class CreateNewVaultChooseFolderViewController: ChooseFolderViewController { - init(viewModel: CreateNewVaultChooseFolderViewModelProtocol) { - super.init(with: viewModel) - } +class CreateNewVaultChooseFolderViewController: ChooseFolderViewController { override func viewDidLoad() { super.viewDidLoad() title = NSLocalizedString("addVault.createNewVault.title", comment: "") @@ -31,10 +28,10 @@ class CreateNewVaultChooseFolderViewController: ChooseFolderViewController { failureView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(failureView) NSLayoutConstraint.activate([ - failureView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), failureView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), - failureView.leadingAnchor.constraint(equalTo: containerView.readableContentGuide.leadingAnchor), - failureView.trailingAnchor.constraint(equalTo: containerView.readableContentGuide.trailingAnchor) + failureView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + failureView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + failureView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) ]) // Prevents the view from being placed under the navigation bar @@ -71,10 +68,31 @@ private class FailureView: DetectedVaultView { #if DEBUG import CryptomatorCloudAccessCore import SwiftUI -struct FailureView_Preview: PreviewProvider { - static var previews: some View { - FailureView().toPreview() + +private class CreateNewVaultChooseFolderViewModelMock: ChooseFolderViewModelProtocol { + let foundMasterkey = true + let canCreateFolder: Bool + let cloudPath: CloudPath + let items: [CloudItemMetadata] = [] + + init(cloudPath: CloudPath, canCreateFolder: Bool) { + self.canCreateFolder = canCreateFolder + self.cloudPath = cloudPath + } + + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) { + onChange() } + + func refreshItems() {} } +struct CreateNewVaultChooseFolderVCPreview: PreviewProvider { + static var previews: some View { + let viewController = CreateNewVaultChooseFolderViewController(with: CreateNewVaultChooseFolderViewModelMock(cloudPath: CloudPath("/Vault"), canCreateFolder: false)) + let vault = Item(type: .folder, path: CloudPath("/Vault/masterkey.cryptomator")) + viewController.showDetectedVault(vault) + return viewController.toPreview() + } +} #endif diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift index aaaf02ec8..87e883698 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift @@ -96,7 +96,7 @@ private class AuthenticatedCreateNewVaultCoordinator: FolderChoosing, VaultInsta func showItems(for path: CloudPath) { let viewModel = CreateNewVaultChooseFolderViewModel(vaultName: vaultName, cloudPath: path, provider: provider) - let chooseFolderVC = CreateNewVaultChooseFolderViewController(viewModel: viewModel) + let chooseFolderVC = CreateNewVaultChooseFolderViewController(with: viewModel) chooseFolderVC.coordinator = self navigationController.pushViewController(chooseFolderVC, animated: true) } diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift index f826c4981..7b271336a 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift @@ -121,6 +121,7 @@ class CreateNewVaultPasswordViewController: UITableViewController { #if DEBUG import Promises import SwiftUI + private class CreateNewVaultPasswordViewModelMock: CreateNewVaultPasswordViewModelProtocol { let vaultUID = "" let vaultName = "" diff --git a/Cryptomator/AddVault/DetectedVaultView.swift b/Cryptomator/AddVault/DetectedVaultView.swift index d7b1075a9..d2e15a513 100644 --- a/Cryptomator/AddVault/DetectedVaultView.swift +++ b/Cryptomator/AddVault/DetectedVaultView.swift @@ -7,6 +7,7 @@ // import UIKit + class DetectedVaultView: UIView { private lazy var label: UILabel = { let label = UILabel() @@ -27,12 +28,12 @@ class DetectedVaultView: UIView { addSubview(label) NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: topAnchor), imageView.centerXAnchor.constraint(equalTo: centerXAnchor), label.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + label.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), + imageView.topAnchor.constraint(equalTo: topAnchor, constant: 20), label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20), - label.bottomAnchor.constraint(equalTo: bottomAnchor), - label.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor) + label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20) ]) } diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift index 9d4707e1c..e517058a0 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift @@ -15,28 +15,12 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { override func viewDidLoad() { super.viewDidLoad() title = NSLocalizedString("addVault.openExistingVault.title", comment: "") + tableView.register(ButtonCell.self, forCellReuseIdentifier: "ButtonCell") } override func showDetectedVault(_ vault: Item) { self.vault = vault - let successView = SuccessView(viewModel: DetectedMasterkeyViewModel(masterkeyPath: vault.path)) - let containerView = UIView() - successView.translatesAutoresizingMaskIntoConstraints = false - containerView.addSubview(successView) - NSLayoutConstraint.activate([ - successView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), - successView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), - successView.leadingAnchor.constraint(equalTo: containerView.readableContentGuide.leadingAnchor), - successView.trailingAnchor.constraint(equalTo: containerView.readableContentGuide.trailingAnchor) - ]) - - // Prevents the view from being placed under the navigation bar - tableView.backgroundView = containerView - tableView.contentInsetAdjustmentBehavior = .never - tableView.separatorStyle = .none - - let addVaultButton = UIBarButtonItem(title: NSLocalizedString("common.button.add", comment: ""), style: .done, target: self, action: #selector(addVault)) - navigationItem.rightBarButtonItem = addVaultButton + refreshControl = nil navigationController?.setToolbarHidden(true, animated: true) } @@ -46,6 +30,46 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { } coordinator?.chooseItem(vault) } + + // MARK: - UITableViewDataSource + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if viewModel.foundMasterkey { + return 1 + } else { + return super.tableView(tableView, numberOfRowsInSection: section) + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if viewModel.foundMasterkey { + // swiftlint:disable:next force_cast + let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCell", for: indexPath) as! ButtonCell + cell.button.setTitle(NSLocalizedString("addVault.openExistingVault.detectedMasterkey.add", comment: ""), for: .normal) + cell.button.addTarget(self, action: #selector(addVault), for: .touchUpInside) + return cell + } else { + return super.tableView(tableView, cellForRowAt: indexPath) + } + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if viewModel.foundMasterkey, let vault = vault { + return SuccessView(viewModel: DetectedMasterkeyViewModel(masterkeyPath: vault.path)) + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if viewModel.foundMasterkey { + // do nothing + } else { + super.tableView(tableView, didSelectRowAt: indexPath) + } + } } private class SuccessView: DetectedVaultView { @@ -55,3 +79,34 @@ private class SuccessView: DetectedVaultView { super.init(imageView: imageView, text: viewModel.text) } } + +#if DEBUG +import SwiftUI + +private class OpenExistingVaultChooseFolderViewModelMock: ChooseFolderViewModelProtocol { + let foundMasterkey = true + let canCreateFolder: Bool + let cloudPath: CloudPath + let items: [CloudItemMetadata] = [] + + init(cloudPath: CloudPath, canCreateFolder: Bool) { + self.canCreateFolder = canCreateFolder + self.cloudPath = cloudPath + } + + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) { + onChange() + } + + func refreshItems() {} +} + +struct OpenExistingVaultChooseFolderVCPreview: PreviewProvider { + static var previews: some View { + let viewController = OpenExistingVaultChooseFolderViewController(with: OpenExistingVaultChooseFolderViewModelMock(cloudPath: CloudPath("/Vault"), canCreateFolder: false)) + let vault = Item(type: .folder, path: CloudPath("/Vault/masterkey.cryptomator")) + viewController.showDetectedVault(vault) + return viewController.toPreview() + } +} +#endif diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift index ef07c729b..9aebd7f91 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift @@ -52,7 +52,7 @@ class ChooseFolderViewController: SingleSectionTableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if let isToolbarHidden = navigationController?.isToolbarHidden, isToolbarHidden { + if let isToolbarHidden = navigationController?.isToolbarHidden, isToolbarHidden, !viewModel.foundMasterkey { navigationController?.setToolbarHidden(false, animated: animated) } } @@ -151,19 +151,18 @@ private class HeaderWithSearchbar: UITableViewHeaderFooterView { NSLayoutConstraint.activate([ searchBar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - searchBar.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), searchBar.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + searchBar.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), self.title.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), - self.title.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + self.title.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), self.title.topAnchor.constraint(equalTo: searchBar.bottomAnchor), - self.title.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor) + self.title.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor) ]) } } #if DEBUG -import CryptomatorCloudAccess import SwiftUI private class ChooseFolderViewModelMock: ChooseFolderViewModelProtocol { diff --git a/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift b/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift index 2b31fba6e..4eff4dd59 100644 --- a/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift @@ -7,6 +7,7 @@ // import UIKit + class CreateNewFolderViewController: SingleSectionHeaderTableViewController { weak var coordinator: (FolderCreating & Coordinator)? private var viewModel: CreateNewFolderViewModelProtocol @@ -73,6 +74,7 @@ class CreateNewFolderViewController: SingleSectionHeaderTableViewController { import CryptomatorCloudAccessCore import Promises import SwiftUI + private class CreateNewFolderViewModelMock: CreateNewFolderViewModelProtocol { var folderName: String? diff --git a/Cryptomator/Common/CloudAccountList/AccountListViewController.swift b/Cryptomator/Common/CloudAccountList/AccountListViewController.swift index 0ee51566d..00cfdb29e 100644 --- a/Cryptomator/Common/CloudAccountList/AccountListViewController.swift +++ b/Cryptomator/Common/CloudAccountList/AccountListViewController.swift @@ -119,10 +119,10 @@ class AccountListViewController: SingleSectionTableViewController { } #if DEBUG -import CryptomatorCloudAccess import CryptomatorCommonCore import Promises import SwiftUI + private class AccountListViewModelMock: AccountListViewModelProtocol { let cloudProviderType = CloudProviderType.googleDrive diff --git a/Cryptomator/VaultList/EmptyListMessage.swift b/Cryptomator/VaultList/EmptyListMessage.swift index 05e441204..7124dc7fb 100644 --- a/Cryptomator/VaultList/EmptyListMessage.swift +++ b/Cryptomator/VaultList/EmptyListMessage.swift @@ -78,7 +78,6 @@ class EmptyListMessage: UIView { } #if DEBUG -import CryptomatorCloudAccess import SwiftUI struct EmptyListMessagePreview: PreviewProvider { diff --git a/Cryptomator/de.lproj/Localizable.strings b/Cryptomator/de.lproj/Localizable.strings index f60ab0a49..9e93f3d8a 100644 --- a/Cryptomator/de.lproj/Localizable.strings +++ b/Cryptomator/de.lproj/Localizable.strings @@ -7,7 +7,6 @@ */ "common.alert.error.title" = "Fehler"; -"common.button.add" = "Hinzufügen"; "common.button.cancel" = "Abbrechen"; "common.button.choose" = "Auswählen"; "common.button.confirm" = "Bestätigen"; @@ -33,6 +32,7 @@ "addVault.openExistingVault.title" = "Existierenden Tresor öffnen"; "addVault.openExistingVault.chooseCloud.header" = "Wo befindet sich der Tresor?"; "addVault.openExistingVault.detectedMasterkey.text" = "Cryptomator hat den Tresor „%@“ erkannt.\nMöchtest du den Tresor hinzufügen?"; +"addVault.openExistingVault.detectedMasterkey.add" = "Diesen Tresor hinzufügen"; "addVault.openExistingVault.password.footer" = "Gib das Passwort für „%@“ ein"; "addVault.success.info" = "Tresor „%@“ erfolgreich hinzugefügt.\nGreife auf diesen Tresor über die Dateien-App zu."; "addVault.success.openFilesApp" = "Dateien-App öffnen"; diff --git a/Cryptomator/en.lproj/Localizable.strings b/Cryptomator/en.lproj/Localizable.strings index f1d103eb6..cef033f7f 100644 --- a/Cryptomator/en.lproj/Localizable.strings +++ b/Cryptomator/en.lproj/Localizable.strings @@ -7,7 +7,6 @@ */ "common.alert.error.title" = "Error"; -"common.button.add" = "Add"; "common.button.cancel" = "Cancel"; "common.button.choose" = "Choose"; "common.button.confirm" = "Confirm"; @@ -33,6 +32,7 @@ "addVault.openExistingVault.title" = "Open Existing Vault"; "addVault.openExistingVault.chooseCloud.header" = "Where is the vault located?"; "addVault.openExistingVault.detectedMasterkey.text" = "Cryptomator detected the vault \"%@\".\nWould you like to add this vault?"; +"addVault.openExistingVault.detectedMasterkey.add" = "Add This Vault"; "addVault.openExistingVault.password.footer" = "Enter password for \"%@\""; "addVault.success.info" = "Successfully added vault \"%@\".\nAccess this vault via the Files app."; "addVault.success.openFilesApp" = "Open Files App"; From ea0913ccbb2a6ba15aecbcde9323387c406234c7 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Thu, 24 Jun 2021 19:28:42 +0200 Subject: [PATCH 02/23] Added support for other file providers; vault names are now no longer necessarily derived from the vault path --- Cryptomator.xcodeproj/project.pbxproj | 40 ++++++ .../xcshareddata/swiftpm/Package.resolved | 4 +- .../CreateNewLocalVaultViewController.swift | 21 ++++ .../CreateNewLocalVaultViewModel.swift | 26 ++++ ...teNewVaultChooseFolderViewController.swift | 8 +- .../CreateNewVaultChooseFolderViewModel.swift | 6 +- .../CreateNewVaultCoordinator.swift | 62 +++++++--- ...ExistingLegacyVaultPasswordViewModel.swift | 11 +- .../OpenExistingLocalVaultViewModel.swift | 25 ++++ ...stingVaultChooseFolderViewController.swift | 18 +-- .../OpenExistingVaultCoordinator.swift | 62 +++++++--- .../OpenExistingVaultPasswordViewModel.swift | 11 +- .../ChooseFolderViewController.swift | 18 ++- .../ChooseFolder/ChooseFolderViewModel.swift | 30 +++-- .../Common/ChooseFolder/FolderChoosing.swift | 18 +-- Cryptomator/Common/VaultDetailItem.swift | 20 +++ .../LocalFileSystemAuthenticating.swift | 14 +++ ...leSystemAuthenticationViewController.swift | 116 ++++++++++++++++++ ...calFileSystemAuthenticationViewModel.swift | 44 +++++++ .../LocalFileSystemAuthenticator.swift | 59 +++++++++ Cryptomator/VaultList/VaultCell.swift | 4 +- Cryptomator/VaultList/VaultInfo.swift | 4 + .../VaultList/VaultListViewController.swift | 4 +- .../CryptomatorDatabase.swift | 1 + .../Manager/CloudProviderDBManager.swift | 7 +- .../Manager/VaultAccountDBManager.swift | 6 +- .../Manager/VaultDBManager.swift | 32 ++--- .../CryptomatorCommonCore/VaultItem.swift | 13 ++ .../Manager/VaultManagerTests.swift | 32 +++-- ...CreateNewVaultPasswordViewModelTests.swift | 4 +- CryptomatorTests/DatabaseManagerTests.swift | 16 +-- .../VaultListViewModelTests.swift | 4 +- 32 files changed, 612 insertions(+), 128 deletions(-) create mode 100644 Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift create mode 100644 Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift create mode 100644 Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift create mode 100644 Cryptomator/Common/VaultDetailItem.swift create mode 100644 Cryptomator/LocalFileSystem/LocalFileSystemAuthenticating.swift create mode 100644 Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift create mode 100644 Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift create mode 100644 Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift create mode 100644 CryptomatorCommon/Sources/CryptomatorCommonCore/VaultItem.swift diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 72bf6029e..ecf75209c 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 4A03255E25A368BF00E63D7A /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A03255D25A368BF00E63D7A /* MainCoordinator.swift */; }; 4A03257825A36A6900E63D7A /* VaultListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A03257725A36A6900E63D7A /* VaultListViewController.swift */; }; 4A03258125A36B7D00E63D7A /* UIViewController+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A03258025A36B7D00E63D7A /* UIViewController+Preview.swift */; }; + 4A09BFC62684D599000E40AB /* VaultDetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */; }; + 4A09BFCA2684EA8D000E40AB /* CreateNewLocalVaultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09BFC92684EA8D000E40AB /* CreateNewLocalVaultViewController.swift */; }; 4A0C07E225AC80C100B83211 /* UIView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */; }; 4A0C07EB25AC832900B83211 /* VaultListPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */; }; 4A123EA824BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A123EA724BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift */; }; @@ -34,6 +36,12 @@ 4A2FD08B25B5E437008565C8 /* OpenExistingVaultCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2FD08A25B5E437008565C8 /* OpenExistingVaultCoordinator.swift */; }; 4A3D6554267CF17B000DA764 /* CreateNewVaultPasswordViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D6553267CF17B000DA764 /* CreateNewVaultPasswordViewModelTests.swift */; }; 4A3D655F268099F9000DA764 /* VaultCoordinatorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D655E268099F9000DA764 /* VaultCoordinatorError.swift */; }; + 4A3D65612680A3CB000DA764 /* LocalFileSystemAuthenticating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */; }; + 4A3D65642680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */; }; + 4A3D65662680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */; }; + 4A3D657C26837D52000DA764 /* LocalFileSystemAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D657B26837D52000DA764 /* LocalFileSystemAuthenticator.swift */; }; + 4A3D658226838991000DA764 /* OpenExistingLocalVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */; }; + 4A3D658626847B11000DA764 /* CreateNewLocalVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */; }; 4A447DB325BEF68100D9520D /* CloudChoosing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A447DB225BEF68100D9520D /* CloudChoosing.swift */; }; 4A447DBC25BF003400D9520D /* ChooseCloudViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A447DBB25BF003400D9520D /* ChooseCloudViewModel.swift */; }; 4A447E0425BF0B0F00D9520D /* SingleSectionTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A447E0325BF0B0F00D9520D /* SingleSectionTableViewController.swift */; }; @@ -280,6 +288,8 @@ 4A03257725A36A6900E63D7A /* VaultListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultListViewController.swift; sourceTree = ""; }; 4A03258025A36B7D00E63D7A /* UIViewController+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Preview.swift"; sourceTree = ""; }; 4A0698692619EF9C00A67F30 /* CryptomatorCommon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CryptomatorCommon; sourceTree = ""; }; + 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailItem.swift; sourceTree = ""; }; + 4A09BFC92684EA8D000E40AB /* CreateNewLocalVaultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultViewController.swift; sourceTree = ""; }; 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Preview.swift"; sourceTree = ""; }; 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultListPosition.swift; sourceTree = ""; }; 4A123EA724BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudProviderPaginationMock.swift; sourceTree = ""; }; @@ -305,6 +315,12 @@ 4A2FD08A25B5E437008565C8 /* OpenExistingVaultCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingVaultCoordinator.swift; sourceTree = ""; }; 4A3D6553267CF17B000DA764 /* CreateNewVaultPasswordViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewVaultPasswordViewModelTests.swift; sourceTree = ""; }; 4A3D655E268099F9000DA764 /* VaultCoordinatorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultCoordinatorError.swift; sourceTree = ""; }; + 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticating.swift; sourceTree = ""; }; + 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticationViewController.swift; sourceTree = ""; }; + 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticationViewModel.swift; sourceTree = ""; }; + 4A3D657B26837D52000DA764 /* LocalFileSystemAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticator.swift; sourceTree = ""; }; + 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLocalVaultViewModel.swift; sourceTree = ""; }; + 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultViewModel.swift; sourceTree = ""; }; 4A447DB225BEF68100D9520D /* CloudChoosing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudChoosing.swift; sourceTree = ""; }; 4A447DBB25BF003400D9520D /* ChooseCloudViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseCloudViewModel.swift; sourceTree = ""; }; 4A447E0325BF0B0F00D9520D /* SingleSectionTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleSectionTableViewController.swift; sourceTree = ""; }; @@ -552,6 +568,17 @@ path = CryptomatorFileProviderTests; sourceTree = ""; }; + 4A3D65622680A3D0000DA764 /* LocalFileSystem */ = { + isa = PBXGroup; + children = ( + 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */, + 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */, + 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */, + 4A3D657B26837D52000DA764 /* LocalFileSystemAuthenticator.swift */, + ); + path = LocalFileSystem; + sourceTree = ""; + }; 4A447DEB25BF064300D9520D /* ChooseFolder */ = { isa = PBXGroup; children = ( @@ -664,7 +691,9 @@ 4A644B46267A3D43008CBB9A /* SetVaultNameViewModel.swift */, 4A53CC12267CC1C100853BB3 /* CreateNewVaultPasswordViewController.swift */, 4A53CC14267CC33100853BB3 /* CreateNewVaultPasswordViewModel.swift */, + 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */, 4A53CC16267CDBFF00853BB3 /* CreateNewVaultChooseFolderViewModel.swift */, + 4A09BFC92684EA8D000E40AB /* CreateNewLocalVaultViewController.swift */, ); path = CreateNewVault; sourceTree = ""; @@ -737,6 +766,7 @@ 4A7B97CA25B6F7340044B7FB /* CloudAccountList */, 7408E6BD2677835C00D7FAEA /* LocalWeb */, 4A8195E425ADB94500F7DDA1 /* Previews */, + 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */, ); path = Common; sourceTree = ""; @@ -831,6 +861,7 @@ 4AA8615025C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift */, 4A66F58A25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift */, 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */, + 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */, ); path = OpenExistingVault; sourceTree = ""; @@ -871,6 +902,7 @@ 4A7BC0E825ADF13100F007B3 /* AddVault */, 4A8195E325ADB92600F7DDA1 /* Common */, 4AE97DB524572E4A00452814 /* LaunchScreen.storyboard */, + 4A3D65622680A3D0000DA764 /* LocalFileSystem */, 7439031325E0008D00BB3B81 /* Localizable.strings */, 7408E6C8267797DC00D7FAEA /* Resources */, 740D367C266A18C80058744D /* Settings */, @@ -1417,6 +1449,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4A09BFCA2684EA8D000E40AB /* CreateNewLocalVaultViewController.swift in Sources */, 4A644B4F267B9E6A008CBB9A /* SetVaultNameCoordinator.swift in Sources */, 4A9D124F261F071F00A670E2 /* WebDAVAuthenticator+VC.swift in Sources */, 4A0C07E225AC80C100B83211 /* UIView+Preview.swift in Sources */, @@ -1439,6 +1472,7 @@ 4A447E4D25BF1E8B00D9520D /* FileCell.swift in Sources */, 4A644B44267A3BEC008CBB9A /* SetVaultNameViewController.swift in Sources */, 4A2FD04425B1C3BB008565C8 /* EmptyListMessage.swift in Sources */, + 4A09BFC62684D599000E40AB /* VaultDetailItem.swift in Sources */, 4A66F58B25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift in Sources */, 4A03258125A36B7D00E63D7A /* UIViewController+Preview.swift in Sources */, 4A7BC0E025ADF12D00F007B3 /* AddVaultViewController.swift in Sources */, @@ -1448,6 +1482,7 @@ 4AFCE53A25B9D6A60069C4FC /* CloudAuthenticator.swift in Sources */, 4A9D1247261E227600A670E2 /* WebDAVAuthenticationCoordinator.swift in Sources */, 4A644B55267C926A008CBB9A /* FolderCreating.swift in Sources */, + 4A3D658626847B11000DA764 /* CreateNewLocalVaultViewModel.swift in Sources */, 4AE97DAB24572E4900452814 /* AppDelegate.swift in Sources */, 4AA22C16261CA8D800A17486 /* URLFieldCell.swift in Sources */, 4A2FD07925B5D98B008565C8 /* CloudCell.swift in Sources */, @@ -1464,15 +1499,18 @@ 740D367E266A18DF0058744D /* SettingsViewController.swift in Sources */, 4AFCE51625B880C90069C4FC /* AccountCell.swift in Sources */, 4A8D061C25C84C450082C5F7 /* SingleSectionHeaderTableViewController.swift in Sources */, + 4A3D65662680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift in Sources */, 4A447E2425BF0E3A00D9520D /* ChooseFolderViewModel.swift in Sources */, 4AA22BFB261CA69F00A17486 /* WebDAVAuthenticationViewController.swift in Sources */, 4A9D123F261E1DD400A670E2 /* WebDAVAuthenticating.swift in Sources */, + 4A3D657C26837D52000DA764 /* LocalFileSystemAuthenticator.swift in Sources */, 4A53CC13267CC1C100853BB3 /* CreateNewVaultPasswordViewController.swift in Sources */, 4A03255525A3685500E63D7A /* Coordinator.swift in Sources */, 4A644B47267A3D43008CBB9A /* SetVaultNameViewModel.swift in Sources */, 4A53CC11267CBFA100853BB3 /* AddVaultSuccessCoordinator.swift in Sources */, 4A8D05D625C5CBE10082C5F7 /* AddVaultSuccessViewController.swift in Sources */, 4A0C07EB25AC832900B83211 /* VaultListPosition.swift in Sources */, + 4A3D658226838991000DA764 /* OpenExistingLocalVaultViewModel.swift in Sources */, 4A2FD08225B5E2BA008565C8 /* VaultInstalling.swift in Sources */, 4A8D05DF25C5CD210082C5F7 /* ButtonCell.swift in Sources */, 7408E6CD26779BCC00D7FAEA /* AboutViewModel.swift in Sources */, @@ -1482,6 +1520,7 @@ 4AFCE4FF25B871500069C4FC /* AccountCellContent.swift in Sources */, 4A2FD08B25B5E437008565C8 /* OpenExistingVaultCoordinator.swift in Sources */, 7469AD9A266E26B0000DCD45 /* URL+Zip.swift in Sources */, + 4A3D65642680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift in Sources */, 4A7B97DC25B6F80A0044B7FB /* AccountInfo.swift in Sources */, 4AA8614825C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift in Sources */, 4A447E5625BF1F6A00D9520D /* CloudItemCell.swift in Sources */, @@ -1493,6 +1532,7 @@ 4AF91CE225A7234500ACF01E /* DatabaseManager.swift in Sources */, 4AFCE51F25B89CD80069C4FC /* CloudProviderType+Localization.swift in Sources */, 4A447E0425BF0B0F00D9520D /* SingleSectionTableViewController.swift in Sources */, + 4A3D65612680A3CB000DA764 /* LocalFileSystemAuthenticating.swift in Sources */, 4A9D1237261DAC5D00A670E2 /* WebDAVAuthenticationViewModel.swift in Sources */, 4A753DB92678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift in Sources */, 7408E6C126778C7A00D7FAEA /* LocalWebViewModel.swift in Sources */, diff --git a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5f9ee1cf3..0f54d4aaa 100644 --- a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/cryptomator/cloud-access-swift.git", "state": { "branch": null, - "revision": "fb5090d76f1f8861f57d2186d9109449c7867918", - "version": "0.12.2" + "revision": "dfeb202bfc52d0e209d85d4865a62ee1a8bbbf9d", + "version": "0.12.3" } }, { diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift new file mode 100644 index 000000000..0653a5c1e --- /dev/null +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift @@ -0,0 +1,21 @@ +// +// CreateNewLocalVaultViewController.swift +// Cryptomator +// +// Created by Philipp Schmid on 24.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class CreateNewLocalVaultViewController: CreateNewVaultChooseFolderViewController { + override func onItemsChange() { + guard let viewModel = viewModel as? CreateNewVaultChooseFolderViewModelProtocol else { + return + } + do { + coordinator?.chooseItem(try viewModel.chooseCurrentFolder()) + } catch { + coordinator?.handleError(error: error) + } + } +} diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift new file mode 100644 index 000000000..e89329ed8 --- /dev/null +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift @@ -0,0 +1,26 @@ +// +// CreateNewLocalVaultViewModel.swift +// Cryptomator +// +// Created by Philipp Schmid on 24.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import Foundation +class CreateNewLocalVaultViewModel: CreateNewVaultChooseFolderViewModel { + let rootFolderName: String + + init(rootFolderName: String, vaultName: String, provider: LocalFileSystemProvider) { + self.rootFolderName = rootFolderName + super.init(vaultName: vaultName, cloudPath: CloudPath("/"), provider: provider) + } + + override var headerTitle: String { + return vaultName + } + + override func getVaultName(for cryptomatorFilePath: CloudPath) -> String { + return vaultName + } +} diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift index cc25cc947..7314b4ab3 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift @@ -22,7 +22,7 @@ class CreateNewVaultChooseFolderViewController: ChooseFolderViewController { toolbarItems?.append(createFolderButton) } - override func showDetectedVault(_ vault: Item) { + override func showDetectedVault(_ vault: VaultDetailItem) { let failureView = FailureView() let containerView = UIView() failureView.translatesAutoresizingMaskIntoConstraints = false @@ -70,6 +70,8 @@ import CryptomatorCloudAccessCore import SwiftUI private class CreateNewVaultChooseFolderViewModelMock: ChooseFolderViewModelProtocol { + var headerTitle: String = "/Vault" + let foundMasterkey = true let canCreateFolder: Bool let cloudPath: CloudPath @@ -80,7 +82,7 @@ private class CreateNewVaultChooseFolderViewModelMock: ChooseFolderViewModelProt self.cloudPath = cloudPath } - func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) { + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (VaultDetailItem) -> Void) { onChange() } @@ -90,7 +92,7 @@ private class CreateNewVaultChooseFolderViewModelMock: ChooseFolderViewModelProt struct CreateNewVaultChooseFolderVCPreview: PreviewProvider { static var previews: some View { let viewController = CreateNewVaultChooseFolderViewController(with: CreateNewVaultChooseFolderViewModelMock(cloudPath: CloudPath("/Vault"), canCreateFolder: false)) - let vault = Item(type: .folder, path: CloudPath("/Vault/masterkey.cryptomator")) + let vault = VaultDetailItem(name: "Vault", vaultPath: CloudPath("/Vault/masterkey.cryptomator"), isLegacyVault: false) viewController.showDetectedVault(vault) return viewController.toPreview() } diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift index 19b10eeb3..9bfad7a41 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift @@ -10,7 +10,7 @@ import CryptomatorCloudAccessCore import Foundation protocol CreateNewVaultChooseFolderViewModelProtocol: ChooseFolderViewModelProtocol { var vaultName: String { get } - func chooseCurrentFolder() throws -> Item + func chooseCurrentFolder() throws -> Folder } class CreateNewVaultChooseFolderViewModel: ChooseFolderViewModel, CreateNewVaultChooseFolderViewModelProtocol { @@ -21,12 +21,12 @@ class CreateNewVaultChooseFolderViewModel: ChooseFolderViewModel, CreateNewVault super.init(canCreateFolder: true, cloudPath: cloudPath, provider: provider) } - func chooseCurrentFolder() throws -> Item { + func chooseCurrentFolder() throws -> Folder { guard !items.contains(where: { $0.name == vaultName }) else { throw CreateNewVaultChooseFolderViewModelError.vaultNameCollision } let vaultPath = cloudPath.appendingPathComponent(vaultName) - return Item(type: .folder, path: vaultPath) + return Folder(path: vaultPath) } } diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift index 87e883698..b8cfaa8c0 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift @@ -6,6 +6,7 @@ // Copyright © 2021 Skymatic GmbH. All rights reserved. // +import CocoaLumberjackSwift import CryptomatorCloudAccessCore import CryptomatorCommonCore import UIKit @@ -32,16 +33,13 @@ class CreateNewVaultCoordinator: AccountListing, CloudChoosing, Coordinator { func showAccountList(for cloudProviderType: CloudProviderType) { if cloudProviderType == .localFileSystem { - let alertController = UIAlertController(title: "Info", message: NSLocalizedString("testFlight.otherFileProviders.alert.text", comment: ""), preferredStyle: .alert) - let okAction = UIAlertAction(title: NSLocalizedString("common.button.ok", comment: ""), style: .default) - alertController.addAction(okAction) - navigationController.present(alertController, animated: true, completion: nil) - return + startLocalFileSystemAuthenticationFlow() + } else { + let viewModel = AccountListViewModel(with: cloudProviderType) + let accountListVC = AccountListViewController(with: viewModel) + accountListVC.coordinator = self + navigationController.pushViewController(accountListVC, animated: true) } - let viewModel = AccountListViewModel(with: cloudProviderType) - let accountListVC = AccountListViewController(with: viewModel) - accountListVC.coordinator = self - navigationController.pushViewController(accountListVC, animated: true) } func showAddAccount(for cloudProviderType: CloudProviderType, from viewController: UIViewController) { @@ -70,6 +68,32 @@ class CreateNewVaultCoordinator: AccountListing, CloudChoosing, Coordinator { navigationController.dismiss(animated: true) parentCoordinator?.childDidFinish(self) } + + // MARK: - LocalFileSystemProvider Flow + + private func startLocalFileSystemAuthenticationFlow() { + LocalFileSystemAuthenticator.authenticateForOpenExistingVault(from: navigationController, onCompletion: { credential in + let account = CloudProviderAccount(accountUID: credential.identifier, cloudProviderType: .localFileSystem) + do { + try CloudProviderAccountDBManager.shared.saveNewAccount(account) + } catch { + DDLogError("startLocalFileSystemAuthenticationFlow saveNewAccount failed with:\(error)") + } + self.startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with: credential, account: account) + }) + } + + private func startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with credential: LocalFileSystemCredential, account: CloudProviderAccount) { + let provider = LocalFileSystemProvider(rootURL: credential.rootURL) + let child = AuthenticatedCreateNewVaultCoordinator(navigationController: navigationController, provider: provider, account: account, vaultName: vaultName) + childCoordinators.append(child) + child.parentCoordinator = self + + let viewModel = CreateNewLocalVaultViewModel(rootFolderName: credential.rootURL.lastPathComponent, vaultName: vaultName, provider: provider) + let chooseFolderVC = CreateNewLocalVaultViewController(with: viewModel) + chooseFolderVC.coordinator = child + navigationController.pushViewController(chooseFolderVC, animated: true) + } } private class AuthenticatedCreateNewVaultCoordinator: FolderChoosing, VaultInstalling, Coordinator { @@ -106,15 +130,14 @@ private class AuthenticatedCreateNewVaultCoordinator: FolderChoosing, VaultInsta } func chooseItem(_ item: Item) { - switch item.type { - case .folder: - let viewModel = CreateNewVaultPasswordViewModel(vaultPath: item.path, account: account, vaultUID: UUID().uuidString) - let passwordVC = CreateNewVaultPasswordViewController(viewModel: viewModel) - passwordVC.coordinator = self - navigationController.pushViewController(passwordVC, animated: true) - default: + guard let vaultFolder = item as? Folder else { handleError(VaultCoordinatorError.wrongItemType, for: navigationController) + return } + let viewModel = CreateNewVaultPasswordViewModel(vaultPath: vaultFolder.path, account: account, vaultUID: UUID().uuidString) + let passwordVC = CreateNewVaultPasswordViewController(viewModel: viewModel) + passwordVC.coordinator = self + navigationController.pushViewController(passwordVC, animated: true) } func showCreateNewFolder(parentPath: CloudPath) { @@ -126,6 +149,13 @@ private class AuthenticatedCreateNewVaultCoordinator: FolderChoosing, VaultInsta child.start() } + func handleError(error: Error) { + navigationController.popViewController(animated: true) + if let topViewController = navigationController.topViewController { + handleError(error, for: topViewController) + } + } + // MARK: - VaultInstalling func showSuccessfullyAddedVault(withName name: String, vaultUID: String) { diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift index 62fcb4ffa..3ee454ead 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift @@ -16,10 +16,9 @@ class OpenExistingLegacyVaultPasswordViewModel: OpenExistingVaultPasswordViewMod let account: CloudProviderAccount // later: localMasterkeyURL: URL instead of masterkeyPath: CloudPath - let masterkeyPath: CloudPath + let vault: VaultItem var vaultName: String { - let masterkeyParentPath = masterkeyPath.deletingLastPathComponent() - return masterkeyParentPath.lastPathComponent + return vault.name } var footerTitle: String { @@ -29,10 +28,10 @@ class OpenExistingLegacyVaultPasswordViewModel: OpenExistingVaultPasswordViewMod private let localMasterkeyURL: URL let vaultUID: String - init(provider: CloudProvider, account: CloudProviderAccount, masterkeyPath: CloudPath, vaultID: String) { + init(provider: CloudProvider, account: CloudProviderAccount, vault: VaultItem, vaultID: String) { self.provider = provider self.account = account - self.masterkeyPath = masterkeyPath + self.vault = vault let tmpDirURL = FileManager.default.temporaryDirectory self.localMasterkeyURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) self.vaultUID = vaultID @@ -42,6 +41,6 @@ class OpenExistingLegacyVaultPasswordViewModel: OpenExistingVaultPasswordViewMod guard let password = password else { return Promise(MasterkeyProcessingViewModelError.noPasswordSet) } - return VaultDBManager.shared.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, masterkeyPath: masterkeyPath, password: password, storePasswordInKeychain: true) + return VaultDBManager.shared.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultDetails: vault, password: password, storePasswordInKeychain: true) } } diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift new file mode 100644 index 000000000..7cf9235fc --- /dev/null +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift @@ -0,0 +1,25 @@ +// +// OpenExistingLocalVaultViewModel.swift +// Cryptomator +// +// Created by Philipp Schmid on 23.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import Foundation +class OpenExistingLocalVaultViewModel: ChooseFolderViewModel { + let rootFolderName: String + init(rootFolderName: String, provider: LocalFileSystemProvider) { + self.rootFolderName = rootFolderName + super.init(canCreateFolder: false, cloudPath: CloudPath("/"), provider: provider) + } + + override var headerTitle: String { + return CloudPath(rootFolderName).path + } + + override func getVaultName(for cryptomatorFilePath: CloudPath) -> String { + return rootFolderName + } +} diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift index e517058a0..8838d0392 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift @@ -7,10 +7,11 @@ // import CryptomatorCloudAccessCore +import CryptomatorCommonCore import UIKit class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { - private var vault: Item? + private var vault: VaultDetailItem? override func viewDidLoad() { super.viewDidLoad() @@ -18,7 +19,7 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { tableView.register(ButtonCell.self, forCellReuseIdentifier: "ButtonCell") } - override func showDetectedVault(_ vault: Item) { + override func showDetectedVault(_ vault: VaultDetailItem) { self.vault = vault refreshControl = nil navigationController?.setToolbarHidden(true, animated: true) @@ -57,7 +58,7 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if viewModel.foundMasterkey, let vault = vault { - return SuccessView(viewModel: DetectedMasterkeyViewModel(masterkeyPath: vault.path)) + return SuccessView(vaultName: vault.name) } else { return super.tableView(tableView, viewForHeaderInSection: section) } @@ -73,10 +74,11 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { } private class SuccessView: DetectedVaultView { - init(viewModel: DetectedMasterkeyViewModel) { + init(vaultName: String) { let botVaultImage = UIImage(named: "bot-vault") let imageView = UIImageView(image: botVaultImage) - super.init(imageView: imageView, text: viewModel.text) + let text = String(format: NSLocalizedString("addVault.openExistingVault.detectedMasterkey.text", comment: ""), vaultName) + super.init(imageView: imageView, text: text) } } @@ -84,6 +86,8 @@ private class SuccessView: DetectedVaultView { import SwiftUI private class OpenExistingVaultChooseFolderViewModelMock: ChooseFolderViewModelProtocol { + var headerTitle: String = "/Vault" + let foundMasterkey = true let canCreateFolder: Bool let cloudPath: CloudPath @@ -94,7 +98,7 @@ private class OpenExistingVaultChooseFolderViewModelMock: ChooseFolderViewModelP self.cloudPath = cloudPath } - func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) { + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (VaultDetailItem) -> Void) { onChange() } @@ -104,7 +108,7 @@ private class OpenExistingVaultChooseFolderViewModelMock: ChooseFolderViewModelP struct OpenExistingVaultChooseFolderVCPreview: PreviewProvider { static var previews: some View { let viewController = OpenExistingVaultChooseFolderViewController(with: OpenExistingVaultChooseFolderViewModelMock(cloudPath: CloudPath("/Vault"), canCreateFolder: false)) - let vault = Item(type: .folder, path: CloudPath("/Vault/masterkey.cryptomator")) + let vault = VaultDetailItem(name: "vault", vaultPath: CloudPath("/Vault/masterkey.cryptomator"), isLegacyVault: false) viewController.showDetectedVault(vault) return viewController.toPreview() } diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift index 3526d7ca2..22391ae58 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift @@ -6,6 +6,7 @@ // Copyright © 2021 Skymatic GmbH. All rights reserved. // +import CocoaLumberjackSwift import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation @@ -30,16 +31,13 @@ class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, Coordinator { func showAccountList(for cloudProviderType: CloudProviderType) { if cloudProviderType == .localFileSystem { - let alertController = UIAlertController(title: "Info", message: NSLocalizedString("testFlight.otherFileProviders.alert.text", comment: ""), preferredStyle: .alert) - let okAction = UIAlertAction(title: NSLocalizedString("common.button.ok", comment: ""), style: .default) - alertController.addAction(okAction) - navigationController.present(alertController, animated: true, completion: nil) - return + startLocalFileSystemAuthenticationFlow() + } else { + let viewModel = AccountListViewModel(with: cloudProviderType) + let accountListVC = AccountListViewController(with: viewModel) + accountListVC.coordinator = self + navigationController.pushViewController(accountListVC, animated: true) } - let viewModel = AccountListViewModel(with: cloudProviderType) - let accountListVC = AccountListViewController(with: viewModel) - accountListVC.coordinator = self - navigationController.pushViewController(accountListVC, animated: true) } func showAddAccount(for cloudProviderType: CloudProviderType, from viewController: UIViewController) { @@ -68,6 +66,32 @@ class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, Coordinator { navigationController.dismiss(animated: true) parentCoordinator?.close() } + + // MARK: - LocalFileSystemProvider Flow + + private func startLocalFileSystemAuthenticationFlow() { + LocalFileSystemAuthenticator.authenticateForOpenExistingVault(from: navigationController, onCompletion: { credential in + let account = CloudProviderAccount(accountUID: credential.identifier, cloudProviderType: .localFileSystem) + do { + try CloudProviderAccountDBManager.shared.saveNewAccount(account) + } catch { + DDLogError("startLocalFileSystemAuthenticationFlow saveNewAccount failed with:\(error)") + } + self.startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with: credential, account: account) + }) + } + + private func startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with credential: LocalFileSystemCredential, account: CloudProviderAccount) { + let provider = LocalFileSystemProvider(rootURL: credential.rootURL) + let child = AuthenticatedOpenExistingVaultCoordinator(navigationController: navigationController, provider: provider, account: account) + childCoordinators.append(child) + child.parentCoordinator = self + + let viewModel = OpenExistingLocalVaultViewModel(rootFolderName: credential.rootURL.lastPathComponent, provider: provider) + let chooseFolderVC = OpenExistingVaultChooseFolderViewController(with: viewModel) + chooseFolderVC.coordinator = child + navigationController.pushViewController(chooseFolderVC, animated: true) + } } private class AuthenticatedOpenExistingVaultCoordinator: VaultInstalling, FolderChoosing, Coordinator { @@ -103,15 +127,16 @@ private class AuthenticatedOpenExistingVaultCoordinator: VaultInstalling, Folder func chooseItem(_ item: Item) { let viewModel: OpenExistingVaultPasswordViewModelProtocol - switch item.type { - case .vaultConfig: - viewModel = OpenExistingVaultPasswordViewModel(provider: provider, account: account, vaultConfigPath: item.path, vaultUID: UUID().uuidString) - case .legacyMasterkey: - viewModel = OpenExistingLegacyVaultPasswordViewModel(provider: provider, account: account, masterkeyPath: item.path, vaultID: UUID().uuidString) - default: + guard let vaultItem = item as? VaultDetailItem else { handleError(VaultCoordinatorError.wrongItemType, for: navigationController) return } + if vaultItem.isLegacyVault { + viewModel = OpenExistingLegacyVaultPasswordViewModel(provider: provider, account: account, vault: vaultItem, vaultID: UUID().uuidString) + } else { + viewModel = OpenExistingVaultPasswordViewModel(provider: provider, account: account, vault: vaultItem, vaultUID: UUID().uuidString) + } + let passwordVC = OpenExistingVaultPasswordViewController(viewModel: viewModel) passwordVC.coordinator = self navigationController.pushViewController(passwordVC, animated: true) @@ -119,6 +144,13 @@ private class AuthenticatedOpenExistingVaultCoordinator: VaultInstalling, Folder func showCreateNewFolder(parentPath: CloudPath) {} + func handleError(error: Error) { + navigationController.popViewController(animated: true) + if let topViewController = navigationController.topViewController { + handleError(error, for: topViewController) + } + } + // MARK: - VaultInstalling func showSuccessfullyAddedVault(withName name: String, vaultUID: String) { diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift index d7182f90d..2c8957844 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift @@ -26,10 +26,9 @@ class OpenExistingVaultPasswordViewModel: OpenExistingVaultPasswordViewModelProt let account: CloudProviderAccount // later: localMasterkeyURL: URL instead of masterkeyPath: CloudPath - let vaultConfigPath: CloudPath + let vault: VaultItem var vaultName: String { - let vaultConfigParentPath = vaultConfigPath.deletingLastPathComponent() - return vaultConfigParentPath.lastPathComponent + return vault.name } var footerTitle: String { @@ -38,10 +37,10 @@ class OpenExistingVaultPasswordViewModel: OpenExistingVaultPasswordViewModelProt let vaultUID: String - init(provider: CloudProvider, account: CloudProviderAccount, vaultConfigPath: CloudPath, vaultUID: String) { + init(provider: CloudProvider, account: CloudProviderAccount, vault: VaultItem, vaultUID: String) { self.provider = provider self.account = account - self.vaultConfigPath = vaultConfigPath + self.vault = vault self.vaultUID = vaultUID } @@ -49,7 +48,7 @@ class OpenExistingVaultPasswordViewModel: OpenExistingVaultPasswordViewModelProt guard let password = password else { return Promise(MasterkeyProcessingViewModelError.noPasswordSet) } - return VaultDBManager.shared.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultConfigPath: vaultConfigPath, password: password, storePasswordInKeychain: true) + return VaultDBManager.shared.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultDetails: vault, password: password, storePasswordInKeychain: true) } } diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift index 9aebd7f91..6a9d93a3c 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift @@ -7,6 +7,7 @@ // import CryptomatorCloudAccessCore +import CryptomatorCommonCore import UIKit class ChooseFolderViewController: SingleSectionTableViewController { @@ -40,9 +41,7 @@ class ChooseFolderViewController: SingleSectionTableViewController { guard let self = self else { return } self.coordinator?.handleError(error, for: self) } onChange: { [weak self] in - guard let self = self else { return } - self.refreshControl?.endRefreshing() - self.tableView.reloadData() + self?.onItemsChange() } onVaultDetection: { [weak self] vault in guard let self = self else { return } self.tableView.reloadData() @@ -64,10 +63,15 @@ class ChooseFolderViewController: SingleSectionTableViewController { } } - func showDetectedVault(_ vault: Item) { + func showDetectedVault(_ vault: VaultDetailItem) { fatalError("not implemented") } + func onItemsChange() { + refreshControl?.endRefreshing() + tableView.reloadData() + } + @objc func cancel() { coordinator?.close() } @@ -166,6 +170,10 @@ private class HeaderWithSearchbar: UITableViewHeaderFooterView { import SwiftUI private class ChooseFolderViewModelMock: ChooseFolderViewModelProtocol { + var headerTitle: String { + cloudPath.path + } + let foundMasterkey = false let canCreateFolder: Bool let cloudPath: CloudPath @@ -179,7 +187,7 @@ private class ChooseFolderViewModelMock: ChooseFolderViewModelProtocol { self.cloudPath = cloudPath } - func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) { + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (VaultDetailItem) -> Void) { onChange() } diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift index 7420e1b14..9b5d92ad4 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift @@ -9,33 +9,32 @@ import CocoaLumberjack import CocoaLumberjackSwift import CryptomatorCloudAccessCore +import CryptomatorCommonCore import Foundation protocol ChooseFolderViewModelProtocol { var canCreateFolder: Bool { get } var cloudPath: CloudPath { get } var foundMasterkey: Bool { get } + var headerTitle: String { get } var items: [CloudItemMetadata] { get } - func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (VaultDetailItem) -> Void) func refreshItems() } -extension ChooseFolderViewModelProtocol { - var headerTitle: String { - return cloudPath.path - } -} - class ChooseFolderViewModel: ChooseFolderViewModelProtocol { var canCreateFolder: Bool var cloudPath: CloudPath var items = [CloudItemMetadata]() var foundMasterkey = false + var headerTitle: String { + return cloudPath.path + } private let provider: CloudProvider private var errorListener: ((Error) -> Void)? private var changeListener: (() -> Void)? - private var vaultListener: ((Item) -> Void)? + private var vaultListener: ((VaultDetailItem) -> Void)? init(canCreateFolder: Bool, cloudPath: CloudPath, provider: CloudProvider) { self.canCreateFolder = canCreateFolder @@ -43,7 +42,7 @@ class ChooseFolderViewModel: ChooseFolderViewModelProtocol { self.provider = provider } - func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (Item) -> Void) { + func startListenForChanges(onError: @escaping (Error) -> Void, onChange: @escaping () -> Void, onVaultDetection: @escaping (VaultDetailItem) -> Void) { errorListener = onError changeListener = onChange vaultListener = onVaultDetection @@ -65,11 +64,13 @@ class ChooseFolderViewModel: ChooseFolderViewModelProtocol { } } - func getVaultItem(items: [CloudItemMetadata]) -> Item? { + func getVaultItem(items: [CloudItemMetadata]) -> VaultDetailItem? { if let vaultConfigPath = getVaultConfigCloudPath(items: items) { - return Item(type: .vaultConfig, path: vaultConfigPath) + let vaultName = getVaultName(for: vaultConfigPath) + return VaultDetailItem(name: vaultName, vaultPath: cloudPath, isLegacyVault: false) } else if let legacyMasterkeyPath = getLegacyMasterkeyPath(items: items) { - return Item(type: .legacyMasterkey, path: legacyMasterkeyPath) + let vaultName = getVaultName(for: legacyMasterkeyPath) + return VaultDetailItem(name: vaultName, vaultPath: cloudPath, isLegacyVault: true) } else { return nil } @@ -92,4 +93,9 @@ class ChooseFolderViewModel: ChooseFolderViewModelProtocol { } return masterkeyItem?.cloudPath } + + func getVaultName(for cryptomatorFilePath: CloudPath) -> String { + let parentPath = cryptomatorFilePath.deletingLastPathComponent() + return parentPath.lastPathComponent + } } diff --git a/Cryptomator/Common/ChooseFolder/FolderChoosing.swift b/Cryptomator/Common/ChooseFolder/FolderChoosing.swift index d74b9ba33..36d9b3596 100644 --- a/Cryptomator/Common/ChooseFolder/FolderChoosing.swift +++ b/Cryptomator/Common/ChooseFolder/FolderChoosing.swift @@ -13,15 +13,19 @@ protocol FolderChoosing: AnyObject { func close() func chooseItem(_ item: Item) func showCreateNewFolder(parentPath: CloudPath) + func handleError(error: Error) } -struct Item { - let type: ItemType - let path: CloudPath +protocol Item { + var path: CloudPath { get } } -enum ItemType { - case folder - case vaultConfig - case legacyMasterkey +struct Folder: Item { + let path: CloudPath } + +// enum ItemType { +// case folder +// case vaultConfig +// case legacyMasterkey +// } diff --git a/Cryptomator/Common/VaultDetailItem.swift b/Cryptomator/Common/VaultDetailItem.swift new file mode 100644 index 000000000..5f1cc518b --- /dev/null +++ b/Cryptomator/Common/VaultDetailItem.swift @@ -0,0 +1,20 @@ +// +// VaultDetailItem.swift +// Cryptomator +// +// Created by Philipp Schmid on 24.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +struct VaultDetailItem: Item, VaultItem { + var path: CloudPath { + return vaultPath + } + + let name: String + let vaultPath: CloudPath + let isLegacyVault: Bool +} diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticating.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticating.swift new file mode 100644 index 000000000..cb5d72433 --- /dev/null +++ b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticating.swift @@ -0,0 +1,14 @@ +// +// LocalFileSystemAuthenticating.swift +// Cryptomator +// +// Created by Philipp Schmid on 21.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +protocol LocalFileSystemAuthenticating { + func authenticated(credential: LocalFileSystemCredential) +} diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift new file mode 100644 index 000000000..f1de41cff --- /dev/null +++ b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift @@ -0,0 +1,116 @@ +// +// LocalFileSystemAuthenticationViewController.swift +// Cryptomator +// +// Created by Philipp Schmid on 21.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import MobileCoreServices +import UIKit +class LocalFileSystemAuthenticationViewController: SingleSectionTableViewController, UIDocumentPickerDelegate { + weak var coordinator: (LocalFileSystemAuthenticating & Coordinator)? + private let viewModel: LocalFileSystemAuthenticationViewModelProtocol + private lazy var openDocumentPickerCell: ButtonCell = { + let cell = ButtonCell() + cell.button.setTitle(viewModel.documentPickerButtonText, for: .normal) + cell.button.addTarget(self, action: #selector(openDocumentPicker), for: .touchUpInside) + return cell + }() + + init(viewModel: LocalFileSystemAuthenticationViewModelProtocol) { + self.viewModel = viewModel + super.init() + } + + @objc func openDocumentPicker() { + let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open) + documentPicker.allowsMultipleSelection = false + documentPicker.delegate = self + present(documentPicker, animated: true) + } + + // MARK: - UIDocumentPickerDelegate + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + do { + let credential = try viewModel.userPicked(urls: urls) + coordinator?.authenticated(credential: credential) + } catch { + #warning("TODO: Add coordinator for error handling") + print(error) + } + } + + // MARK: - UITableViewDataSource + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return openDocumentPickerCell + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return LocalFileSystemAuthenticationHeaderView(text: viewModel.headerText) + } +} + +private class LocalFileSystemAuthenticationHeaderView: UIView { + private lazy var image: UIImageView = { + let image = UIImage(named: "bot-vault") + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFit + return imageView + }() + + private lazy var infoText: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.numberOfLines = 0 + return label + }() + + init(text: String) { + super.init(frame: .zero) + infoText.text = text + let stack = UIStackView(arrangedSubviews: [image, infoText]) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 20 + addSubview(stack) + + NSLayoutConstraint.activate([ + stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + stack.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 12), + stack.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: -12) + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG +import CryptomatorCommonCore +import SwiftUI +struct LocalFileSystemViewModelMock: LocalFileSystemAuthenticationViewModelProtocol { + let documentPickerButtonText = "Select Storage Location" + let headerText = "In the next screen, choose the storage location for your new vault." + func userPicked(urls: [URL]) throws -> LocalFileSystemCredential { + fatalError("Not mocked") + } +} + +struct LocalFileSystemAuthenticationVCPreview: PreviewProvider { + static var previews: some View { + LocalFileSystemAuthenticationViewController(viewModel: LocalFileSystemViewModelMock()).toPreview() + } +} +#endif diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift new file mode 100644 index 000000000..ae53f22e8 --- /dev/null +++ b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift @@ -0,0 +1,44 @@ +// +// LocalFileSystemAuthenticationViewModel.swift +// Cryptomator +// +// Created by Philipp Schmid on 21.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +protocol LocalFileSystemAuthenticationViewModelProtocol { + var documentPickerButtonText: String { get } + var headerText: String { get } + func userPicked(urls: [URL]) throws -> LocalFileSystemCredential +} + +class LocalFileSystemAuthenticationViewModel: LocalFileSystemAuthenticationViewModelProtocol { + let documentPickerButtonText: String + let headerText: String + + init(documentPickerButtonText: String, headerText: String) { + self.documentPickerButtonText = documentPickerButtonText + self.headerText = headerText + } + + func userPicked(urls: [URL]) throws -> LocalFileSystemCredential { + guard let rootURL = urls.first else { + throw LocalFileSystemAuthenticationViewModelError.invalidURL + } + let credential = LocalFileSystemCredential(rootURL: rootURL, identifier: UUID().uuidString) + try LocalFileSystemBookmarkManager.saveBookmarkForRootURL(credential.rootURL, for: credential.identifier) + return credential + } +} + +struct LocalFileSystemCredential { + let rootURL: URL + let identifier: String +} + +enum LocalFileSystemAuthenticationViewModelError: Error { + case invalidURL +} diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift new file mode 100644 index 000000000..a1a815151 --- /dev/null +++ b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift @@ -0,0 +1,59 @@ +// +// LocalFileSystemAuthenticator.swift +// Cryptomator +// +// Created by Philipp Schmid on 23.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCommonCore +import Promises +import UIKit +class LocalFileSystemAuthenticator { + private static var coordinator: LocalFileSystemCoordinator? + + static func authenticateForOpenExistingVault(from navigationController: UINavigationController, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { + let documentPickerButtonText = "Select Vault Folder" + let headerText = "In the next screen, choose the storage location for your new vault." + let viewModel = LocalFileSystemAuthenticationViewModel(documentPickerButtonText: documentPickerButtonText, headerText: headerText) + authenticate(from: navigationController, viewModel: viewModel, onCompletion: onCompletion) + } + + static func authenticateForCreateNewVault(from navigationController: UINavigationController, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { + let documentPickerButtonText = "Select Storage Location" + let headerText = "In the next screen, choose the folder of your existing vault." + let viewModel = LocalFileSystemAuthenticationViewModel(documentPickerButtonText: documentPickerButtonText, headerText: headerText) + authenticate(from: navigationController, viewModel: viewModel, onCompletion: onCompletion) + } + + static func authenticate(from navigationController: UINavigationController, viewModel: LocalFileSystemAuthenticationViewModelProtocol, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { + let coordinator = LocalFileSystemCoordinator(navigationController: navigationController, viewModel: viewModel, onAuthenticated: onCompletion) + self.coordinator = coordinator + coordinator.start() + } +} + +private class LocalFileSystemCoordinator: Coordinator, LocalFileSystemAuthenticating { + var childCoordinators = [Coordinator]() + + var navigationController: UINavigationController + + private let viewModel: LocalFileSystemAuthenticationViewModelProtocol + let onAuthenticated: (LocalFileSystemCredential) -> Void + + init(navigationController: UINavigationController, viewModel: LocalFileSystemAuthenticationViewModelProtocol, onAuthenticated: @escaping (LocalFileSystemCredential) -> Void) { + self.navigationController = navigationController + self.viewModel = viewModel + self.onAuthenticated = onAuthenticated + } + + func start() { + let localFSAuthVC = LocalFileSystemAuthenticationViewController(viewModel: viewModel) + localFSAuthVC.coordinator = self + navigationController.pushViewController(localFSAuthVC, animated: true) + } + + func authenticated(credential: LocalFileSystemCredential) { + onAuthenticated(credential) + } +} diff --git a/Cryptomator/VaultList/VaultCell.swift b/Cryptomator/VaultList/VaultCell.swift index 709d8f5f7..92715668c 100644 --- a/Cryptomator/VaultList/VaultCell.swift +++ b/Cryptomator/VaultList/VaultCell.swift @@ -25,7 +25,7 @@ class VaultCell: UITableViewCell { func configure(with vault: VaultInfo) { imageView?.image = UIImage(vaultIconFor: vault.cloudProviderType, state: .normal) imageView?.highlightedImage = UIImage(vaultIconFor: vault.cloudProviderType, state: .highlighted) - textLabel?.text = vault.vaultPath.lastPathComponent + textLabel?.text = vault.vaultName detailTextLabel?.text = vault.vaultPath.path detailTextLabel?.textColor = UIColor(named: "secondaryLabel") } @@ -41,7 +41,7 @@ class VaultCell: UITableViewCell { } else { content.image = UIImage(vaultIconFor: vault.cloudProviderType, state: .normal) } - content.text = vault.vaultPath.lastPathComponent + content.text = vault.vaultName content.secondaryText = vault.vaultPath.path content.secondaryTextProperties.color = .secondaryLabel contentConfiguration = content diff --git a/Cryptomator/VaultList/VaultInfo.swift b/Cryptomator/VaultList/VaultInfo.swift index 068f735e3..5c5edd967 100644 --- a/Cryptomator/VaultList/VaultInfo.swift +++ b/Cryptomator/VaultList/VaultInfo.swift @@ -26,6 +26,10 @@ public struct VaultInfo: Decodable, FetchableRecord { return vaultAccount.vaultPath } + var vaultName: String { + return vaultAccount.vaultName + } + var vaultUID: String { return vaultAccount.vaultUID } diff --git a/Cryptomator/VaultList/VaultListViewController.swift b/Cryptomator/VaultList/VaultListViewController.swift index a0a7d0038..393f00a6d 100644 --- a/Cryptomator/VaultList/VaultListViewController.swift +++ b/Cryptomator/VaultList/VaultListViewController.swift @@ -143,8 +143,8 @@ import SwiftUI private class VaultListViewModelMock: VaultListViewModelProtocol { let vaults = [ - VaultInfo(vaultAccount: VaultAccount(vaultUID: "1", delegateAccountUID: "1", vaultPath: CloudPath("/Work")), cloudProviderAccount: CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV), vaultListPosition: VaultListPosition(position: 1, vaultUID: "1")), - VaultInfo(vaultAccount: VaultAccount(vaultUID: "2", delegateAccountUID: "2", vaultPath: CloudPath("/Family")), cloudProviderAccount: CloudProviderAccount(accountUID: "2", cloudProviderType: .googleDrive), vaultListPosition: VaultListPosition(position: 2, vaultUID: "2")) + VaultInfo(vaultAccount: VaultAccount(vaultUID: "1", delegateAccountUID: "1", vaultPath: CloudPath("/Work"), vaultName: "Work"), cloudProviderAccount: CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV), vaultListPosition: VaultListPosition(position: 1, vaultUID: "1")), + VaultInfo(vaultAccount: VaultAccount(vaultUID: "2", delegateAccountUID: "2", vaultPath: CloudPath("/Family"), vaultName: "Family"), cloudProviderAccount: CloudProviderAccount(accountUID: "2", cloudProviderType: .googleDrive), vaultListPosition: VaultListPosition(position: 2, vaultUID: "2")) ] func refreshItems() throws {} diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift index 102740e79..7ff6d2a9a 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift @@ -40,6 +40,7 @@ public class CryptomatorDatabase { table.column("vaultUID", .text).primaryKey() table.column("delegateAccountUID", .text).notNull().references("cloudProviderAccounts") table.column("vaultPath", .text).notNull() + table.column("vaultName", .text).notNull() table.column("lastUpToDateCheck", .date).notNull() } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift index f98c0fe91..8e870c318 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift @@ -54,8 +54,11 @@ public class CloudProviderDBManager: CloudProviderManager { client = WebDAVClient(credential: credential) } provider = WebDAVProvider(with: client) - default: - throw CloudProviderAccountError.accountNotFoundError + case .localFileSystem: + guard let rootURL = try LocalFileSystemBookmarkManager.getBookmarkedRootURL(for: accountUID) else { + throw CloudProviderAccountError.accountNotFoundError + } + provider = LocalFileSystemProvider(rootURL: rootURL) } CloudProviderDBManager.cachedProvider[accountUID] = provider return provider diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultAccountDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultAccountDBManager.swift index 824a817e2..e99cd1bbf 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultAccountDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultAccountDBManager.swift @@ -16,17 +16,20 @@ public struct VaultAccount: Decodable, FetchableRecord, TableRecord { static let delegateAccountUIDKey = "delegateAccountUID" static let vaultPathKey = "vaultPath" static let lastUpToDateCheckKey = "lastUpToDateCheck" + static let vaultNameKey = "vaultName" public let vaultUID: String let delegateAccountUID: String public let vaultPath: CloudPath + public let vaultName: String let lastUpToDateCheck: Date public static let delegateAccount = belongsTo(CloudProviderAccount.self) - public init(vaultUID: String, delegateAccountUID: String, vaultPath: CloudPath, lastUpToDateCheck: Date = Date()) { + public init(vaultUID: String, delegateAccountUID: String, vaultPath: CloudPath, vaultName: String, lastUpToDateCheck: Date = Date()) { self.vaultUID = vaultUID self.delegateAccountUID = delegateAccountUID self.vaultPath = vaultPath + self.vaultName = vaultName self.lastUpToDateCheck = lastUpToDateCheck } } @@ -36,6 +39,7 @@ extension VaultAccount: PersistableRecord { container[VaultAccount.vaultUIDKey] = vaultUID container[VaultAccount.delegateAccountUIDKey] = delegateAccountUID container[VaultAccount.vaultPathKey] = vaultPath + container[VaultAccount.vaultNameKey] = vaultName container[VaultAccount.lastUpToDateCheckKey] = lastUpToDateCheck } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index cc52ffe8e..ff1207a22 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -30,8 +30,8 @@ public protocol VaultManager { func createNewVault(withVaultUID vaultUID: String, delegateAccountUID: String, vaultPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise func manualUnlockVault(withUID vaultUID: String, password: String) throws -> CloudProvider func getDecorator(forVaultUID vaultUID: String) throws -> CloudProvider - func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultConfigPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise - func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, masterkeyPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise + func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise + func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise func removeVault(withUID vaultUID: String) throws -> Promise func removeAllUnusedFileProviderDomains() -> Promise func getVaultPath(from masterkeyPath: CloudPath) -> CloudPath @@ -88,12 +88,12 @@ public class VaultDBManager: VaultManager { }.then { _ -> Void in let unverifiedVaultConfig = try UnverifiedVaultConfig(token: vaultConfigToken) let decorator = try VaultProviderFactory.createVaultProvider(from: unverifiedVaultConfig, masterkey: masterkey, vaultPath: vaultPath, with: delegate) - let account = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let account = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultPath.lastPathComponent, lastUpToDateCheck: Date()) try self.vaultAccountManager.saveNewAccount(account) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultConfigToken: vaultConfigToken, password: password, storePasswordInKeychain: storePasswordInKeychain) VaultDBManager.cachedDecorators[vaultUID] = decorator }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, vaultPath: vaultPath) + self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultPath.lastPathComponent) } } @@ -175,7 +175,7 @@ public class VaultDBManager: VaultManager { // MARK: Open Existing Vault - public func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultConfigPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise { + public func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { let delegate: CloudProvider do { guard VaultDBManager.cachedDecorators[vaultUID] == nil else { @@ -188,7 +188,9 @@ public class VaultDBManager: VaultManager { let tmpDirURL = FileManager.default.temporaryDirectory let localVaultConfigURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) let localMasterkeyURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) - let masterkeyPath = vaultConfigPath.deletingLastPathComponent().appendingPathComponent("masterkey.cryptomator") + let vaultPath = vaultDetails.vaultPath + let vaultConfigPath = vaultPath.appendingPathComponent("vault.cryptomator") + let masterkeyPath = vaultPath.appendingPathComponent("masterkey.cryptomator") return delegate.downloadFile(from: vaultConfigPath, to: localVaultConfigURL).then { delegate.downloadFile(from: masterkeyPath, to: localMasterkeyURL) }.then { @@ -196,14 +198,13 @@ public class VaultDBManager: VaultManager { let unverifiedVaultConfig = try UnverifiedVaultConfig(token: token) let masterkeyFile = try MasterkeyFile.withContentFromURL(url: localMasterkeyURL) let masterkey = try masterkeyFile.unlock(passphrase: password) - let vaultPath = self.getVaultPath(from: masterkeyPath) let vaultProvider = try VaultProviderFactory.createVaultProvider(from: unverifiedVaultConfig, masterkey: masterkey, vaultPath: vaultPath, with: delegate) - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultDetails.name, lastUpToDateCheck: Date()) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultConfigToken: token, password: password, storePasswordInKeychain: storePasswordInKeychain) try self.vaultAccountManager.saveNewAccount(vaultAccount) VaultDBManager.cachedDecorators[vaultUID] = vaultProvider }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, vaultPath: self.getVaultPath(from: masterkeyPath)) + self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultDetails.name) }.catch { _ in VaultDBManager.cachedDecorators[vaultUID] = nil } @@ -217,7 +218,7 @@ public class VaultDBManager: VaultManager { - Postcondition: The passed `vaultUID`, `delegateAccountUID` and the `vaultPath` derived from `masterkeyPath` are stored as VaultAccount in the database - Postcondition: The created VaultDecorator is cached under the corresponding `vaultUID` */ - public func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, masterkeyPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise { + public func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { do { guard VaultDBManager.cachedDecorators[vaultUID] == nil else { throw VaultManagerError.vaultAlreadyExists @@ -225,16 +226,17 @@ public class VaultDBManager: VaultManager { let delegate = try providerManager.getProvider(with: delegateAccountUID) let tmpDirURL = FileManager.default.temporaryDirectory let localMasterkeyURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) + let vaultPath = vaultDetails.vaultPath + let masterkeyPath = vaultPath.appendingPathComponent("masterkey.cryptomator") return delegate.downloadFile(from: masterkeyPath, to: localMasterkeyURL).then { let masterkeyFile = try MasterkeyFile.withContentFromURL(url: localMasterkeyURL) let masterkey = try masterkeyFile.unlock(passphrase: password) - let vaultPath = self.getVaultPath(from: masterkeyPath) _ = try self.createLegacyVaultDecorator(from: masterkey, delegate: delegate, vaultPath: vaultPath, vaultUID: vaultUID, vaultVersion: masterkeyFile.version) - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultDetails.name, lastUpToDateCheck: Date()) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultVersion: masterkeyFile.version, password: password, storePasswordInKeychain: storePasswordInKeychain) try self.vaultAccountManager.saveNewAccount(vaultAccount) }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, vaultPath: self.getVaultPath(from: masterkeyPath)) + self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultDetails.name) } } catch { VaultDBManager.cachedDecorators[vaultUID] = nil @@ -346,9 +348,9 @@ public class VaultDBManager: VaultManager { } } - func addFileProviderDomain(forVaultUID vaultUID: String, vaultPath: CloudPath) -> Promise { + func addFileProviderDomain(forVaultUID vaultUID: String, displayName: String) -> Promise { let identifier = NSFileProviderDomainIdentifier(vaultUID) - let domain = NSFileProviderDomain(identifier: identifier, displayName: vaultPath.lastPathComponent, pathRelativeToDocumentStorage: vaultUID) + let domain = NSFileProviderDomain(identifier: identifier, displayName: displayName, pathRelativeToDocumentStorage: vaultUID) return NSFileProviderManager.add(domain) } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/VaultItem.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/VaultItem.swift new file mode 100644 index 000000000..04e18fb62 --- /dev/null +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/VaultItem.swift @@ -0,0 +1,13 @@ +// +// VaultItem.swift +// CryptomatorCommonCore +// +// Created by Philipp Schmid on 24.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// +import CryptomatorCloudAccessCore +import Foundation +public protocol VaultItem { + var name: String { get } + var vaultPath: CloudPath { get } +} diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift index 9951041a0..8cae40319 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift @@ -19,7 +19,7 @@ class VaultManagerMock: VaultDBManager { var savedPasswords = [String: String]() var savedVaultConfigTokens = [String: String]() var removedVaultUIDs = [String]() - var addedFileProviderDomains = [String: CloudPath]() + var addedFileProviderDomains = [String: String]() override func saveFileProviderConformMasterkeyToKeychain(_ masterkey: Masterkey, forVaultUID vaultUID: String, vaultVersion: Int, password: String, storePasswordInKeychain: Bool) throws { savedMasterkeys[vaultUID] = masterkey @@ -47,8 +47,8 @@ class VaultManagerMock: VaultDBManager { return Promise(()) } - override func addFileProviderDomain(forVaultUID vaultUID: String, vaultPath: CloudPath) -> Promise { - addedFileProviderDomains[vaultUID] = vaultPath + override func addFileProviderDomain(forVaultUID vaultUID: String, displayName: String) -> Promise { + addedFileProviderDomains[vaultUID] = displayName return Promise(()) } } @@ -80,6 +80,7 @@ class VaultManagerTests: XCTestCase { table.column(VaultAccount.vaultUIDKey, .text).primaryKey() table.column(VaultAccount.delegateAccountUIDKey, .text).notNull() table.column(VaultAccount.vaultPathKey, .text).notNull() + table.column(VaultAccount.vaultNameKey, .text).notNull() table.column(VaultAccount.lastUpToDateCheckKey, .date).notNull() } } @@ -137,7 +138,7 @@ class VaultManagerTests: XCTestCase { } XCTAssertEqual(masterkey, managerMock.savedMasterkeys[vaultUID]) XCTAssertEqual(1, managerMock.addedFileProviderDomains.count) - XCTAssertEqual(vaultPath, managerMock.addedFileProviderDomains[vaultUID]) + XCTAssertEqual(vaultPath.lastPathComponent, managerMock.addedFileProviderDomains[vaultUID]) // Vault config checks let vaultConfigPath = vaultPath.appendingPathComponent("vault.cryptomator") @@ -187,7 +188,8 @@ class VaultManagerTests: XCTestCase { cloudProviderMock.filesToDownload[vaultConfigPath.path] = token.data(using: .utf8) let vaultUID = UUID().uuidString - manager.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultConfigPath: vaultConfigPath, password: "pw", storePasswordInKeychain: true).then { [self] in + let vaultDetails = VaultDetails(name: "ExistingVault", vaultPath: vaultPath) + manager.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultDetails: vaultDetails, password: "pw", storePasswordInKeychain: true).then { [self] in XCTAssertNotNil(VaultDBManager.cachedDecorators[vaultUID]) guard VaultDBManager.cachedDecorators[vaultUID] is VaultFormat8ShorteningProviderDecorator else { XCTFail("VaultDecorator has wrong type") @@ -203,7 +205,7 @@ class VaultManagerTests: XCTestCase { } XCTAssertEqual(managerMock.savedMasterkeys[vaultUID], masterkey) XCTAssertEqual(1, managerMock.addedFileProviderDomains.count) - XCTAssertEqual(vaultPath, managerMock.addedFileProviderDomains[vaultUID]) + XCTAssertEqual(vaultPath.lastPathComponent, managerMock.addedFileProviderDomains[vaultUID]) XCTAssertEqual(1, managerMock.savedVaultConfigTokens.count) guard let savedVaultConfigToken = managerMock.savedVaultConfigTokens[vaultUID] else { @@ -239,7 +241,8 @@ class VaultManagerTests: XCTestCase { let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) cloudProviderMock.filesToDownload[masterkeyPath.path] = try managerMock.exportMasterkey(masterkey, vaultVersion: 7, password: "pw") let vaultUID = UUID().uuidString - manager.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, masterkeyPath: masterkeyPath, password: "pw", storePasswordInKeychain: true).then { [self] in + let legacyVaultDetails = VaultDetails(name: "ExistingVault", vaultPath: vaultPath) + manager.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultDetails: legacyVaultDetails, password: "pw", storePasswordInKeychain: true).then { [self] in XCTAssertNotNil(VaultDBManager.cachedDecorators[vaultUID]) guard VaultDBManager.cachedDecorators[vaultUID] is VaultFormat7ShorteningProviderDecorator else { XCTFail("VaultDecorator has wrong type") @@ -255,7 +258,7 @@ class VaultManagerTests: XCTestCase { } XCTAssertEqual(managerMock.savedMasterkeys[vaultUID], masterkey) XCTAssertEqual(1, managerMock.addedFileProviderDomains.count) - XCTAssertEqual(vaultPath, managerMock.addedFileProviderDomains[vaultUID]) + XCTAssertEqual(vaultPath.lastPathComponent, managerMock.addedFileProviderDomains[vaultUID]) }.catch { error in XCTFail("Promise failed with error: \(error)") }.always { @@ -270,7 +273,7 @@ class VaultManagerTests: XCTestCase { try providerAccountManager.saveNewAccount(account) let vaultUID = UUID().uuidString let vaultPath = CloudPath("/VaultV8/") - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: "VaultV8", lastUpToDateCheck: Date()) try accountManager.saveNewAccount(vaultAccount) let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) @@ -289,7 +292,7 @@ class VaultManagerTests: XCTestCase { try providerAccountManager.saveNewAccount(account) let vaultUID = UUID().uuidString let vaultPath = CloudPath("/VaultV7/") - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: "VaultV7", lastUpToDateCheck: Date()) try accountManager.saveNewAccount(vaultAccount) let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) let decorator = try manager.createVaultDecorator(from: masterkey, vaultUID: vaultUID, vaultVersion: 7, vaultConfigToken: nil) @@ -306,7 +309,7 @@ class VaultManagerTests: XCTestCase { try providerAccountManager.saveNewAccount(account) let vaultUID = UUID().uuidString let vaultPath = CloudPath("/VaultV6/") - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: "VaultV6", lastUpToDateCheck: Date()) try accountManager.saveNewAccount(vaultAccount) let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) let decorator = try manager.createVaultDecorator(from: masterkey, vaultUID: vaultUID, vaultVersion: 6, vaultConfigToken: nil) @@ -323,7 +326,7 @@ class VaultManagerTests: XCTestCase { try providerAccountManager.saveNewAccount(account) let vaultUID = UUID().uuidString let vaultPath = CloudPath("/VaultV1/") - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: "VaultV1", lastUpToDateCheck: Date()) try accountManager.saveNewAccount(vaultAccount) let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) XCTAssertThrowsError(try manager.createVaultDecorator(from: masterkey, vaultUID: vaultUID, vaultVersion: 1, vaultConfigToken: nil)) { error in @@ -335,6 +338,11 @@ class VaultManagerTests: XCTestCase { } } +struct VaultDetails: VaultItem { + let name: String + let vaultPath: CloudPath +} + extension Masterkey: Equatable { public static func == (lhs: Masterkey, rhs: Masterkey) -> Bool { return lhs.aesMasterKey == rhs.aesMasterKey && lhs.macMasterKey == rhs.macMasterKey diff --git a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift index fb8c324bc..ade1cc930 100644 --- a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift +++ b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift @@ -185,11 +185,11 @@ private class VaultManagerMock: VaultManager { throw MockError.notMocked } - func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultConfigPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise { + func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { return Promise(MockError.notMocked) } - func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, masterkeyPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise { + func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { return Promise(MockError.notMocked) } diff --git a/CryptomatorTests/DatabaseManagerTests.swift b/CryptomatorTests/DatabaseManagerTests.swift index 7056cea14..689cfddf5 100644 --- a/CryptomatorTests/DatabaseManagerTests.swift +++ b/CryptomatorTests/DatabaseManagerTests.swift @@ -41,7 +41,7 @@ class DatabaseManagerTests: XCTestCase { let cloudProviderAccount = CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV) try cloudAccountManager.saveNewAccount(cloudProviderAccount) - let vaultAccount = VaultAccount(vaultUID: "Vault1", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault1")) + let vaultAccount = VaultAccount(vaultUID: "Vault1", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault1"), vaultName: "Vault1") try vaultAccountManager.saveNewAccount(vaultAccount) let firstVaultListPosition = try dbPool.read { db in try VaultListPosition.filter(Column("vaultUID") == "Vault1").fetchOne(db) @@ -50,7 +50,7 @@ class DatabaseManagerTests: XCTestCase { XCTAssertEqual(0, firstVaultListPosition?.position) XCTAssertEqual(1, firstVaultListPosition?.id) - let secondVaultAccount = VaultAccount(vaultUID: "Vault2", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault2")) + let secondVaultAccount = VaultAccount(vaultUID: "Vault2", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault2"), vaultName: "Vault2") try vaultAccountManager.saveNewAccount(secondVaultAccount) let secondVaultListPosition = try dbPool.read { db in @@ -67,11 +67,11 @@ class DatabaseManagerTests: XCTestCase { let cloudProviderAccount = CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV) try cloudAccountManager.saveNewAccount(cloudProviderAccount) - let vaultAccount = VaultAccount(vaultUID: "Vault1", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault1")) + let vaultAccount = VaultAccount(vaultUID: "Vault1", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault1"), vaultName: "Vault1") try vaultAccountManager.saveNewAccount(vaultAccount) - let secondVaultAccount = VaultAccount(vaultUID: "Vault2", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault2")) + let secondVaultAccount = VaultAccount(vaultUID: "Vault2", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault2"), vaultName: "Vault2") try vaultAccountManager.saveNewAccount(secondVaultAccount) - let thirdVaultAccount = VaultAccount(vaultUID: "Vault3", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault3")) + let thirdVaultAccount = VaultAccount(vaultUID: "Vault3", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault3"), vaultName: "Vault3") try vaultAccountManager.saveNewAccount(thirdVaultAccount) _ = try dbPool.write { db in @@ -102,11 +102,11 @@ class DatabaseManagerTests: XCTestCase { let cloudProviderAccount = CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV) try cloudAccountManager.saveNewAccount(cloudProviderAccount) - let vaultAccount = VaultAccount(vaultUID: "Vault1", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault1")) + let vaultAccount = VaultAccount(vaultUID: "Vault1", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault1"), vaultName: "Vault1") try vaultAccountManager.saveNewAccount(vaultAccount) - let secondVaultAccount = VaultAccount(vaultUID: "Vault2", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault2")) + let secondVaultAccount = VaultAccount(vaultUID: "Vault2", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault2"), vaultName: "Vault2") try vaultAccountManager.saveNewAccount(secondVaultAccount) - let thirdVaultAccount = VaultAccount(vaultUID: "Vault3", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault3")) + let thirdVaultAccount = VaultAccount(vaultUID: "Vault3", delegateAccountUID: cloudProviderAccount.accountUID, vaultPath: CloudPath("/Vault3"), vaultName: "Vault3") try vaultAccountManager.saveNewAccount(thirdVaultAccount) let vaults = try dbManager.getAllVaults() diff --git a/CryptomatorTests/VaultListViewModelTests.swift b/CryptomatorTests/VaultListViewModelTests.swift index 79096667a..a02a90bec 100644 --- a/CryptomatorTests/VaultListViewModelTests.swift +++ b/CryptomatorTests/VaultListViewModelTests.swift @@ -100,10 +100,10 @@ class VaultListViewModelTests: XCTestCase { private class DatabaseManagerMock: DatabaseManager { var updatedPositions = [VaultListPosition]() - let vaults = [VaultInfo(vaultAccount: VaultAccount(vaultUID: "vault1", delegateAccountUID: "1", vaultPath: CloudPath("/vault1")), + let vaults = [VaultInfo(vaultAccount: VaultAccount(vaultUID: "vault1", delegateAccountUID: "1", vaultPath: CloudPath("/vault1"), vaultName: "vault1"), cloudProviderAccount: CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV), vaultListPosition: VaultListPosition(position: 1, vaultUID: "vault1")), - VaultInfo(vaultAccount: VaultAccount(vaultUID: "vault2", delegateAccountUID: "1", vaultPath: CloudPath("/vault1")), + VaultInfo(vaultAccount: VaultAccount(vaultUID: "vault2", delegateAccountUID: "1", vaultPath: CloudPath("/vault1"), vaultName: "vautlt1"), cloudProviderAccount: CloudProviderAccount(accountUID: "1", cloudProviderType: .webDAV), vaultListPosition: VaultListPosition(position: 0, vaultUID: "vault2"))] From d909d4a6e00b8cf66e662adb229324f31e9329c0 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Fri, 25 Jun 2021 12:01:20 +0200 Subject: [PATCH 03/23] The database now gets nuked when a scheme change occurs --- Cryptomator/Common/DatabaseManager.swift | 61 ------------- .../CryptomatorDatabase.swift | 86 ++++++++++++++++--- 2 files changed, 75 insertions(+), 72 deletions(-) diff --git a/Cryptomator/Common/DatabaseManager.swift b/Cryptomator/Common/DatabaseManager.swift index 1d59bd9c7..e2953309c 100644 --- a/Cryptomator/Common/DatabaseManager.swift +++ b/Cryptomator/Common/DatabaseManager.swift @@ -17,67 +17,6 @@ class DatabaseManager { init(dbPool: DatabasePool) throws { self.dbPool = dbPool - try DatabaseManager.migrator.migrate(dbPool) - } - - private static var migrator: DatabaseMigrator { - var migrator = DatabaseMigrator() - migrator.registerMigration("main-v1") { db in - try db.create(table: "vaultListPosition") { table in - table.column("position", .integer).unique() - table.column("vaultUID", .text).unique().notNull().references("vaultAccounts", onDelete: .cascade) - table.check(Column("position") != nil) - } - try db.execute(sql: """ - CREATE TRIGGER position_creation - AFTER INSERT - ON vaultAccounts - BEGIN - INSERT INTO vaultListPosition (position, vaultUID) - VALUES (IFNULL((SELECT MAX(position) FROM vaultListPosition), -1)+1, NEW.vaultUID); - END; - """) - - try db.execute(sql: """ - CREATE TRIGGER position_update - AFTER DELETE - ON vaultListPosition - BEGIN - UPDATE vaultListPosition - SET position = position - 1 - WHERE position > OLD.position; - END; - """) - - try db.create(table: "accountListPosition") { table in - table.column("position", .integer) - table.column("cloudProviderType", .text) - table.column("accountUID", .text).unique().notNull().references("cloudProviderAccounts", onDelete: .cascade) - table.uniqueKey(["position", "cloudProviderType"]) - table.check(Column("position") != nil && Column("cloudProviderType") != nil) - } - try db.execute(sql: """ - CREATE TRIGGER accountList_position_creation - AFTER INSERT - ON cloudProviderAccounts - BEGIN - INSERT INTO accountListPosition (position, cloudProviderType, accountUID) - VALUES (IFNULL((SELECT MAX(position) FROM accountListPosition WHERE cloudProviderType = NEW.cloudProviderType), -1)+1, NEW.cloudProviderType, NEW.accountUID); - END; - """) - - try db.execute(sql: """ - CREATE TRIGGER accountList_position_update - AFTER DELETE - ON accountListPosition - BEGIN - UPDATE accountListPosition - SET position = position - 1 - WHERE position > OLD.position AND cloudProviderType = OLD.cloudProviderType; - END; - """) - } - return migrator } func getAllVaults() throws -> [VaultInfo] { diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift index 7ff6d2a9a..ae97dd1f6 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift @@ -31,22 +31,86 @@ public class CryptomatorDatabase { private static var migrator: DatabaseMigrator { var migrator = DatabaseMigrator() + // Speed up development by nuking the database when migrations change + // See https://github.com/groue/GRDB.swift/blob/master/Documentation/Migrations.md#the-erasedatabaseonschemachange-option + migrator.eraseDatabaseOnSchemaChange = true + migrator.registerMigration("v1") { db in - try db.create(table: "cloudProviderAccounts") { table in - table.column("accountUID", .text).primaryKey() - table.column("cloudProviderType", .text).notNull() - } - try db.create(table: "vaultAccounts") { table in - table.column("vaultUID", .text).primaryKey() - table.column("delegateAccountUID", .text).notNull().references("cloudProviderAccounts") - table.column("vaultPath", .text).notNull() - table.column("vaultName", .text).notNull() - table.column("lastUpToDateCheck", .date).notNull() - } + try v1Migration(db) } return migrator } + // swiftlint:disable:next function_body_length + public class func v1Migration(_ db: Database) throws { + // Common + try db.create(table: "cloudProviderAccounts") { table in + table.column("accountUID", .text).primaryKey() + table.column("cloudProviderType", .text).notNull() + } + try db.create(table: "vaultAccounts") { table in + table.column("vaultUID", .text).primaryKey() + table.column("delegateAccountUID", .text).notNull().references("cloudProviderAccounts") + table.column("vaultPath", .text).notNull() + table.column("vaultName", .text).notNull() + table.column("lastUpToDateCheck", .date).notNull() + } + // Main App + try db.create(table: "vaultListPosition") { table in + table.column("position", .integer).unique() + table.column("vaultUID", .text).unique().notNull().references("vaultAccounts", onDelete: .cascade) + table.check(Column("position") != nil) + } + try db.execute(sql: """ + CREATE TRIGGER position_creation + AFTER INSERT + ON vaultAccounts + BEGIN + INSERT INTO vaultListPosition (position, vaultUID) + VALUES (IFNULL((SELECT MAX(position) FROM vaultListPosition), -1)+1, NEW.vaultUID); + END; + """) + + try db.execute(sql: """ + CREATE TRIGGER position_update + AFTER DELETE + ON vaultListPosition + BEGIN + UPDATE vaultListPosition + SET position = position - 1 + WHERE position > OLD.position; + END; + """) + + try db.create(table: "accountListPosition") { table in + table.column("position", .integer) + table.column("cloudProviderType", .text) + table.column("accountUID", .text).unique().notNull().references("cloudProviderAccounts", onDelete: .cascade) + table.uniqueKey(["position", "cloudProviderType"]) + table.check(Column("position") != nil && Column("cloudProviderType") != nil) + } + try db.execute(sql: """ + CREATE TRIGGER accountList_position_creation + AFTER INSERT + ON cloudProviderAccounts + BEGIN + INSERT INTO accountListPosition (position, cloudProviderType, accountUID) + VALUES (IFNULL((SELECT MAX(position) FROM accountListPosition WHERE cloudProviderType = NEW.cloudProviderType), -1)+1, NEW.cloudProviderType, NEW.accountUID); + END; + """) + + try db.execute(sql: """ + CREATE TRIGGER accountList_position_update + AFTER DELETE + ON accountListPosition + BEGIN + UPDATE accountListPosition + SET position = position - 1 + WHERE position > OLD.position AND cloudProviderType = OLD.cloudProviderType; + END; + """) + } + public static func openSharedDatabase(at databaseURL: URL) throws -> DatabasePool { let coordinator = NSFileCoordinator(filePresenter: nil) var coordinatorError: NSError? From ec05f9a7e655fa305b36063617fcb6e63d5e77c3 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Mon, 28 Jun 2021 09:55:14 +0200 Subject: [PATCH 04/23] Added suggestions from code review --- .../CreateNewVaultCoordinator.swift | 6 +++--- ...ExistingLegacyVaultPasswordViewModel.swift | 3 +-- .../OpenExistingVaultPasswordViewModel.swift | 3 +-- .../Common/ChooseFolder/FolderChoosing.swift | 6 ------ ...leSystemAuthenticationViewController.swift | 10 +++++++--- .../LocalFileSystemAuthenticator.swift | 8 ++++---- Cryptomator/de.lproj/Localizable.strings | 5 ++++- Cryptomator/en.lproj/Localizable.strings | 6 ++++-- .../Manager/VaultDBManager.swift | 20 +++++++++---------- .../Manager/VaultManagerTests.swift | 4 ++-- ...CreateNewVaultPasswordViewModelTests.swift | 4 ++-- 11 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift index b8cfaa8c0..00848e60f 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift @@ -77,13 +77,13 @@ class CreateNewVaultCoordinator: AccountListing, CloudChoosing, Coordinator { do { try CloudProviderAccountDBManager.shared.saveNewAccount(account) } catch { - DDLogError("startLocalFileSystemAuthenticationFlow saveNewAccount failed with:\(error)") + DDLogError("startLocalFileSystemAuthenticationFlow saveNewAccount failed with: \(error)") } - self.startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with: credential, account: account) + self.startAuthenticatedLocalFileSystemCreateNewVaultFlow(with: credential, account: account) }) } - private func startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with credential: LocalFileSystemCredential, account: CloudProviderAccount) { + private func startAuthenticatedLocalFileSystemCreateNewVaultFlow(with credential: LocalFileSystemCredential, account: CloudProviderAccount) { let provider = LocalFileSystemProvider(rootURL: credential.rootURL) let child = AuthenticatedCreateNewVaultCoordinator(navigationController: navigationController, provider: provider, account: account, vaultName: vaultName) childCoordinators.append(child) diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift index 3ee454ead..95b06fe44 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift @@ -15,7 +15,6 @@ class OpenExistingLegacyVaultPasswordViewModel: OpenExistingVaultPasswordViewMod let provider: CloudProvider let account: CloudProviderAccount - // later: localMasterkeyURL: URL instead of masterkeyPath: CloudPath let vault: VaultItem var vaultName: String { return vault.name @@ -41,6 +40,6 @@ class OpenExistingLegacyVaultPasswordViewModel: OpenExistingVaultPasswordViewMod guard let password = password else { return Promise(MasterkeyProcessingViewModelError.noPasswordSet) } - return VaultDBManager.shared.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultDetails: vault, password: password, storePasswordInKeychain: true) + return VaultDBManager.shared.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultItem: vault, password: password, storePasswordInKeychain: true) } } diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift index 2c8957844..ac970f312 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultPasswordViewModel.swift @@ -25,7 +25,6 @@ class OpenExistingVaultPasswordViewModel: OpenExistingVaultPasswordViewModelProt let provider: CloudProvider let account: CloudProviderAccount - // later: localMasterkeyURL: URL instead of masterkeyPath: CloudPath let vault: VaultItem var vaultName: String { return vault.name @@ -48,7 +47,7 @@ class OpenExistingVaultPasswordViewModel: OpenExistingVaultPasswordViewModelProt guard let password = password else { return Promise(MasterkeyProcessingViewModelError.noPasswordSet) } - return VaultDBManager.shared.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultDetails: vault, password: password, storePasswordInKeychain: true) + return VaultDBManager.shared.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: account.accountUID, vaultItem: vault, password: password, storePasswordInKeychain: true) } } diff --git a/Cryptomator/Common/ChooseFolder/FolderChoosing.swift b/Cryptomator/Common/ChooseFolder/FolderChoosing.swift index 36d9b3596..d85b98c04 100644 --- a/Cryptomator/Common/ChooseFolder/FolderChoosing.swift +++ b/Cryptomator/Common/ChooseFolder/FolderChoosing.swift @@ -23,9 +23,3 @@ protocol Item { struct Folder: Item { let path: CloudPath } - -// enum ItemType { -// case folder -// case vaultConfig -// case legacyMasterkey -// } diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift index f1de41cff..6855e1cd2 100644 --- a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift +++ b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift @@ -24,7 +24,12 @@ class LocalFileSystemAuthenticationViewController: SingleSectionTableViewControl } @objc func openDocumentPicker() { - let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open) + let documentPicker: UIDocumentPickerViewController + if #available(iOS 14, *) { + documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder]) + } else { + documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open) + } documentPicker.allowsMultipleSelection = false documentPicker.delegate = self present(documentPicker, animated: true) @@ -37,8 +42,7 @@ class LocalFileSystemAuthenticationViewController: SingleSectionTableViewControl let credential = try viewModel.userPicked(urls: urls) coordinator?.authenticated(credential: credential) } catch { - #warning("TODO: Add coordinator for error handling") - print(error) + coordinator?.handleError(error, for: self) } } diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift index a1a815151..a5364d89b 100644 --- a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift +++ b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift @@ -13,15 +13,15 @@ class LocalFileSystemAuthenticator { private static var coordinator: LocalFileSystemCoordinator? static func authenticateForOpenExistingVault(from navigationController: UINavigationController, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { - let documentPickerButtonText = "Select Vault Folder" - let headerText = "In the next screen, choose the storage location for your new vault." + let documentPickerButtonText = NSLocalizedString("localFileSystemAuthentication.openExistingVault.button", comment: "") + let headerText = NSLocalizedString("localFileSystemAuthentication.openExistingVault.header", comment: "") let viewModel = LocalFileSystemAuthenticationViewModel(documentPickerButtonText: documentPickerButtonText, headerText: headerText) authenticate(from: navigationController, viewModel: viewModel, onCompletion: onCompletion) } static func authenticateForCreateNewVault(from navigationController: UINavigationController, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { - let documentPickerButtonText = "Select Storage Location" - let headerText = "In the next screen, choose the folder of your existing vault." + let documentPickerButtonText = NSLocalizedString("localFileSystemAuthentication.createNewVault.button", comment: "") + let headerText = NSLocalizedString("localFileSystemAuthentication.createNewVault.header", comment: "") let viewModel = LocalFileSystemAuthenticationViewModel(documentPickerButtonText: documentPickerButtonText, headerText: headerText) authenticate(from: navigationController, viewModel: viewModel, onCompletion: onCompletion) } diff --git a/Cryptomator/de.lproj/Localizable.strings b/Cryptomator/de.lproj/Localizable.strings index 9e93f3d8a..c1fa1bf88 100644 --- a/Cryptomator/de.lproj/Localizable.strings +++ b/Cryptomator/de.lproj/Localizable.strings @@ -61,4 +61,7 @@ "createNewFolder.header.title" = "Wähle einen Namen für den Ordner."; "createNewFolder.cells.name" = "Ordnername"; -"testFlight.otherFileProviders.alert.text" = "Andere Datei-Anbieter sind derzeit nicht verfügbar. Diese Funktion folgt bald in einem Update!"; +"localFileSystemAuthentication.createNewVault.header" = "Wähle im nächsten Schritt den Speicherort für deinen neuen Tresor aus."; +"localFileSystemAuthentication.createNewVault.button" = "Speicherort auswählen"; +"localFileSystemAuthentication.openExistingVault.header" = "Wähle im nächsten Schritt den Ordner deines vorhandenen Tresors aus."; +"localFileSystemAuthentication.openExistingVault.button" = "Tresorordner auswählen"; diff --git a/Cryptomator/en.lproj/Localizable.strings b/Cryptomator/en.lproj/Localizable.strings index cef033f7f..3367fe24e 100644 --- a/Cryptomator/en.lproj/Localizable.strings +++ b/Cryptomator/en.lproj/Localizable.strings @@ -61,5 +61,7 @@ "createNewFolder.header.title" = "Choose a name for the folder."; "createNewFolder.cells.name" = "Folder Name"; - -"testFlight.otherFileProviders.alert.text" = "Other file providers are currently not available. Stay tuned for an update!"; +"localFileSystemAuthentication.createNewVault.header" = "In the next screen, choose the storage location for your new vault."; +"localFileSystemAuthentication.createNewVault.button" = "Select Storage Location"; +"localFileSystemAuthentication.openExistingVault.header" = "In the next screen, choose the folder of your existing vault."; +"localFileSystemAuthentication.openExistingVault.button" = "Select Vault Folder"; diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index ff1207a22..8d9a2e6af 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -30,8 +30,8 @@ public protocol VaultManager { func createNewVault(withVaultUID vaultUID: String, delegateAccountUID: String, vaultPath: CloudPath, password: String, storePasswordInKeychain: Bool) -> Promise func manualUnlockVault(withUID vaultUID: String, password: String) throws -> CloudProvider func getDecorator(forVaultUID vaultUID: String) throws -> CloudProvider - func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise - func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise + func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultItem: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise + func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultItem: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise func removeVault(withUID vaultUID: String) throws -> Promise func removeAllUnusedFileProviderDomains() -> Promise func getVaultPath(from masterkeyPath: CloudPath) -> CloudPath @@ -175,7 +175,7 @@ public class VaultDBManager: VaultManager { // MARK: Open Existing Vault - public func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { + public func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultItem: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { let delegate: CloudProvider do { guard VaultDBManager.cachedDecorators[vaultUID] == nil else { @@ -188,7 +188,7 @@ public class VaultDBManager: VaultManager { let tmpDirURL = FileManager.default.temporaryDirectory let localVaultConfigURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) let localMasterkeyURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) - let vaultPath = vaultDetails.vaultPath + let vaultPath = vaultItem.vaultPath let vaultConfigPath = vaultPath.appendingPathComponent("vault.cryptomator") let masterkeyPath = vaultPath.appendingPathComponent("masterkey.cryptomator") return delegate.downloadFile(from: vaultConfigPath, to: localVaultConfigURL).then { @@ -199,12 +199,12 @@ public class VaultDBManager: VaultManager { let masterkeyFile = try MasterkeyFile.withContentFromURL(url: localMasterkeyURL) let masterkey = try masterkeyFile.unlock(passphrase: password) let vaultProvider = try VaultProviderFactory.createVaultProvider(from: unverifiedVaultConfig, masterkey: masterkey, vaultPath: vaultPath, with: delegate) - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultDetails.name, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultItem.name, lastUpToDateCheck: Date()) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultConfigToken: token, password: password, storePasswordInKeychain: storePasswordInKeychain) try self.vaultAccountManager.saveNewAccount(vaultAccount) VaultDBManager.cachedDecorators[vaultUID] = vaultProvider }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultDetails.name) + self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultItem.name) }.catch { _ in VaultDBManager.cachedDecorators[vaultUID] = nil } @@ -218,7 +218,7 @@ public class VaultDBManager: VaultManager { - Postcondition: The passed `vaultUID`, `delegateAccountUID` and the `vaultPath` derived from `masterkeyPath` are stored as VaultAccount in the database - Postcondition: The created VaultDecorator is cached under the corresponding `vaultUID` */ - public func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { + public func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultItem: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { do { guard VaultDBManager.cachedDecorators[vaultUID] == nil else { throw VaultManagerError.vaultAlreadyExists @@ -226,17 +226,17 @@ public class VaultDBManager: VaultManager { let delegate = try providerManager.getProvider(with: delegateAccountUID) let tmpDirURL = FileManager.default.temporaryDirectory let localMasterkeyURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) - let vaultPath = vaultDetails.vaultPath + let vaultPath = vaultItem.vaultPath let masterkeyPath = vaultPath.appendingPathComponent("masterkey.cryptomator") return delegate.downloadFile(from: masterkeyPath, to: localMasterkeyURL).then { let masterkeyFile = try MasterkeyFile.withContentFromURL(url: localMasterkeyURL) let masterkey = try masterkeyFile.unlock(passphrase: password) _ = try self.createLegacyVaultDecorator(from: masterkey, delegate: delegate, vaultPath: vaultPath, vaultUID: vaultUID, vaultVersion: masterkeyFile.version) - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultDetails.name, lastUpToDateCheck: Date()) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultItem.name, lastUpToDateCheck: Date()) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultVersion: masterkeyFile.version, password: password, storePasswordInKeychain: storePasswordInKeychain) try self.vaultAccountManager.saveNewAccount(vaultAccount) }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultDetails.name) + self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultItem.name) } } catch { VaultDBManager.cachedDecorators[vaultUID] = nil diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift index 8cae40319..6956f0d6c 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Manager/VaultManagerTests.swift @@ -189,7 +189,7 @@ class VaultManagerTests: XCTestCase { let vaultUID = UUID().uuidString let vaultDetails = VaultDetails(name: "ExistingVault", vaultPath: vaultPath) - manager.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultDetails: vaultDetails, password: "pw", storePasswordInKeychain: true).then { [self] in + manager.createFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultItem: vaultDetails, password: "pw", storePasswordInKeychain: true).then { [self] in XCTAssertNotNil(VaultDBManager.cachedDecorators[vaultUID]) guard VaultDBManager.cachedDecorators[vaultUID] is VaultFormat8ShorteningProviderDecorator else { XCTFail("VaultDecorator has wrong type") @@ -242,7 +242,7 @@ class VaultManagerTests: XCTestCase { cloudProviderMock.filesToDownload[masterkeyPath.path] = try managerMock.exportMasterkey(masterkey, vaultVersion: 7, password: "pw") let vaultUID = UUID().uuidString let legacyVaultDetails = VaultDetails(name: "ExistingVault", vaultPath: vaultPath) - manager.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultDetails: legacyVaultDetails, password: "pw", storePasswordInKeychain: true).then { [self] in + manager.createLegacyFromExisting(withVaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultItem: legacyVaultDetails, password: "pw", storePasswordInKeychain: true).then { [self] in XCTAssertNotNil(VaultDBManager.cachedDecorators[vaultUID]) guard VaultDBManager.cachedDecorators[vaultUID] is VaultFormat7ShorteningProviderDecorator else { XCTFail("VaultDecorator has wrong type") diff --git a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift index ade1cc930..aca912f83 100644 --- a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift +++ b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift @@ -185,11 +185,11 @@ private class VaultManagerMock: VaultManager { throw MockError.notMocked } - func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { + func createFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultItem: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { return Promise(MockError.notMocked) } - func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultDetails: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { + func createLegacyFromExisting(withVaultUID vaultUID: String, delegateAccountUID: String, vaultItem: VaultItem, password: String, storePasswordInKeychain: Bool) -> Promise { return Promise(MockError.notMocked) } From 2405f2d158282295bbec9d7ed8b60724ab0f199b Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Mon, 28 Jun 2021 13:08:03 +0200 Subject: [PATCH 05/23] Updated README [ci skip] --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 84e99b20c..4357ff2cc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ # Cryptomator for iOS +[![Build](https://github.com/cryptomator/ios/actions/workflows/build.yml/badge.svg)](https://github.com/cryptomator/ios/actions/workflows/build.yml) +[![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator) +[![Crowdin](https://badges.crowdin.net/cryptomator-ios/localized.svg)](https://crowdin.com/project/cryptomator-ios) +[![Community](https://img.shields.io/badge/help-Community-orange.svg)](https://community.cryptomator.org) + +Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud. + +This is the repository for the new iOS app of Cryptomator, which has been rewritten from the ground up and is currently in a beta phase. + +Try it out for free via TestFlight: https://testflight.apple.com/join/WMtYSrzD + +If you're looking for the current App Store version and would like to report an issue, check out the other repository: https://github.com/cryptomator/cryptomator-ios + ## Building ### Create Secrets From b680dbb9b7d2cb4dae47b410f5ea7e5e320109ee Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 13:24:29 +0200 Subject: [PATCH 06/23] Changed "Add Local Vault"-flow - no longer uses the ChooseFolderViewController - removed detected vault screen for open existing local vault --- Cryptomator.xcodeproj/project.pbxproj | 112 ++++++++++++++---- .../CreateNewLocalVaultViewController.swift | 21 ---- .../CreateNewLocalVaultViewModel.swift | 26 ---- ...teNewVaultChooseFolderViewController.swift | 16 +-- .../CreateNewVaultCoordinator.swift | 25 ++-- .../DetectedVaultFailureView.swift | 18 +++ .../DetectedVaultFailureViewController.swift | 40 +++++++ .../CreateNewLocalVaultCoordinator.swift | 50 ++++++++ .../CreateNewLocalVaultViewModel.swift | 49 ++++++++ .../AddLocalVaultViewController.swift | 34 ++++++ .../LocalFileSystemAuthenticating.swift | 0 ...leSystemAuthenticationViewController.swift | 8 +- ...calFileSystemAuthenticationViewModel.swift | 83 +++++++++++++ .../LocalVault/LocalVaultAdding.swift | 13 ++ .../OpenExistingLocalVaultCoordinator.swift | 42 +++++++ .../OpenExistingLocalVaultViewModel.swift | 45 +++++++ .../OpenExistingLocalVaultViewModel.swift | 25 ---- ...stingVaultChooseFolderViewController.swift | 5 +- .../OpenExistingVaultCoordinator.swift | 25 ++-- .../ChooseFolder/ChooseFolderViewModel.swift | 37 +----- Cryptomator/Common/VaultDetector.swift | 48 ++++++++ ...calFileSystemAuthenticationViewModel.swift | 44 ------- .../LocalFileSystemAuthenticator.swift | 59 --------- .../AddLocalVaultViewModelTestCase.swift | 89 ++++++++++++++ .../CreateNewLocalVaultViewModelTests.swift | 97 +++++++++++++++ ...OpenExistingLocalVaultViewModelTests.swift | 95 +++++++++++++++ ...CreateNewVaultPasswordViewModelTests.swift | 4 - CryptomatorTests/MockError.swift | 12 ++ .../VaultListViewModelTests.swift | 4 - 29 files changed, 828 insertions(+), 298 deletions(-) delete mode 100644 Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift delete mode 100644 Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift create mode 100644 Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift create mode 100644 Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift create mode 100644 Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift create mode 100644 Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift create mode 100644 Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift rename Cryptomator/{LocalFileSystem => AddVault/LocalVault}/LocalFileSystemAuthenticating.swift (100%) rename Cryptomator/{LocalFileSystem => AddVault/LocalVault}/LocalFileSystemAuthenticationViewController.swift (93%) create mode 100644 Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift create mode 100644 Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift create mode 100644 Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift create mode 100644 Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift delete mode 100644 Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift create mode 100644 Cryptomator/Common/VaultDetector.swift delete mode 100644 Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift delete mode 100644 Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift create mode 100644 CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift create mode 100644 CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift create mode 100644 CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift create mode 100644 CryptomatorTests/MockError.swift diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index ecf75209c..527f96228 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -12,10 +12,15 @@ 4A03257825A36A6900E63D7A /* VaultListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A03257725A36A6900E63D7A /* VaultListViewController.swift */; }; 4A03258125A36B7D00E63D7A /* UIViewController+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A03258025A36B7D00E63D7A /* UIViewController+Preview.swift */; }; 4A09BFC62684D599000E40AB /* VaultDetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */; }; - 4A09BFCA2684EA8D000E40AB /* CreateNewLocalVaultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09BFC92684EA8D000E40AB /* CreateNewLocalVaultViewController.swift */; }; 4A0C07E225AC80C100B83211 /* UIView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */; }; 4A0C07EB25AC832900B83211 /* VaultListPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */; }; 4A123EA824BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A123EA724BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift */; }; + 4A1EB0CA2689C373006D072B /* LocalVaultAdding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1EB0C92689C373006D072B /* LocalVaultAdding.swift */; }; + 4A1EB0CC2689C3DE006D072B /* CreateNewLocalVaultCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1EB0CB2689C3DE006D072B /* CreateNewLocalVaultCoordinator.swift */; }; + 4A1EB0CE2689C7A3006D072B /* DetectedVaultFailureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1EB0CD2689C7A3006D072B /* DetectedVaultFailureViewController.swift */; }; + 4A1EB0D02689C7F8006D072B /* DetectedVaultFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1EB0CF2689C7F8006D072B /* DetectedVaultFailureView.swift */; }; + 4A1EB0D5268A5AA3006D072B /* VaultDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1EB0D4268A5AA3006D072B /* VaultDetector.swift */; }; + 4A1EB0D8268A6DE1006D072B /* AddLocalVaultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1EB0D7268A6DE1006D072B /* AddLocalVaultViewController.swift */; }; 4A2245DC24A5E1C600DBA437 /* MetadataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2245DB24A5E1C600DBA437 /* MetadataManagerTests.swift */; }; 4A248221266B8D37002D9F59 /* FileProviderAdapterImportDocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A248220266B8D37002D9F59 /* FileProviderAdapterImportDocumentTests.swift */; }; 4A248223266E266E002D9F59 /* FolderCreationTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A248222266E266E002D9F59 /* FolderCreationTask.swift */; }; @@ -39,7 +44,6 @@ 4A3D65612680A3CB000DA764 /* LocalFileSystemAuthenticating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */; }; 4A3D65642680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */; }; 4A3D65662680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */; }; - 4A3D657C26837D52000DA764 /* LocalFileSystemAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D657B26837D52000DA764 /* LocalFileSystemAuthenticator.swift */; }; 4A3D658226838991000DA764 /* OpenExistingLocalVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */; }; 4A3D658626847B11000DA764 /* CreateNewLocalVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */; }; 4A447DB325BEF68100D9520D /* CloudChoosing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A447DB225BEF68100D9520D /* CloudChoosing.swift */; }; @@ -85,6 +89,11 @@ 4A66F57925C47BB2001BE15E /* TextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A66F57825C47BB2001BE15E /* TextFieldCell.swift */; }; 4A66F58225C487C9001BE15E /* PasswordFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A66F58125C487C9001BE15E /* PasswordFieldCell.swift */; }; 4A66F58B25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A66F58A25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift */; }; + 4A6A51FF268B1BEB006F7368 /* OpenExistingLocalVaultCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A51FE268B1BEB006F7368 /* OpenExistingLocalVaultCoordinator.swift */; }; + 4A6A5204268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5203268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift */; }; + 4A6A5206268B2B24006F7368 /* MockError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5205268B2B24006F7368 /* MockError.swift */; }; + 4A6A5208268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5207268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift */; }; + 4A6A520B268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A520A268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift */; }; 4A717CD924C835740048E08F /* ReparentTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */; }; 4A753DB92678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */; }; 4A797F8F24AC6731007DDBE1 /* FileProviderItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */; }; @@ -289,10 +298,15 @@ 4A03258025A36B7D00E63D7A /* UIViewController+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Preview.swift"; sourceTree = ""; }; 4A0698692619EF9C00A67F30 /* CryptomatorCommon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CryptomatorCommon; sourceTree = ""; }; 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailItem.swift; sourceTree = ""; }; - 4A09BFC92684EA8D000E40AB /* CreateNewLocalVaultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultViewController.swift; sourceTree = ""; }; 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Preview.swift"; sourceTree = ""; }; 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultListPosition.swift; sourceTree = ""; }; 4A123EA724BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudProviderPaginationMock.swift; sourceTree = ""; }; + 4A1EB0C92689C373006D072B /* LocalVaultAdding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalVaultAdding.swift; sourceTree = ""; }; + 4A1EB0CB2689C3DE006D072B /* CreateNewLocalVaultCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultCoordinator.swift; sourceTree = ""; }; + 4A1EB0CD2689C7A3006D072B /* DetectedVaultFailureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedVaultFailureViewController.swift; sourceTree = ""; }; + 4A1EB0CF2689C7F8006D072B /* DetectedVaultFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedVaultFailureView.swift; sourceTree = ""; }; + 4A1EB0D4268A5AA3006D072B /* VaultDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetector.swift; sourceTree = ""; }; + 4A1EB0D7268A6DE1006D072B /* AddLocalVaultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLocalVaultViewController.swift; sourceTree = ""; }; 4A2245D024A5E16300DBA437 /* CryptomatorFileProviderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CryptomatorFileProviderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4A2245D424A5E16300DBA437 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4A2245DB24A5E1C600DBA437 /* MetadataManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataManagerTests.swift; sourceTree = ""; }; @@ -318,7 +332,6 @@ 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticating.swift; sourceTree = ""; }; 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticationViewController.swift; sourceTree = ""; }; 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticationViewModel.swift; sourceTree = ""; }; - 4A3D657B26837D52000DA764 /* LocalFileSystemAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFileSystemAuthenticator.swift; sourceTree = ""; }; 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLocalVaultViewModel.swift; sourceTree = ""; }; 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultViewModel.swift; sourceTree = ""; }; 4A447DB225BEF68100D9520D /* CloudChoosing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudChoosing.swift; sourceTree = ""; }; @@ -366,6 +379,11 @@ 4A66F57825C47BB2001BE15E /* TextFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCell.swift; sourceTree = ""; }; 4A66F58125C487C9001BE15E /* PasswordFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordFieldCell.swift; sourceTree = ""; }; 4A66F58A25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingVaultPasswordViewModel.swift; sourceTree = ""; }; + 4A6A51FE268B1BEB006F7368 /* OpenExistingLocalVaultCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLocalVaultCoordinator.swift; sourceTree = ""; }; + 4A6A5203268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLocalVaultViewModelTests.swift; sourceTree = ""; }; + 4A6A5205268B2B24006F7368 /* MockError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockError.swift; sourceTree = ""; }; + 4A6A5207268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLocalVaultViewModelTestCase.swift; sourceTree = ""; }; + 4A6A520A268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultViewModelTests.swift; sourceTree = ""; }; 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReparentTaskManagerTests.swift; sourceTree = ""; }; 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLegacyVaultPasswordViewModel.swift; sourceTree = ""; }; 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItemTests.swift; sourceTree = ""; }; @@ -551,6 +569,27 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4A1EB0D3268A5A44006D072B /* LocalVault */ = { + isa = PBXGroup; + children = ( + 4A1EB0CB2689C3DE006D072B /* CreateNewLocalVaultCoordinator.swift */, + 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */, + ); + path = LocalVault; + sourceTree = ""; + }; + 4A1EB0D6268A6CF5006D072B /* LocalVault */ = { + isa = PBXGroup; + children = ( + 4A1EB0C92689C373006D072B /* LocalVaultAdding.swift */, + 4A1EB0D7268A6DE1006D072B /* AddLocalVaultViewController.swift */, + 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */, + 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */, + 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */, + ); + path = LocalVault; + sourceTree = ""; + }; 4A2245D124A5E16300DBA437 /* CryptomatorFileProviderTests */ = { isa = PBXGroup; children = ( @@ -568,17 +607,6 @@ path = CryptomatorFileProviderTests; sourceTree = ""; }; - 4A3D65622680A3D0000DA764 /* LocalFileSystem */ = { - isa = PBXGroup; - children = ( - 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */, - 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */, - 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */, - 4A3D657B26837D52000DA764 /* LocalFileSystemAuthenticator.swift */, - ); - path = LocalFileSystem; - sourceTree = ""; - }; 4A447DEB25BF064300D9520D /* ChooseFolder */ = { isa = PBXGroup; children = ( @@ -684,20 +712,40 @@ 4A644B45267A3D21008CBB9A /* CreateNewVault */ = { isa = PBXGroup; children = ( + 4A1EB0D3268A5A44006D072B /* LocalVault */, 4A644B4A267B4C08008CBB9A /* CreateNewVaultChooseFolderViewController.swift */, + 4A53CC16267CDBFF00853BB3 /* CreateNewVaultChooseFolderViewModel.swift */, 4A644B4C267B55E4008CBB9A /* CreateNewVaultCoordinator.swift */, + 4A53CC12267CC1C100853BB3 /* CreateNewVaultPasswordViewController.swift */, + 4A53CC14267CC33100853BB3 /* CreateNewVaultPasswordViewModel.swift */, + 4A1EB0CF2689C7F8006D072B /* DetectedVaultFailureView.swift */, + 4A1EB0CD2689C7A3006D072B /* DetectedVaultFailureViewController.swift */, 4A644B4E267B9E6A008CBB9A /* SetVaultNameCoordinator.swift */, 4A644B43267A3BEC008CBB9A /* SetVaultNameViewController.swift */, 4A644B46267A3D43008CBB9A /* SetVaultNameViewModel.swift */, - 4A53CC12267CC1C100853BB3 /* CreateNewVaultPasswordViewController.swift */, - 4A53CC14267CC33100853BB3 /* CreateNewVaultPasswordViewModel.swift */, - 4A3D658526847B11000DA764 /* CreateNewLocalVaultViewModel.swift */, - 4A53CC16267CDBFF00853BB3 /* CreateNewVaultChooseFolderViewModel.swift */, - 4A09BFC92684EA8D000E40AB /* CreateNewLocalVaultViewController.swift */, ); path = CreateNewVault; sourceTree = ""; }; + 4A6A5200268B1BEF006F7368 /* LocalVault */ = { + isa = PBXGroup; + children = ( + 4A6A51FE268B1BEB006F7368 /* OpenExistingLocalVaultCoordinator.swift */, + 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */, + ); + path = LocalVault; + sourceTree = ""; + }; + 4A6A5209268B2B7B006F7368 /* AddLocalVault */ = { + isa = PBXGroup; + children = ( + 4A6A5207268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift */, + 4A6A5203268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift */, + 4A6A520A268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift */, + ); + path = AddLocalVault; + sourceTree = ""; + }; 4A7B97CA25B6F7340044B7FB /* CloudAccountList */ = { isa = PBXGroup; children = ( @@ -728,6 +776,7 @@ 4A644B5A267CA972008CBB9A /* DetectedVaultView.swift */, 4A3D655E268099F9000DA764 /* VaultCoordinatorError.swift */, 4A2FD08125B5E2BA008565C8 /* VaultInstalling.swift */, + 4A1EB0D6268A6CF5006D072B /* LocalVault */, 4A644B45267A3D21008CBB9A /* CreateNewVault */, 4AA8613F25C1AC4D002A59F5 /* OpenExistingVault */, ); @@ -767,6 +816,7 @@ 7408E6BD2677835C00D7FAEA /* LocalWeb */, 4A8195E425ADB94500F7DDA1 /* Previews */, 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */, + 4A1EB0D4268A5AA3006D072B /* VaultDetector.swift */, ); path = Common; sourceTree = ""; @@ -861,7 +911,7 @@ 4AA8615025C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift */, 4A66F58A25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift */, 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */, - 4A3D658126838991000DA764 /* OpenExistingLocalVaultViewModel.swift */, + 4A6A5200268B1BEF006F7368 /* LocalVault */, ); path = OpenExistingVault; sourceTree = ""; @@ -902,7 +952,6 @@ 4A7BC0E825ADF13100F007B3 /* AddVault */, 4A8195E325ADB92600F7DDA1 /* Common */, 4AE97DB524572E4A00452814 /* LaunchScreen.storyboard */, - 4A3D65622680A3D0000DA764 /* LocalFileSystem */, 7439031325E0008D00BB3B81 /* Localizable.strings */, 7408E6C8267797DC00D7FAEA /* Resources */, 740D367C266A18C80058744D /* Settings */, @@ -917,11 +966,13 @@ children = ( 4AE97DC324572E4A00452814 /* Info.plist */, 4AFCE56925BAEE890069C4FC /* AccountListViewModelTests.swift */, + 4A644B58267CA3AD008CBB9A /* CreateNewFolderViewModelTests.swift */, + 4A3D6553267CF17B000DA764 /* CreateNewVaultPasswordViewModelTests.swift */, 4AF91CEA25A7306E00ACF01E /* DatabaseManagerTests.swift */, + 4A6A5205268B2B24006F7368 /* MockError.swift */, 4A644B48267B40C3008CBB9A /* SetVaultNameViewModelTests.swift */, 4AF91CF325A8BB0D00ACF01E /* VaultListViewModelTests.swift */, - 4A644B58267CA3AD008CBB9A /* CreateNewFolderViewModelTests.swift */, - 4A3D6553267CF17B000DA764 /* CreateNewVaultPasswordViewModelTests.swift */, + 4A6A5209268B2B7B006F7368 /* AddLocalVault */, ); path = CryptomatorTests; sourceTree = ""; @@ -1449,7 +1500,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A09BFCA2684EA8D000E40AB /* CreateNewLocalVaultViewController.swift in Sources */, 4A644B4F267B9E6A008CBB9A /* SetVaultNameCoordinator.swift in Sources */, 4A9D124F261F071F00A670E2 /* WebDAVAuthenticator+VC.swift in Sources */, 4A0C07E225AC80C100B83211 /* UIView+Preview.swift in Sources */, @@ -1465,6 +1515,7 @@ 4A7B97D325B6F7520044B7FB /* AccountListViewModel.swift in Sources */, 4A7BC0F125ADFAD600F007B3 /* AddVaultCoordinator.swift in Sources */, 4A3D655F268099F9000DA764 /* VaultCoordinatorError.swift in Sources */, + 4A1EB0CC2689C3DE006D072B /* CreateNewLocalVaultCoordinator.swift in Sources */, 4A644B57267C958F008CBB9A /* ChildCoordinator.swift in Sources */, 4A53CC15267CC33100853BB3 /* CreateNewVaultPasswordViewModel.swift in Sources */, 4AA22C1E261CA94700A17486 /* UsernameFieldCell.swift in Sources */, @@ -1489,11 +1540,13 @@ 4A447DBC25BF003400D9520D /* ChooseCloudViewModel.swift in Sources */, 740D3684266A1B180058744D /* SettingsCoordinator.swift in Sources */, 7408E6C326778D9B00D7FAEA /* AboutCoordinator.swift in Sources */, + 4A1EB0D5268A5AA3006D072B /* VaultDetector.swift in Sources */, 4AFCE4D425B842830069C4FC /* AccountListing.swift in Sources */, 4A644B53267BAFDA008CBB9A /* CreateNewFolderViewModel.swift in Sources */, 4AA8615125C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift in Sources */, 4AFCE4DD25B8514F0069C4FC /* EditableTableViewHeader.swift in Sources */, 4A2FD07025B5D5FB008565C8 /* ChooseCloudViewController.swift in Sources */, + 4A1EB0CA2689C373006D072B /* LocalVaultAdding.swift in Sources */, 4AF91D0D25A8D5EF00ACF01E /* ListViewModel.swift in Sources */, 4A8D060525C82F1F0082C5F7 /* AddVaultSuccesing.swift in Sources */, 740D367E266A18DF0058744D /* SettingsViewController.swift in Sources */, @@ -1503,10 +1556,11 @@ 4A447E2425BF0E3A00D9520D /* ChooseFolderViewModel.swift in Sources */, 4AA22BFB261CA69F00A17486 /* WebDAVAuthenticationViewController.swift in Sources */, 4A9D123F261E1DD400A670E2 /* WebDAVAuthenticating.swift in Sources */, - 4A3D657C26837D52000DA764 /* LocalFileSystemAuthenticator.swift in Sources */, 4A53CC13267CC1C100853BB3 /* CreateNewVaultPasswordViewController.swift in Sources */, + 4A6A51FF268B1BEB006F7368 /* OpenExistingLocalVaultCoordinator.swift in Sources */, 4A03255525A3685500E63D7A /* Coordinator.swift in Sources */, 4A644B47267A3D43008CBB9A /* SetVaultNameViewModel.swift in Sources */, + 4A1EB0D02689C7F8006D072B /* DetectedVaultFailureView.swift in Sources */, 4A53CC11267CBFA100853BB3 /* AddVaultSuccessCoordinator.swift in Sources */, 4A8D05D625C5CBE10082C5F7 /* AddVaultSuccessViewController.swift in Sources */, 4A0C07EB25AC832900B83211 /* VaultListPosition.swift in Sources */, @@ -1525,6 +1579,7 @@ 4AA8614825C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift in Sources */, 4A447E5625BF1F6A00D9520D /* CloudItemCell.swift in Sources */, 4AA8613725C19D4F002A59F5 /* DetectedMasterkeyViewModel.swift in Sources */, + 4A1EB0D8268A6DE1006D072B /* AddLocalVaultViewController.swift in Sources */, 4A7B97E525B6F86E0044B7FB /* AccountListPosition.swift in Sources */, 4A447E3D25BF1AD400D9520D /* FolderCell.swift in Sources */, 4A644B4B267B4C08008CBB9A /* CreateNewVaultChooseFolderViewController.swift in Sources */, @@ -1536,6 +1591,7 @@ 4A9D1237261DAC5D00A670E2 /* WebDAVAuthenticationViewModel.swift in Sources */, 4A753DB92678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift in Sources */, 7408E6C126778C7A00D7FAEA /* LocalWebViewModel.swift in Sources */, + 4A1EB0CE2689C7A3006D072B /* DetectedVaultFailureViewController.swift in Sources */, 4A644B51267BAAF4008CBB9A /* CreateNewFolderViewController.swift in Sources */, 4A644B5B267CA972008CBB9A /* DetectedVaultView.swift in Sources */, ); @@ -1545,12 +1601,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4A6A520B268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift in Sources */, + 4A6A5208268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift in Sources */, 4AF91CF425A8BB0D00ACF01E /* VaultListViewModelTests.swift in Sources */, 4A3D6554267CF17B000DA764 /* CreateNewVaultPasswordViewModelTests.swift in Sources */, 4AF91CEB25A7306E00ACF01E /* DatabaseManagerTests.swift in Sources */, 4A644B49267B40C3008CBB9A /* SetVaultNameViewModelTests.swift in Sources */, 4AFCE56A25BAEE890069C4FC /* AccountListViewModelTests.swift in Sources */, 4A644B59267CA3AD008CBB9A /* CreateNewFolderViewModelTests.swift in Sources */, + 4A6A5206268B2B24006F7368 /* MockError.swift in Sources */, + 4A6A5204268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift deleted file mode 100644 index 0653a5c1e..000000000 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewController.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// CreateNewLocalVaultViewController.swift -// Cryptomator -// -// Created by Philipp Schmid on 24.06.21. -// Copyright © 2021 Skymatic GmbH. All rights reserved. -// - -import UIKit -class CreateNewLocalVaultViewController: CreateNewVaultChooseFolderViewController { - override func onItemsChange() { - guard let viewModel = viewModel as? CreateNewVaultChooseFolderViewModelProtocol else { - return - } - do { - coordinator?.chooseItem(try viewModel.chooseCurrentFolder()) - } catch { - coordinator?.handleError(error: error) - } - } -} diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift deleted file mode 100644 index e89329ed8..000000000 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewLocalVaultViewModel.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// CreateNewLocalVaultViewModel.swift -// Cryptomator -// -// Created by Philipp Schmid on 24.06.21. -// Copyright © 2021 Skymatic GmbH. All rights reserved. -// - -import CryptomatorCloudAccessCore -import Foundation -class CreateNewLocalVaultViewModel: CreateNewVaultChooseFolderViewModel { - let rootFolderName: String - - init(rootFolderName: String, vaultName: String, provider: LocalFileSystemProvider) { - self.rootFolderName = rootFolderName - super.init(vaultName: vaultName, cloudPath: CloudPath("/"), provider: provider) - } - - override var headerTitle: String { - return vaultName - } - - override func getVaultName(for cryptomatorFilePath: CloudPath) -> String { - return vaultName - } -} diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift index 7314b4ab3..78ab37162 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewController.swift @@ -23,7 +23,7 @@ class CreateNewVaultChooseFolderViewController: ChooseFolderViewController { } override func showDetectedVault(_ vault: VaultDetailItem) { - let failureView = FailureView() + let failureView = DetectedVaultFailureView() let containerView = UIView() failureView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(failureView) @@ -55,16 +55,6 @@ class CreateNewVaultChooseFolderViewController: ChooseFolderViewController { } } -private class FailureView: DetectedVaultView { - init() { - let configuration = UIImage.SymbolConfiguration(pointSize: 120) - let warningSymbol = UIImage(systemName: "exclamationmark.triangle.fill", withConfiguration: configuration) - let imageView = UIImageView(image: warningSymbol) - imageView.tintColor = UIColor(named: "yellow") - super.init(imageView: imageView, text: NSLocalizedString("addVault.createNewVault.detectedMasterkey.text", comment: "")) - } -} - #if DEBUG import CryptomatorCloudAccessCore import SwiftUI @@ -92,8 +82,8 @@ private class CreateNewVaultChooseFolderViewModelMock: ChooseFolderViewModelProt struct CreateNewVaultChooseFolderVCPreview: PreviewProvider { static var previews: some View { let viewController = CreateNewVaultChooseFolderViewController(with: CreateNewVaultChooseFolderViewModelMock(cloudPath: CloudPath("/Vault"), canCreateFolder: false)) - let vault = VaultDetailItem(name: "Vault", vaultPath: CloudPath("/Vault/masterkey.cryptomator"), isLegacyVault: false) - viewController.showDetectedVault(vault) + let item = VaultDetailItem(name: "Vault", vaultPath: CloudPath("/Vault/masterkey.cryptomator"), isLegacyVault: false) + viewController.showDetectedVault(item) return viewController.toPreview() } } diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift index 00848e60f..da43e20d5 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift @@ -72,27 +72,18 @@ class CreateNewVaultCoordinator: AccountListing, CloudChoosing, Coordinator { // MARK: - LocalFileSystemProvider Flow private func startLocalFileSystemAuthenticationFlow() { - LocalFileSystemAuthenticator.authenticateForOpenExistingVault(from: navigationController, onCompletion: { credential in - let account = CloudProviderAccount(accountUID: credential.identifier, cloudProviderType: .localFileSystem) - do { - try CloudProviderAccountDBManager.shared.saveNewAccount(account) - } catch { - DDLogError("startLocalFileSystemAuthenticationFlow saveNewAccount failed with: \(error)") - } - self.startAuthenticatedLocalFileSystemCreateNewVaultFlow(with: credential, account: account) - }) - } - - private func startAuthenticatedLocalFileSystemCreateNewVaultFlow(with credential: LocalFileSystemCredential, account: CloudProviderAccount) { + let child = CreateNewLocalVaultCoordinator(vaultName: vaultName, navigationController: navigationController) + childCoordinators.append(child) + child.parentCoordinator = self + child.start() + } + + func startAuthenticatedLocalFileSystemCreateNewVaultFlow(credential: LocalFileSystemCredential, account: CloudProviderAccount, item: Item) { let provider = LocalFileSystemProvider(rootURL: credential.rootURL) let child = AuthenticatedCreateNewVaultCoordinator(navigationController: navigationController, provider: provider, account: account, vaultName: vaultName) childCoordinators.append(child) child.parentCoordinator = self - - let viewModel = CreateNewLocalVaultViewModel(rootFolderName: credential.rootURL.lastPathComponent, vaultName: vaultName, provider: provider) - let chooseFolderVC = CreateNewLocalVaultViewController(with: viewModel) - chooseFolderVC.coordinator = child - navigationController.pushViewController(chooseFolderVC, animated: true) + child.chooseItem(item) } } diff --git a/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift new file mode 100644 index 000000000..3002c6ce4 --- /dev/null +++ b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift @@ -0,0 +1,18 @@ +// +// DetectedVaultFailureView.swift +// Cryptomator +// +// Created by Philipp Schmid on 28.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class DetectedVaultFailureView: DetectedVaultView { + init() { + let configuration = UIImage.SymbolConfiguration(pointSize: 120) + let warningSymbol = UIImage(systemName: "exclamationmark.triangle.fill", withConfiguration: configuration) + let imageView = UIImageView(image: warningSymbol) + imageView.tintColor = UIColor(named: "yellow") + super.init(imageView: imageView, text: NSLocalizedString("addVault.createNewVault.detectedMasterkey.text", comment: "")) + } +} diff --git a/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift new file mode 100644 index 000000000..1d2786690 --- /dev/null +++ b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift @@ -0,0 +1,40 @@ +// +// DetectedVaultFailureViewController.swift +// Cryptomator +// +// Created by Philipp Schmid on 28.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class DetectedVaultFailureViewController: UIViewController { + override func viewDidLoad() { + let failureView = DetectedVaultFailureView() + let containerView = UIView() + containerView.backgroundColor = .systemGroupedBackground + view.addSubview(containerView) + failureView.translatesAutoresizingMaskIntoConstraints = false + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(failureView) + NSLayoutConstraint.activate([ + containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + + failureView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), + failureView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + failureView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + failureView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) + ]) + } +} + +#if DEBUG +import SwiftUI +struct DetectedVaultFailureVC_Preview: PreviewProvider { + static var previews: some View { + DetectedVaultFailureViewController().toPreview() + } +} +#endif diff --git a/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift new file mode 100644 index 000000000..f027637d7 --- /dev/null +++ b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift @@ -0,0 +1,50 @@ +// +// CreateNewLocalVaultCoordinator.swift +// Cryptomator +// +// Created by Philipp Schmid on 28.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class CreateNewLocalVaultCoordinator: LocalVaultAdding, LocalFileSystemAuthenticating, Coordinator { + var childCoordinators = [Coordinator]() + var navigationController: UINavigationController + weak var parentCoordinator: CreateNewVaultCoordinator? + private let vaultName: String + + init(vaultName: String, navigationController: UINavigationController) { + self.vaultName = vaultName + self.navigationController = navigationController + } + + func start() { + let viewModel = CreateNewLocalVaultViewModel(vaultName: vaultName) + let localFSAuthVC = AddLocalVaultViewController(viewModel: viewModel) + localFSAuthVC.coordinator = self + navigationController.pushViewController(localFSAuthVC, animated: true) + } + + // MARK: - LocalFileSystemAuthenticating + + func authenticated(credential: LocalFileSystemCredential) { + // TODO: Show Progress-HUD + } + + // MARK: - LocalVaultAdding + + func validationFailed(with error: Error, at viewController: UIViewController) { + // TODO: Disable Progress-HUD + if case CreateNewLocalVaultViewModelError.detectedExistingVault = error { + let failureVC = DetectedVaultFailureViewController() + failureVC.title = NSLocalizedString("addVault.createNewVault.title", comment: "") + navigationController.pushViewController(failureVC, animated: true) + } else { + handleError(error, for: viewController) + } + } + + func showPasswordScreen(for result: LocalFileSystemAuthenticationResult) { + parentCoordinator?.startAuthenticatedLocalFileSystemCreateNewVaultFlow(credential: result.credential, account: result.account, item: result.item) + } +} diff --git a/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift new file mode 100644 index 000000000..2d84f5619 --- /dev/null +++ b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift @@ -0,0 +1,49 @@ +// +// CreateNewLocalVaultViewModel.swift +// Cryptomator +// +// Created by Philipp Schmid on 24.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +import Promises +class CreateNewLocalVaultViewModel: LocalFileSystemAuthenticationViewModel, LocalFileSystemVaultInstallingViewModelProtocol { + private let vaultName: String + + init(vaultName: String, accountManager: CloudProviderAccountManager = CloudProviderAccountDBManager.shared) { + let documentPickerButtonText = NSLocalizedString("localFileSystemAuthentication.createNewVault.button", comment: "") + let headerText = NSLocalizedString("localFileSystemAuthentication.createNewVault.header", comment: "") + self.vaultName = vaultName + super.init(documentPickerButtonText: documentPickerButtonText, headerText: headerText, validationLogic: CreateNewLocalVaultValidationLogic(vaultName: vaultName), accountManager: accountManager) + } + + func addVault(for credential: LocalFileSystemCredential) -> Promise { + return validateAndSave(credential: credential).then { account -> LocalFileSystemAuthenticationResult in + let vault = Folder(path: CloudPath("/\(self.vaultName)")) + return LocalFileSystemAuthenticationResult(credential: credential, account: account, item: vault) + } + } +} + +private class CreateNewLocalVaultValidationLogic: LocalFileSystemAuthenticationValidationLogic { + private let vaultName: String + init(vaultName: String) { + self.vaultName = vaultName + } + + func validate(items: [CloudItemMetadata]) throws { + guard VaultDetector.getVaultItem(items: items, parentCloudPath: CloudPath("/")) == nil else { + throw CreateNewLocalVaultViewModelError.detectedExistingVault + } + guard !items.contains(where: { $0.name == vaultName }) else { + throw CreateNewVaultChooseFolderViewModelError.vaultNameCollision + } + } +} + +enum CreateNewLocalVaultViewModelError: Error { + case detectedExistingVault +} diff --git a/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift b/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift new file mode 100644 index 000000000..9a7d38725 --- /dev/null +++ b/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift @@ -0,0 +1,34 @@ +// +// AddLocalVaultViewController.swift +// Cryptomator +// +// Created by Philipp Schmid on 28.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class AddLocalVaultViewController: LocalFileSystemAuthenticationViewController { + typealias AddLocalVaultViewModel = LocalFileSystemAuthenticationBaseViewModelProtocol & LocalFileSystemVaultInstallingViewModelProtocol + let viewModel: AddLocalVaultViewModel + + init(viewModel: AddLocalVaultViewModel) { + self.viewModel = viewModel + super.init(viewModel: viewModel) + } + + override func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + let credential: LocalFileSystemCredential + do { + credential = try viewModel.userPicked(urls: urls) + } catch { + coordinator?.handleError(error, for: self) + return + } + coordinator?.authenticated(credential: credential) + viewModel.addVault(for: credential).then { result in + self.coordinator?.showPasswordScreen(for: result) + }.catch { error in + self.coordinator?.validationFailed(with: error, at: self) + } + } +} diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticating.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticating.swift similarity index 100% rename from Cryptomator/LocalFileSystem/LocalFileSystemAuthenticating.swift rename to Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticating.swift diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift similarity index 93% rename from Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift rename to Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift index 6855e1cd2..91296c5cc 100644 --- a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewController.swift +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift @@ -9,8 +9,8 @@ import MobileCoreServices import UIKit class LocalFileSystemAuthenticationViewController: SingleSectionTableViewController, UIDocumentPickerDelegate { - weak var coordinator: (LocalFileSystemAuthenticating & Coordinator)? - private let viewModel: LocalFileSystemAuthenticationViewModelProtocol + weak var coordinator: (LocalFileSystemAuthenticating & LocalVaultAdding & Coordinator)? + private let viewModel: LocalFileSystemAuthenticationBaseViewModelProtocol private lazy var openDocumentPickerCell: ButtonCell = { let cell = ButtonCell() cell.button.setTitle(viewModel.documentPickerButtonText, for: .normal) @@ -18,7 +18,7 @@ class LocalFileSystemAuthenticationViewController: SingleSectionTableViewControl return cell }() - init(viewModel: LocalFileSystemAuthenticationViewModelProtocol) { + init(viewModel: LocalFileSystemAuthenticationBaseViewModelProtocol) { self.viewModel = viewModel super.init() } @@ -104,7 +104,7 @@ private class LocalFileSystemAuthenticationHeaderView: UIView { #if DEBUG import CryptomatorCommonCore import SwiftUI -struct LocalFileSystemViewModelMock: LocalFileSystemAuthenticationViewModelProtocol { +struct LocalFileSystemViewModelMock: LocalFileSystemAuthenticationBaseViewModelProtocol { let documentPickerButtonText = "Select Storage Location" let headerText = "In the next screen, choose the storage location for your new vault." func userPicked(urls: [URL]) throws -> LocalFileSystemCredential { diff --git a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift new file mode 100644 index 000000000..33f495507 --- /dev/null +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift @@ -0,0 +1,83 @@ +// +// LocalFileSystemAuthenticationViewModel.swift +// Cryptomator +// +// Created by Philipp Schmid on 21.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +import Promises + +protocol LocalFileSystemAuthenticationBaseViewModelProtocol { + var documentPickerButtonText: String { get } + var headerText: String { get } + func userPicked(urls: [URL]) throws -> LocalFileSystemCredential +} + +protocol LocalFileSystemVaultInstallingViewModelProtocol { + func addVault(for credential: LocalFileSystemCredential) -> Promise +} + +protocol LocalFileSystemAuthenticationValidationLogic { + func validate(items: [CloudItemMetadata]) throws +} + +class LocalFileSystemAuthenticationViewModel: LocalFileSystemAuthenticationBaseViewModelProtocol { + let documentPickerButtonText: String + let headerText: String + private let validationLogic: LocalFileSystemAuthenticationValidationLogic + private let accountManager: CloudProviderAccountManager + + init(documentPickerButtonText: String, headerText: String, validationLogic: LocalFileSystemAuthenticationValidationLogic, accountManager: CloudProviderAccountManager) { + self.documentPickerButtonText = documentPickerButtonText + self.headerText = headerText + self.validationLogic = validationLogic + self.accountManager = accountManager + } + + func userPicked(urls: [URL]) throws -> LocalFileSystemCredential { + guard let rootURL = urls.first else { + throw LocalFileSystemAuthenticationViewModelError.invalidURL + } + let credential = LocalFileSystemCredential(rootURL: rootURL, identifier: UUID().uuidString) + return credential + } + + func validateAndSave(credential: LocalFileSystemCredential) -> Promise { + return validate(credential: credential).then { + try self.save(credential: credential) + } + } + + private func validate(credential: LocalFileSystemCredential) -> Promise { + let provider = LocalFileSystemProvider(rootURL: credential.rootURL) + return provider.fetchItemListExhaustively(forFolderAt: CloudPath("/")).then { itemList in + try self.validationLogic.validate(items: itemList.items) + } + } + + private func save(credential: LocalFileSystemCredential) throws -> CloudProviderAccount { + try LocalFileSystemBookmarkManager.saveBookmarkForRootURL(credential.rootURL, for: credential.identifier) + let account = CloudProviderAccount(accountUID: credential.identifier, cloudProviderType: .localFileSystem) + try accountManager.saveNewAccount(account) + return account + } +} + +struct LocalFileSystemCredential { + let rootURL: URL + let identifier: String +} + +struct LocalFileSystemAuthenticationResult { + let credential: LocalFileSystemCredential + let account: CloudProviderAccount + let item: Item +} + +enum LocalFileSystemAuthenticationViewModelError: Error { + case invalidURL +} diff --git a/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift b/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift new file mode 100644 index 000000000..418b5ec66 --- /dev/null +++ b/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift @@ -0,0 +1,13 @@ +// +// LocalVaultAdding.swift +// Cryptomator +// +// Created by Philipp Schmid on 28.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +protocol LocalVaultAdding { + func validationFailed(with error: Error, at viewController: UIViewController) + func showPasswordScreen(for result: LocalFileSystemAuthenticationResult) +} diff --git a/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift new file mode 100644 index 000000000..820e2c961 --- /dev/null +++ b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift @@ -0,0 +1,42 @@ +// +// OpenExistingLocalVaultCoordinator.swift +// Cryptomator +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class OpenExistingLocalVaultCoordinator: LocalVaultAdding, LocalFileSystemAuthenticating, Coordinator { + var childCoordinators = [Coordinator]() + var navigationController: UINavigationController + weak var parentCoordinator: OpenExistingVaultCoordinator? + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + let viewModel = OpenExistingLocalVaultViewModel() + let localFSAuthVC = AddLocalVaultViewController(viewModel: viewModel) + localFSAuthVC.coordinator = self + navigationController.pushViewController(localFSAuthVC, animated: true) + } + + // MARK: - LocalFileSystemAuthenticating + + func authenticated(credential: LocalFileSystemCredential) { + // TODO: Show Progress-HUD + } + + // MARK: - LocalVaultAdding + + func validationFailed(with error: Error, at viewController: UIViewController) { + // TODO: Disable Progress-HUD + handleError(error, for: viewController) + } + + func showPasswordScreen(for result: LocalFileSystemAuthenticationResult) { + parentCoordinator?.startAuthenticatedLocalFileSystemOpenExistingVaultFlow(credential: result.credential, account: result.account, item: result.item) + } +} diff --git a/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift new file mode 100644 index 000000000..6dae3b952 --- /dev/null +++ b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift @@ -0,0 +1,45 @@ +// +// OpenExistingLocalVaultViewModel.swift +// Cryptomator +// +// Created by Philipp Schmid on 23.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import Foundation +import Promises +class OpenExistingLocalVaultViewModel: LocalFileSystemAuthenticationViewModel, LocalFileSystemVaultInstallingViewModelProtocol { + private let validator: OpenExistingLocalVaultValidationLogic + init(accountManager: CloudProviderAccountManager = CloudProviderAccountDBManager.shared) { + let documentPickerButtonText = NSLocalizedString("localFileSystemAuthentication.openExistingVault.button", comment: "") + let headerText = NSLocalizedString("localFileSystemAuthentication.openExistingVault.header", comment: "") + self.validator = OpenExistingLocalVaultValidationLogic() + super.init(documentPickerButtonText: documentPickerButtonText, headerText: headerText, validationLogic: validator, accountManager: accountManager) + } + + func addVault(for credential: LocalFileSystemCredential) -> Promise { + return validateAndSave(credential: credential).then { account -> LocalFileSystemAuthenticationResult in + guard let detectedVaultItem = self.validator.vaultItem else { + throw OpenExistingLocalVaultViewModelError.noVaultFound + } + let vaultItem = VaultDetailItem(name: credential.rootURL.lastPathComponent, vaultPath: detectedVaultItem.vaultPath, isLegacyVault: detectedVaultItem.isLegacyVault) + return LocalFileSystemAuthenticationResult(credential: credential, account: account, item: vaultItem) + } + } +} + +private class OpenExistingLocalVaultValidationLogic: LocalFileSystemAuthenticationValidationLogic { + var vaultItem: VaultDetailItem? + func validate(items: [CloudItemMetadata]) throws { + guard let vaultItem = VaultDetector.getVaultItem(items: items, parentCloudPath: CloudPath("/")) else { + throw OpenExistingLocalVaultViewModelError.noVaultFound + } + self.vaultItem = vaultItem + } +} + +enum OpenExistingLocalVaultViewModelError: Error { + case noVaultFound +} diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift deleted file mode 100644 index 7cf9235fc..000000000 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLocalVaultViewModel.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// OpenExistingLocalVaultViewModel.swift -// Cryptomator -// -// Created by Philipp Schmid on 23.06.21. -// Copyright © 2021 Skymatic GmbH. All rights reserved. -// - -import CryptomatorCloudAccessCore -import Foundation -class OpenExistingLocalVaultViewModel: ChooseFolderViewModel { - let rootFolderName: String - init(rootFolderName: String, provider: LocalFileSystemProvider) { - self.rootFolderName = rootFolderName - super.init(canCreateFolder: false, cloudPath: CloudPath("/"), provider: provider) - } - - override var headerTitle: String { - return CloudPath(rootFolderName).path - } - - override func getVaultName(for cryptomatorFilePath: CloudPath) -> String { - return rootFolderName - } -} diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift index 8838d0392..f15c95519 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift @@ -20,6 +20,7 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { } override func showDetectedVault(_ vault: VaultDetailItem) { + tableView.reloadData() self.vault = vault refreshControl = nil navigationController?.setToolbarHidden(true, animated: true) @@ -108,8 +109,8 @@ private class OpenExistingVaultChooseFolderViewModelMock: ChooseFolderViewModelP struct OpenExistingVaultChooseFolderVCPreview: PreviewProvider { static var previews: some View { let viewController = OpenExistingVaultChooseFolderViewController(with: OpenExistingVaultChooseFolderViewModelMock(cloudPath: CloudPath("/Vault"), canCreateFolder: false)) - let vault = VaultDetailItem(name: "vault", vaultPath: CloudPath("/Vault/masterkey.cryptomator"), isLegacyVault: false) - viewController.showDetectedVault(vault) + let item = VaultDetailItem(name: "vault", vaultPath: CloudPath("/Vault/masterkey.cryptomator"), isLegacyVault: false) + viewController.showDetectedVault(item) return viewController.toPreview() } } diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift index 22391ae58..d7eb8f229 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultCoordinator.swift @@ -70,27 +70,18 @@ class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, Coordinator { // MARK: - LocalFileSystemProvider Flow private func startLocalFileSystemAuthenticationFlow() { - LocalFileSystemAuthenticator.authenticateForOpenExistingVault(from: navigationController, onCompletion: { credential in - let account = CloudProviderAccount(accountUID: credential.identifier, cloudProviderType: .localFileSystem) - do { - try CloudProviderAccountDBManager.shared.saveNewAccount(account) - } catch { - DDLogError("startLocalFileSystemAuthenticationFlow saveNewAccount failed with:\(error)") - } - self.startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with: credential, account: account) - }) - } - - private func startAuthenticatedLocalFileSystemOpenExistingVaultFlow(with credential: LocalFileSystemCredential, account: CloudProviderAccount) { + let child = OpenExistingLocalVaultCoordinator(navigationController: navigationController) + childCoordinators.append(child) + child.parentCoordinator = self + child.start() + } + + func startAuthenticatedLocalFileSystemOpenExistingVaultFlow(credential: LocalFileSystemCredential, account: CloudProviderAccount, item: Item) { let provider = LocalFileSystemProvider(rootURL: credential.rootURL) let child = AuthenticatedOpenExistingVaultCoordinator(navigationController: navigationController, provider: provider, account: account) childCoordinators.append(child) child.parentCoordinator = self - - let viewModel = OpenExistingLocalVaultViewModel(rootFolderName: credential.rootURL.lastPathComponent, provider: provider) - let chooseFolderVC = OpenExistingVaultChooseFolderViewController(with: viewModel) - chooseFolderVC.coordinator = child - navigationController.pushViewController(chooseFolderVC, animated: true) + child.chooseItem(item) } } diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift index 9b5d92ad4..adb54ad31 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift @@ -51,7 +51,7 @@ class ChooseFolderViewModel: ChooseFolderViewModelProtocol { func refreshItems() { provider.fetchItemListExhaustively(forFolderAt: cloudPath).then { itemList in - if let vaultItem = self.getVaultItem(items: itemList.items) { + if let vaultItem = VaultDetector.getVaultItem(items: itemList.items, parentCloudPath: self.cloudPath) { self.foundMasterkey = true self.vaultListener?(vaultItem) } else { @@ -63,39 +63,4 @@ class ChooseFolderViewModel: ChooseFolderViewModelProtocol { self.errorListener?(error) } } - - func getVaultItem(items: [CloudItemMetadata]) -> VaultDetailItem? { - if let vaultConfigPath = getVaultConfigCloudPath(items: items) { - let vaultName = getVaultName(for: vaultConfigPath) - return VaultDetailItem(name: vaultName, vaultPath: cloudPath, isLegacyVault: false) - } else if let legacyMasterkeyPath = getLegacyMasterkeyPath(items: items) { - let vaultName = getVaultName(for: legacyMasterkeyPath) - return VaultDetailItem(name: vaultName, vaultPath: cloudPath, isLegacyVault: true) - } else { - return nil - } - } - - func getVaultConfigCloudPath(items: [CloudItemMetadata]) -> CloudPath? { - let vaultConfigItem = items.first(where: { $0.name == "vault.cryptomator" && $0.itemType == .file }) - guard items.contains(where: { $0.name == "d" && $0.itemType == .folder }) else { - DDLogDebug("Missing d folder") - return nil - } - return vaultConfigItem?.cloudPath - } - - func getLegacyMasterkeyPath(items: [CloudItemMetadata]) -> CloudPath? { - let masterkeyItem = items.first(where: { $0.name == "masterkey.cryptomator" && $0.itemType == .file }) - guard items.contains(where: { $0.name == "d" && $0.itemType == .folder }) else { - DDLogDebug("Missing d folder") - return nil - } - return masterkeyItem?.cloudPath - } - - func getVaultName(for cryptomatorFilePath: CloudPath) -> String { - let parentPath = cryptomatorFilePath.deletingLastPathComponent() - return parentPath.lastPathComponent - } } diff --git a/Cryptomator/Common/VaultDetector.swift b/Cryptomator/Common/VaultDetector.swift new file mode 100644 index 000000000..80c8961a3 --- /dev/null +++ b/Cryptomator/Common/VaultDetector.swift @@ -0,0 +1,48 @@ +// +// VaultDetector.swift +// Cryptomator +// +// Created by Philipp Schmid on 28.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CocoaLumberjackSwift +import CryptomatorCloudAccessCore +import Foundation + +class VaultDetector { + class func getVaultItem(items: [CloudItemMetadata], parentCloudPath: CloudPath) -> VaultDetailItem? { + if let vaultConfigPath = getVaultConfigCloudPath(items: items) { + let vaultName = getVaultName(for: vaultConfigPath) + return VaultDetailItem(name: vaultName, vaultPath: parentCloudPath, isLegacyVault: false) + } else if let legacyMasterkeyPath = getLegacyMasterkeyPath(items: items) { + let vaultName = getVaultName(for: legacyMasterkeyPath) + return VaultDetailItem(name: vaultName, vaultPath: parentCloudPath, isLegacyVault: true) + } else { + return nil + } + } + + class func getVaultConfigCloudPath(items: [CloudItemMetadata]) -> CloudPath? { + let vaultConfigItem = items.first(where: { $0.name == "vault.cryptomator" && $0.itemType == .file }) + guard items.contains(where: { $0.name == "d" && $0.itemType == .folder }) else { + DDLogDebug("Missing d folder") + return nil + } + return vaultConfigItem?.cloudPath + } + + class func getLegacyMasterkeyPath(items: [CloudItemMetadata]) -> CloudPath? { + let masterkeyItem = items.first(where: { $0.name == "masterkey.cryptomator" && $0.itemType == .file }) + guard items.contains(where: { $0.name == "d" && $0.itemType == .folder }) else { + DDLogDebug("Missing d folder") + return nil + } + return masterkeyItem?.cloudPath + } + + class func getVaultName(for cryptomatorFilePath: CloudPath) -> String { + let parentPath = cryptomatorFilePath.deletingLastPathComponent() + return parentPath.lastPathComponent + } +} diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift deleted file mode 100644 index ae53f22e8..000000000 --- a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticationViewModel.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// LocalFileSystemAuthenticationViewModel.swift -// Cryptomator -// -// Created by Philipp Schmid on 21.06.21. -// Copyright © 2021 Skymatic GmbH. All rights reserved. -// - -import CryptomatorCloudAccessCore -import CryptomatorCommonCore -import Foundation -protocol LocalFileSystemAuthenticationViewModelProtocol { - var documentPickerButtonText: String { get } - var headerText: String { get } - func userPicked(urls: [URL]) throws -> LocalFileSystemCredential -} - -class LocalFileSystemAuthenticationViewModel: LocalFileSystemAuthenticationViewModelProtocol { - let documentPickerButtonText: String - let headerText: String - - init(documentPickerButtonText: String, headerText: String) { - self.documentPickerButtonText = documentPickerButtonText - self.headerText = headerText - } - - func userPicked(urls: [URL]) throws -> LocalFileSystemCredential { - guard let rootURL = urls.first else { - throw LocalFileSystemAuthenticationViewModelError.invalidURL - } - let credential = LocalFileSystemCredential(rootURL: rootURL, identifier: UUID().uuidString) - try LocalFileSystemBookmarkManager.saveBookmarkForRootURL(credential.rootURL, for: credential.identifier) - return credential - } -} - -struct LocalFileSystemCredential { - let rootURL: URL - let identifier: String -} - -enum LocalFileSystemAuthenticationViewModelError: Error { - case invalidURL -} diff --git a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift b/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift deleted file mode 100644 index a5364d89b..000000000 --- a/Cryptomator/LocalFileSystem/LocalFileSystemAuthenticator.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// LocalFileSystemAuthenticator.swift -// Cryptomator -// -// Created by Philipp Schmid on 23.06.21. -// Copyright © 2021 Skymatic GmbH. All rights reserved. -// - -import CryptomatorCommonCore -import Promises -import UIKit -class LocalFileSystemAuthenticator { - private static var coordinator: LocalFileSystemCoordinator? - - static func authenticateForOpenExistingVault(from navigationController: UINavigationController, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { - let documentPickerButtonText = NSLocalizedString("localFileSystemAuthentication.openExistingVault.button", comment: "") - let headerText = NSLocalizedString("localFileSystemAuthentication.openExistingVault.header", comment: "") - let viewModel = LocalFileSystemAuthenticationViewModel(documentPickerButtonText: documentPickerButtonText, headerText: headerText) - authenticate(from: navigationController, viewModel: viewModel, onCompletion: onCompletion) - } - - static func authenticateForCreateNewVault(from navigationController: UINavigationController, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { - let documentPickerButtonText = NSLocalizedString("localFileSystemAuthentication.createNewVault.button", comment: "") - let headerText = NSLocalizedString("localFileSystemAuthentication.createNewVault.header", comment: "") - let viewModel = LocalFileSystemAuthenticationViewModel(documentPickerButtonText: documentPickerButtonText, headerText: headerText) - authenticate(from: navigationController, viewModel: viewModel, onCompletion: onCompletion) - } - - static func authenticate(from navigationController: UINavigationController, viewModel: LocalFileSystemAuthenticationViewModelProtocol, onCompletion: @escaping (LocalFileSystemCredential) -> Void) { - let coordinator = LocalFileSystemCoordinator(navigationController: navigationController, viewModel: viewModel, onAuthenticated: onCompletion) - self.coordinator = coordinator - coordinator.start() - } -} - -private class LocalFileSystemCoordinator: Coordinator, LocalFileSystemAuthenticating { - var childCoordinators = [Coordinator]() - - var navigationController: UINavigationController - - private let viewModel: LocalFileSystemAuthenticationViewModelProtocol - let onAuthenticated: (LocalFileSystemCredential) -> Void - - init(navigationController: UINavigationController, viewModel: LocalFileSystemAuthenticationViewModelProtocol, onAuthenticated: @escaping (LocalFileSystemCredential) -> Void) { - self.navigationController = navigationController - self.viewModel = viewModel - self.onAuthenticated = onAuthenticated - } - - func start() { - let localFSAuthVC = LocalFileSystemAuthenticationViewController(viewModel: viewModel) - localFSAuthVC.coordinator = self - navigationController.pushViewController(localFSAuthVC, animated: true) - } - - func authenticated(credential: LocalFileSystemCredential) { - onAuthenticated(credential) - } -} diff --git a/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift b/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift new file mode 100644 index 000000000..7a8c6861b --- /dev/null +++ b/CryptomatorTests/AddLocalVault/AddLocalVaultViewModelTestCase.swift @@ -0,0 +1,89 @@ +// +// AddLocalVaultViewModelTestCase.swift +// CryptomatorTests +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCommonCore +import XCTest +@testable import Cryptomator +@testable import CryptomatorCloudAccessCore +@testable import CryptomatorCryptoLib + +class AddLocalVaultViewModelTestCase: XCTestCase { + var tmpDirURL: URL! + var accountManagerMock: CloudProviderAccountManagerMock! + override func setUpWithError() throws { + tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString, isDirectory: true) + try FileManager.default.createDirectory(at: tmpDirURL, withIntermediateDirectories: false) + accountManagerMock = CloudProviderAccountManagerMock() + } + + override func tearDownWithError() throws { + try FileManager.default.removeItem(at: tmpDirURL) + } + + func createVault(at url: URL) throws { + let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) + let vaultConfig = VaultConfig(id: "ABB9F673-F3E8-41A7-A43B-D29F5DA65068", format: 8, cipherCombo: .sivCTRMAC, shorteningThreshold: 220) + + let cryptor = Cryptor(masterkey: masterkey) + let rootDirPath = try getRootDirectoryURL(for: cryptor, vaultURL: url) + try FileManager.default.createDirectory(at: rootDirPath, withIntermediateDirectories: true, attributes: nil) + let masterkeyData = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 999, passphrase: "password", scryptCostParam: 2) + let masterkeyURL = url.appendingPathComponent("masterkey.cryptomator") + try masterkeyData.write(to: masterkeyURL) + let token = try vaultConfig.toToken(keyId: "masterkeyfile:masterkey.cryptomator", rawKey: masterkey.rawKey) + let vaultConfigURL = url.appendingPathComponent("vault.cryptomator") + try token.write(to: vaultConfigURL, atomically: true, encoding: .utf8) + } + + func createLegacyVault(at url: URL) throws { + let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) + let cryptor = Cryptor(masterkey: masterkey) + let rootDirPath = try getRootDirectoryURL(for: cryptor, vaultURL: url) + try FileManager.default.createDirectory(at: rootDirPath, withIntermediateDirectories: true, attributes: nil) + let masterkeyData = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "password", scryptCostParam: 2) + let masterkeyURL = url.appendingPathComponent("masterkey.cryptomator") + try masterkeyData.write(to: masterkeyURL) + } + + private func getRootDirectoryURL(for cryptor: Cryptor, vaultURL: URL) throws -> URL { + let digest = try cryptor.encryptDirId(Data()) + let i = digest.index(digest.startIndex, offsetBy: 2) + return vaultURL.appendingPathComponent("d/\(digest[.. CloudProviderType { + throw MockError.notMocked + } + + func getAllAccountUIDs(for type: CloudProviderType) throws -> [String] { + throw MockError.notMocked + } + + func saveNewAccount(_ account: CloudProviderAccount) throws { + savedAccounts.append(account) + } + + func removeAccount(with accountUID: String) throws { + throw MockError.notMocked + } + } +} + +extension LocalFileSystemCredential: Equatable { + public static func == (lhs: LocalFileSystemCredential, rhs: LocalFileSystemCredential) -> Bool { + lhs.identifier == rhs.identifier && lhs.rootURL == rhs.rootURL + } +} + +extension CloudProviderAccount: Equatable { + public static func == (lhs: CloudProviderAccount, rhs: CloudProviderAccount) -> Bool { + lhs.accountUID == rhs.accountUID && lhs.cloudProviderType == rhs.cloudProviderType + } +} diff --git a/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift b/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift new file mode 100644 index 000000000..970f4c2bd --- /dev/null +++ b/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift @@ -0,0 +1,97 @@ +// +// CreateNewLocalVaultViewModelTests.swift +// CryptomatorTests +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import XCTest + +@testable import Cryptomator + +class CreateNewLocalVaultViewModelTests: AddLocalVaultViewModelTestCase { + func testAddVault() throws { + let expectation = XCTestExpectation() + let viewModel = CreateNewLocalVaultViewModel(vaultName: "MyVault", accountManager: accountManagerMock) + let credential = LocalFileSystemCredential(rootURL: tmpDirURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { result in + XCTAssertEqual(credential, result.credential) + XCTAssertEqual(credential.identifier, result.account.accountUID) + XCTAssertEqual(CloudProviderType.localFileSystem, result.account.cloudProviderType) + + guard let chosenFolder = result.item as? Folder else { + XCTFail("result item is not a Folder") + return + } + XCTAssertEqual(CloudPath("/MyVault"), chosenFolder.path) + + XCTAssertEqual(1, self.accountManagerMock.savedAccounts.count) + XCTAssertEqual(result.account, self.accountManagerMock.savedAccounts[0]) + }.catch { error in + XCTFail("Promise failed with error: \(error)") + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func testAddVaultWithNameCollision() throws { + let expectation = XCTestExpectation() + let viewModel = CreateNewLocalVaultViewModel(vaultName: "MyVault", accountManager: accountManagerMock) + try FileManager.default.createDirectory(at: tmpDirURL.appendingPathComponent("MyVault"), withIntermediateDirectories: false) + let credential = LocalFileSystemCredential(rootURL: tmpDirURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { _ in + XCTFail("Promise fulfilled") + }.catch { error in + guard case CreateNewVaultChooseFolderViewModelError.vaultNameCollision = error else { + XCTFail("Promise rejected with wrong error: \(error)") + return + } + XCTAssertEqual(0, self.accountManagerMock.savedAccounts.count) + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func testAddVaultWithExistingVaultAtChosenURL() throws { + let expectation = XCTestExpectation() + let viewModel = CreateNewLocalVaultViewModel(vaultName: "MyVault", accountManager: accountManagerMock) + try createVault(at: tmpDirURL) + let credential = LocalFileSystemCredential(rootURL: tmpDirURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { _ in + XCTFail("Promise fulfilled") + }.catch { error in + guard case CreateNewLocalVaultViewModelError.detectedExistingVault = error else { + XCTFail("Promise rejected with wrong error: \(error)") + return + } + XCTAssertEqual(0, self.accountManagerMock.savedAccounts.count) + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func testAddVaultWithExistingLegacyVaultAtChosenURL() throws { + let expectation = XCTestExpectation() + let viewModel = CreateNewLocalVaultViewModel(vaultName: "MyVault", accountManager: accountManagerMock) + try createLegacyVault(at: tmpDirURL) + let credential = LocalFileSystemCredential(rootURL: tmpDirURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { _ in + XCTFail("Promise fulfilled") + }.catch { error in + guard case CreateNewLocalVaultViewModelError.detectedExistingVault = error else { + XCTFail("Promise rejected with wrong error: \(error)") + return + } + XCTAssertEqual(0, self.accountManagerMock.savedAccounts.count) + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} diff --git a/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift b/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift new file mode 100644 index 000000000..0691a1929 --- /dev/null +++ b/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift @@ -0,0 +1,95 @@ +// +// OpenExistingLocalVaultViewModelTests.swift +// CryptomatorTests +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CryptomatorCloudAccessCore +import CryptomatorCommonCore +import XCTest +@testable import Cryptomator +class OpenExistingLocalVaultViewModelTests: AddLocalVaultViewModelTestCase { + var viewModel: OpenExistingLocalVaultViewModel! + + override func setUpWithError() throws { + try super.setUpWithError() + viewModel = OpenExistingLocalVaultViewModel(accountManager: accountManagerMock) + } + + func testAddVault() throws { + let expectation = XCTestExpectation() + let vaultURL = tmpDirURL.appendingPathComponent("MyVault") + try createVault(at: vaultURL) + let rootURL = vaultURL.appendingPathComponent("/") + let credential = LocalFileSystemCredential(rootURL: rootURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { result in + XCTAssertEqual(credential, result.credential) + XCTAssertEqual(credential.identifier, result.account.accountUID) + XCTAssertEqual(CloudProviderType.localFileSystem, result.account.cloudProviderType) + + guard let vaultDetailItem = result.item as? VaultDetailItem else { + XCTFail("result item is not a VaultDetailItem") + return + } + XCTAssertEqual("MyVault", vaultDetailItem.name) + XCTAssertEqual(CloudPath("/"), vaultDetailItem.path) + XCTAssertFalse(vaultDetailItem.isLegacyVault) + + XCTAssertEqual(1, self.accountManagerMock.savedAccounts.count) + XCTAssertEqual(result.account, self.accountManagerMock.savedAccounts[0]) + }.catch { error in + XCTFail("Promise failed with error: \(error)") + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func testAddLegacyVault() throws { + let expectation = XCTestExpectation() + let vaultURL = tmpDirURL.appendingPathComponent("MyLegacyVault") + try createLegacyVault(at: vaultURL) + let rootURL = vaultURL.appendingPathComponent("/") + let credential = LocalFileSystemCredential(rootURL: rootURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { result in + XCTAssertEqual(credential, result.credential) + XCTAssertEqual(credential.identifier, result.account.accountUID) + XCTAssertEqual(CloudProviderType.localFileSystem, result.account.cloudProviderType) + + guard let vaultDetailItem = result.item as? VaultDetailItem else { + XCTFail("result item is not a VaultDetailItem") + return + } + XCTAssertEqual("MyLegacyVault", vaultDetailItem.name) + XCTAssertEqual(CloudPath("/"), vaultDetailItem.path) + XCTAssertTrue(vaultDetailItem.isLegacyVault) + + XCTAssertEqual(1, self.accountManagerMock.savedAccounts.count) + XCTAssertEqual(result.account, self.accountManagerMock.savedAccounts[0]) + }.catch { error in + XCTFail("Promise failed with error: \(error)") + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func testAddVaultWithMissingVault() throws { + let expectation = XCTestExpectation() + let credential = LocalFileSystemCredential(rootURL: tmpDirURL, identifier: UUID().uuidString) + viewModel.addVault(for: credential).then { _ in + XCTFail("Promise fulfilled") + }.catch { error in + guard case OpenExistingLocalVaultViewModelError.noVaultFound = error else { + XCTFail("Promise rejected with wrong error: \(error)") + return + } + XCTAssertEqual(0, self.accountManagerMock.savedAccounts.count) + }.always { + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} diff --git a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift index aca912f83..ebcb38837 100644 --- a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift +++ b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift @@ -206,10 +206,6 @@ private class VaultManagerMock: VaultManager { } } -private enum MockError: Error { - case notMocked -} - private struct CreatedVault { let vaultUID: String let delegateAccountUID: String diff --git a/CryptomatorTests/MockError.swift b/CryptomatorTests/MockError.swift new file mode 100644 index 000000000..edc3e6296 --- /dev/null +++ b/CryptomatorTests/MockError.swift @@ -0,0 +1,12 @@ +// +// MockError.swift +// CryptomatorTests +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import Foundation +enum MockError: Error { + case notMocked +} diff --git a/CryptomatorTests/VaultListViewModelTests.swift b/CryptomatorTests/VaultListViewModelTests.swift index a02a90bec..e86c10d9a 100644 --- a/CryptomatorTests/VaultListViewModelTests.swift +++ b/CryptomatorTests/VaultListViewModelTests.swift @@ -136,10 +136,6 @@ private class VaultAccountManagerMock: VaultAccountManager { } } -private enum MockError: Error { - case notMocked -} - private class VaultManagerMock: VaultDBManager { var removedFileProviderDomains = [String]() override func removeFileProviderDomain(withVaultUID vaultUID: String) -> Promise { From de955326f45d9d02523a546c75978d26d2227f8e Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 14:35:45 +0200 Subject: [PATCH 07/23] Renamed LocalFileSystemAuthenticationBaseViewModelProtocol back to LocalFileSystemAuthenticationViewModelProtocol --- .../AddVault/LocalVault/AddLocalVaultViewController.swift | 2 +- .../LocalFileSystemAuthenticationViewController.swift | 6 +++--- .../LocalVault/LocalFileSystemAuthenticationViewModel.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift b/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift index 9a7d38725..223cc93fb 100644 --- a/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift +++ b/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift @@ -8,7 +8,7 @@ import UIKit class AddLocalVaultViewController: LocalFileSystemAuthenticationViewController { - typealias AddLocalVaultViewModel = LocalFileSystemAuthenticationBaseViewModelProtocol & LocalFileSystemVaultInstallingViewModelProtocol + typealias AddLocalVaultViewModel = LocalFileSystemAuthenticationViewModelProtocol & LocalFileSystemVaultInstallingViewModelProtocol let viewModel: AddLocalVaultViewModel init(viewModel: AddLocalVaultViewModel) { diff --git a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift index 91296c5cc..7a86a7fc8 100644 --- a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift @@ -10,7 +10,7 @@ import MobileCoreServices import UIKit class LocalFileSystemAuthenticationViewController: SingleSectionTableViewController, UIDocumentPickerDelegate { weak var coordinator: (LocalFileSystemAuthenticating & LocalVaultAdding & Coordinator)? - private let viewModel: LocalFileSystemAuthenticationBaseViewModelProtocol + private let viewModel: LocalFileSystemAuthenticationViewModelProtocol private lazy var openDocumentPickerCell: ButtonCell = { let cell = ButtonCell() cell.button.setTitle(viewModel.documentPickerButtonText, for: .normal) @@ -18,7 +18,7 @@ class LocalFileSystemAuthenticationViewController: SingleSectionTableViewControl return cell }() - init(viewModel: LocalFileSystemAuthenticationBaseViewModelProtocol) { + init(viewModel: LocalFileSystemAuthenticationViewModelProtocol) { self.viewModel = viewModel super.init() } @@ -104,7 +104,7 @@ private class LocalFileSystemAuthenticationHeaderView: UIView { #if DEBUG import CryptomatorCommonCore import SwiftUI -struct LocalFileSystemViewModelMock: LocalFileSystemAuthenticationBaseViewModelProtocol { +struct LocalFileSystemViewModelMock: LocalFileSystemAuthenticationViewModelProtocol { let documentPickerButtonText = "Select Storage Location" let headerText = "In the next screen, choose the storage location for your new vault." func userPicked(urls: [URL]) throws -> LocalFileSystemCredential { diff --git a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift index 33f495507..3c6f38d44 100644 --- a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewModel.swift @@ -11,7 +11,7 @@ import CryptomatorCommonCore import Foundation import Promises -protocol LocalFileSystemAuthenticationBaseViewModelProtocol { +protocol LocalFileSystemAuthenticationViewModelProtocol { var documentPickerButtonText: String { get } var headerText: String { get } func userPicked(urls: [URL]) throws -> LocalFileSystemCredential @@ -25,7 +25,7 @@ protocol LocalFileSystemAuthenticationValidationLogic { func validate(items: [CloudItemMetadata]) throws } -class LocalFileSystemAuthenticationViewModel: LocalFileSystemAuthenticationBaseViewModelProtocol { +class LocalFileSystemAuthenticationViewModel: LocalFileSystemAuthenticationViewModelProtocol { let documentPickerButtonText: String let headerText: String private let validationLogic: LocalFileSystemAuthenticationValidationLogic From 0265a8fdf4ae76263dc42c51391ff84b574551d2 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 14:38:53 +0200 Subject: [PATCH 08/23] Updated cloud-access-swift dependency [ci skip] --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0f54d4aaa..d7b677d6b 100644 --- a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/cryptomator/cloud-access-swift.git", "state": { "branch": null, - "revision": "dfeb202bfc52d0e209d85d4865a62ee1a8bbbf9d", - "version": "0.12.3" + "revision": "785aba8f7d8b0c4762667b5b12c70870ca56b224", + "version": "0.12.4" } }, { From ed0ed14e7bc2285eedf429f9b07fbba1d43673f4 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 15:16:40 +0200 Subject: [PATCH 09/23] localized CreateNewVaultPasswordViewModel [ci skip] --- .../CreateNewVault/CreateNewVaultPasswordViewModel.swift | 5 ++++- Cryptomator/de.lproj/Localizable.strings | 2 ++ Cryptomator/en.lproj/Localizable.strings | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift index 6598f8b2b..6588366e8 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift @@ -26,7 +26,10 @@ class CreateNewVaultPasswordViewModel: CreateNewVaultPasswordViewModelProtocol { } let vaultUID: String - let headerTitles = ["Enter a new password.", "Confirm the new password."] + let headerTitles = [ + NSLocalizedString("addVault.createNewVault.enterPassword.header", comment: ""), + NSLocalizedString("addVault.createNewVault.confirmPassword.header", comment: "") + ] var password: String? var confirmingPassword: String? diff --git a/Cryptomator/de.lproj/Localizable.strings b/Cryptomator/de.lproj/Localizable.strings index c1fa1bf88..b4ec117d7 100644 --- a/Cryptomator/de.lproj/Localizable.strings +++ b/Cryptomator/de.lproj/Localizable.strings @@ -27,6 +27,8 @@ "addVault.createNewVault.title" = "Neuen Tresor erstellen"; "addVault.createNewVault.chooseCloud.header" = "Wo soll Cryptomator die verschlüsselten Dateien deines Tresors speichern?"; "addVault.createNewVault.detectedMasterkey.text" = "Cryptomator hat an diesem Ort einen bestehenden Tresor erkannt.\nUm einen neuen Tresor zu erstellen, gehe bitte zurück und wähle einen anderen Ordner aus."; +"addVault.createNewVault.enterPassword.header" = "Gib ein neues Passwort ein."; +"addVault.createNewVault.confirmPassword.header" = "Bestätige das neue Passwort."; "addVault.createNewVault.alert.confirmPassword.title" = "Passwort bestätigen?"; "addVault.createNewVault.alert.confirmPassword.message" = "WICHTIG: Wenn du dein Passwort vergisst, gibt es keine Möglichkeit, deine Daten wiederherzustellen."; "addVault.openExistingVault.title" = "Existierenden Tresor öffnen"; diff --git a/Cryptomator/en.lproj/Localizable.strings b/Cryptomator/en.lproj/Localizable.strings index 3367fe24e..861c4ec28 100644 --- a/Cryptomator/en.lproj/Localizable.strings +++ b/Cryptomator/en.lproj/Localizable.strings @@ -27,6 +27,8 @@ "addVault.createNewVault.title" = "Create New Vault"; "addVault.createNewVault.chooseCloud.header" = "Where should Cryptomator store the encrypted files of your vault?"; "addVault.createNewVault.detectedMasterkey.text" = "Cryptomator detected an existing vault at this location.\nIn order to create a new vault, please go back and choose a different folder."; +"addVault.createNewVault.enterPassword.header" = "Enter a new password."; +"addVault.createNewVault.confirmPassword.header" = "Confirm the new password."; "addVault.createNewVault.alert.confirmPassword.title" = "Confirm Password?"; "addVault.createNewVault.alert.confirmPassword.message" = "IMPORTANT: If you forget your password, there is no way to recover your data."; "addVault.openExistingVault.title" = "Open Existing Vault"; From 569ad2a770716ca57c9b9af7dd43223ad51a1a0e Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 15:22:43 +0200 Subject: [PATCH 10/23] Save VaultAccount to DB after adding the FileProviderDomain instead of the other way around --- .../Manager/VaultDBManager.swift | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index 8d9a2e6af..9ce7c9356 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -85,15 +85,15 @@ public class VaultDBManager: VaultManager { try self.uploadVaultConfigToken(vaultConfigToken, vaultPath: vaultPath, delegate: delegate, tmpDirURL: tmpDirURL) }.then { _ -> Promise in try self.createVaultFolderStructure(masterkey: masterkey, vaultPath: vaultPath, delegate: delegate) - }.then { _ -> Void in + }.then { _ -> Promise in let unverifiedVaultConfig = try UnverifiedVaultConfig(token: vaultConfigToken) let decorator = try VaultProviderFactory.createVaultProvider(from: unverifiedVaultConfig, masterkey: masterkey, vaultPath: vaultPath, with: delegate) - let account = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultPath.lastPathComponent, lastUpToDateCheck: Date()) - try self.vaultAccountManager.saveNewAccount(account) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultConfigToken: vaultConfigToken, password: password, storePasswordInKeychain: storePasswordInKeychain) VaultDBManager.cachedDecorators[vaultUID] = decorator + return self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultPath.lastPathComponent) }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultPath.lastPathComponent) + let account = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultPath.lastPathComponent, lastUpToDateCheck: Date()) + try self.vaultAccountManager.saveNewAccount(account) } } @@ -193,18 +193,18 @@ public class VaultDBManager: VaultManager { let masterkeyPath = vaultPath.appendingPathComponent("masterkey.cryptomator") return delegate.downloadFile(from: vaultConfigPath, to: localVaultConfigURL).then { delegate.downloadFile(from: masterkeyPath, to: localMasterkeyURL) - }.then { + }.then { _ -> Promise in let token = try String(contentsOf: localVaultConfigURL, encoding: .utf8) let unverifiedVaultConfig = try UnverifiedVaultConfig(token: token) let masterkeyFile = try MasterkeyFile.withContentFromURL(url: localMasterkeyURL) let masterkey = try masterkeyFile.unlock(passphrase: password) let vaultProvider = try VaultProviderFactory.createVaultProvider(from: unverifiedVaultConfig, masterkey: masterkey, vaultPath: vaultPath, with: delegate) - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultItem.name, lastUpToDateCheck: Date()) try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultConfigToken: token, password: password, storePasswordInKeychain: storePasswordInKeychain) - try self.vaultAccountManager.saveNewAccount(vaultAccount) VaultDBManager.cachedDecorators[vaultUID] = vaultProvider + return self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultItem.name) }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultItem.name) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultItem.name, lastUpToDateCheck: Date()) + try self.vaultAccountManager.saveNewAccount(vaultAccount) }.catch { _ in VaultDBManager.cachedDecorators[vaultUID] = nil } @@ -228,15 +228,16 @@ public class VaultDBManager: VaultManager { let localMasterkeyURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) let vaultPath = vaultItem.vaultPath let masterkeyPath = vaultPath.appendingPathComponent("masterkey.cryptomator") - return delegate.downloadFile(from: masterkeyPath, to: localMasterkeyURL).then { + return delegate.downloadFile(from: masterkeyPath, to: localMasterkeyURL).then { _ -> Promise in let masterkeyFile = try MasterkeyFile.withContentFromURL(url: localMasterkeyURL) let masterkey = try masterkeyFile.unlock(passphrase: password) - _ = try self.createLegacyVaultDecorator(from: masterkey, delegate: delegate, vaultPath: vaultPath, vaultUID: vaultUID, vaultVersion: masterkeyFile.version) - let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultItem.name, lastUpToDateCheck: Date()) + let vaultProvider = try self.createLegacyVaultDecorator(from: masterkey, delegate: delegate, vaultPath: vaultPath, vaultUID: vaultUID, vaultVersion: masterkeyFile.version) + VaultDBManager.cachedDecorators[vaultUID] = vaultProvider try self.saveFileProviderConformMasterkeyToKeychain(masterkey, forVaultUID: vaultUID, vaultVersion: masterkeyFile.version, password: password, storePasswordInKeychain: storePasswordInKeychain) - try self.vaultAccountManager.saveNewAccount(vaultAccount) + return self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultItem.name) }.then { - self.addFileProviderDomain(forVaultUID: vaultUID, displayName: vaultItem.name) + let vaultAccount = VaultAccount(vaultUID: vaultUID, delegateAccountUID: delegateAccountUID, vaultPath: vaultPath, vaultName: vaultItem.name, lastUpToDateCheck: Date()) + try self.vaultAccountManager.saveNewAccount(vaultAccount) } } catch { VaultDBManager.cachedDecorators[vaultUID] = nil From 34ca270bc381ef7bdc54eabeb71ced131b4b63f9 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 15:34:37 +0200 Subject: [PATCH 11/23] added "empty folder" footer to ChooseFolder screen [ci skip] --- .../Common/ChooseFolder/ChooseFolderViewController.swift | 9 +++++++++ Cryptomator/de.lproj/Localizable.strings | 2 ++ Cryptomator/en.lproj/Localizable.strings | 2 ++ 3 files changed, 13 insertions(+) diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift index 6a9d93a3c..d86e87e71 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift @@ -118,6 +118,15 @@ class ChooseFolderViewController: SingleSectionTableViewController { return cell } + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + let itemsLoading = refreshControl?.isRefreshing ?? true + if !itemsLoading, viewModel.items.isEmpty { + return NSLocalizedString("chooseFolder.emptyFolder.footer", comment: "") + } else { + return nil + } + } + // MARK: - UITableViewDelegate override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { diff --git a/Cryptomator/de.lproj/Localizable.strings b/Cryptomator/de.lproj/Localizable.strings index b4ec117d7..d4115030f 100644 --- a/Cryptomator/de.lproj/Localizable.strings +++ b/Cryptomator/de.lproj/Localizable.strings @@ -40,6 +40,8 @@ "addVault.success.openFilesApp" = "Dateien-App öffnen"; "addVault.success.footer" = "Falls du es noch nicht getan hast, aktiviere Cryptomator in der Dateien-App."; +"chooseFolder.emptyFolder.footer" = "Ordner ist leer"; + "settings.title" = "Einstellungen"; "settings.aboutCryptomator" = "Über Cryptomator"; "settings.aboutCryptomator.title" = "Version %@ (%@)"; diff --git a/Cryptomator/en.lproj/Localizable.strings b/Cryptomator/en.lproj/Localizable.strings index 861c4ec28..07c6fa3c6 100644 --- a/Cryptomator/en.lproj/Localizable.strings +++ b/Cryptomator/en.lproj/Localizable.strings @@ -40,6 +40,8 @@ "addVault.success.openFilesApp" = "Open Files App"; "addVault.success.footer" = "If you haven't already, enable Cryptomator in the Files app."; +"chooseFolder.emptyFolder.footer" = "Folder is Empty"; + "settings.title" = "Settings"; "settings.aboutCryptomator" = "About Cryptomator"; "settings.aboutCryptomator.title" = "Version %@ (%@)"; From 081eab4610b71aebc4865b6ef10864e0ea89837e Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 15:47:41 +0200 Subject: [PATCH 12/23] sort item list in ChooseFolder by name [ci skip] --- Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift index adb54ad31..569b2022e 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewModel.swift @@ -56,7 +56,7 @@ class ChooseFolderViewModel: ChooseFolderViewModelProtocol { self.vaultListener?(vaultItem) } else { self.foundMasterkey = false - self.items = itemList.items + self.items = itemList.items.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending } self.changeListener?() } }.catch { error in From 44de765887af908f8e00c68cd6f8d8f085d23f5c Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 15:52:59 +0200 Subject: [PATCH 13/23] Temporarily disabled the SearchBar in ChooseFolderViewController [ci skip] --- .../ChooseFolder/ChooseFolderViewController.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift index d86e87e71..e8aab4fce 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift @@ -19,7 +19,7 @@ class ChooseFolderViewController: SingleSectionTableViewController { }() private lazy var searchController: UISearchController = { - return UISearchController(searchResultsController: self) + return UISearchController() }() init(with viewModel: ChooseFolderViewModelProtocol) { @@ -129,11 +129,18 @@ class ChooseFolderViewController: SingleSectionTableViewController { // MARK: - UITableViewDelegate - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + /* override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if viewModel.foundMasterkey { + return nil + } + return header + } */ + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if viewModel.foundMasterkey { return nil } - return header + return viewModel.headerTitle } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { From cddf82334a2191fc92295d427c8c5a2ac55c9bb7 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 16:00:51 +0200 Subject: [PATCH 14/23] show files app when selecting vault in VaultList [ci skip] --- Cryptomator.xcodeproj/project.pbxproj | 4 +++ .../AddVault/AddVaultSuccessCoordinator.swift | 17 +--------- Cryptomator/Common/FilesAppUtil.swift | 33 +++++++++++++++++++ .../VaultList/VaultListViewController.swift | 5 +++ 4 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 Cryptomator/Common/FilesAppUtil.swift diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 527f96228..88b8eda4b 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -209,6 +209,7 @@ 747F2F3B2587BC4B0072FB30 /* FileSystemLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740375FF2587AEB60023FF53 /* FileSystemLock.swift */; }; 747F2F3C2587BC4B0072FB30 /* RWLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740375FE2587AEB60023FF53 /* RWLock.swift */; }; 747F2F3D2587BC4B0072FB30 /* LockNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740376002587AEB60023FF53 /* LockNode.swift */; }; + 74D365BA268B5DB0005ECD69 /* FilesAppUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D365B9268B5DB0005ECD69 /* FilesAppUtil.swift */; }; 74FC576125ADED030003ED27 /* VaultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FC576025ADED030003ED27 /* VaultCell.swift */; }; /* End PBXBuildFile section */ @@ -509,6 +510,7 @@ 7439031225E0008D00BB3B81 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 7439031B25E0009D00BB3B81 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 7469AD99266E26B0000DCD45 /* URL+Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Zip.swift"; sourceTree = ""; }; + 74D365B9268B5DB0005ECD69 /* FilesAppUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesAppUtil.swift; sourceTree = ""; }; 74FC576025ADED030003ED27 /* VaultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -817,6 +819,7 @@ 4A8195E425ADB94500F7DDA1 /* Previews */, 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */, 4A1EB0D4268A5AA3006D072B /* VaultDetector.swift */, + 74D365B9268B5DB0005ECD69 /* FilesAppUtil.swift */, ); path = Common; sourceTree = ""; @@ -1510,6 +1513,7 @@ 4A53CC17267CDBFF00853BB3 /* CreateNewVaultChooseFolderViewModel.swift in Sources */, 4ADD2342267383BE00374E4E /* AddVaultSuccessViewModel.swift in Sources */, 4AF91CD925A722A600ACF01E /* VaultInfo.swift in Sources */, + 74D365BA268B5DB0005ECD69 /* FilesAppUtil.swift in Sources */, 4A644B4D267B55E4008CBB9A /* CreateNewVaultCoordinator.swift in Sources */, 740D3682266A19150058744D /* SettingsViewModel.swift in Sources */, 4A7B97D325B6F7520044B7FB /* AccountListViewModel.swift in Sources */, diff --git a/Cryptomator/AddVault/AddVaultSuccessCoordinator.swift b/Cryptomator/AddVault/AddVaultSuccessCoordinator.swift index aa28563a8..1d34d62f5 100644 --- a/Cryptomator/AddVault/AddVaultSuccessCoordinator.swift +++ b/Cryptomator/AddVault/AddVaultSuccessCoordinator.swift @@ -43,22 +43,7 @@ class AddVaultSuccessCoordinator: AddVaultSuccesing, Coordinator { } func showFilesApp(forVaultUID vaultUID: String) { - guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: CryptomatorConstants.appGroupName) else { - DDLogDebug("containerURL is nil") - return - } - let url = containerURL.appendingPathComponent("File Provider Storage").appendingPathComponent(vaultUID) - guard let sharedDocumentsURL = changeSchemeToSharedDocuments(for: url) else { - DDLogDebug("Conversion to shared documents url failed") - return - } - UIApplication.shared.open(sharedDocumentsURL) + FilesAppUtil.showFilesApp(forVaultUID: vaultUID) done() } - - private func changeSchemeToSharedDocuments(for url: URL) -> URL? { - var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) - comps?.scheme = "shareddocuments" - return comps?.url - } } diff --git a/Cryptomator/Common/FilesAppUtil.swift b/Cryptomator/Common/FilesAppUtil.swift new file mode 100644 index 000000000..2149b45e7 --- /dev/null +++ b/Cryptomator/Common/FilesAppUtil.swift @@ -0,0 +1,33 @@ +// +// FilesAppUtil.swift +// Cryptomator +// +// Created by Tobias Hagemann on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import CocoaLumberjack +import CocoaLumberjackSwift +import CryptomatorCommonCore +import UIKit + +public enum FilesAppUtil { + public static func showFilesApp(forVaultUID vaultUID: String) { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: CryptomatorConstants.appGroupName) else { + DDLogDebug("containerURL is nil") + return + } + let url = containerURL.appendingPathComponent("File Provider Storage").appendingPathComponent(vaultUID) + guard let sharedDocumentsURL = FilesAppUtil.changeSchemeToSharedDocuments(for: url) else { + DDLogDebug("Conversion to shared documents url failed") + return + } + UIApplication.shared.open(sharedDocumentsURL) + } + + private static func changeSchemeToSharedDocuments(for url: URL) -> URL? { + var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) + comps?.scheme = "shareddocuments" + return comps?.url + } +} diff --git a/Cryptomator/VaultList/VaultListViewController.swift b/Cryptomator/VaultList/VaultListViewController.swift index 393f00a6d..2518a2573 100644 --- a/Cryptomator/VaultList/VaultListViewController.swift +++ b/Cryptomator/VaultList/VaultListViewController.swift @@ -135,6 +135,11 @@ class VaultListViewController: UITableViewController { } return header } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + FilesAppUtil.showFilesApp(forVaultUID: viewModel.vaults[indexPath.row].vaultUID) + } } #if DEBUG From a131c9cfd26c02925ff2f0561b46b30d0f5b5981 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:03:56 +0200 Subject: [PATCH 15/23] Added onboarding screen to the FileProviderExtensionUI [ci skip] --- Cryptomator.xcodeproj/project.pbxproj | 40 +++++-- .../Base.lproj/MainInterface.storyboard | 102 ------------------ .../DocumentActionViewController.swift | 34 ------ .../FileProviderCoordinator.swift | 29 +++++ FileProviderExtensionUI/Info.plist | 4 +- FileProviderExtensionUI/OnboardingView.swift | 61 +++++++++++ .../OnboardingViewController.swift | 92 ++++++++++++++++ .../RootViewController.swift | 31 ++++++ .../de.lproj/Localizable.strings | 11 ++ .../en.lproj/Localizable.strings | 11 ++ 10 files changed, 266 insertions(+), 149 deletions(-) delete mode 100644 FileProviderExtensionUI/Base.lproj/MainInterface.storyboard delete mode 100644 FileProviderExtensionUI/DocumentActionViewController.swift create mode 100644 FileProviderExtensionUI/FileProviderCoordinator.swift create mode 100644 FileProviderExtensionUI/OnboardingView.swift create mode 100644 FileProviderExtensionUI/OnboardingViewController.swift create mode 100644 FileProviderExtensionUI/RootViewController.swift create mode 100644 FileProviderExtensionUI/de.lproj/Localizable.strings create mode 100644 FileProviderExtensionUI/en.lproj/Localizable.strings diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 88b8eda4b..59b091e87 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -94,6 +94,13 @@ 4A6A5206268B2B24006F7368 /* MockError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5205268B2B24006F7368 /* MockError.swift */; }; 4A6A5208268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5207268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift */; }; 4A6A520B268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A520A268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift */; }; + 4A6A520D268B5EF7006F7368 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A520C268B5EF7006F7368 /* RootViewController.swift */; }; + 4A6A520F268B5FA1006F7368 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A520E268B5FA1006F7368 /* OnboardingView.swift */; }; + 4A6A5212268B6697006F7368 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AE97DB324572E4A00452814 /* Assets.xcassets */; }; + 4A6A5213268B66AC006F7368 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4AF91CC625A6437000ACF01E /* Colors.xcassets */; }; + 4A6A5214268B6937006F7368 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4A6A5216268B6937006F7368 /* Localizable.strings */; }; + 4A6A5219268B6D32006F7368 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5218268B6D31006F7368 /* OnboardingViewController.swift */; }; + 4A6A521B268B7147006F7368 /* FileProviderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A521A268B7147006F7368 /* FileProviderCoordinator.swift */; }; 4A717CD924C835740048E08F /* ReparentTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */; }; 4A753DB92678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */; }; 4A797F8F24AC6731007DDBE1 /* FileProviderItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */; }; @@ -126,8 +133,6 @@ 4AA22C1E261CA94700A17486 /* UsernameFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA22C1D261CA94700A17486 /* UsernameFieldCell.swift */; }; 4AA621D9249A6A8400A0BCBD /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA621D8249A6A8400A0BCBD /* FileProviderExtension.swift */; }; 4AA621DD249A6A8400A0BCBD /* FileProviderEnumerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA621DC249A6A8400A0BCBD /* FileProviderEnumerator.swift */; }; - 4AA621E7249A6A8400A0BCBD /* DocumentActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA621E6249A6A8400A0BCBD /* DocumentActionViewController.swift */; }; - 4AA621EA249A6A8400A0BCBD /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4AA621E8249A6A8400A0BCBD /* MainInterface.storyboard */; }; 4AA8613725C19D4F002A59F5 /* DetectedMasterkeyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA8613625C19D4F002A59F5 /* DetectedMasterkeyViewModel.swift */; }; 4AA8614825C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA8614725C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift */; }; 4AA8615125C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA8615025C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift */; }; @@ -385,6 +390,12 @@ 4A6A5205268B2B24006F7368 /* MockError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockError.swift; sourceTree = ""; }; 4A6A5207268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLocalVaultViewModelTestCase.swift; sourceTree = ""; }; 4A6A520A268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewLocalVaultViewModelTests.swift; sourceTree = ""; }; + 4A6A520C268B5EF7006F7368 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; + 4A6A520E268B5FA1006F7368 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 4A6A5215268B6937006F7368 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 4A6A5217268B693E006F7368 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 4A6A5218268B6D31006F7368 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; + 4A6A521A268B7147006F7368 /* FileProviderCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderCoordinator.swift; sourceTree = ""; }; 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReparentTaskManagerTests.swift; sourceTree = ""; }; 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLegacyVaultPasswordViewModel.swift; sourceTree = ""; }; 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItemTests.swift; sourceTree = ""; }; @@ -421,8 +432,6 @@ 4AA621DE249A6A8400A0BCBD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4AA621DF249A6A8400A0BCBD /* FileProviderExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderExtension.entitlements; sourceTree = ""; }; 4AA621E4249A6A8400A0BCBD /* FileProviderExtensionUI.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExtensionUI.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 4AA621E6249A6A8400A0BCBD /* DocumentActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentActionViewController.swift; sourceTree = ""; }; - 4AA621E9249A6A8400A0BCBD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 4AA621EB249A6A8400A0BCBD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4AA8613625C19D4F002A59F5 /* DetectedMasterkeyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedMasterkeyViewModel.swift; sourceTree = ""; }; 4AA8614725C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingVaultChooseFolderViewController.swift; sourceTree = ""; }; @@ -899,8 +908,11 @@ children = ( 4A9FCB0B251A02A3002A8B41 /* FileProviderExtensionUI.entitlements */, 4AA621EB249A6A8400A0BCBD /* Info.plist */, - 4AA621E6249A6A8400A0BCBD /* DocumentActionViewController.swift */, - 4AA621E8249A6A8400A0BCBD /* MainInterface.storyboard */, + 4A6A521A268B7147006F7368 /* FileProviderCoordinator.swift */, + 4A6A520E268B5FA1006F7368 /* OnboardingView.swift */, + 4A6A5218268B6D31006F7368 /* OnboardingViewController.swift */, + 4A6A520C268B5EF7006F7368 /* RootViewController.swift */, + 4A6A5216268B6937006F7368 /* Localizable.strings */, ); path = FileProviderExtensionUI; sourceTree = ""; @@ -1329,7 +1341,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4AA621EA249A6A8400A0BCBD /* MainInterface.storyboard in Resources */, + 4A6A5213268B66AC006F7368 /* Colors.xcassets in Resources */, + 4A6A5212268B6697006F7368 /* Assets.xcassets in Resources */, + 4A6A5214268B6937006F7368 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1495,7 +1509,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4AA621E7249A6A8400A0BCBD /* DocumentActionViewController.swift in Sources */, + 4A6A520F268B5FA1006F7368 /* OnboardingView.swift in Sources */, + 4A6A520D268B5EF7006F7368 /* RootViewController.swift in Sources */, + 4A6A521B268B7147006F7368 /* FileProviderCoordinator.swift in Sources */, + 4A6A5219268B6D32006F7368 /* OnboardingViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1715,12 +1732,13 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 4AA621E8249A6A8400A0BCBD /* MainInterface.storyboard */ = { + 4A6A5216268B6937006F7368 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 4AA621E9249A6A8400A0BCBD /* Base */, + 4A6A5215268B6937006F7368 /* en */, + 4A6A5217268B693E006F7368 /* de */, ); - name = MainInterface.storyboard; + name = Localizable.strings; sourceTree = ""; }; 4AE97DB524572E4A00452814 /* LaunchScreen.storyboard */ = { diff --git a/FileProviderExtensionUI/Base.lproj/MainInterface.storyboard b/FileProviderExtensionUI/Base.lproj/MainInterface.storyboard deleted file mode 100644 index bba4f6337..000000000 --- a/FileProviderExtensionUI/Base.lproj/MainInterface.storyboard +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FileProviderExtensionUI/DocumentActionViewController.swift b/FileProviderExtensionUI/DocumentActionViewController.swift deleted file mode 100644 index 49d5f0466..000000000 --- a/FileProviderExtensionUI/DocumentActionViewController.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// DocumentActionViewController.swift -// FileProviderExtensionUI -// -// Created by Philipp Schmid on 17.06.20. -// Copyright © 2020 Skymatic GmbH. All rights reserved. -// - -import FileProviderUI -import UIKit - -class DocumentActionViewController: FPUIActionExtensionViewController { - @IBOutlet var identifierLabel: UILabel! - @IBOutlet var actionTypeLabel: UILabel! - - override func prepare(forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]) { - identifierLabel?.text = actionIdentifier - actionTypeLabel?.text = "Custom action" - } - - override func prepare(forError error: Error) { - identifierLabel?.text = error.localizedDescription - actionTypeLabel?.text = "Authenticate" - } - - @IBAction func doneButtonTapped(_ sender: Any) { - // Perform the action and call the completion block. If an unrecoverable error occurs you must still call the completion block with an error. Use the error code FPUIExtensionErrorCode.failed to signal the failure. - extensionContext.completeRequest() - } - - @IBAction func cancelButtonTapped(_ sender: Any) { - extensionContext.cancelRequest(withError: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil)) - } -} diff --git a/FileProviderExtensionUI/FileProviderCoordinator.swift b/FileProviderExtensionUI/FileProviderCoordinator.swift new file mode 100644 index 000000000..23bdf892d --- /dev/null +++ b/FileProviderExtensionUI/FileProviderCoordinator.swift @@ -0,0 +1,29 @@ +// +// FileProviderCoordinator.swift +// FileProviderExtensionUI +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import FileProviderUI +import UIKit +class FileProviderCoordinator { + let navigationController: UINavigationController + let extensionContext: FPUIActionExtensionContext + + init(extensionContext: FPUIActionExtensionContext, navigationController: UINavigationController) { + self.extensionContext = extensionContext + self.navigationController = navigationController + } + + func showOnboarding() { + let onboardingVC = OnboardingViewController() + onboardingVC.coordinator = self + navigationController.pushViewController(onboardingVC, animated: false) + } + + func userCancelled() { + extensionContext.cancelRequest(withError: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil)) + } +} diff --git a/FileProviderExtensionUI/Info.plist b/FileProviderExtensionUI/Info.plist index 3ca6d2a9e..1e91f62cd 100644 --- a/FileProviderExtensionUI/Info.plist +++ b/FileProviderExtensionUI/Info.plist @@ -33,8 +33,8 @@ Custom Action - NSExtensionMainStoryboard - MainInterface + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).RootViewController NSExtensionPointIdentifier com.apple.fileprovider-actionsui diff --git a/FileProviderExtensionUI/OnboardingView.swift b/FileProviderExtensionUI/OnboardingView.swift new file mode 100644 index 000000000..5215b1d25 --- /dev/null +++ b/FileProviderExtensionUI/OnboardingView.swift @@ -0,0 +1,61 @@ +// +// OnboardingView.swift +// FileProviderExtensionUI +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit + +class OnboardingView: UIView { + private lazy var label: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + label.textAlignment = .center + return label + }() + + lazy var cancelButton: UIButton = { + let button = UIButton() + button.setTitle(NSLocalizedString("onboarding.button.ok", comment: ""), for: .normal) + button.setTitleColor(UIColor(named: "primary"), for: .normal) + return button + }() + + init() { + super.init(frame: .zero) + + let botVaultImage = UIImage(named: "bot-vault") + let imageView = UIImageView(image: botVaultImage) + + label.text = NSLocalizedString("onboarding.text", comment: "") + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + label.translatesAutoresizingMaskIntoConstraints = false + cancelButton.translatesAutoresizingMaskIntoConstraints = false + + addSubview(imageView) + addSubview(label) + addSubview(cancelButton) + + NSLayoutConstraint.activate([ + imageView.centerXAnchor.constraint(equalTo: centerXAnchor), + label.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + label.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), + imageView.topAnchor.constraint(equalTo: topAnchor, constant: 20), + + label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20), + label.bottomAnchor.constraint(equalTo: cancelButton.topAnchor, constant: -20), + + cancelButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20), + cancelButton.centerXAnchor.constraint(equalTo: centerXAnchor) + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/FileProviderExtensionUI/OnboardingViewController.swift b/FileProviderExtensionUI/OnboardingViewController.swift new file mode 100644 index 000000000..7a0eb6ae2 --- /dev/null +++ b/FileProviderExtensionUI/OnboardingViewController.swift @@ -0,0 +1,92 @@ +// +// OnboardingViewController.swift +// FileProviderExtensionUI +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class OnboardingViewController: UITableViewController { + weak var coordinator: FileProviderCoordinator? + + private lazy var openCryptomatorCell: UITableViewCell = { + let cell = UITableViewCell() + cell.textLabel?.text = NSLocalizedString("onboarding.button.openCryptomator", comment: "") + cell.textLabel?.textColor = UIColor(named: "primary") + return cell + }() + + override func loadView() { + tableView = UITableView(frame: .zero, style: .grouped) + } + + override func viewDidLoad() { + super.viewDidLoad() + title = NSLocalizedString("onboarding.title", comment: "") + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)) + navigationItem.rightBarButtonItem = doneButton + } + + @objc func done() { + coordinator?.userCancelled() + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + // MARK: - UITableViewDataSource + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return openCryptomatorCell + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return OnboardingHeaderView() + } +} + +private class OnboardingHeaderView: UIView { + private lazy var imageView: UIImageView = { + let image = UIImage(named: "bot") + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFit + return imageView + }() + + private lazy var infoLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.numberOfLines = 0 + return label + }() + + init() { + super.init(frame: .zero) + infoLabel.text = NSLocalizedString("onboarding.info", comment: "") + let stack = UIStackView(arrangedSubviews: [imageView, infoLabel]) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 20 + addSubview(stack) + + NSLayoutConstraint.activate([ + stack.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + stack.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), + stack.topAnchor.constraint(equalTo: topAnchor, constant: 20), + stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20) + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/FileProviderExtensionUI/RootViewController.swift b/FileProviderExtensionUI/RootViewController.swift new file mode 100644 index 000000000..f1e680769 --- /dev/null +++ b/FileProviderExtensionUI/RootViewController.swift @@ -0,0 +1,31 @@ +// +// RootViewController.swift +// FileProviderExtensionUI +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import FileProviderUI +import UIKit + +class RootViewController: FPUIActionExtensionViewController { + private var coordinator: FileProviderCoordinator? + override func viewDidLoad() { + super.viewDidLoad() + let navigationController = UINavigationController() + navigationController.navigationBar.barTintColor = UIColor(named: "primary") + navigationController.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white] + navigationController.navigationBar.tintColor = .white + addChild(navigationController) + view.addSubview(navigationController.view) + navigationController.didMove(toParent: self) + + coordinator = FileProviderCoordinator(extensionContext: extensionContext, navigationController: navigationController) + coordinator?.showOnboarding() + } + + @objc func cancel() { + extensionContext.cancelRequest(withError: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil)) + } +} diff --git a/FileProviderExtensionUI/de.lproj/Localizable.strings b/FileProviderExtensionUI/de.lproj/Localizable.strings new file mode 100644 index 000000000..11175395e --- /dev/null +++ b/FileProviderExtensionUI/de.lproj/Localizable.strings @@ -0,0 +1,11 @@ +/* + Localizable.strings + Cryptomator + + Created by Philipp Schmid on 29.06.21. + Copyright © 2021 Skymatic GmbH. All rights reserved. +*/ + +"onboarding.title" = "Willkommen"; +"onboarding.info" = "Vielen Dank, dass du Cryptomator zum Schutz deiner Dateien gewählt hast. Um loszulegen, gehe zur Haupt-App und füge einen Tresor hinzu."; +"onboarding.button.openCryptomator" = "Cryptomator öffnen"; diff --git a/FileProviderExtensionUI/en.lproj/Localizable.strings b/FileProviderExtensionUI/en.lproj/Localizable.strings new file mode 100644 index 000000000..ed6b8482a --- /dev/null +++ b/FileProviderExtensionUI/en.lproj/Localizable.strings @@ -0,0 +1,11 @@ +/* + Localizable.strings + Cryptomator + + Created by Philipp Schmid on 29.06.21. + Copyright © 2021 Skymatic GmbH. All rights reserved. +*/ + +"onboarding.title" = "Welcome"; +"onboarding.info" = "Thanks for choosing Cryptomator to protect your files. To get started, go to the main app and add a vault."; +"onboarding.button.openCryptomator" = "Open Cryptomator"; From eee15fcd59642f280e2a726c8a8395e83dbfa37f Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:04:43 +0200 Subject: [PATCH 16/23] Changed filename for the RootFileProviderItem from Home to Cryptomator [ci skip] --- CryptomatorFileProvider/RootFileProviderItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CryptomatorFileProvider/RootFileProviderItem.swift b/CryptomatorFileProvider/RootFileProviderItem.swift index 941845c50..414360eb7 100644 --- a/CryptomatorFileProvider/RootFileProviderItem.swift +++ b/CryptomatorFileProvider/RootFileProviderItem.swift @@ -12,7 +12,7 @@ import MobileCoreServices public class RootFileProviderItem: NSObject, NSFileProviderItem { public var itemIdentifier = NSFileProviderItemIdentifier.rootContainer public let parentItemIdentifier = NSFileProviderItemIdentifier.rootContainer - public let filename = "Home" + public let filename = "Cryptomator" public let typeIdentifier = kUTTypeFolder as String public let documentSize: NSNumber? = nil public let capabilities: NSFileProviderItemCapabilities = [.allowsAll] From a75fe59fe0a8743541e4f84afe9305ac6dd1ddfb Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:20:05 +0200 Subject: [PATCH 17/23] Added BaseNavigationController for application-wide styling instead of overwriting the default appearance of the UINavigationBar [ci skip] --- Cryptomator.xcodeproj/project.pbxproj | 10 +++++++--- .../CreateNewVaultCoordinator.swift | 2 +- Cryptomator/AppDelegate.swift | 7 +------ .../Common/BaseNavigationController.swift | 17 +++++++++++++++++ Cryptomator/MainCoordinator.swift | 4 ++-- Cryptomator/WebDAV/WebDAVAuthenticator+VC.swift | 2 +- 6 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 Cryptomator/Common/BaseNavigationController.swift diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 59b091e87..fe602e7d3 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ 4A6A5214268B6937006F7368 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4A6A5216268B6937006F7368 /* Localizable.strings */; }; 4A6A5219268B6D32006F7368 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A5218268B6D31006F7368 /* OnboardingViewController.swift */; }; 4A6A521B268B7147006F7368 /* FileProviderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A521A268B7147006F7368 /* FileProviderCoordinator.swift */; }; + 4A6A521D268B7C8F006F7368 /* BaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6A521C268B7C8F006F7368 /* BaseNavigationController.swift */; }; 4A717CD924C835740048E08F /* ReparentTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */; }; 4A753DB92678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */; }; 4A797F8F24AC6731007DDBE1 /* FileProviderItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */; }; @@ -396,6 +397,7 @@ 4A6A5217268B693E006F7368 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 4A6A5218268B6D31006F7368 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 4A6A521A268B7147006F7368 /* FileProviderCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderCoordinator.swift; sourceTree = ""; }; + 4A6A521C268B7C8F006F7368 /* BaseNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationController.swift; sourceTree = ""; }; 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReparentTaskManagerTests.swift; sourceTree = ""; }; 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLegacyVaultPasswordViewModel.swift; sourceTree = ""; }; 4A797F8E24AC6731007DDBE1 /* FileProviderItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItemTests.swift; sourceTree = ""; }; @@ -810,25 +812,26 @@ 4A8195E325ADB92600F7DDA1 /* Common */ = { isa = PBXGroup; children = ( + 4A6A521C268B7C8F006F7368 /* BaseNavigationController.swift */, 4A644B56267C958F008CBB9A /* ChildCoordinator.swift */, 4AFCE53925B9D6A60069C4FC /* CloudAuthenticator.swift */, 4AFCE51E25B89CD80069C4FC /* CloudProviderType+Localization.swift */, 4A03255425A3685500E63D7A /* Coordinator.swift */, 4AF91CE125A7234500ACF01E /* DatabaseManager.swift */, 4AFCE4DC25B8514F0069C4FC /* EditableTableViewHeader.swift */, + 74D365B9268B5DB0005ECD69 /* FilesAppUtil.swift */, 4AF91D0C25A8D5EF00ACF01E /* ListViewModel.swift */, 4A8D061B25C84C450082C5F7 /* SingleSectionHeaderTableViewController.swift */, 4A447E0325BF0B0F00D9520D /* SingleSectionTableViewController.swift */, 4AF91CCF25A71C5800ACF01E /* UIImage+CloudProviderType.swift */, 7469AD99266E26B0000DCD45 /* URL+Zip.swift */, + 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */, + 4A1EB0D4268A5AA3006D072B /* VaultDetector.swift */, 4AA22C25261CA96600A17486 /* Cells */, 4A447DEB25BF064300D9520D /* ChooseFolder */, 4A7B97CA25B6F7340044B7FB /* CloudAccountList */, 7408E6BD2677835C00D7FAEA /* LocalWeb */, 4A8195E425ADB94500F7DDA1 /* Previews */, - 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */, - 4A1EB0D4268A5AA3006D072B /* VaultDetector.swift */, - 74D365B9268B5DB0005ECD69 /* FilesAppUtil.swift */, ); path = Common; sourceTree = ""; @@ -1528,6 +1531,7 @@ 4A7B97C225B6F7200044B7FB /* AccountListViewController.swift in Sources */, 4A66F57925C47BB2001BE15E /* TextFieldCell.swift in Sources */, 4A53CC17267CDBFF00853BB3 /* CreateNewVaultChooseFolderViewModel.swift in Sources */, + 4A6A521D268B7C8F006F7368 /* BaseNavigationController.swift in Sources */, 4ADD2342267383BE00374E4E /* AddVaultSuccessViewModel.swift in Sources */, 4AF91CD925A722A600ACF01E /* VaultInfo.swift in Sources */, 74D365BA268B5DB0005ECD69 /* FilesAppUtil.swift in Sources */, diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift index da43e20d5..114602c35 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift @@ -132,7 +132,7 @@ private class AuthenticatedCreateNewVaultCoordinator: FolderChoosing, VaultInsta } func showCreateNewFolder(parentPath: CloudPath) { - let modalNavigationController = UINavigationController() + let modalNavigationController = BaseNavigationController() let child = AuthenticatedFolderCreationCoordinator(navigationController: modalNavigationController, provider: provider, parentPath: parentPath) child.parentCoordinator = self childCoordinators.append(child) diff --git a/Cryptomator/AppDelegate.swift b/Cryptomator/AppDelegate.swift index 5eb5857d6..24b2f04ec 100644 --- a/Cryptomator/AppDelegate.swift +++ b/Cryptomator/AppDelegate.swift @@ -62,13 +62,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { DDLogError("Error while setting up OneDrive: \(error)") } - // Application-wide styling - UINavigationBar.appearance().barTintColor = UIColor(named: "primary") - UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white] - UIBarButtonItem.appearance().tintColor = UIColor.white - // Create window - let navigationController = UINavigationController() + let navigationController = BaseNavigationController() coordinator = MainCoordinator(navigationController: navigationController) coordinator?.start() window = UIWindow(frame: UIScreen.main.bounds) diff --git a/Cryptomator/Common/BaseNavigationController.swift b/Cryptomator/Common/BaseNavigationController.swift new file mode 100644 index 000000000..ebdeee616 --- /dev/null +++ b/Cryptomator/Common/BaseNavigationController.swift @@ -0,0 +1,17 @@ +// +// BaseNavigationController.swift +// Cryptomator +// +// Created by Philipp Schmid on 29.06.21. +// Copyright © 2021 Skymatic GmbH. All rights reserved. +// + +import UIKit +class BaseNavigationController: UINavigationController { + override func viewDidLoad() { + super.viewDidLoad() + navigationBar.tintColor = .white + navigationBar.barTintColor = UIColor(named: "primary") + navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white] + } +} diff --git a/Cryptomator/MainCoordinator.swift b/Cryptomator/MainCoordinator.swift index 80b10a1bf..329db03ee 100644 --- a/Cryptomator/MainCoordinator.swift +++ b/Cryptomator/MainCoordinator.swift @@ -24,7 +24,7 @@ class MainCoordinator: NSObject, Coordinator, UINavigationControllerDelegate { } func addVault() { - let modalNavigationController = UINavigationController() + let modalNavigationController = BaseNavigationController() let child = AddVaultCoordinator(navigationController: modalNavigationController) child.parentCoordinator = self childCoordinators.append(child) @@ -33,7 +33,7 @@ class MainCoordinator: NSObject, Coordinator, UINavigationControllerDelegate { } func showSettings() { - let modalNavigationController = UINavigationController() + let modalNavigationController = BaseNavigationController() let child = SettingsCoordinator(navigationController: modalNavigationController) child.parentCoordinator = self childCoordinators.append(child) diff --git a/Cryptomator/WebDAV/WebDAVAuthenticator+VC.swift b/Cryptomator/WebDAV/WebDAVAuthenticator+VC.swift index 6d6a20d57..cc4a0ad46 100644 --- a/Cryptomator/WebDAV/WebDAVAuthenticator+VC.swift +++ b/Cryptomator/WebDAV/WebDAVAuthenticator+VC.swift @@ -14,7 +14,7 @@ extension WebDAVAuthenticator { private static var coordinator: WebDAVAuthenticationCoordinator? static func authenticate(from viewController: UIViewController) -> Promise { - let navigationController = UINavigationController() + let navigationController = BaseNavigationController() let webDAVCoordinator = WebDAVAuthenticationCoordinator(navigationController: navigationController) coordinator = webDAVCoordinator viewController.present(navigationController, animated: true) From 736ea6e96968a2e4c9acf7fd4d2967c2439ad1dc Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:47:40 +0200 Subject: [PATCH 18/23] Added the option to open the main App from the FileProviderExtensionUI [ci skip] --- Cryptomator.xcodeproj/project.pbxproj | 2 +- Cryptomator/Info.plist | 10 ++++++++++ .../FileProviderCoordinator.swift | 15 +++++++++++++-- .../OnboardingViewController.swift | 4 ++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index fe602e7d3..0cce01a4c 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -1391,7 +1391,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ -f ./.cloud-access-secrets.sh ]; then\n source ./.cloud-access-secrets.sh\nelse\n echo \"warning: .cloud-access-secrets.sh could not be found, please see README for instructions\"\nfi\n/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string db-${DROPBOX_APP_KEY}\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes:1 string ${GOOGLE_DRIVE_REDIRECT_URL_SCHEME}\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes:2 string ${ONEDRIVE_REDIRECT_URI_SCHEME}\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\necho \"Updated ${TARGET_BUILD_DIR}/${INFOPLIST_PATH} by adding URL schemes\"\n"; + shellScript = "if [ -f ./.cloud-access-secrets.sh ]; then\n source ./.cloud-access-secrets.sh\nelse\n echo \"warning: .cloud-access-secrets.sh could not be found, please see README for instructions\"\nfi\n/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:1:CFBundleURLSchemes:0 string db-${DROPBOX_APP_KEY}\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:1:CFBundleURLSchemes:1 string ${GOOGLE_DRIVE_REDIRECT_URL_SCHEME}\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:1:CFBundleURLSchemes:2 string ${ONEDRIVE_REDIRECT_URI_SCHEME}\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\necho \"Updated ${TARGET_BUILD_DIR}/${INFOPLIST_PATH} by adding URL schemes\"\n"; }; 742595D72552EE0000A8A008 /* Set Build Number */ = { isa = PBXShellScriptBuildPhase; diff --git a/Cryptomator/Info.plist b/Cryptomator/Info.plist index 9fbadfb02..2ded9a7e0 100644 --- a/Cryptomator/Info.plist +++ b/Cryptomator/Info.plist @@ -18,6 +18,16 @@ $(MARKETING_VERSION) CFBundleURLTypes + + CFBundleTypeRole + Editor + CFBundleURLName + org.cryptomator.ios + CFBundleURLSchemes + + cryptomator + + CFBundleURLName diff --git a/FileProviderExtensionUI/FileProviderCoordinator.swift b/FileProviderExtensionUI/FileProviderCoordinator.swift index 23bdf892d..eb541431a 100644 --- a/FileProviderExtensionUI/FileProviderCoordinator.swift +++ b/FileProviderExtensionUI/FileProviderCoordinator.swift @@ -17,13 +17,24 @@ class FileProviderCoordinator { self.navigationController = navigationController } + func userCancelled() { + extensionContext.cancelRequest(withError: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil)) + } + + // MARK: - Onboarding + func showOnboarding() { let onboardingVC = OnboardingViewController() onboardingVC.coordinator = self navigationController.pushViewController(onboardingVC, animated: false) } - func userCancelled() { - extensionContext.cancelRequest(withError: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil)) + func openCryptomatorApp() { + let url = URL(string: "cryptomator:")! + extensionContext.open(url) { success in + if success { + self.userCancelled() + } + } } } diff --git a/FileProviderExtensionUI/OnboardingViewController.swift b/FileProviderExtensionUI/OnboardingViewController.swift index 7a0eb6ae2..c70e0f0a3 100644 --- a/FileProviderExtensionUI/OnboardingViewController.swift +++ b/FileProviderExtensionUI/OnboardingViewController.swift @@ -51,6 +51,10 @@ class OnboardingViewController: UITableViewController { override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return OnboardingHeaderView() } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + coordinator?.openCryptomatorApp() + } } private class OnboardingHeaderView: UIView { From a6e3fdc2288d48b06043276648c6a4dacb2ff5ce Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:53:28 +0200 Subject: [PATCH 19/23] Animated deselection of row after selection in OnboardingViewController [ci skip] --- FileProviderExtensionUI/OnboardingViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/FileProviderExtensionUI/OnboardingViewController.swift b/FileProviderExtensionUI/OnboardingViewController.swift index c70e0f0a3..58cad6d06 100644 --- a/FileProviderExtensionUI/OnboardingViewController.swift +++ b/FileProviderExtensionUI/OnboardingViewController.swift @@ -53,6 +53,7 @@ class OnboardingViewController: UITableViewController { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) coordinator?.openCryptomatorApp() } } From 3043f0dd3c710899fd9be3dffcd392da790b4f30 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 29 Jun 2021 19:36:58 +0200 Subject: [PATCH 20/23] Prevent the header title in ChooseFolderViewController from being displayed in uppercase --- .../Common/ChooseFolder/ChooseFolderViewController.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift index e8aab4fce..6d8938e42 100644 --- a/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/ChooseFolderViewController.swift @@ -143,6 +143,14 @@ class ChooseFolderViewController: SingleSectionTableViewController { return viewModel.headerTitle } + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + // Prevents the header title from being displayed in uppercase + guard let headerView = view as? UITableViewHeaderFooterView else { + return + } + headerView.textLabel?.text = viewModel.headerTitle + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = viewModel.items[indexPath.row] coordinator?.showItems(for: item.cloudPath) From baf6dbde94a55a47b973d6ef186f0dbea37c8f87 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 20:06:38 +0200 Subject: [PATCH 21/23] Cleanup [ci skip] --- Cryptomator.xcodeproj/project.pbxproj | 22 ++++++++++++------ .../AddVault/AddVaultSuccessViewModel.swift | 1 + .../CreateNewVaultChooseFolderViewModel.swift | 1 + ...CreateNewVaultPasswordViewController.swift | 1 + .../CreateNewVaultPasswordViewModel.swift | 1 + .../DetectedVaultFailureView.swift | 1 + .../DetectedVaultFailureViewController.swift | 2 ++ .../CreateNewLocalVaultCoordinator.swift | 1 + .../CreateNewLocalVaultViewModel.swift | 1 + .../SetVaultNameCoordinator.swift | 1 + .../SetVaultNameViewController.swift | 1 + .../SetVaultNameViewModel.swift | 1 + .../AddLocalVaultViewController.swift | 1 + .../LocalFileSystemAuthenticating.swift | 1 + ...leSystemAuthenticationViewController.swift | 2 ++ .../LocalVault/LocalVaultAdding.swift | 1 + .../OpenExistingLocalVaultCoordinator.swift | 1 + .../OpenExistingLocalVaultViewModel.swift | 1 + ...ExistingLegacyVaultPasswordViewModel.swift | 1 + .../AddVault/VaultCoordinatorError.swift | 1 + .../Common/BaseNavigationController.swift | 1 + Cryptomator/Common/ChildCoordinator.swift | 1 + .../CreateNewFolderViewModel.swift | 1 + .../Common/ChooseFolder/FolderCreating.swift | 1 + Cryptomator/Common/VaultDetailItem.swift | 1 + Cryptomator/Info.plist | 16 ++++++------- .../VaultList/VaultListViewModel.swift | 1 + .../RootFileProviderItem.swift | 1 + .../CreateNewLocalVaultViewModelTests.swift | 1 - ...OpenExistingLocalVaultViewModelTests.swift | 1 + .../CreateNewFolderViewModelTests.swift | 1 + ...CreateNewVaultPasswordViewModelTests.swift | 1 + CryptomatorTests/MockError.swift | 1 + .../SetVaultNameViewModelTests.swift | 1 + .../FileProviderCoordinator.swift | 1 + .../OnboardingViewController.swift | 1 + .../740-gear.imageset/740-gear.png | Bin .../740-gear.imageset/740-gear@2x.png | Bin .../740-gear.imageset/740-gear@3x.png | Bin .../740-gear.imageset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-60.png | Bin .../AppIcon.appiconset/Icon-60@2x.png | Bin .../AppIcon.appiconset/Icon-60@3x.png | Bin .../AppIcon.appiconset/Icon-76.png | Bin .../AppIcon.appiconset/Icon-76@2x.png | Bin .../AppIcon.appiconset/Icon-83.5@2x.png | Bin .../AppIcon.appiconset/Icon-Small-20.png | Bin .../AppIcon.appiconset/Icon-Small-40.png | Bin .../AppIcon.appiconset/Icon-Small-40@2x.png | Bin .../AppIcon.appiconset/Icon-Small.png | Bin .../AppIcon.appiconset/Icon-Small@2x.png | Bin .../AppIcon.appiconset/Icon-Small@3x.png | Bin .../AppIcon.appiconset/Icon.png | Bin .../Assets.xcassets/Contents.json | 0 .../actions-selected.imageset/Contents.json | 0 .../actions-selected.png | Bin .../actions-selected@2x.png | Bin .../actions-selected@3x.png | Bin .../actions.imageset/Contents.json | 0 .../actions.imageset/actions.png | Bin .../actions.imageset/actions@2x.png | Bin .../actions.imageset/actions@3x.png | Bin .../bot-vault.imageset/Contents.json | 0 .../bot-vault.imageset/bot-vault.png | Bin .../bot-vault.imageset/bot-vault@2x.png | Bin .../bot-vault.imageset/bot-vault@3x.png | Bin .../bot.imageset/Contents.json | 0 .../Assets.xcassets/bot.imageset/bot.png | Bin .../Assets.xcassets/bot.imageset/bot@2x.png | Bin .../Assets.xcassets/bot.imageset/bot@3x.png | Bin .../Contents.json | 0 .../dropbox-vault-selected.png | Bin .../dropbox-vault-selected@2x.png | Bin .../dropbox-vault-selected@3x.png | Bin .../dropbox-vault.imageset/Contents.json | 0 .../dropbox-vault.imageset/dropbox-vault.png | Bin .../dropbox-vault@2x.png | Bin .../dropbox-vault@3x.png | Bin .../dropbox.imageset/Contents.json | 0 .../dropbox.imageset/dropbox.png | Bin .../dropbox.imageset/dropbox@2x.png | Bin .../dropbox.imageset/dropbox@3x.png | Bin .../Contents.json | 0 .../file-provider-vault-selected.png | Bin .../file-provider-vault-selected@2x.png | Bin .../file-provider-vault-selected@3x.png | Bin .../Contents.json | 0 .../file-provider-vault.png | Bin .../file-provider-vault@2x.png | Bin .../file-provider-vault@3x.png | Bin .../file-provider.imageset/Contents.json | 0 .../file-provider.imageset/file-provider.png | Bin .../file-provider@2x.png | Bin .../file-provider@3x.png | Bin .../Contents.json | 0 .../file-type-unknown-selected.png | Bin .../file-type-unknown-selected@2x.png | Bin .../file-type-unknown-selected@3x.png | Bin .../file-type-unknown.imageset/Contents.json | 0 .../file-type-unknown.png | Bin .../file-type-unknown@2x.png | Bin .../file-type-unknown@3x.png | Bin .../folder-selected.imageset/Contents.json | 0 .../folder-selected.png | Bin .../folder-selected@2x.png | Bin .../folder-selected@3x.png | Bin .../folder.imageset/Contents.json | 0 .../folder.imageset/folder.png | Bin .../folder.imageset/folder@2x.png | Bin .../folder.imageset/folder@3x.png | Bin .../Contents.json | 0 .../google-drive-vault-selected.png | Bin .../google-drive-vault-selected@2x.png | Bin .../google-drive-vault-selected@3x.png | Bin .../google-drive-vault.imageset/Contents.json | 0 .../google-drive-vault.png | Bin .../google-drive-vault@2x.png | Bin .../google-drive-vault@3x.png | Bin .../google-drive.imageset/Contents.json | 0 .../google-drive.imageset/google-drive.png | Bin .../google-drive.imageset/google-drive@2x.png | Bin .../google-drive.imageset/google-drive@3x.png | Bin .../Contents.json | 0 .../icloud-drive-vault-selected.png | Bin .../icloud-drive-vault-selected@2x.png | Bin .../icloud-drive-vault-selected@3x.png | Bin .../icloud-drive-vault.imageset/Contents.json | 0 .../icloud-drive-vault.png | Bin .../icloud-drive-vault@2x.png | Bin .../icloud-drive-vault@3x.png | Bin .../icloud-drive.imageset/Contents.json | 0 .../icloud-drive.imageset/icloud-drive.png | Bin .../icloud-drive.imageset/icloud-drive@2x.png | Bin .../icloud-drive.imageset/icloud-drive@3x.png | Bin .../Contents.json | 0 .../onedrive-vault-selected.png | Bin .../onedrive-vault-selected@2x.png | Bin .../onedrive-vault-selected@3x.png | Bin .../onedrive-vault.imageset/Contents.json | 0 .../onedrive-vault.png | Bin .../onedrive-vault@2x.png | Bin .../onedrive-vault@3x.png | Bin .../onedrive.imageset/Contents.json | 0 .../onedrive.imageset/onedrive.png | Bin .../onedrive.imageset/onedrive@2x.png | Bin .../onedrive.imageset/onedrive@3x.png | Bin .../Contents.json | 0 .../webdav-vault-selected.png | Bin .../webdav-vault-selected@2x.png | Bin .../webdav-vault-selected@3x.png | Bin .../webdav-vault.imageset/Contents.json | 0 .../webdav-vault.imageset/webdav-vault.png | Bin .../webdav-vault.imageset/webdav-vault@2x.png | Bin .../webdav-vault.imageset/webdav-vault@3x.png | Bin .../webdav.imageset/Contents.json | 0 .../webdav.imageset/webdav.png | Bin .../webdav.imageset/webdav@2x.png | Bin .../webdav.imageset/webdav@3x.png | Bin .../Colors.xcassets/Contents.json | 0 .../primary-D1.colorset/Contents.json | 0 .../primary.colorset/Contents.json | 0 .../secondaryLabel.colorset/Contents.json | 0 .../yellow.colorset/Contents.json | 0 164 files changed, 58 insertions(+), 16 deletions(-) rename {Cryptomator => SharedResources}/Assets.xcassets/740-gear.imageset/740-gear.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/740-gear.imageset/740-gear@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/740-gear.imageset/740-gear@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/740-gear.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-60.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-76.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-Small-20.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-Small.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/AppIcon.appiconset/Icon.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions-selected.imageset/actions-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions-selected.imageset/actions-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions-selected.imageset/actions-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions.imageset/actions.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions.imageset/actions@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/actions.imageset/actions@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot-vault.imageset/bot-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot-vault.imageset/bot-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot-vault.imageset/bot-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot.imageset/bot.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot.imageset/bot@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/bot.imageset/bot@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault.imageset/dropbox-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox.imageset/dropbox.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox.imageset/dropbox@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/dropbox.imageset/dropbox@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault.imageset/file-provider-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider.imageset/file-provider.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider.imageset/file-provider@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-provider.imageset/file-provider@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown.imageset/file-type-unknown.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder-selected.imageset/folder-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder-selected.imageset/folder-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder-selected.imageset/folder-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder.imageset/folder.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder.imageset/folder@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/folder.imageset/folder@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault.imageset/google-drive-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive.imageset/google-drive.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive.imageset/google-drive@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/google-drive.imageset/google-drive@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive.imageset/icloud-drive.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive.imageset/icloud-drive@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/icloud-drive.imageset/icloud-drive@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault.imageset/onedrive-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive.imageset/onedrive.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive.imageset/onedrive@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/onedrive.imageset/onedrive@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault-selected.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault.imageset/webdav-vault.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault.imageset/webdav-vault@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav-vault.imageset/webdav-vault@3x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav.imageset/Contents.json (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav.imageset/webdav.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav.imageset/webdav@2x.png (100%) rename {Cryptomator => SharedResources}/Assets.xcassets/webdav.imageset/webdav@3x.png (100%) rename {Cryptomator => SharedResources}/Colors.xcassets/Contents.json (100%) rename {Cryptomator => SharedResources}/Colors.xcassets/primary-D1.colorset/Contents.json (100%) rename {Cryptomator => SharedResources}/Colors.xcassets/primary.colorset/Contents.json (100%) rename {Cryptomator => SharedResources}/Colors.xcassets/secondaryLabel.colorset/Contents.json (100%) rename {Cryptomator => SharedResources}/Colors.xcassets/yellow.colorset/Contents.json (100%) diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index 0cce01a4c..67ad40441 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -594,11 +594,11 @@ 4A1EB0D6268A6CF5006D072B /* LocalVault */ = { isa = PBXGroup; children = ( - 4A1EB0C92689C373006D072B /* LocalVaultAdding.swift */, 4A1EB0D7268A6DE1006D072B /* AddLocalVaultViewController.swift */, 4A3D65602680A3CB000DA764 /* LocalFileSystemAuthenticating.swift */, 4A3D65632680A4B7000DA764 /* LocalFileSystemAuthenticationViewController.swift */, 4A3D65652680A842000DA764 /* LocalFileSystemAuthenticationViewModel.swift */, + 4A1EB0C92689C373006D072B /* LocalVaultAdding.swift */, ); path = LocalVault; sourceTree = ""; @@ -705,6 +705,7 @@ 4AA621E5249A6A8400A0BCBD /* FileProviderExtensionUI */, 4A828287252617D600A4EAD4 /* Frameworks */, 4A5E5B2A2453119100BD6298 /* Products */, + 74D365BC268B965B005ECD69 /* SharedResources */, ); sourceTree = ""; }; @@ -725,7 +726,6 @@ 4A644B45267A3D21008CBB9A /* CreateNewVault */ = { isa = PBXGroup; children = ( - 4A1EB0D3268A5A44006D072B /* LocalVault */, 4A644B4A267B4C08008CBB9A /* CreateNewVaultChooseFolderViewController.swift */, 4A53CC16267CDBFF00853BB3 /* CreateNewVaultChooseFolderViewModel.swift */, 4A644B4C267B55E4008CBB9A /* CreateNewVaultCoordinator.swift */, @@ -736,6 +736,7 @@ 4A644B4E267B9E6A008CBB9A /* SetVaultNameCoordinator.swift */, 4A644B43267A3BEC008CBB9A /* SetVaultNameViewController.swift */, 4A644B46267A3D43008CBB9A /* SetVaultNameViewModel.swift */, + 4A1EB0D3268A5A44006D072B /* LocalVault */, ); path = CreateNewVault; sourceTree = ""; @@ -753,8 +754,8 @@ isa = PBXGroup; children = ( 4A6A5207268B2B75006F7368 /* AddLocalVaultViewModelTestCase.swift */, - 4A6A5203268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift */, 4A6A520A268B3710006F7368 /* CreateNewLocalVaultViewModelTests.swift */, + 4A6A5203268B2915006F7368 /* OpenExistingLocalVaultViewModelTests.swift */, ); path = AddLocalVault; sourceTree = ""; @@ -789,8 +790,8 @@ 4A644B5A267CA972008CBB9A /* DetectedVaultView.swift */, 4A3D655E268099F9000DA764 /* VaultCoordinatorError.swift */, 4A2FD08125B5E2BA008565C8 /* VaultInstalling.swift */, - 4A1EB0D6268A6CF5006D072B /* LocalVault */, 4A644B45267A3D21008CBB9A /* CreateNewVault */, + 4A1EB0D6268A6CF5006D072B /* LocalVault */, 4AA8613F25C1AC4D002A59F5 /* OpenExistingVault */, ); path = AddVault; @@ -924,11 +925,11 @@ isa = PBXGroup; children = ( 4AA8613625C19D4F002A59F5 /* DetectedMasterkeyViewModel.swift */, + 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */, 4AA8614725C1C670002A59F5 /* OpenExistingVaultChooseFolderViewController.swift */, 4A2FD08A25B5E437008565C8 /* OpenExistingVaultCoordinator.swift */, 4AA8615025C1DB5E002A59F5 /* OpenExistingVaultPasswordViewController.swift */, 4A66F58A25C489C7001BE15E /* OpenExistingVaultPasswordViewModel.swift */, - 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */, 4A6A5200268B1BEF006F7368 /* LocalVault */, ); path = OpenExistingVault; @@ -965,8 +966,6 @@ 4AE97DB824572E4A00452814 /* Info.plist */, 4AE97DAA24572E4900452814 /* AppDelegate.swift */, 4A03255D25A368BF00E63D7A /* MainCoordinator.swift */, - 4AE97DB324572E4A00452814 /* Assets.xcassets */, - 4AF91CC625A6437000ACF01E /* Colors.xcassets */, 4A7BC0E825ADF13100F007B3 /* AddVault */, 4A8195E325ADB92600F7DDA1 /* Common */, 4AE97DB524572E4A00452814 /* LaunchScreen.storyboard */, @@ -1114,6 +1113,15 @@ path = Settings; sourceTree = ""; }; + 74D365BC268B965B005ECD69 /* SharedResources */ = { + isa = PBXGroup; + children = ( + 4AE97DB324572E4A00452814 /* Assets.xcassets */, + 4AF91CC625A6437000ACF01E /* Colors.xcassets */, + ); + path = SharedResources; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ diff --git a/Cryptomator/AddVault/AddVaultSuccessViewModel.swift b/Cryptomator/AddVault/AddVaultSuccessViewModel.swift index 5030aee59..f97aa5eff 100644 --- a/Cryptomator/AddVault/AddVaultSuccessViewModel.swift +++ b/Cryptomator/AddVault/AddVaultSuccessViewModel.swift @@ -7,6 +7,7 @@ // import Foundation + struct AddVaultSuccessViewModel { let vaultName: String let vaultUID: String diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift index 9bfad7a41..9c008d28b 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultChooseFolderViewModel.swift @@ -8,6 +8,7 @@ import CryptomatorCloudAccessCore import Foundation + protocol CreateNewVaultChooseFolderViewModelProtocol: ChooseFolderViewModelProtocol { var vaultName: String { get } func chooseCurrentFolder() throws -> Folder diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift index 7b271336a..58a1e7712 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewController.swift @@ -7,6 +7,7 @@ // import UIKit + class CreateNewVaultPasswordViewController: UITableViewController { weak var coordinator: (Coordinator & VaultInstalling)? private var viewModel: CreateNewVaultPasswordViewModelProtocol diff --git a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift index 6588366e8..7ba5072cd 100644 --- a/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/CreateNewVault/CreateNewVaultPasswordViewModel.swift @@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation import Promises + protocol CreateNewVaultPasswordViewModelProtocol { var headerTitles: [String] { get } var vaultUID: String { get } diff --git a/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift index 3002c6ce4..2a6f94472 100644 --- a/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift +++ b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureView.swift @@ -7,6 +7,7 @@ // import UIKit + class DetectedVaultFailureView: DetectedVaultView { init() { let configuration = UIImage.SymbolConfiguration(pointSize: 120) diff --git a/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift index 1d2786690..7fce631c1 100644 --- a/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/DetectedVaultFailureViewController.swift @@ -7,6 +7,7 @@ // import UIKit + class DetectedVaultFailureViewController: UIViewController { override func viewDidLoad() { let failureView = DetectedVaultFailureView() @@ -32,6 +33,7 @@ class DetectedVaultFailureViewController: UIViewController { #if DEBUG import SwiftUI + struct DetectedVaultFailureVC_Preview: PreviewProvider { static var previews: some View { DetectedVaultFailureViewController().toPreview() diff --git a/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift index f027637d7..b96c758ae 100644 --- a/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultCoordinator.swift @@ -7,6 +7,7 @@ // import UIKit + class CreateNewLocalVaultCoordinator: LocalVaultAdding, LocalFileSystemAuthenticating, Coordinator { var childCoordinators = [Coordinator]() var navigationController: UINavigationController diff --git a/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift index 2d84f5619..34a4c03b0 100644 --- a/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift +++ b/Cryptomator/AddVault/CreateNewVault/LocalVault/CreateNewLocalVaultViewModel.swift @@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation import Promises + class CreateNewLocalVaultViewModel: LocalFileSystemAuthenticationViewModel, LocalFileSystemVaultInstallingViewModelProtocol { private let vaultName: String diff --git a/Cryptomator/AddVault/CreateNewVault/SetVaultNameCoordinator.swift b/Cryptomator/AddVault/CreateNewVault/SetVaultNameCoordinator.swift index 64f52fb80..1326cd34d 100644 --- a/Cryptomator/AddVault/CreateNewVault/SetVaultNameCoordinator.swift +++ b/Cryptomator/AddVault/CreateNewVault/SetVaultNameCoordinator.swift @@ -7,6 +7,7 @@ // import UIKit + protocol VaultNaming: AnyObject { func setVaultName(_ name: String) } diff --git a/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewController.swift b/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewController.swift index 765952e4f..c9a74d341 100644 --- a/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewController.swift +++ b/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewController.swift @@ -7,6 +7,7 @@ // import UIKit + class SetVaultNameViewController: SingleSectionHeaderTableViewController { weak var coordinator: (VaultNaming & Coordinator)? private var viewModel: SetVaultNameViewModelProtocol diff --git a/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewModel.swift b/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewModel.swift index 44b928c2a..c2a4b5df4 100644 --- a/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewModel.swift +++ b/Cryptomator/AddVault/CreateNewVault/SetVaultNameViewModel.swift @@ -7,6 +7,7 @@ // import Foundation + protocol SetVaultNameViewModelProtocol: SingleSectionHeaderTableViewModelProtocol { var vaultName: String? { get set } func getValidatedVaultName() throws -> String diff --git a/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift b/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift index 223cc93fb..f9ed73a66 100644 --- a/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift +++ b/Cryptomator/AddVault/LocalVault/AddLocalVaultViewController.swift @@ -7,6 +7,7 @@ // import UIKit + class AddLocalVaultViewController: LocalFileSystemAuthenticationViewController { typealias AddLocalVaultViewModel = LocalFileSystemAuthenticationViewModelProtocol & LocalFileSystemVaultInstallingViewModelProtocol let viewModel: AddLocalVaultViewModel diff --git a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticating.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticating.swift index cb5d72433..66f632bbf 100644 --- a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticating.swift +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticating.swift @@ -9,6 +9,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation + protocol LocalFileSystemAuthenticating { func authenticated(credential: LocalFileSystemCredential) } diff --git a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift index 7a86a7fc8..ae16f75ea 100644 --- a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift @@ -8,6 +8,7 @@ import MobileCoreServices import UIKit + class LocalFileSystemAuthenticationViewController: SingleSectionTableViewController, UIDocumentPickerDelegate { weak var coordinator: (LocalFileSystemAuthenticating & LocalVaultAdding & Coordinator)? private let viewModel: LocalFileSystemAuthenticationViewModelProtocol @@ -104,6 +105,7 @@ private class LocalFileSystemAuthenticationHeaderView: UIView { #if DEBUG import CryptomatorCommonCore import SwiftUI + struct LocalFileSystemViewModelMock: LocalFileSystemAuthenticationViewModelProtocol { let documentPickerButtonText = "Select Storage Location" let headerText = "In the next screen, choose the storage location for your new vault." diff --git a/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift b/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift index 418b5ec66..58ba1030d 100644 --- a/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift +++ b/Cryptomator/AddVault/LocalVault/LocalVaultAdding.swift @@ -7,6 +7,7 @@ // import UIKit + protocol LocalVaultAdding { func validationFailed(with error: Error, at viewController: UIViewController) func showPasswordScreen(for result: LocalFileSystemAuthenticationResult) diff --git a/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift index 820e2c961..130670cbe 100644 --- a/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift +++ b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultCoordinator.swift @@ -7,6 +7,7 @@ // import UIKit + class OpenExistingLocalVaultCoordinator: LocalVaultAdding, LocalFileSystemAuthenticating, Coordinator { var childCoordinators = [Coordinator]() var navigationController: UINavigationController diff --git a/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift index 6dae3b952..5036df854 100644 --- a/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift +++ b/Cryptomator/AddVault/OpenExistingVault/LocalVault/OpenExistingLocalVaultViewModel.swift @@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation import Promises + class OpenExistingLocalVaultViewModel: LocalFileSystemAuthenticationViewModel, LocalFileSystemVaultInstallingViewModelProtocol { private let validator: OpenExistingLocalVaultValidationLogic init(accountManager: CloudProviderAccountManager = CloudProviderAccountDBManager.shared) { diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift index 95b06fe44..a8c2659f9 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingLegacyVaultPasswordViewModel.swift @@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation import Promises + class OpenExistingLegacyVaultPasswordViewModel: OpenExistingVaultPasswordViewModelProtocol { var password: String? let provider: CloudProvider diff --git a/Cryptomator/AddVault/VaultCoordinatorError.swift b/Cryptomator/AddVault/VaultCoordinatorError.swift index 0799874f6..2b523042d 100644 --- a/Cryptomator/AddVault/VaultCoordinatorError.swift +++ b/Cryptomator/AddVault/VaultCoordinatorError.swift @@ -7,6 +7,7 @@ // import Foundation + enum VaultCoordinatorError: Error { case wrongItemType } diff --git a/Cryptomator/Common/BaseNavigationController.swift b/Cryptomator/Common/BaseNavigationController.swift index ebdeee616..291f694c8 100644 --- a/Cryptomator/Common/BaseNavigationController.swift +++ b/Cryptomator/Common/BaseNavigationController.swift @@ -7,6 +7,7 @@ // import UIKit + class BaseNavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() diff --git a/Cryptomator/Common/ChildCoordinator.swift b/Cryptomator/Common/ChildCoordinator.swift index 78c28761b..174634cac 100644 --- a/Cryptomator/Common/ChildCoordinator.swift +++ b/Cryptomator/Common/ChildCoordinator.swift @@ -7,6 +7,7 @@ // import Foundation + protocol ChildCoordinator: Coordinator { func stop() var parentCoordinator: Coordinator? { get set } diff --git a/Cryptomator/Common/ChooseFolder/CreateNewFolderViewModel.swift b/Cryptomator/Common/ChooseFolder/CreateNewFolderViewModel.swift index bf888d2e6..e923f6f61 100644 --- a/Cryptomator/Common/ChooseFolder/CreateNewFolderViewModel.swift +++ b/Cryptomator/Common/ChooseFolder/CreateNewFolderViewModel.swift @@ -9,6 +9,7 @@ import CryptomatorCloudAccessCore import Foundation import Promises + protocol CreateNewFolderViewModelProtocol: SingleSectionHeaderTableViewModelProtocol { var folderName: String? { get set } func createFolder() -> Promise diff --git a/Cryptomator/Common/ChooseFolder/FolderCreating.swift b/Cryptomator/Common/ChooseFolder/FolderCreating.swift index 800d98446..cb49747c5 100644 --- a/Cryptomator/Common/ChooseFolder/FolderCreating.swift +++ b/Cryptomator/Common/ChooseFolder/FolderCreating.swift @@ -8,6 +8,7 @@ import CryptomatorCloudAccessCore import Foundation + protocol FolderCreating: AnyObject { func createdNewFolder(at folderPath: CloudPath) func stop() diff --git a/Cryptomator/Common/VaultDetailItem.swift b/Cryptomator/Common/VaultDetailItem.swift index 5f1cc518b..8f557daa2 100644 --- a/Cryptomator/Common/VaultDetailItem.swift +++ b/Cryptomator/Common/VaultDetailItem.swift @@ -9,6 +9,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation + struct VaultDetailItem: Item, VaultItem { var path: CloudPath { return vaultPath diff --git a/Cryptomator/Info.plist b/Cryptomator/Info.plist index 2ded9a7e0..f2af1529c 100644 --- a/Cryptomator/Info.plist +++ b/Cryptomator/Info.plist @@ -19,14 +19,14 @@ CFBundleURLTypes - CFBundleTypeRole - Editor - CFBundleURLName - org.cryptomator.ios - CFBundleURLSchemes - - cryptomator - + CFBundleTypeRole + Editor + CFBundleURLName + org.cryptomator.ios + CFBundleURLSchemes + + cryptomator + CFBundleURLName diff --git a/Cryptomator/VaultList/VaultListViewModel.swift b/Cryptomator/VaultList/VaultListViewModel.swift index ac06939ae..085a0feaa 100644 --- a/Cryptomator/VaultList/VaultListViewModel.swift +++ b/Cryptomator/VaultList/VaultListViewModel.swift @@ -11,6 +11,7 @@ import CocoaLumberjackSwift import CryptomatorCommonCore import Foundation import GRDB + class VaultListViewModel: VaultListViewModelProtocol { var vaults = [VaultInfo]() diff --git a/CryptomatorFileProvider/RootFileProviderItem.swift b/CryptomatorFileProvider/RootFileProviderItem.swift index 414360eb7..dd3b2149b 100644 --- a/CryptomatorFileProvider/RootFileProviderItem.swift +++ b/CryptomatorFileProvider/RootFileProviderItem.swift @@ -9,6 +9,7 @@ import FileProvider import Foundation import MobileCoreServices + public class RootFileProviderItem: NSObject, NSFileProviderItem { public var itemIdentifier = NSFileProviderItemIdentifier.rootContainer public let parentItemIdentifier = NSFileProviderItemIdentifier.rootContainer diff --git a/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift b/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift index 970f4c2bd..a28ce9f4c 100644 --- a/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift +++ b/CryptomatorTests/AddLocalVault/CreateNewLocalVaultViewModelTests.swift @@ -9,7 +9,6 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import XCTest - @testable import Cryptomator class CreateNewLocalVaultViewModelTests: AddLocalVaultViewModelTestCase { diff --git a/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift b/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift index 0691a1929..23352dbb9 100644 --- a/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift +++ b/CryptomatorTests/AddLocalVault/OpenExistingLocalVaultViewModelTests.swift @@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore import CryptomatorCommonCore import XCTest @testable import Cryptomator + class OpenExistingLocalVaultViewModelTests: AddLocalVaultViewModelTestCase { var viewModel: OpenExistingLocalVaultViewModel! diff --git a/CryptomatorTests/CreateNewFolderViewModelTests.swift b/CryptomatorTests/CreateNewFolderViewModelTests.swift index 879682ac0..ef305fe51 100644 --- a/CryptomatorTests/CreateNewFolderViewModelTests.swift +++ b/CryptomatorTests/CreateNewFolderViewModelTests.swift @@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore import Promises import XCTest @testable import Cryptomator + class CreateNewFolderViewModelTests: XCTestCase { private var cloudProviderMock: CloudProviderMock! diff --git a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift index ebcb38837..985735262 100644 --- a/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift +++ b/CryptomatorTests/CreateNewVaultPasswordViewModelTests.swift @@ -11,6 +11,7 @@ import CryptomatorCommonCore import Promises import XCTest @testable import Cryptomator + class CreateNewVaultPasswordViewModelTests: XCTestCase { private var vaultManagerMock: VaultManagerMock! diff --git a/CryptomatorTests/MockError.swift b/CryptomatorTests/MockError.swift index edc3e6296..f2649f309 100644 --- a/CryptomatorTests/MockError.swift +++ b/CryptomatorTests/MockError.swift @@ -7,6 +7,7 @@ // import Foundation + enum MockError: Error { case notMocked } diff --git a/CryptomatorTests/SetVaultNameViewModelTests.swift b/CryptomatorTests/SetVaultNameViewModelTests.swift index 7c3bb19d1..3a2de39da 100644 --- a/CryptomatorTests/SetVaultNameViewModelTests.swift +++ b/CryptomatorTests/SetVaultNameViewModelTests.swift @@ -8,6 +8,7 @@ import XCTest @testable import Cryptomator + class SetVaultNameViewModelTests: XCTestCase { var viewModel: SetVaultNameViewModel! override func setUpWithError() throws { diff --git a/FileProviderExtensionUI/FileProviderCoordinator.swift b/FileProviderExtensionUI/FileProviderCoordinator.swift index eb541431a..882828784 100644 --- a/FileProviderExtensionUI/FileProviderCoordinator.swift +++ b/FileProviderExtensionUI/FileProviderCoordinator.swift @@ -8,6 +8,7 @@ import FileProviderUI import UIKit + class FileProviderCoordinator { let navigationController: UINavigationController let extensionContext: FPUIActionExtensionContext diff --git a/FileProviderExtensionUI/OnboardingViewController.swift b/FileProviderExtensionUI/OnboardingViewController.swift index 58cad6d06..1decb92b1 100644 --- a/FileProviderExtensionUI/OnboardingViewController.swift +++ b/FileProviderExtensionUI/OnboardingViewController.swift @@ -7,6 +7,7 @@ // import UIKit + class OnboardingViewController: UITableViewController { weak var coordinator: FileProviderCoordinator? diff --git a/Cryptomator/Assets.xcassets/740-gear.imageset/740-gear.png b/SharedResources/Assets.xcassets/740-gear.imageset/740-gear.png similarity index 100% rename from Cryptomator/Assets.xcassets/740-gear.imageset/740-gear.png rename to SharedResources/Assets.xcassets/740-gear.imageset/740-gear.png diff --git a/Cryptomator/Assets.xcassets/740-gear.imageset/740-gear@2x.png b/SharedResources/Assets.xcassets/740-gear.imageset/740-gear@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/740-gear.imageset/740-gear@2x.png rename to SharedResources/Assets.xcassets/740-gear.imageset/740-gear@2x.png diff --git a/Cryptomator/Assets.xcassets/740-gear.imageset/740-gear@3x.png b/SharedResources/Assets.xcassets/740-gear.imageset/740-gear@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/740-gear.imageset/740-gear@3x.png rename to SharedResources/Assets.xcassets/740-gear.imageset/740-gear@3x.png diff --git a/Cryptomator/Assets.xcassets/740-gear.imageset/Contents.json b/SharedResources/Assets.xcassets/740-gear.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/740-gear.imageset/Contents.json rename to SharedResources/Assets.xcassets/740-gear.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Contents.json b/SharedResources/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Contents.json rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-60.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-60.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-60.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-60.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-76.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-76.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-76.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small-20.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small-20.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small-20.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small-20.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png diff --git a/Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon.png b/SharedResources/Assets.xcassets/AppIcon.appiconset/Icon.png similarity index 100% rename from Cryptomator/Assets.xcassets/AppIcon.appiconset/Icon.png rename to SharedResources/Assets.xcassets/AppIcon.appiconset/Icon.png diff --git a/Cryptomator/Assets.xcassets/Contents.json b/SharedResources/Assets.xcassets/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/Contents.json rename to SharedResources/Assets.xcassets/Contents.json diff --git a/Cryptomator/Assets.xcassets/actions-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/actions-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/actions-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/actions-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/actions-selected.imageset/actions-selected.png b/SharedResources/Assets.xcassets/actions-selected.imageset/actions-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/actions-selected.imageset/actions-selected.png rename to SharedResources/Assets.xcassets/actions-selected.imageset/actions-selected.png diff --git a/Cryptomator/Assets.xcassets/actions-selected.imageset/actions-selected@2x.png b/SharedResources/Assets.xcassets/actions-selected.imageset/actions-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/actions-selected.imageset/actions-selected@2x.png rename to SharedResources/Assets.xcassets/actions-selected.imageset/actions-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/actions-selected.imageset/actions-selected@3x.png b/SharedResources/Assets.xcassets/actions-selected.imageset/actions-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/actions-selected.imageset/actions-selected@3x.png rename to SharedResources/Assets.xcassets/actions-selected.imageset/actions-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/actions.imageset/Contents.json b/SharedResources/Assets.xcassets/actions.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/actions.imageset/Contents.json rename to SharedResources/Assets.xcassets/actions.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/actions.imageset/actions.png b/SharedResources/Assets.xcassets/actions.imageset/actions.png similarity index 100% rename from Cryptomator/Assets.xcassets/actions.imageset/actions.png rename to SharedResources/Assets.xcassets/actions.imageset/actions.png diff --git a/Cryptomator/Assets.xcassets/actions.imageset/actions@2x.png b/SharedResources/Assets.xcassets/actions.imageset/actions@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/actions.imageset/actions@2x.png rename to SharedResources/Assets.xcassets/actions.imageset/actions@2x.png diff --git a/Cryptomator/Assets.xcassets/actions.imageset/actions@3x.png b/SharedResources/Assets.xcassets/actions.imageset/actions@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/actions.imageset/actions@3x.png rename to SharedResources/Assets.xcassets/actions.imageset/actions@3x.png diff --git a/Cryptomator/Assets.xcassets/bot-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/bot-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/bot-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/bot-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/bot-vault.imageset/bot-vault.png b/SharedResources/Assets.xcassets/bot-vault.imageset/bot-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/bot-vault.imageset/bot-vault.png rename to SharedResources/Assets.xcassets/bot-vault.imageset/bot-vault.png diff --git a/Cryptomator/Assets.xcassets/bot-vault.imageset/bot-vault@2x.png b/SharedResources/Assets.xcassets/bot-vault.imageset/bot-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/bot-vault.imageset/bot-vault@2x.png rename to SharedResources/Assets.xcassets/bot-vault.imageset/bot-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/bot-vault.imageset/bot-vault@3x.png b/SharedResources/Assets.xcassets/bot-vault.imageset/bot-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/bot-vault.imageset/bot-vault@3x.png rename to SharedResources/Assets.xcassets/bot-vault.imageset/bot-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/bot.imageset/Contents.json b/SharedResources/Assets.xcassets/bot.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/bot.imageset/Contents.json rename to SharedResources/Assets.xcassets/bot.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/bot.imageset/bot.png b/SharedResources/Assets.xcassets/bot.imageset/bot.png similarity index 100% rename from Cryptomator/Assets.xcassets/bot.imageset/bot.png rename to SharedResources/Assets.xcassets/bot.imageset/bot.png diff --git a/Cryptomator/Assets.xcassets/bot.imageset/bot@2x.png b/SharedResources/Assets.xcassets/bot.imageset/bot@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/bot.imageset/bot@2x.png rename to SharedResources/Assets.xcassets/bot.imageset/bot@2x.png diff --git a/Cryptomator/Assets.xcassets/bot.imageset/bot@3x.png b/SharedResources/Assets.xcassets/bot.imageset/bot@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/bot.imageset/bot@3x.png rename to SharedResources/Assets.xcassets/bot.imageset/bot@3x.png diff --git a/Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected.png b/SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected.png rename to SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected.png diff --git a/Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@2x.png b/SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@2x.png rename to SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@3x.png b/SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@3x.png rename to SharedResources/Assets.xcassets/dropbox-vault-selected.imageset/dropbox-vault-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/dropbox-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/dropbox-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/dropbox-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/dropbox-vault.imageset/dropbox-vault.png b/SharedResources/Assets.xcassets/dropbox-vault.imageset/dropbox-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault.imageset/dropbox-vault.png rename to SharedResources/Assets.xcassets/dropbox-vault.imageset/dropbox-vault.png diff --git a/Cryptomator/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@2x.png b/SharedResources/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@2x.png rename to SharedResources/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@3x.png b/SharedResources/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@3x.png rename to SharedResources/Assets.xcassets/dropbox-vault.imageset/dropbox-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/dropbox.imageset/Contents.json b/SharedResources/Assets.xcassets/dropbox.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox.imageset/Contents.json rename to SharedResources/Assets.xcassets/dropbox.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/dropbox.imageset/dropbox.png b/SharedResources/Assets.xcassets/dropbox.imageset/dropbox.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox.imageset/dropbox.png rename to SharedResources/Assets.xcassets/dropbox.imageset/dropbox.png diff --git a/Cryptomator/Assets.xcassets/dropbox.imageset/dropbox@2x.png b/SharedResources/Assets.xcassets/dropbox.imageset/dropbox@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox.imageset/dropbox@2x.png rename to SharedResources/Assets.xcassets/dropbox.imageset/dropbox@2x.png diff --git a/Cryptomator/Assets.xcassets/dropbox.imageset/dropbox@3x.png b/SharedResources/Assets.xcassets/dropbox.imageset/dropbox@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/dropbox.imageset/dropbox@3x.png rename to SharedResources/Assets.xcassets/dropbox.imageset/dropbox@3x.png diff --git a/Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected.png b/SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected.png rename to SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected.png diff --git a/Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@2x.png b/SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@2x.png rename to SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@3x.png b/SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@3x.png rename to SharedResources/Assets.xcassets/file-provider-vault-selected.imageset/file-provider-vault-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/file-provider-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/file-provider-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/file-provider-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/file-provider-vault.imageset/file-provider-vault.png b/SharedResources/Assets.xcassets/file-provider-vault.imageset/file-provider-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault.imageset/file-provider-vault.png rename to SharedResources/Assets.xcassets/file-provider-vault.imageset/file-provider-vault.png diff --git a/Cryptomator/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@2x.png b/SharedResources/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@2x.png rename to SharedResources/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@3x.png b/SharedResources/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@3x.png rename to SharedResources/Assets.xcassets/file-provider-vault.imageset/file-provider-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/file-provider.imageset/Contents.json b/SharedResources/Assets.xcassets/file-provider.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider.imageset/Contents.json rename to SharedResources/Assets.xcassets/file-provider.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/file-provider.imageset/file-provider.png b/SharedResources/Assets.xcassets/file-provider.imageset/file-provider.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider.imageset/file-provider.png rename to SharedResources/Assets.xcassets/file-provider.imageset/file-provider.png diff --git a/Cryptomator/Assets.xcassets/file-provider.imageset/file-provider@2x.png b/SharedResources/Assets.xcassets/file-provider.imageset/file-provider@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider.imageset/file-provider@2x.png rename to SharedResources/Assets.xcassets/file-provider.imageset/file-provider@2x.png diff --git a/Cryptomator/Assets.xcassets/file-provider.imageset/file-provider@3x.png b/SharedResources/Assets.xcassets/file-provider.imageset/file-provider@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-provider.imageset/file-provider@3x.png rename to SharedResources/Assets.xcassets/file-provider.imageset/file-provider@3x.png diff --git a/Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected.png b/SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected.png rename to SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected.png diff --git a/Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@2x.png b/SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@2x.png rename to SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@3x.png b/SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@3x.png rename to SharedResources/Assets.xcassets/file-type-unknown-selected.imageset/file-type-unknown-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/file-type-unknown.imageset/Contents.json b/SharedResources/Assets.xcassets/file-type-unknown.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown.imageset/Contents.json rename to SharedResources/Assets.xcassets/file-type-unknown.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/file-type-unknown.imageset/file-type-unknown.png b/SharedResources/Assets.xcassets/file-type-unknown.imageset/file-type-unknown.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown.imageset/file-type-unknown.png rename to SharedResources/Assets.xcassets/file-type-unknown.imageset/file-type-unknown.png diff --git a/Cryptomator/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@2x.png b/SharedResources/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@2x.png rename to SharedResources/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@2x.png diff --git a/Cryptomator/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@3x.png b/SharedResources/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@3x.png rename to SharedResources/Assets.xcassets/file-type-unknown.imageset/file-type-unknown@3x.png diff --git a/Cryptomator/Assets.xcassets/folder-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/folder-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/folder-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/folder-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/folder-selected.imageset/folder-selected.png b/SharedResources/Assets.xcassets/folder-selected.imageset/folder-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/folder-selected.imageset/folder-selected.png rename to SharedResources/Assets.xcassets/folder-selected.imageset/folder-selected.png diff --git a/Cryptomator/Assets.xcassets/folder-selected.imageset/folder-selected@2x.png b/SharedResources/Assets.xcassets/folder-selected.imageset/folder-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/folder-selected.imageset/folder-selected@2x.png rename to SharedResources/Assets.xcassets/folder-selected.imageset/folder-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/folder-selected.imageset/folder-selected@3x.png b/SharedResources/Assets.xcassets/folder-selected.imageset/folder-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/folder-selected.imageset/folder-selected@3x.png rename to SharedResources/Assets.xcassets/folder-selected.imageset/folder-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/folder.imageset/Contents.json b/SharedResources/Assets.xcassets/folder.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/folder.imageset/Contents.json rename to SharedResources/Assets.xcassets/folder.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/folder.imageset/folder.png b/SharedResources/Assets.xcassets/folder.imageset/folder.png similarity index 100% rename from Cryptomator/Assets.xcassets/folder.imageset/folder.png rename to SharedResources/Assets.xcassets/folder.imageset/folder.png diff --git a/Cryptomator/Assets.xcassets/folder.imageset/folder@2x.png b/SharedResources/Assets.xcassets/folder.imageset/folder@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/folder.imageset/folder@2x.png rename to SharedResources/Assets.xcassets/folder.imageset/folder@2x.png diff --git a/Cryptomator/Assets.xcassets/folder.imageset/folder@3x.png b/SharedResources/Assets.xcassets/folder.imageset/folder@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/folder.imageset/folder@3x.png rename to SharedResources/Assets.xcassets/folder.imageset/folder@3x.png diff --git a/Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected.png b/SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected.png rename to SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected.png diff --git a/Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@2x.png b/SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@2x.png rename to SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@3x.png b/SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@3x.png rename to SharedResources/Assets.xcassets/google-drive-vault-selected.imageset/google-drive-vault-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/google-drive-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/google-drive-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/google-drive-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/google-drive-vault.imageset/google-drive-vault.png b/SharedResources/Assets.xcassets/google-drive-vault.imageset/google-drive-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault.imageset/google-drive-vault.png rename to SharedResources/Assets.xcassets/google-drive-vault.imageset/google-drive-vault.png diff --git a/Cryptomator/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@2x.png b/SharedResources/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@2x.png rename to SharedResources/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@3x.png b/SharedResources/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@3x.png rename to SharedResources/Assets.xcassets/google-drive-vault.imageset/google-drive-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/google-drive.imageset/Contents.json b/SharedResources/Assets.xcassets/google-drive.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive.imageset/Contents.json rename to SharedResources/Assets.xcassets/google-drive.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/google-drive.imageset/google-drive.png b/SharedResources/Assets.xcassets/google-drive.imageset/google-drive.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive.imageset/google-drive.png rename to SharedResources/Assets.xcassets/google-drive.imageset/google-drive.png diff --git a/Cryptomator/Assets.xcassets/google-drive.imageset/google-drive@2x.png b/SharedResources/Assets.xcassets/google-drive.imageset/google-drive@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive.imageset/google-drive@2x.png rename to SharedResources/Assets.xcassets/google-drive.imageset/google-drive@2x.png diff --git a/Cryptomator/Assets.xcassets/google-drive.imageset/google-drive@3x.png b/SharedResources/Assets.xcassets/google-drive.imageset/google-drive@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/google-drive.imageset/google-drive@3x.png rename to SharedResources/Assets.xcassets/google-drive.imageset/google-drive@3x.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected.png b/SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected.png rename to SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@2x.png b/SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@2x.png rename to SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@3x.png b/SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@3x.png rename to SharedResources/Assets.xcassets/icloud-drive-vault-selected.imageset/icloud-drive-vault-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/icloud-drive-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/icloud-drive-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault.png b/SharedResources/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault.png rename to SharedResources/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@2x.png b/SharedResources/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@2x.png rename to SharedResources/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@3x.png b/SharedResources/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@3x.png rename to SharedResources/Assets.xcassets/icloud-drive-vault.imageset/icloud-drive-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive.imageset/Contents.json b/SharedResources/Assets.xcassets/icloud-drive.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive.imageset/Contents.json rename to SharedResources/Assets.xcassets/icloud-drive.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/icloud-drive.imageset/icloud-drive.png b/SharedResources/Assets.xcassets/icloud-drive.imageset/icloud-drive.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive.imageset/icloud-drive.png rename to SharedResources/Assets.xcassets/icloud-drive.imageset/icloud-drive.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive.imageset/icloud-drive@2x.png b/SharedResources/Assets.xcassets/icloud-drive.imageset/icloud-drive@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive.imageset/icloud-drive@2x.png rename to SharedResources/Assets.xcassets/icloud-drive.imageset/icloud-drive@2x.png diff --git a/Cryptomator/Assets.xcassets/icloud-drive.imageset/icloud-drive@3x.png b/SharedResources/Assets.xcassets/icloud-drive.imageset/icloud-drive@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/icloud-drive.imageset/icloud-drive@3x.png rename to SharedResources/Assets.xcassets/icloud-drive.imageset/icloud-drive@3x.png diff --git a/Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected.png b/SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected.png rename to SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected.png diff --git a/Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@2x.png b/SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@2x.png rename to SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@3x.png b/SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@3x.png rename to SharedResources/Assets.xcassets/onedrive-vault-selected.imageset/onedrive-vault-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/onedrive-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/onedrive-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/onedrive-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/onedrive-vault.imageset/onedrive-vault.png b/SharedResources/Assets.xcassets/onedrive-vault.imageset/onedrive-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault.imageset/onedrive-vault.png rename to SharedResources/Assets.xcassets/onedrive-vault.imageset/onedrive-vault.png diff --git a/Cryptomator/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@2x.png b/SharedResources/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@2x.png rename to SharedResources/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@3x.png b/SharedResources/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@3x.png rename to SharedResources/Assets.xcassets/onedrive-vault.imageset/onedrive-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/onedrive.imageset/Contents.json b/SharedResources/Assets.xcassets/onedrive.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive.imageset/Contents.json rename to SharedResources/Assets.xcassets/onedrive.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/onedrive.imageset/onedrive.png b/SharedResources/Assets.xcassets/onedrive.imageset/onedrive.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive.imageset/onedrive.png rename to SharedResources/Assets.xcassets/onedrive.imageset/onedrive.png diff --git a/Cryptomator/Assets.xcassets/onedrive.imageset/onedrive@2x.png b/SharedResources/Assets.xcassets/onedrive.imageset/onedrive@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive.imageset/onedrive@2x.png rename to SharedResources/Assets.xcassets/onedrive.imageset/onedrive@2x.png diff --git a/Cryptomator/Assets.xcassets/onedrive.imageset/onedrive@3x.png b/SharedResources/Assets.xcassets/onedrive.imageset/onedrive@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/onedrive.imageset/onedrive@3x.png rename to SharedResources/Assets.xcassets/onedrive.imageset/onedrive@3x.png diff --git a/Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/Contents.json b/SharedResources/Assets.xcassets/webdav-vault-selected.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/Contents.json rename to SharedResources/Assets.xcassets/webdav-vault-selected.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected.png b/SharedResources/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected.png rename to SharedResources/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected.png diff --git a/Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@2x.png b/SharedResources/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@2x.png rename to SharedResources/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@2x.png diff --git a/Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@3x.png b/SharedResources/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@3x.png rename to SharedResources/Assets.xcassets/webdav-vault-selected.imageset/webdav-vault-selected@3x.png diff --git a/Cryptomator/Assets.xcassets/webdav-vault.imageset/Contents.json b/SharedResources/Assets.xcassets/webdav-vault.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault.imageset/Contents.json rename to SharedResources/Assets.xcassets/webdav-vault.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/webdav-vault.imageset/webdav-vault.png b/SharedResources/Assets.xcassets/webdav-vault.imageset/webdav-vault.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault.imageset/webdav-vault.png rename to SharedResources/Assets.xcassets/webdav-vault.imageset/webdav-vault.png diff --git a/Cryptomator/Assets.xcassets/webdav-vault.imageset/webdav-vault@2x.png b/SharedResources/Assets.xcassets/webdav-vault.imageset/webdav-vault@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault.imageset/webdav-vault@2x.png rename to SharedResources/Assets.xcassets/webdav-vault.imageset/webdav-vault@2x.png diff --git a/Cryptomator/Assets.xcassets/webdav-vault.imageset/webdav-vault@3x.png b/SharedResources/Assets.xcassets/webdav-vault.imageset/webdav-vault@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav-vault.imageset/webdav-vault@3x.png rename to SharedResources/Assets.xcassets/webdav-vault.imageset/webdav-vault@3x.png diff --git a/Cryptomator/Assets.xcassets/webdav.imageset/Contents.json b/SharedResources/Assets.xcassets/webdav.imageset/Contents.json similarity index 100% rename from Cryptomator/Assets.xcassets/webdav.imageset/Contents.json rename to SharedResources/Assets.xcassets/webdav.imageset/Contents.json diff --git a/Cryptomator/Assets.xcassets/webdav.imageset/webdav.png b/SharedResources/Assets.xcassets/webdav.imageset/webdav.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav.imageset/webdav.png rename to SharedResources/Assets.xcassets/webdav.imageset/webdav.png diff --git a/Cryptomator/Assets.xcassets/webdav.imageset/webdav@2x.png b/SharedResources/Assets.xcassets/webdav.imageset/webdav@2x.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav.imageset/webdav@2x.png rename to SharedResources/Assets.xcassets/webdav.imageset/webdav@2x.png diff --git a/Cryptomator/Assets.xcassets/webdav.imageset/webdav@3x.png b/SharedResources/Assets.xcassets/webdav.imageset/webdav@3x.png similarity index 100% rename from Cryptomator/Assets.xcassets/webdav.imageset/webdav@3x.png rename to SharedResources/Assets.xcassets/webdav.imageset/webdav@3x.png diff --git a/Cryptomator/Colors.xcassets/Contents.json b/SharedResources/Colors.xcassets/Contents.json similarity index 100% rename from Cryptomator/Colors.xcassets/Contents.json rename to SharedResources/Colors.xcassets/Contents.json diff --git a/Cryptomator/Colors.xcassets/primary-D1.colorset/Contents.json b/SharedResources/Colors.xcassets/primary-D1.colorset/Contents.json similarity index 100% rename from Cryptomator/Colors.xcassets/primary-D1.colorset/Contents.json rename to SharedResources/Colors.xcassets/primary-D1.colorset/Contents.json diff --git a/Cryptomator/Colors.xcassets/primary.colorset/Contents.json b/SharedResources/Colors.xcassets/primary.colorset/Contents.json similarity index 100% rename from Cryptomator/Colors.xcassets/primary.colorset/Contents.json rename to SharedResources/Colors.xcassets/primary.colorset/Contents.json diff --git a/Cryptomator/Colors.xcassets/secondaryLabel.colorset/Contents.json b/SharedResources/Colors.xcassets/secondaryLabel.colorset/Contents.json similarity index 100% rename from Cryptomator/Colors.xcassets/secondaryLabel.colorset/Contents.json rename to SharedResources/Colors.xcassets/secondaryLabel.colorset/Contents.json diff --git a/Cryptomator/Colors.xcassets/yellow.colorset/Contents.json b/SharedResources/Colors.xcassets/yellow.colorset/Contents.json similarity index 100% rename from Cryptomator/Colors.xcassets/yellow.colorset/Contents.json rename to SharedResources/Colors.xcassets/yellow.colorset/Contents.json From cb83192ecf610dc67d922987164cd0bc7037d556 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 20:18:18 +0200 Subject: [PATCH 22/23] Updated changelog --- fastlane/changelog.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index 87a99d411..31a987895 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1,7 +1,5 @@ -Welcome to the first alpha version of the new Cryptomator for iOS app! +Welcome to the first beta version of the new Cryptomator for iOS app! This new app is fully integrated into the Files app of iOS. -This new app is fully integrated into the Files app. - -Be aware that a lot of things aren't working yet. E.g., you can't use iCloud Drive and "other" file providers yet. +Keep in mind that this is a pre-release version intended for testing and that some features like auto-lock are still missing. Check out our open-source repository on GitHub: https://github.com/cryptomator/ios We're looking forward to your feedback! \ No newline at end of file From 7339b8c03602e66b96e0e57ed9cc9d17a12f72b7 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 29 Jun 2021 20:37:05 +0200 Subject: [PATCH 23/23] Probably fixes build error (for release) --- .../LocalFileSystemAuthenticationViewController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift index ae16f75ea..e7d4f15df 100644 --- a/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift +++ b/Cryptomator/AddVault/LocalVault/LocalFileSystemAuthenticationViewController.swift @@ -8,6 +8,9 @@ import MobileCoreServices import UIKit +#if canImport(UniformTypeIdentifiers) +import UniformTypeIdentifiers +#endif class LocalFileSystemAuthenticationViewController: SingleSectionTableViewController, UIDocumentPickerDelegate { weak var coordinator: (LocalFileSystemAuthenticating & LocalVaultAdding & Coordinator)?