From 9f145fa9b59f112ce344b6775af7aa37d4e933c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 20 Jun 2024 18:10:41 +0200 Subject: [PATCH 1/7] feat: Detect double tap on profile picture --- kDrive/UI/View/Menu/MainTabBar.swift | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/kDrive/UI/View/Menu/MainTabBar.swift b/kDrive/UI/View/Menu/MainTabBar.swift index 29ed22ec8..c91c2ab3e 100644 --- a/kDrive/UI/View/Menu/MainTabBar.swift +++ b/kDrive/UI/View/Menu/MainTabBar.swift @@ -16,6 +16,7 @@ along with this program. If not, see . */ +import InfomaniakDI import kDriveCore import kDriveResources import UIKit @@ -73,6 +74,7 @@ final class MainTabBar: UITabBar { self.shapeLayer = shapeLayer setupBackgroundGradient() setupMiddleButton() + setupDoubleTapRecognizer() } override func layoutSubviews() { @@ -163,6 +165,34 @@ final class MainTabBar: UITabBar { centerButton.addTarget(self, action: #selector(centerButtonAction), for: .touchUpInside) } + private func setupDoubleTapRecognizer() { + let doubleTap = UITapGestureRecognizer(target: self, action: #selector(Self.handleDoubleTap(recognizer:))) + doubleTap.numberOfTapsRequired = 2 + addGestureRecognizer(doubleTap) + doubleTap.delaysTouchesBegan = false + } + + @objc func handleDoubleTap(recognizer: UITapGestureRecognizer) { + let touchPoint = recognizer.location(in: self) + + // Tap is over the 5th button + if touchPoint.x > bounds.width / 5 * 4 { + print("Double Tap profile") + @InjectService var accountManager: AccountManageable + guard let account = accountManager.accounts.values.randomElement() else { + fatalError("woops") + } + accountManager.switchAccount(newAccount: account) + + // TODO: "Respring" with restoration code + guard let driveFileManager = try? accountManager.getFirstAvailableDriveFileManager(for: account.userId) else { + fatalError("woops V2") + } + let newMainTabViewController = MainTabViewController(driveFileManager: driveFileManager) + (UIApplication.shared.delegate as? AppDelegate)?.setRootViewController(newMainTabViewController) + } + } + @objc func centerButtonAction(sender: UIButton) { tabDelegate?.plusButtonPressed() } From a58611aa4b568631dffd79477595c02054de71e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 28 Jun 2024 15:15:46 +0200 Subject: [PATCH 2/7] feat: Detect both long and double touch events without slowing tap detection over other tab items --- .../UI/Controller/MainTabViewController.swift | 32 ++++++++++++-- kDrive/UI/View/Menu/MainTabBar.swift | 42 +++++++++---------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/kDrive/UI/Controller/MainTabViewController.swift b/kDrive/UI/Controller/MainTabViewController.swift index 1bcd6fda7..827f0220b 100644 --- a/kDrive/UI/Controller/MainTabViewController.swift +++ b/kDrive/UI/Controller/MainTabViewController.swift @@ -25,6 +25,12 @@ import kDriveResources import UIKit 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() @@ -251,13 +257,31 @@ extension MainTabViewController: MainTabBarDelegate { 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 let viewController = topViewController as? MenuViewController, + let lastDate = lastInteraction, + Date().timeIntervalSince(lastDate) <= Self.doubleTapInterval { + print("double tap detected") + return true + } + + if let viewController = topViewController as? TopScrollable { + viewController.scrollToTop() + } } return true diff --git a/kDrive/UI/View/Menu/MainTabBar.swift b/kDrive/UI/View/Menu/MainTabBar.swift index c91c2ab3e..f3bc1d114 100644 --- a/kDrive/UI/View/Menu/MainTabBar.swift +++ b/kDrive/UI/View/Menu/MainTabBar.swift @@ -74,7 +74,7 @@ final class MainTabBar: UITabBar { self.shapeLayer = shapeLayer setupBackgroundGradient() setupMiddleButton() - setupDoubleTapRecognizer() + setupGestureRecognizer() } override func layoutSubviews() { @@ -165,32 +165,28 @@ final class MainTabBar: UITabBar { centerButton.addTarget(self, action: #selector(centerButtonAction), for: .touchUpInside) } - private func setupDoubleTapRecognizer() { - let doubleTap = UITapGestureRecognizer(target: self, action: #selector(Self.handleDoubleTap(recognizer:))) - doubleTap.numberOfTapsRequired = 2 - addGestureRecognizer(doubleTap) - doubleTap.delaysTouchesBegan = false + private func setupGestureRecognizer() { + let longTouch = UILongPressGestureRecognizer(target: self, + action: #selector(Self.handleLongTouch(recognizer:))) + addGestureRecognizer(longTouch) } - @objc func handleDoubleTap(recognizer: UITapGestureRecognizer) { - let touchPoint = recognizer.location(in: self) + @objc func handleLongTouch(recognizer: UITapGestureRecognizer) { + guard recognizer.state == .began else { + return + } - // Tap is over the 5th button - if touchPoint.x > bounds.width / 5 * 4 { - print("Double Tap profile") - @InjectService var accountManager: AccountManageable - guard let account = accountManager.accounts.values.randomElement() else { - fatalError("woops") - } - accountManager.switchAccount(newAccount: account) - - // TODO: "Respring" with restoration code - guard let driveFileManager = try? accountManager.getFirstAvailableDriveFileManager(for: account.userId) else { - fatalError("woops V2") - } - let newMainTabViewController = MainTabViewController(driveFileManager: driveFileManager) - (UIApplication.shared.delegate as? AppDelegate)?.setRootViewController(newMainTabViewController) + // Touch is over the 5th button's x position + let touchPoint = recognizer.location(in: self) + guard touchPoint.x > bounds.width / 5 * 4 else { + return } + + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + + print("Long touch") + // TODO: Nav to detail View, wait for AppNavigable to be available } @objc func centerButtonAction(sender: UIButton) { From c14612114acd2908b3ec023e7d1ef76261b925a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Fri, 28 Jun 2024 15:41:07 +0200 Subject: [PATCH 3/7] chore: SonarCloud feedback --- kDrive/UI/Controller/MainTabViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kDrive/UI/Controller/MainTabViewController.swift b/kDrive/UI/Controller/MainTabViewController.swift index 827f0220b..996c940a3 100644 --- a/kDrive/UI/Controller/MainTabViewController.swift +++ b/kDrive/UI/Controller/MainTabViewController.swift @@ -272,7 +272,7 @@ extension MainTabViewController: UITabBarControllerDelegate { if tabBarController.selectedViewController == viewController { // Detect double tap on menu - if let viewController = topViewController as? MenuViewController, + if topViewController as? MenuViewController != nil, let lastDate = lastInteraction, Date().timeIntervalSince(lastDate) <= Self.doubleTapInterval { print("double tap detected") From 718e6e5cbfd37d2c22a79316b696babf40116b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Mon, 1 Jul 2024 13:54:38 +0200 Subject: [PATCH 4/7] feat: Routing for account switching plugged in --- kDrive/AppRouter.swift | 23 +++++++++++-- .../UI/Controller/Files/FilePresenter.swift | 2 +- .../UI/Controller/MainTabViewController.swift | 34 +++++++++++++++++-- .../Menu/StoreSuccessViewController.swift | 2 +- kDrive/UI/View/Menu/MainTabBar.swift | 9 +++-- kDriveCore/Data/Cache/AccountManager.swift | 31 +++++++++++++++++ .../kDrive/Launch/MockAccountManager.swift | 2 ++ 7 files changed, 91 insertions(+), 12 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 59783ad93..59e335c4d 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 @@ -521,7 +530,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 +686,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 +776,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 c719af42a..f3d697b07 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 @@ -45,6 +45,7 @@ class MainTabViewController: UITabBarController, Restorable, PlusButtonObserver @LazyInjectService var accountManager: AccountManageable @LazyInjectService var uploadQueue: UploadQueue @LazyInjectService var fileImportHelper: FileImportHelper + @LazyInjectService var router: AppNavigable let driveFileManager: DriveFileManager @@ -249,6 +250,35 @@ extension MainTabViewController: MainTabBarDelegate { floatingPanelViewController.trackAndObserve(scrollView: plusButtonFloatingPanel.tableView) present(floatingPanelViewController, animated: true) } + + func avatarLongTouch() { + let viewControllers = viewControllers + 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() + + @InjectService var router: AppNavigable + _ = router.showMainViewController(driveFileManager: accountManager, + selectedIndex: MainTabBarIndex.profile.rawValue) + } } // MARK: - Tab bar controller delegate @@ -273,7 +303,7 @@ extension MainTabViewController: UITabBarControllerDelegate { if topViewController as? MenuViewController != nil, let lastDate = lastInteraction, Date().timeIntervalSince(lastDate) <= Self.doubleTapInterval { - print("double tap detected") + avatarDoubleTap() 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 f3bc1d114..e99dbedfa 100644 --- a/kDrive/UI/View/Menu/MainTabBar.swift +++ b/kDrive/UI/View/Menu/MainTabBar.swift @@ -21,8 +21,11 @@ import kDriveCore import kDriveResources import UIKit +/// Delegation from MainTabBar towards MainTabViewController protocol MainTabBarDelegate: AnyObject { func plusButtonPressed() + func avatarLongTouch() + func avatarDoubleTap() } final class MainTabBar: UITabBar { @@ -182,11 +185,7 @@ final class MainTabBar: UITabBar { return } - let generator = UIImpactFeedbackGenerator(style: .light) - generator.impactOccurred() - - print("Long touch") - // TODO: Nav to detail View, wait for AppNavigable to be available + tabDelegate?.avatarLongTouch() } @objc func centerButtonAction(sender: UIButton) { diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index 6258079fc..67651d352 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 accounts = accounts.values + guard accounts.count > 1 else { + return nil + } + + guard let currentAccount else { + return nil + } + + guard let currentIndex = accounts.firstIndex(of: currentAccount) else { + return nil + } + + let nextIndex = currentIndex + 1 + guard let nextAccount = accounts[safe: nextIndex] else { + return accounts.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) {} From 4765700fe3b855bab50c8cad2731b4254361728f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Mon, 1 Jul 2024 14:00:31 +0200 Subject: [PATCH 5/7] chore: SonarCloud feedback --- kDriveCore/Data/Cache/AccountManager.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index 67651d352..3f096e08c 100644 --- a/kDriveCore/Data/Cache/AccountManager.swift +++ b/kDriveCore/Data/Cache/AccountManager.swift @@ -384,8 +384,8 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { } private var nextAvailableAccount: Account? { - let accounts = accounts.values - guard accounts.count > 1 else { + let allAccounts = accounts.values + guard allAccounts.count > 1 else { return nil } @@ -393,13 +393,13 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { return nil } - guard let currentIndex = accounts.firstIndex(of: currentAccount) else { + guard let currentIndex = allAccounts.firstIndex(of: currentAccount) else { return nil } let nextIndex = currentIndex + 1 - guard let nextAccount = accounts[safe: nextIndex] else { - return accounts.first + guard let nextAccount = allAccounts[safe: nextIndex] else { + return allAccounts.first } return nextAccount From 24b7bf62c73e5333a400c47c158b4e06130fe9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Mon, 1 Jul 2024 14:04:17 +0200 Subject: [PATCH 6/7] fix: Fix build broken on APIV3 branch --- kDrive/AppRouter.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 59e335c4d..0dcd0e9d0 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -172,9 +172,11 @@ public struct AppRouter: AppNavigable { @LazyInjectService private var keychainHelper: KeychainHelper @LazyInjectService private var reviewManager: ReviewManageable @LazyInjectService private var availableOfflineManager: AvailableOfflineManageable - @LazyInjectService private var backgroundUploadSessionManager: BackgroundUploadSessionManager @LazyInjectService private var accountManager: AccountManageable + @LazyInjectService var backgroundDownloadSessionManager: BackgroundDownloadSessionManager + @LazyInjectService var backgroundUploadSessionManager: BackgroundUploadSessionManager + /// Get the current window from the app scene @MainActor private var window: UIWindow? { let scene = UIApplication.shared.connectedScenes.first { scene in From 54cc4276373d0d2c9a22bc0132662a6455fed554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 3 Jul 2024 09:01:19 +0200 Subject: [PATCH 7/7] chore: PR Feedback --- kDrive/UI/Controller/MainTabViewController.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/kDrive/UI/Controller/MainTabViewController.swift b/kDrive/UI/Controller/MainTabViewController.swift index f3d697b07..85ade40c4 100644 --- a/kDrive/UI/Controller/MainTabViewController.swift +++ b/kDrive/UI/Controller/MainTabViewController.swift @@ -252,7 +252,6 @@ extension MainTabViewController: MainTabBarDelegate { } func avatarLongTouch() { - let viewControllers = viewControllers guard let rootNavigationController = viewControllers?[safe: MainTabBarIndex.profile.rawValue] as? UINavigationController else { return @@ -275,7 +274,6 @@ extension MainTabViewController: MainTabBarDelegate { let generator = UIImpactFeedbackGenerator(style: .light) generator.impactOccurred() - @InjectService var router: AppNavigable _ = router.showMainViewController(driveFileManager: accountManager, selectedIndex: MainTabBarIndex.profile.rawValue) }