diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 7ceecb00d..0dcd0e9d0 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -114,6 +114,15 @@ public protocol RouterFileNavigable { navigationController: UINavigationController, animated: Bool ) + + /// Present the SwitchAccountViewController + /// - Parameters: + /// - navigationController: The navigation controller to use + /// - animated: Should be animated + @MainActor func presentAccountViewController( + navigationController: UINavigationController, + animated: Bool + ) } /// Something that can set an arbitrary RootView controller @@ -164,7 +173,9 @@ public struct AppRouter: AppNavigable { @LazyInjectService private var reviewManager: ReviewManageable @LazyInjectService private var availableOfflineManager: AvailableOfflineManageable @LazyInjectService private var accountManager: AccountManageable - @LazyInjectService private var backgroundUploadSessionManager: BackgroundUploadSessionManager + + @LazyInjectService var backgroundDownloadSessionManager: BackgroundDownloadSessionManager + @LazyInjectService var backgroundUploadSessionManager: BackgroundUploadSessionManager /// Get the current window from the app scene @MainActor private var window: UIWindow? { @@ -521,7 +532,7 @@ public struct AppRouter: AppNavigable { } rootViewController.dismiss(animated: false) - rootViewController.selectedIndex = MainTabIndex.profile.rawValue + rootViewController.selectedIndex = MainTabBarIndex.profile.rawValue guard let navController = rootViewController.selectedViewController as? UINavigationController else { return @@ -677,7 +688,7 @@ public struct AppRouter: AppNavigable { } rootViewController.dismiss(animated: false) { - rootViewController.selectedIndex = MainTabIndex.files.rawValue + rootViewController.selectedIndex = MainTabBarIndex.files.rawValue guard let navController = rootViewController.selectedViewController as? UINavigationController, let viewController = navController.topViewController as? FileListViewController else { @@ -767,6 +778,14 @@ public struct AppRouter: AppNavigable { animated: Bool ) { let storeViewController = StoreViewController.instantiate(driveFileManager: driveFileManager) - navigationController.pushViewController(storeViewController, animated: false) + navigationController.pushViewController(storeViewController, animated: animated) + } + + @MainActor public func presentAccountViewController( + navigationController: UINavigationController, + animated: Bool + ) { + let accountViewController = SwitchUserViewController.instantiate() + navigationController.pushViewController(accountViewController, animated: animated) } } diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift index f0b8914aa..520c9fec0 100644 --- a/kDrive/UI/Controller/Files/FilePresenter.swift +++ b/kDrive/UI/Controller/Files/FilePresenter.swift @@ -44,7 +44,7 @@ final class FilePresenter { viewController.navigationController?.popToRootViewController(animated: false) rootViewController.dismiss(animated: false) { - rootViewController.selectedIndex = MainTabIndex.files.rawValue + rootViewController.selectedIndex = MainTabBarIndex.files.rawValue guard let navigationController = rootViewController.selectedViewController as? UINavigationController else { return diff --git a/kDrive/UI/Controller/MainTabViewController.swift b/kDrive/UI/Controller/MainTabViewController.swift index ec7232b48..85ade40c4 100644 --- a/kDrive/UI/Controller/MainTabViewController.swift +++ b/kDrive/UI/Controller/MainTabViewController.swift @@ -25,7 +25,7 @@ import kDriveResources import UIKit /// Enum to explicit tab names -enum MainTabIndex: Int { +public enum MainTabBarIndex: Int { case home = 0 case files = 1 case gallery = 3 @@ -33,12 +33,19 @@ enum MainTabIndex: Int { } class MainTabViewController: UITabBarController, Restorable, PlusButtonObserver { + /// Tracking the last selection date to detect double tap + private var lastInteraction: Date? + + /// Time between two tap events that feels alright for a double tap + private static let doubleTapInterval = TimeInterval(0.350) + // swiftlint:disable:next weak_delegate var photoPickerDelegate = PhotoPickerDelegate() @LazyInjectService var accountManager: AccountManageable @LazyInjectService var uploadQueue: UploadQueue @LazyInjectService var fileImportHelper: FileImportHelper + @LazyInjectService var router: AppNavigable let driveFileManager: DriveFileManager @@ -243,19 +250,64 @@ extension MainTabViewController: MainTabBarDelegate { floatingPanelViewController.trackAndObserve(scrollView: plusButtonFloatingPanel.tableView) present(floatingPanelViewController, animated: true) } + + func avatarLongTouch() { + guard let rootNavigationController = viewControllers?[safe: MainTabBarIndex.profile.rawValue] as? UINavigationController + else { + return + } + + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + + selectedIndex = MainTabBarIndex.profile.rawValue + + router.presentAccountViewController(navigationController: rootNavigationController, animated: true) + } + + func avatarDoubleTap() { + accountManager.switchToNextAvailableAccount() + guard let accountManager = accountManager.currentDriveFileManager else { + return + } + + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + + _ = router.showMainViewController(driveFileManager: accountManager, + selectedIndex: MainTabBarIndex.profile.rawValue) + } } // MARK: - Tab bar controller delegate extension MainTabViewController: UITabBarControllerDelegate { func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { - if let homeViewController = (viewController as? UINavigationController)?.topViewController as? HomeViewController { + guard let navigationController = viewController as? UINavigationController else { + return false + } + + defer { + lastInteraction = Date() + } + + let topViewController = navigationController.topViewController + if let homeViewController = topViewController as? HomeViewController { homeViewController.presentedFromTabBar() } - if tabBarController.selectedViewController == viewController, - let viewController = (viewController as? UINavigationController)?.topViewController as? TopScrollable { - viewController.scrollToTop() + if tabBarController.selectedViewController == viewController { + // Detect double tap on menu + if topViewController as? MenuViewController != nil, + let lastDate = lastInteraction, + Date().timeIntervalSince(lastDate) <= Self.doubleTapInterval { + avatarDoubleTap() + return true + } + + if let viewController = topViewController as? TopScrollable { + viewController.scrollToTop() + } } return true diff --git a/kDrive/UI/Controller/Menu/StoreSuccessViewController.swift b/kDrive/UI/Controller/Menu/StoreSuccessViewController.swift index 925820dce..79e032eae 100644 --- a/kDrive/UI/Controller/Menu/StoreSuccessViewController.swift +++ b/kDrive/UI/Controller/Menu/StoreSuccessViewController.swift @@ -29,7 +29,7 @@ class StoreSuccessViewController: UIViewController { if let rootViewController = sender.window?.rootViewController as? MainTabViewController { rootViewController.dismiss(animated: true) (rootViewController.selectedViewController as? UINavigationController)?.popToRootViewController(animated: true) - rootViewController.selectedIndex = MainTabIndex.home.rawValue + rootViewController.selectedIndex = MainTabBarIndex.home.rawValue } else { dismiss(animated: true) } diff --git a/kDrive/UI/View/Menu/MainTabBar.swift b/kDrive/UI/View/Menu/MainTabBar.swift index 29ed22ec8..e99dbedfa 100644 --- a/kDrive/UI/View/Menu/MainTabBar.swift +++ b/kDrive/UI/View/Menu/MainTabBar.swift @@ -16,12 +16,16 @@ along with this program. If not, see . */ +import InfomaniakDI import kDriveCore import kDriveResources import UIKit +/// Delegation from MainTabBar towards MainTabViewController protocol MainTabBarDelegate: AnyObject { func plusButtonPressed() + func avatarLongTouch() + func avatarDoubleTap() } final class MainTabBar: UITabBar { @@ -73,6 +77,7 @@ final class MainTabBar: UITabBar { self.shapeLayer = shapeLayer setupBackgroundGradient() setupMiddleButton() + setupGestureRecognizer() } override func layoutSubviews() { @@ -163,6 +168,26 @@ final class MainTabBar: UITabBar { centerButton.addTarget(self, action: #selector(centerButtonAction), for: .touchUpInside) } + private func setupGestureRecognizer() { + let longTouch = UILongPressGestureRecognizer(target: self, + action: #selector(Self.handleLongTouch(recognizer:))) + addGestureRecognizer(longTouch) + } + + @objc func handleLongTouch(recognizer: UITapGestureRecognizer) { + guard recognizer.state == .began else { + return + } + + // Touch is over the 5th button's x position + let touchPoint = recognizer.location(in: self) + guard touchPoint.x > bounds.width / 5 * 4 else { + return + } + + tabDelegate?.avatarLongTouch() + } + @objc func centerButtonAction(sender: UIButton) { tabDelegate?.plusButtonPressed() } diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index 6258079fc..3f096e08c 100644 --- a/kDriveCore/Data/Cache/AccountManager.swift +++ b/kDriveCore/Data/Cache/AccountManager.swift @@ -73,6 +73,7 @@ public protocol AccountManageable: AnyObject { func loadAccounts() -> [Account] func saveAccounts() func switchAccount(newAccount: Account) + func switchToNextAvailableAccount() func setCurrentDriveForCurrentAccount(for driveId: Int, userId: Int) func addAccount(account: Account, token: ApiToken) func removeAccount(toDeleteAccount: Account) @@ -374,6 +375,36 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { saveAccounts() } + public func switchToNextAvailableAccount() { + guard let nextAccount = nextAvailableAccount else { + return + } + + switchAccount(newAccount: nextAccount) + } + + private var nextAvailableAccount: Account? { + let allAccounts = accounts.values + guard allAccounts.count > 1 else { + return nil + } + + guard let currentAccount else { + return nil + } + + guard let currentIndex = allAccounts.firstIndex(of: currentAccount) else { + return nil + } + + let nextIndex = currentIndex + 1 + guard let nextAccount = allAccounts[safe: nextIndex] else { + return allAccounts.first + } + + return nextAccount + } + private func setCurrentAccount(account: Account) { currentAccount = account currentUserId = account.userId diff --git a/kDriveTests/kDrive/Launch/MockAccountManager.swift b/kDriveTests/kDrive/Launch/MockAccountManager.swift index c797d1733..beb6c9671 100644 --- a/kDriveTests/kDrive/Launch/MockAccountManager.swift +++ b/kDriveTests/kDrive/Launch/MockAccountManager.swift @@ -75,6 +75,8 @@ class MockAccountManager: AccountManageable, RefreshTokenDelegate { func switchAccount(newAccount: Account) {} + func switchToNextAvailableAccount() {} + func setCurrentDriveForCurrentAccount(for driveId: Int, userId: Int) {} func addAccount(account: Account, token apiToken: ApiToken) {}