Skip to content

Commit

Permalink
Add SharePoint drive selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
iammajid committed Jan 6, 2025
1 parent 2e557ea commit 2f3c070
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 19 deletions.
44 changes: 34 additions & 10 deletions Cryptomator/AddVault/CreateNewVault/CreateNewVaultCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,49 @@ class CreateNewVaultCoordinator: AccountListing, CloudChoosing, DefaultShowEditA
navigationController.pushViewController(enterURLVC, animated: true)
}

func setSharePointURL(_ url: String) {
guard let account = currentSharePointAccount else { return }
do {
let provider = try CloudProviderDBManager.shared.getProvider(with: account.accountUID)
startFolderChooser(with: provider, account: account.cloudProviderAccount)
} catch {
handleError(error, for: navigationController)
}
}

func selectedAccont(_ account: AccountInfo) throws {
if account.cloudProviderType == .sharePoint {
currentSharePointAccount = account
showEnterSharePointURL(for: account)
} else {
let provider = try CloudProviderDBManager.shared.getProvider(with: account.accountUID)
startFolderChooser(with: provider, account: account.cloudProviderAccount)
}
}

func setSharePointURL(_ url: String) {
guard let account = currentSharePointAccount else { return }

let credential = MicrosoftGraphCredential.createForSharePoint(with: account.accountUID)
let discovery = MicrosoftGraphDiscovery(credential: credential)

showDriveList(discovery: discovery, sharePointURL: url)
}

private func showDriveList(discovery: MicrosoftGraphDiscovery, sharePointURL: String) {
guard let account = currentSharePointAccount else { return }
let viewModel = SharePointDriveListViewModel(discovery: discovery, sharePointURL: sharePointURL, account: account)
viewModel.didSelectDrive = { [weak self] drive in
self?.handleDriveSelection(drive: drive)
}
let driveListVC = SharePointDriveListViewController(viewModel: viewModel)
navigationController.pushViewController(driveListVC, animated: true)
}

private func handleDriveSelection(drive: MicrosoftGraphDrive) {
guard let account = currentSharePointAccount else {
print("No current SharePoint account available")
return
}
do {
let credential = MicrosoftGraphCredential.createForSharePoint(with: account.accountUID)
let provider = try MicrosoftGraphCloudProvider(credential: credential, driveIdentifier: drive.identifier)
startFolderChooser(with: provider, account: account.cloudProviderAccount)
} catch {
handleError(error, for: navigationController)
}
}

private func startFolderChooser(with provider: CloudProvider, account: CloudProviderAccount) {
let child = AuthenticatedCreateNewVaultCoordinator(navigationController: navigationController, provider: provider, account: account, vaultName: vaultName)
childCoordinators.append(child)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ class EnterSharePointURLViewController: SingleSectionStaticUITableViewController
}

@objc func nextButtonClicked() {
guard let coordinator = coordinator else { return }
do {
try coordinator?.setSharePointURL(viewModel.getValidatedSharePointURL())
let url = try viewModel.getValidatedSharePointURL()
coordinator.setSharePointURL(url)
} catch {
coordinator?.handleError(error, for: self)
print("Error validating SharePoint URL: \(error)")
coordinator.handleError(error, for: self)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// SharePointDriveListViewController.swift
// Cryptomator
//
// Created by Majid Achhoud
// Copyright © 2024 Skymatic GmbH. All rights reserved.
//

import CryptomatorCloudAccessCore
import CryptomatorCommonCore
import Foundation
import UIKit

class SharePointDriveListViewController: BaseUITableViewController {
private var viewModel: SharePointDriveListViewModel

init(viewModel: SharePointDriveListViewModel) {
self.viewModel = viewModel
super.init()
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CloudCell.self, forCellReuseIdentifier: "SharePointDriveCell")
viewModel.reloadData = { [weak self] in
self?.tableView.reloadData()
}

self.title = LocalizedString.getValue("addVault.selectDrive.navigation.title")
}

// MARK: - UITableViewDataSource

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.drives.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SharePointDriveCell", for: indexPath) as? CloudCell else {
fatalError("Could not dequeue CloudCell")
}

let drive = viewModel.drives[indexPath.row]
configure(cell, with: drive)

return cell
}

// MARK: - Styling Configuration

private func configure(_ cell: CloudCell, with drive: MicrosoftGraphDrive) {
cell.textLabel?.text = drive.name
cell.imageView?.image = UIImage(systemName: "folder")
}

// MARK: - UITableViewDelegate

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedDrive = viewModel.drives[indexPath.row]
viewModel.selectDrive(selectedDrive)
tableView.deselectRow(at: indexPath, animated: true)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// SharePointDriveListViewModel.swift
// Cryptomator
//
// Created by Majid Achhoud
// Copyright © 2024 Skymatic GmbH. All rights reserved.
//

import CryptomatorCloudAccessCore
import CryptomatorCommonCore
import Foundation

class SharePointDriveListViewModel: SingleSectionTableViewModel {
private let discovery: MicrosoftGraphDiscovery
private let sharePointURL: String
private let account: AccountInfo

var drives: [MicrosoftGraphDrive] = [] {
didSet {
reloadData?()
}
}

var reloadData: (() -> Void)?
var didSelectDrive: ((MicrosoftGraphDrive) -> Void)?

init(discovery: MicrosoftGraphDiscovery, sharePointURL: String, account: AccountInfo) {
self.discovery = discovery
self.sharePointURL = sharePointURL
self.account = account
super.init()
fetchSiteAndDrives()
}

func selectDrive(_ drive: MicrosoftGraphDrive) {
didSelectDrive?(drive)
}

private func fetchSiteAndDrives() {
guard let urlComponents = URL(string: sharePointURL),
let hostName = urlComponents.host else {
print("Invalid SharePoint URL")
return
}

var serverRelativePath = urlComponents.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
if serverRelativePath.hasPrefix("sites/") {
serverRelativePath = String(serverRelativePath.dropFirst("sites/".count))
}

discovery.fetchSharePointSite(for: hostName, serverRelativePath: serverRelativePath)
.then { site in
self.fetchDrives(for: site.identifier)
}.catch { error in
print("Failed to fetch SharePoint site: \(error)")
}
}

private func fetchDrives(for siteIdentifier: String) {
discovery.fetchSharePointDocumentLibraries(for: siteIdentifier).then { drives in
self.drives = drives
}.catch { error in
print("Failed to fetch drives: \(error)")
}
}

override func getHeaderTitle(for section: Int) -> String? {
guard section == 0 else { return nil }
return LocalizedString.getValue("addVault.selectDrive.navigation.title")
}

override var title: String? {
return LocalizedString.getValue("addVault.selectDrive.header.description")
}
}
16 changes: 12 additions & 4 deletions Cryptomator/AddVault/CreateNewVault/URLValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@ extension URLValidatorError: LocalizedError {

public enum URLValidator {
public static func validateSharePointURL(urlString: String) throws {
let pattern = #"^https:\/\/[a-zA-Z0-9\-]+\.sharepoint\.com\/sites\/[a-zA-Z0-9\-]+$"#
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(location: 0, length: urlString.utf16.count)
if regex.firstMatch(in: urlString, options: [], range: range) == nil {
guard let url = URL(string: urlString) else {
throw URLValidatorError.invalidURLFormat
}

guard url.scheme == "https",
let host = url.host,
host.contains(".sharepoint.com") else {
throw URLValidatorError.invalidURLFormat
}

let path = url.path
guard path.contains("/sites/") else {
throw URLValidatorError.invalidURLFormat
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import Foundation
import Promises
import UIKit

class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, DefaultShowEditAccountBehavior, Coordinator {
class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, DefaultShowEditAccountBehavior, Coordinator, SharePointURLSetting {
var navigationController: UINavigationController
var childCoordinators = [Coordinator]()
weak var parentCoordinator: AddVaultCoordinator?

private var currentSharePointAccount: AccountInfo?

init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
Expand All @@ -43,6 +45,13 @@ class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, DefaultShowEd
}
}

func showEnterSharePointURL(for account: AccountInfo) {
let viewModel = EnterSharePointURLViewModel(account: account)
let enterURLVC = EnterSharePointURLViewController(viewModel: viewModel)
enterURLVC.coordinator = self
navigationController.pushViewController(enterURLVC, animated: true)
}

func showAddAccount(for cloudProviderType: CloudProviderType, from viewController: UIViewController) {
let authenticator = CloudAuthenticator(accountManager: CloudProviderAccountDBManager.shared)
authenticator.authenticate(cloudProviderType, from: viewController).then { account in
Expand All @@ -52,8 +61,47 @@ class OpenExistingVaultCoordinator: AccountListing, CloudChoosing, DefaultShowEd
}

func selectedAccont(_ account: AccountInfo) throws {
let provider = try CloudProviderDBManager.shared.getProvider(with: account.accountUID)
startFolderChooser(with: provider, account: account.cloudProviderAccount)
if account.cloudProviderType == .sharePoint {
currentSharePointAccount = account
showEnterSharePointURL(for: account)
} else {
let provider = try CloudProviderDBManager.shared.getProvider(with: account.accountUID)
startFolderChooser(with: provider, account: account.cloudProviderAccount)
}
}

func setSharePointURL(_ url: String) {
guard let account = currentSharePointAccount else { return }

let credential = MicrosoftGraphCredential.createForSharePoint(with: account.accountUID)
let discovery = MicrosoftGraphDiscovery(credential: credential)

showDriveList(discovery: discovery, sharePointURL: url)
}

private func showDriveList(discovery: MicrosoftGraphDiscovery, sharePointURL: String) {
guard let account = currentSharePointAccount else { return }
let viewModel = SharePointDriveListViewModel(discovery: discovery, sharePointURL: sharePointURL, account: account)
viewModel.didSelectDrive = { [weak self] drive in
self?.handleDriveSelection(drive: drive)
}
let driveListVC = SharePointDriveListViewController(viewModel: viewModel)
navigationController.pushViewController(driveListVC, animated: true)
}

private func handleDriveSelection(drive: MicrosoftGraphDrive) {
guard let account = currentSharePointAccount else {
print("No current SharePoint account available")
return
}
do {
let credential = MicrosoftGraphCredential.createForSharePoint(with: account.accountUID)
let provider = try MicrosoftGraphCloudProvider(credential: credential, driveIdentifier: drive.identifier)
startFolderChooser(with: provider, account: account.cloudProviderAccount)
} catch {
print("Error creating provider: \(error)")
handleError(error, for: navigationController)
}
}

private func startFolderChooser(with provider: CloudProvider, account: CloudProviderAccount) {
Expand Down
4 changes: 4 additions & 0 deletions SharedResources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
"addVault.openExistingVault.downloadVault.progress" = "Downloading Vault…";
"addVault.openExistingVault.password.footer" = "Enter password for \"%@\".";
"addVault.openExistingVault.progress" = "Adding Vault…";

"addVault.selectDrive.navigation.title" = "Select Drive";
"addVault.selectDrive.header.description" = "Select the SharePoint drive where the encrypted vault will be located.";

"addVault.success.info" = "Successfully added vault \"%@\".\nAccess this vault via the Files app.";
"addVault.success.footer" = "If you haven't already, enable Cryptomator in the Files app.";

Expand Down

0 comments on commit 2f3c070

Please sign in to comment.