diff --git a/.package.resolved b/.package.resolved
index 9f8ace9bd..70590637a 100644
--- a/.package.resolved
+++ b/.package.resolved
@@ -60,8 +60,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/SCENEE/FloatingPanel",
"state" : {
- "revision" : "8f2be39bf49b4d5e22bbf7bdde69d5b76d0ecd2a",
- "version" : "2.8.2"
+ "revision" : "22d46c526084724a718b8c39ab77f12452712cc7",
+ "version" : "2.8.3"
}
},
{
@@ -69,8 +69,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/raspu/Highlightr",
"state" : {
- "revision" : "93199b9e434f04bda956a613af8f571933f9f037",
- "version" : "2.1.2"
+ "revision" : "fa483d37c692961ecc2391eac568ba3d3935e663",
+ "version" : "2.2.0"
}
},
{
@@ -87,8 +87,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/ios-core",
"state" : {
- "revision" : "6ec34998530da3e5ebf92c1a1b0209cd65cff3b6",
- "version" : "10.0.0"
+ "revision" : "33ce7bc356d9938078a4ec596a535390e25d8650",
+ "version" : "10.0.3"
}
},
{
@@ -132,8 +132,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/onevcat/Kingfisher",
"state" : {
- "revision" : "5b92f029fab2cce44386d28588098b5be0824ef5",
- "version" : "7.11.0"
+ "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50",
+ "version" : "7.12.0"
}
},
{
@@ -213,8 +213,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/realm-core.git",
"state" : {
- "revision" : "374dd672af357732dccc135fecc905406fec3223",
- "version" : "14.4.1"
+ "revision" : "e55176982ed9154899a39693d24609645b81586b",
+ "version" : "14.3.0"
}
},
{
@@ -222,8 +222,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/realm-swift",
"state" : {
- "revision" : "e0c2fbb442979fbf1e4be80e01d142f310a9c762",
- "version" : "10.49.1"
+ "revision" : "0a97dda4dd0b77449e55b997cf636651e6187634",
+ "version" : "10.49.0"
}
},
{
@@ -231,8 +231,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/getsentry/sentry-cocoa",
"state" : {
- "revision" : "ef4fec9dfb8dd5027b09a4a5c9362feafd118e1a",
- "version" : "8.24.0"
+ "revision" : "8fd4e804f2e72e0b9c1b189ce4e8349c4d10b6a2",
+ "version" : "8.30.0"
}
},
{
@@ -276,8 +276,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
- "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
- "version" : "1.1.0"
+ "revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
+ "version" : "1.1.1"
}
},
{
@@ -285,8 +285,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/swift-concurrency",
"state" : {
- "revision" : "02960fd5d2cf57c7ba38d13bbbf580d7f6ac7102",
- "version" : "0.0.5"
+ "revision" : "55fe7541cbfcfb4b73e7836b390e2c566a9ba910",
+ "version" : "0.0.6"
}
},
{
@@ -294,8 +294,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
- "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
- "version" : "1.5.4"
+ "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
+ "version" : "1.6.1"
}
},
{
@@ -303,8 +303,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
- "revision" : "fc63f0cf4e55a4597407a9fc95b16a2bc44b4982",
- "version" : "2.64.0"
+ "revision" : "e5a216ba89deba84356bad9d4c2eab99071c745b",
+ "version" : "2.67.0"
}
},
{
@@ -312,8 +312,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
- "revision" : "7c381eb6083542b124a6c18fae742f55001dc2b5",
- "version" : "2.26.0"
+ "revision" : "2b09805797f21c380f7dc9bedaab3157c5508efb",
+ "version" : "2.27.0"
}
},
{
@@ -321,8 +321,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
- "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce",
- "version" : "1.20.1"
+ "revision" : "38ac8221dd20674682148d6451367f89c2652980",
+ "version" : "1.21.0"
}
},
{
@@ -339,8 +339,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
- "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496",
- "version" : "1.2.1"
+ "revision" : "6a9e38e7bd22a3b8ba80bddf395623cf68f57807",
+ "version" : "1.3.1"
}
},
{
diff --git a/Project.swift b/Project.swift
index a5c87fd70..451b094a3 100644
--- a/Project.swift
+++ b/Project.swift
@@ -30,7 +30,7 @@ let project = Project(name: "kDrive",
.package(url: "https://github.com/Infomaniak/ios-dependency-injection", .upToNextMajor(from: "2.0.0")),
.package(url: "https://github.com/Infomaniak/swift-concurrency", .upToNextMajor(from: "0.0.4")),
.package(url: "https://github.com/Infomaniak/ios-version-checker", .upToNextMajor(from: "5.0.0")),
- .package(url: "https://github.com/realm/realm-swift", .upToNextMajor(from: "10.43.0")),
+ .package(url: "https://github.com/realm/realm-swift", .exact("10.49.0")),
.package(url: "https://github.com/SCENEE/FloatingPanel", .upToNextMajor(from: "2.0.0")),
.package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.6.2")),
.package(url: "https://github.com/flowbe/MaterialOutlinedTextField", .upToNextMajor(from: "0.1.0")),
@@ -93,8 +93,8 @@ let project = Project(name: "kDrive",
deploymentTarget: Constants.deploymentTarget,
infoPlist: .default,
sources: [
- "kDriveTests/**",
- "kDriveTestShared/**"
+ "kDriveTests/**",
+ "kDriveTestShared/**"
],
resources: [
"kDriveTests/**/*.jpg",
@@ -111,8 +111,8 @@ let project = Project(name: "kDrive",
deploymentTarget: Constants.deploymentTarget,
infoPlist: .default,
sources: [
- "kDriveAPITests/**",
- "kDriveTestShared/**"
+ "kDriveAPITests/**",
+ "kDriveTestShared/**"
],
dependencies: [
.target(name: "kDrive")
@@ -181,11 +181,12 @@ let project = Project(name: "kDrive",
deploymentTarget: Constants.deploymentTarget,
infoPlist: .file(path: "kDriveFileProvider/Info.plist"),
sources: [
- "kDriveFileProvider/**",
- "kDrive/Utils/AppFactoryService.swift",
- "kDrive/Utils/NavigationManager.swift"],
+ "kDriveFileProvider/**",
+ "kDrive/Utils/AppFactoryService.swift",
+ "kDrive/Utils/NavigationManager.swift"
+ ],
resources: [
- "kDrive/**/PrivacyInfo.xcprivacy"
+ "kDrive/**/PrivacyInfo.xcprivacy"
],
headers: .headers(project: "kDriveFileProvider/**"),
entitlements: "kDriveFileProvider/FileProvider.entitlements",
diff --git a/Tuist/ProjectDescriptionHelpers/Constants.swift b/Tuist/ProjectDescriptionHelpers/Constants.swift
index 46d49df85..cd56ea72f 100644
--- a/Tuist/ProjectDescriptionHelpers/Constants.swift
+++ b/Tuist/ProjectDescriptionHelpers/Constants.swift
@@ -20,7 +20,7 @@ import ProjectDescription
public enum Constants {
public static let testSettings: [String: SettingValue] = [
- "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "TEST DEBUG"
+ "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "DEBUG"
]
public static let baseSettings = SettingsDictionary()
diff --git a/kDrive/AppDelegate+Launch.swift b/kDrive/AppDelegate+Launch.swift
deleted file mode 100644
index 121a178e6..000000000
--- a/kDrive/AppDelegate+Launch.swift
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- Infomaniak kDrive - iOS App
- Copyright (C) 2023 Infomaniak Network SA
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
- */
-
-import Foundation
-import InfomaniakCore
-import InfomaniakDI
-import kDriveCore
-import kDriveResources
-import SafariServices
-import StoreKit
-import UIKit
-
-extension AppDelegate {
- // MARK: Launch
-
- func prepareRootViewController(currentState: RootViewControllerState) {
- switch currentState {
- case .appLock:
- showAppLock()
- case .mainViewController(let driveFileManager):
- showMainViewController(driveFileManager: driveFileManager)
- showLaunchFloatingPanel()
- askForReview()
- askUserToRemovePicturesIfNecessary()
- case .onboarding:
- showOnboarding()
- case .updateRequired:
- showUpdateRequired()
- case .preloading(let currentAccount):
- showPreloading(currentAccount: currentAccount)
- }
- }
-
- func updateRootViewControllerState() {
- let newState = RootViewControllerState.getCurrentState()
- prepareRootViewController(currentState: newState)
- }
-
- // MARK: Set root VC
-
- func showMainViewController(driveFileManager: DriveFileManager) {
- guard let window else {
- SentryDebug.captureNoWindow()
- return
- }
-
- let currentDriveObjectId = (window.rootViewController as? MainTabViewController)?.driveFileManager.drive.objectId
- guard currentDriveObjectId != driveFileManager.drive.objectId else {
- return
- }
-
- window.rootViewController = MainTabViewController(driveFileManager: driveFileManager)
- window.makeKeyAndVisible()
- }
-
- func showPreloading(currentAccount: Account) {
- guard let window else {
- SentryDebug.captureNoWindow()
- return
- }
-
- window.rootViewController = PreloadingViewController(currentAccount: currentAccount)
- window.makeKeyAndVisible()
- }
-
- private func showOnboarding() {
- guard let window else {
- SentryDebug.captureNoWindow()
- return
- }
-
- defer {
- // Clean File Provider domains on first launch in case we had some dangling
- driveInfosManager.deleteAllFileProviderDomains()
- }
-
- // Check if presenting onboarding
- let isNotPresentingOnboarding = window.rootViewController?.isKind(of: OnboardingViewController.self) != true
- guard isNotPresentingOnboarding else {
- return
- }
-
- keychainHelper.deleteAllTokens()
- window.rootViewController = OnboardingViewController.instantiate()
- window.makeKeyAndVisible()
- }
-
- private func showAppLock() {
- guard let window else {
- SentryDebug.captureNoWindow()
- return
- }
-
- window.rootViewController = LockedAppViewController.instantiate()
- window.makeKeyAndVisible()
- }
-
- private func showLaunchFloatingPanel() {
- guard let window else {
- SentryDebug.captureNoWindow()
- return
- }
-
- let launchPanelsController = LaunchPanelsController()
- if let viewController = window.rootViewController {
- launchPanelsController.pickAndDisplayPanel(viewController: viewController)
- }
- }
-
- private func showUpdateRequired() {
- guard let window else {
- SentryDebug.captureNoWindow()
- return
- }
-
- window.rootViewController = DriveUpdateRequiredViewController()
- window.makeKeyAndVisible()
- }
-
- // MARK: Misc
-
- private func askForReview() {
- guard let presentingViewController = window?.rootViewController,
- !Bundle.main.isRunningInTestFlight
- else { return }
-
- let shouldRequestReview = reviewManager.shouldRequestReview()
-
- if shouldRequestReview {
- let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String
- let alert = AlertTextViewController(
- title: appName,
- message: KDriveResourcesStrings.Localizable.reviewAlertTitle,
- action: KDriveResourcesStrings.Localizable.buttonYes,
- hasCancelButton: true,
- cancelString: KDriveResourcesStrings.Localizable.buttonNo,
- handler: requestAppStoreReview,
- cancelHandler: openUserReport
- )
-
- presentingViewController.present(alert, animated: true)
- MatomoUtils.track(eventWithCategory: .appReview, name: "alertPresented")
- }
- }
-
- private func requestAppStoreReview() {
- MatomoUtils.track(eventWithCategory: .appReview, name: "like")
- UserDefaults.shared.appReview = .readyForReview
- reviewManager.requestReview()
- }
-
- private func openUserReport() {
- MatomoUtils.track(eventWithCategory: .appReview, name: "dislike")
- guard let url = URL(string: KDriveResourcesStrings.Localizable.urlUserReportiOS),
- let presentingViewController = window?.rootViewController else {
- return
- }
- UserDefaults.shared.appReview = .feedback
- presentingViewController.present(SFSafariViewController(url: url), animated: true)
- }
-
- // TODO: Refactor to async
- func uploadEditedFiles() {
- Log.appDelegate("uploadEditedFiles")
- guard let folderURL = DriveFileManager.constants.openInPlaceDirectoryURL,
- FileManager.default.fileExists(atPath: folderURL.path) else {
- return
- }
-
- let group = DispatchGroup()
- var shouldCleanFolder = false
- let driveFolders = (try? FileManager.default.contentsOfDirectory(atPath: folderURL.path)) ?? []
- // Hierarchy inside folderURL should be /driveId/fileId/fileName.extension
- for driveFolder in driveFolders {
- // Read drive folder
- let driveFolderURL = folderURL.appendingPathComponent(driveFolder)
- guard let driveId = Int(driveFolder),
- let drive = driveInfosManager.getDrive(id: driveId, userId: accountManager.currentUserId),
- let fileFolders = try? FileManager.default.contentsOfDirectory(atPath: driveFolderURL.path) else {
- Log.appDelegate("[OPEN-IN-PLACE UPLOAD] Could not infer drive from \(driveFolderURL)")
- continue
- }
-
- for fileFolder in fileFolders {
- // Read file folder
- let fileFolderURL = driveFolderURL.appendingPathComponent(fileFolder)
- guard let fileId = Int(fileFolder),
- let driveFileManager = accountManager.getDriveFileManager(for: drive),
- let file = driveFileManager.getCachedFile(id: fileId) else {
- Log.appDelegate("[OPEN-IN-PLACE UPLOAD] Could not infer file from \(fileFolderURL)")
- continue
- }
-
- let fileURL = fileFolderURL.appendingPathComponent(file.name)
- guard FileManager.default.fileExists(atPath: fileURL.path) else {
- continue
- }
-
- // Compare modification date
- let attributes = try? FileManager.default.attributesOfItem(atPath: fileURL.path)
- let modificationDate = attributes?[.modificationDate] as? Date ?? Date(timeIntervalSince1970: 0)
-
- guard modificationDate > file.lastModifiedAt else {
- continue
- }
-
- // Copy and upload file
- let uploadFile = UploadFile(parentDirectoryId: file.parentId,
- userId: accountManager.currentUserId,
- driveId: file.driveId,
- url: fileURL,
- name: file.name,
- conflictOption: .version,
- shouldRemoveAfterUpload: false)
- group.enter()
- shouldCleanFolder = true
- @InjectService var uploadQueue: UploadQueue
- var observationToken: ObservationToken?
- observationToken = uploadQueue
- .observeFileUploaded(self, fileId: uploadFile.id) { [fileId = file.id] uploadFile, _ in
- observationToken?.cancel()
- if let error = uploadFile.error {
- shouldCleanFolder = false
- Log.appDelegate("[OPEN-IN-PLACE UPLOAD] Error while uploading: \(error)", level: .error)
- } else {
- // Update file to get the new modification date
- Task {
- let file = try await driveFileManager.file(id: fileId, forceRefresh: true)
- try? FileManager.default.setAttributes([.modificationDate: file.lastModifiedAt],
- ofItemAtPath: file.localUrl.path)
- driveFileManager.notifyObserversWith(file: file)
- }
- }
- group.leave()
- }
- uploadQueue.saveToRealm(uploadFile, itemIdentifier: nil)
- }
- }
-
- // Clean folder after completing all uploads
- group.notify(queue: DispatchQueue.global(qos: .utility)) {
- if shouldCleanFolder {
- Log.appDelegate("[OPEN-IN-PLACE UPLOAD] Cleaning folder")
- try? FileManager.default.removeItem(at: folderURL)
- }
- }
- }
-
- /// Ask the user to remove pictures if configured
- private func askUserToRemovePicturesIfNecessary() {
- @InjectService var photoCleaner: PhotoLibraryCleanerServiceable
- guard photoCleaner.hasPicturesToRemove else {
- Log.appDelegate("No pictures to remove", level: .info)
- return
- }
-
- let alert = AlertTextViewController(title: KDriveResourcesStrings.Localizable.modalDeletePhotosTitle,
- message: KDriveResourcesStrings.Localizable.modalDeletePhotosDescription,
- action: KDriveResourcesStrings.Localizable.buttonDelete,
- destructive: true,
- loading: false) {
- Task {
- // Proceed with removal
- await photoCleaner.removePicturesScheduledForDeletion()
- }
- }
-
- Task { @MainActor in
- self.window?.rootViewController?.present(alert, animated: true)
- }
- }
-}
diff --git a/kDrive/AppDelegate+Scene.swift b/kDrive/AppDelegate+Scene.swift
new file mode 100644
index 000000000..1c30c1ced
--- /dev/null
+++ b/kDrive/AppDelegate+Scene.swift
@@ -0,0 +1,33 @@
+/*
+ Infomaniak kDrive - iOS App
+ Copyright (C) 2023 Infomaniak Network SA
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+
+import kDriveCore
+import UIKit
+
+extension AppDelegate {
+ func application(_ application: UIApplication,
+ configurationForConnecting connectingSceneSession: UISceneSession,
+ options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ Log.appDelegate("application configurationForConnecting:\(connectingSceneSession)")
+ return connectingSceneSession.configuration
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ Log.appDelegate("application didDiscardSceneSessions:\(sceneSessions)")
+ }
+}
diff --git a/kDrive/AppDelegate.swift b/kDrive/AppDelegate.swift
index b9ada886f..64da65562 100644
--- a/kDrive/AppDelegate.swift
+++ b/kDrive/AppDelegate.swift
@@ -34,30 +34,18 @@ import UserNotifications
import VersionChecker
@main
-final class AppDelegate: UIResponder, UIApplicationDelegate, AccountManagerDelegate {
+final class AppDelegate: UIResponder, UIApplicationDelegate {
/// Making sure the DI is registered at a very early stage of the app launch.
private let dependencyInjectionHook = EarlyDIHook(context: .app)
private var reachabilityListener: ReachabilityListener!
- private var shortcutItemToProcess: UIApplicationShortcutItem?
- var window: UIWindow?
-
- @LazyInjectService var lockHelper: AppLockHelper
@LazyInjectService var infomaniakLogin: InfomaniakLogin
- @LazyInjectService var backgroundUploadSessionManager: BackgroundUploadSessionManager
- @LazyInjectService var backgroundDownloadSessionManager: BackgroundDownloadSessionManager
- @LazyInjectService var photoLibraryUploader: PhotoLibraryUploader
@LazyInjectService var notificationHelper: NotificationsHelpable
- @LazyInjectService var driveInfosManager: DriveInfosManager
- @LazyInjectService var keychainHelper: KeychainHelper
+ @LazyInjectService var accountManager: AccountManageable
@LazyInjectService var backgroundTasksService: BackgroundTasksServiceable
- @LazyInjectService var reviewManager: ReviewManageable
- @LazyInjectService var availableOfflineManager: AvailableOfflineManageable
- @LazyInjectService var appRestorationService: AppRestorationService
-
- // Not lazy to force init of the object early, and set a userID in Sentry
- @InjectService var accountManager: AccountManageable
+ @LazyInjectService var appRestorationService: AppRestorationServiceable
+ @LazyInjectService private var appNavigable: AppNavigable
// MARK: - UIApplicationDelegate
@@ -85,33 +73,13 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, AccountManagerDeleg
notificationHelper.registerCategories()
UNUserNotificationCenter.current().delegate = self
- window = UIWindow()
- setGlobalTint()
-
- let state = UIApplication.shared.applicationState
- if state != .background {
- appWillBePresentedToTheUser()
- }
-
- accountManager.delegate = self
-
if CommandLine.arguments.contains("testing") {
UIView.setAnimationsEnabled(false)
}
- window?.overrideUserInterfaceStyle = UserDefaults.shared.theme.interfaceStyle
-
// Attach an observer to the payment queue.
SKPaymentQueue.default().add(StoreObserver.shared)
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(handleLocateUploadNotification),
- name: .locateUploadActionTapped,
- object: nil
- )
- NotificationCenter.default.addObserver(self, selector: #selector(reloadDrive), name: .reloadDrive, object: nil)
-
return true
}
@@ -129,10 +97,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, AccountManagerDeleg
}
application.registerForRemoteNotifications()
- if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
- shortcutItemToProcess = shortcutItem
- }
-
return true
}
@@ -175,177 +139,12 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, AccountManagerDeleg
Log.appDelegate("Unable to register for remote notifications: \(error.localizedDescription)", level: .error)
}
- func applicationDidEnterBackground(_ application: UIApplication) {
- Log.appDelegate("applicationDidEnterBackground")
- backgroundTasksService.scheduleBackgroundRefresh()
-
- if UserDefaults.shared.isAppLockEnabled,
- !(window?.rootViewController?.isKind(of: LockedAppViewController.self) ?? false) {
- lockHelper.setTime()
- }
- }
-
- func application(
- _ application: UIApplication,
- performActionFor shortcutItem: UIApplicationShortcutItem,
- completionHandler: @escaping (Bool) -> Void
- ) {
- shortcutItemToProcess = shortcutItem
- }
-
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
Log.appDelegate("application app open url\(url)")
return DeeplinkParser().parse(url: url)
}
- func applicationWillEnterForeground(_ application: UIApplication) {
- Log.appDelegate("applicationWillEnterForeground")
- appWillBePresentedToTheUser()
- }
-
- private func appWillBePresentedToTheUser() {
- @InjectService var uploadQueue: UploadQueue
- uploadQueue.pausedNotificationSent = false
-
- let currentState = RootViewControllerState.getCurrentState()
- prepareRootViewController(currentState: currentState)
- switch currentState {
- case .mainViewController, .appLock:
- UserDefaults.shared.numberOfConnections += 1
- UserDefaults.shared.openingUntilReview -= 1
- refreshCacheScanLibraryAndUpload(preload: false, isSwitching: false)
- uploadEditedFiles()
- case .onboarding, .updateRequired, .preloading: break
- }
-
- // Remove all notifications on App Opening
- UNUserNotificationCenter.current().removeAllDeliveredNotifications()
-
- Task {
- if try await VersionChecker.standard.checkAppVersionStatus() == .updateIsRequired {
- prepareRootViewController(currentState: .updateRequired)
- }
- }
- }
-
- /// Set global tint color
- private func setGlobalTint() {
- window?.tintColor = KDriveResourcesAsset.infomaniakColor.color
- UITabBar.appearance().unselectedItemTintColor = KDriveResourcesAsset.iconColor.color
- // Migration from old UserDefaults
- if UserDefaults.shared.legacyIsFirstLaunch {
- UserDefaults.shared.legacyIsFirstLaunch = UserDefaults.standard.legacyIsFirstLaunch
- }
- }
-
- func applicationDidBecomeActive(_ application: UIApplication) {
- if let shortcutItem = shortcutItemToProcess {
- guard let rootViewController = window?.rootViewController as? MainTabViewController else {
- return
- }
-
- // Dismiss all view controllers presented
- rootViewController.dismiss(animated: false)
-
- guard let navController = rootViewController.selectedViewController as? UINavigationController,
- let viewController = navController.topViewController,
- let driveFileManager = accountManager.currentDriveFileManager else {
- return
- }
-
- switch shortcutItem.type {
- case Constants.applicationShortcutScan:
- let openMediaHelper = OpenMediaHelper(driveFileManager: driveFileManager)
- openMediaHelper.openScan(rootViewController, false)
- MatomoUtils.track(eventWithCategory: .shortcuts, name: "scan")
- case Constants.applicationShortcutSearch:
- let viewModel = SearchFilesViewModel(driveFileManager: driveFileManager)
- viewController.present(
- SearchViewController.instantiateInNavigationController(viewModel: viewModel),
- animated: true
- )
- MatomoUtils.track(eventWithCategory: .shortcuts, name: "search")
- case Constants.applicationShortcutUpload:
- let openMediaHelper = OpenMediaHelper(driveFileManager: driveFileManager)
- openMediaHelper.openMedia(rootViewController, .library)
- MatomoUtils.track(eventWithCategory: .shortcuts, name: "upload")
- case Constants.applicationShortcutSupport:
- UIApplication.shared.open(URLConstants.support.url)
- MatomoUtils.track(eventWithCategory: .shortcuts, name: "support")
- default:
- break
- }
-
- // reset the shortcut item
- shortcutItemToProcess = nil
- }
- }
-
- func refreshCacheScanLibraryAndUpload(preload: Bool, isSwitching: Bool) {
- Log.appDelegate("refreshCacheScanLibraryAndUpload preload:\(preload) isSwitching:\(preload)")
-
- guard let currentAccount = accountManager.currentAccount else {
- Log.appDelegate("No account to refresh", level: .error)
- return
- }
-
- let rootViewController = window?.rootViewController as? UpdateAccountDelegate
-
- availableOfflineManager.updateAvailableOfflineFiles(status: ReachabilityListener.instance.currentStatus)
-
- Task {
- do {
- let oldDriveId = accountManager.currentDriveFileManager?.drive.objectId
- let account = try await accountManager.updateUser(for: currentAccount, registerToken: true)
- rootViewController?.didUpdateCurrentAccountInformations(account)
-
- if let oldDriveId,
- let newDrive = driveInfosManager.getDrive(primaryKey: oldDriveId),
- !newDrive.inMaintenance {
- // The current drive is still usable, do not switch
- scanLibraryAndRestartUpload()
- return
- }
-
- let driveFileManager = try accountManager.getFirstAvailableDriveFileManager(for: account.userId)
- accountManager.setCurrentDriveForCurrentAccount(drive: driveFileManager.drive)
- showMainViewController(driveFileManager: driveFileManager)
- scanLibraryAndRestartUpload()
- } catch DriveError.NoDriveError.noDrive {
- let driveErrorNavigationViewController = DriveErrorViewController.instantiateInNavigationController(
- errorType: .noDrive,
- drive: nil
- )
- setRootViewController(driveErrorNavigationViewController)
- } catch DriveError.NoDriveError.blocked(let drive), DriveError.NoDriveError.maintenance(let drive) {
- let driveErrorNavigationViewController = DriveErrorViewController.instantiateInNavigationController(
- errorType: drive.isInTechnicalMaintenance ? .maintenance : .blocked,
- drive: drive
- )
- setRootViewController(driveErrorNavigationViewController)
- } catch {
- UIConstants.showSnackBarIfNeeded(error: DriveError.unknownError)
- Log.appDelegate("Error while updating user account: \(error)", level: .error)
- }
- }
- }
-
- private func scanLibraryAndRestartUpload() {
- // Resolving an upload queue will restart it if this is the first time
- @InjectService var uploadQueue: UploadQueue
-
- backgroundUploadSessionManager.reconnectBackgroundTasks()
- DispatchQueue.global(qos: .utility).async {
- Log.appDelegate("Restart queue")
- @InjectService var photoUploader: PhotoLibraryUploader
- _ = photoUploader.scheduleNewPicturesForUpload()
-
- @InjectService var uploadQueue: UploadQueue
- uploadQueue.rebuildUploadQueueFromObjectsInRealm()
- }
- }
-
func application(_ application: UIApplication,
open url: URL,
sourceApplication: String?,
@@ -354,112 +153,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, AccountManagerDeleg
return infomaniakLogin.handleRedirectUri(url: url)
}
- func setRootViewController(_ vc: UIViewController,
- animated: Bool = true) {
- guard let window else {
- return
- }
-
- window.rootViewController = vc
- window.makeKeyAndVisible()
-
- guard animated else {
- return
- }
-
- UIView.transition(with: window, duration: 0.3,
- options: .transitionCrossDissolve,
- animations: nil,
- completion: nil)
- }
-
- func present(file: File, driveFileManager: DriveFileManager, office: Bool = false) {
- guard let rootViewController = window?.rootViewController as? MainTabViewController else {
- return
- }
-
- // Dismiss all view controllers presented
- rootViewController.dismiss(animated: false) {
- // Select Files tab
- rootViewController.selectedIndex = 1
-
- guard let navController = rootViewController.selectedViewController as? UINavigationController,
- let viewController = navController.topViewController as? FileListViewController else {
- return
- }
-
- guard !file.isRoot,
- viewController.viewModel.currentDirectory.id != file.id else {
- Log.appDelegate("Already presenting the correct screen")
- return
- }
-
- // Pop to root
- navController.popToRootViewController(animated: false)
-
- // Present file
- guard let fileListViewController = navController.topViewController as? RootMenuViewController else {
- Log.appDelegate("Top navigation is not a RootMenuViewController")
- return
- }
-
- if office {
- OnlyOfficeViewController.open(driveFileManager: driveFileManager,
- file: file,
- viewController: fileListViewController)
- } else {
- let filePresenter = FilePresenter(viewController: fileListViewController)
- filePresenter.present(for: file,
- files: [file],
- driveFileManager: driveFileManager,
- normalFolderHierarchy: false)
- }
- }
- }
-
- @objc func handleLocateUploadNotification(_ notification: Notification) {
- guard let parentId = notification.userInfo?["parentId"] as? Int else {
- Log.appDelegate("No parentId")
- return
- }
-
- guard let driveFileManager = accountManager.currentDriveFileManager else {
- Log.appDelegate("No driveFileManager")
- return
- }
-
- guard let folder = driveFileManager.getCachedFile(id: parentId) else {
- Log.appDelegate("No matching cached files")
- return
- }
-
- present(file: folder, driveFileManager: driveFileManager)
- }
-
- @objc func reloadDrive(_ notification: Notification) {
- Task { @MainActor in
- self.refreshCacheScanLibraryAndUpload(preload: false, isSwitching: false)
- }
- }
-
- // MARK: - Account manager delegate
-
- func currentAccountNeedsAuthentication() {
- Task { @MainActor in
- setRootViewController(SwitchUserViewController.instantiateInNavigationController())
- }
- }
-
- // MARK: - State restoration
-
- func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
- return appRestorationService.shouldSaveApplicationState(coder: coder)
- }
-
- func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
- return appRestorationService.shouldRestoreApplicationState(coder: coder)
- }
-
// MARK: - User activity
func application(_ application: UIApplication,
@@ -487,7 +180,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
_ = notification.request.content.userInfo
- // Change this to your preferred presentation option
completionHandler([.alert, .sound])
}
@@ -510,7 +202,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
if let parentId,
let driveFileManager = accountManager.currentDriveFileManager,
let folder = driveFileManager.getCachedFile(id: parentId) {
- present(file: folder, driveFileManager: driveFileManager)
+ appNavigable.present(file: folder, driveFileManager: driveFileManager)
}
default:
break
@@ -518,22 +210,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
} else if response.notification.request.content.categoryIdentifier == NotificationsHelper.CategoryIdentifier
.photoSyncError {
// Show photo sync settings
- guard let rootViewController = window?.rootViewController as? MainTabViewController else {
- return
- }
-
- // Dismiss all view controllers presented
- rootViewController.dismiss(animated: false)
- // Select Menu tab
- rootViewController.selectedIndex = 4
-
- guard let navController = rootViewController.selectedViewController as? UINavigationController else {
- return
- }
-
- let photoSyncSettingsViewController = PhotoSyncSettingsViewController.instantiate()
- navController.popToRootViewController(animated: false)
- navController.pushViewController(photoSyncSettingsViewController, animated: true)
+ appNavigable.showPhotoSyncSettings()
} else {
// Handle other notification types...
}
@@ -557,15 +234,3 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
// Messaging.messaging().appDidReceiveMessage(userInfo)
}
}
-
-// MARK: - Navigation
-
-extension AppDelegate {
- var topMostViewController: UIViewController? {
- var topViewController = window?.rootViewController
- while let presentedViewController = topViewController?.presentedViewController {
- topViewController = presentedViewController
- }
- return topViewController
- }
-}
diff --git a/kDrive/AppRestorationService.swift b/kDrive/AppRestorationService.swift
index b1ade3fac..b0ead6434 100644
--- a/kDrive/AppRestorationService.swift
+++ b/kDrive/AppRestorationService.swift
@@ -21,8 +21,23 @@ import InfomaniakDI
import kDriveCore
import UIKit
-// TODO: Refactor with Scenes / NSUserActivity
-public final class AppRestorationService {
+/// Something that centralize the App Restoration logic
+public protocol AppRestorationServiceable {
+ /// Is restoration enabled
+ var shouldRestoreApplicationState: Bool { get }
+
+ /// Should save the scene sate
+ var shouldSaveApplicationState: Bool { get }
+
+ /// Saves a restoration version, for forward compatibility
+ func saveRestorationVersion()
+
+ func reloadAppUI(for driveId: Int, userId: Int) async
+}
+
+public final class AppRestorationService: AppRestorationServiceable {
+ @LazyInjectService var appNavigable: AppNavigable
+
/// Path where the state restoration state is saved
private static let statePath = FileManager.default
.urls(for: .libraryDirectory, in: .userDomainMask)
@@ -32,34 +47,35 @@ public final class AppRestorationService {
@LazyInjectService private var accountManager: AccountManageable
/// State restoration version
- private static let currentStateVersion = 4
-
- /// State restoration key
- private static let appStateVersionKey = "appStateVersionKey"
+ private static let currentStateVersion = 5
public init() {
// META: keep SonarCloud happy
}
- public func shouldSaveApplicationState(coder: NSCoder) -> Bool {
- Log.appDelegate("shouldSaveApplicationState")
- Log.appDelegate("Restoration files:\(String(describing: Self.statePath))")
- coder.encode(Self.currentStateVersion, forKey: Self.appStateVersionKey)
+ public var shouldSaveApplicationState: Bool {
+ Log.sceneDelegate("shouldSaveApplicationState")
+ Log.sceneDelegate("Restoration files:\(String(describing: Self.statePath))")
+
return true
}
- public func shouldRestoreApplicationState(coder: NSCoder) -> Bool {
- return false
- /* TODO: Rework app restoration before re-enabling
- let encodedVersion = coder.decodeInteger(forKey: Self.appStateVersionKey)
- let shouldRestoreApplicationState = Self.currentStateVersion == encodedVersion &&
- !(UserDefaults.shared.legacyIsFirstLaunch || accountManager.accounts.isEmpty)
- Log.appDelegate("shouldRestoreApplicationState:\(shouldRestoreApplicationState)")
- return shouldRestoreApplicationState*/
+ public var shouldRestoreApplicationState: Bool {
+ let storedVersion = UserDefaults.shared.appRestorationVersion
+ let shouldRestore = Self.currentStateVersion == storedVersion &&
+ !(UserDefaults.shared.legacyIsFirstLaunch || accountManager.accounts.isEmpty)
+
+ Log.sceneDelegate("shouldRestoreApplicationState:\(shouldRestore) appRestorationVersion:\(storedVersion)")
+ return shouldRestore
+ }
+
+ public func saveRestorationVersion() {
+ UserDefaults.shared.appRestorationVersion = Self.currentStateVersion
+ Log.sceneDelegate("saveRestorationVersion to \(Self.currentStateVersion)")
}
- public func reloadAppUI(for drive: Drive) {
- accountManager.setCurrentDriveForCurrentAccount(drive: drive)
+ public func reloadAppUI(for driveId: Int, userId: Int) async {
+ accountManager.setCurrentDriveForCurrentAccount(for: driveId, userId: userId)
accountManager.saveAccounts()
guard let currentDriveFileManager = accountManager.currentDriveFileManager else {
@@ -67,12 +83,8 @@ public final class AppRestorationService {
}
// Read the last tab selected in order to properly reload the App's UI.
- // This should be migrated to NSUserActivity at some point
let lastSelectedTab = UserDefaults.shared.lastSelectedTab
- let newMainTabViewController = MainTabViewController(
- driveFileManager: currentDriveFileManager,
- selectedIndex: lastSelectedTab
- )
- (UIApplication.shared.delegate as? AppDelegate)?.setRootViewController(newMainTabViewController)
+
+ await appNavigable.showMainViewController(driveFileManager: currentDriveFileManager, selectedIndex: lastSelectedTab)
}
}
diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift
new file mode 100644
index 000000000..59783ad93
--- /dev/null
+++ b/kDrive/AppRouter.swift
@@ -0,0 +1,772 @@
+/*
+ Infomaniak kDrive - iOS App
+ Copyright (C) 2024 Infomaniak Network SA
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+
+import InfomaniakCore
+import InfomaniakCoreUI
+import InfomaniakDI
+import kDriveCore
+import kDriveResources
+import SafariServices
+import UIKit
+import VersionChecker
+
+/// Something that can navigate to specific places of the kDrive app
+public protocol RouterAppNavigable {
+ /// Show the main view with a customizable selected index
+ /// - Parameters:
+ /// - driveFileManager: driveFileManager to use
+ /// - selectedIndex: Nil will try to use state restoration if available
+ @MainActor func showMainViewController(driveFileManager: DriveFileManager, selectedIndex: Int?) -> UITabBarController?
+
+ @MainActor func showPreloading(currentAccount: Account)
+
+ @MainActor func showOnboarding()
+
+ @MainActor func showAppLock()
+
+ @MainActor func showLaunchFloatingPanel()
+
+ @MainActor func showUpdateRequired()
+
+ @MainActor func showPhotoSyncSettings()
+}
+
+/// Something that can present a File within the app
+public protocol RouterFileNavigable {
+ /// Pop to root and present file, will never open OnlyOffice
+ /// - Parameters:
+ /// - file: File to display
+ /// - driveFileManager: driveFileManager
+ @MainActor func present(file: File, driveFileManager: DriveFileManager)
+
+ /// Pop to root and present file
+ /// - Parameters:
+ /// - file: File to display
+ /// - driveFileManager: driveFileManager
+ /// - office: Open in only office
+ @MainActor func present(file: File, driveFileManager: DriveFileManager, office: Bool)
+
+ /// Present a list of files from a folder
+ /// - Parameters:
+ /// - frozenFolder: Folder to display
+ /// - driveFileManager: driveFileManager
+ /// - navigationController: The navigation controller to use
+ @MainActor func presentFileList(
+ frozenFolder: File,
+ driveFileManager: DriveFileManager,
+ navigationController: UINavigationController
+ )
+
+ /// Present PreviewViewController
+ /// - Parameters:
+ /// - frozenFiles: File list to display, must be frozen
+ /// - index: The Index of the file to display
+ /// - driveFileManager: The driveFileManager
+ /// - normalFolderHierarchy: See FileListViewModel.Configuration for details
+ /// - fromActivities: Opening from an activity
+ /// - navigationController: The navigation controller to use
+ /// - animated: Should be animated
+ @MainActor func presentPreviewViewController(
+ frozenFiles: [File],
+ index: Int,
+ driveFileManager: DriveFileManager,
+ normalFolderHierarchy: Bool,
+ fromActivities: Bool,
+ navigationController: UINavigationController,
+ animated: Bool
+ )
+
+ /// Present the details of a file and all the linked metadata
+ /// - Parameters:
+ /// - frozenFile: A frozen file to display
+ /// - driveFileManager: driveFileManager
+ /// - navigationController: The navigation controller to use
+ /// - animated: Should be animated
+ @MainActor func presentFileDetails(
+ frozenFile: File,
+ driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ animated: Bool
+ )
+
+ /// Present the InApp purchase StoreViewController
+ /// - Parameters:
+ /// - driveFileManager: driveFileManager
+ /// - navigationController: The navigation controller to use
+ /// - animated: Should be animated
+ @MainActor func presentStoreViewController(
+ driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ animated: Bool
+ )
+}
+
+/// Something that can set an arbitrary RootView controller
+public protocol RouterRootNavigable {
+ /// Something that can set an arbitrary RootView controller
+ ///
+ /// Should not be used externally except by SceneDelegate.
+ @MainActor func setRootViewController(_ viewController: UIViewController,
+ animated: Bool)
+
+ /// Setup the root of the view stack
+ /// - Parameters:
+ /// - currentState: the state to present
+ /// - restoration: try to restore scene or not
+ @MainActor func prepareRootViewController(currentState: RootViewControllerState, restoration: Bool)
+
+ /// Set the main theme color
+ @MainActor func updateTheme()
+}
+
+public protocol TopmostViewControllerFetchable {
+ /// Access the current top most ViewController
+ @MainActor var topMostViewController: UIViewController? { get }
+}
+
+/// Actions performed by router, `async` by design
+public protocol RouterActionable {
+ /// Ask the user to review the app
+ func askForReview() async
+
+ /// Ask the user to remove pictures if configured
+ func askUserToRemovePicturesIfNecessary() async
+
+ func refreshCacheScanLibraryAndUpload(preload: Bool, isSwitching: Bool) async
+}
+
+/// Something that can navigate within the kDrive app
+public typealias AppNavigable = RouterActionable
+ & RouterAppNavigable
+ & RouterFileNavigable
+ & RouterRootNavigable
+ & TopmostViewControllerFetchable
+
+public struct AppRouter: AppNavigable {
+ @LazyInjectService private var appRestorationService: AppRestorationServiceable
+ @LazyInjectService private var driveInfosManager: DriveInfosManager
+ @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
+
+ /// Get the current window from the app scene
+ @MainActor private var window: UIWindow? {
+ let scene = UIApplication.shared.connectedScenes.first { scene in
+ guard let delegate = scene.delegate,
+ delegate as? SceneDelegate != nil else {
+ return false
+ }
+
+ return true
+ }
+
+ guard let sceneDelegate = scene?.delegate as? SceneDelegate,
+ let window = sceneDelegate.window else {
+ return nil
+ }
+
+ return window
+ }
+
+ @MainActor var sceneUserInfo: [AnyHashable: Any]? {
+ guard let scene = window?.windowScene,
+ let userInfo = scene.userActivity?.userInfo else {
+ return nil
+ }
+
+ return userInfo
+ }
+
+ // MARK: TopmostViewControllerFetchable
+
+ @MainActor public var topMostViewController: UIViewController? {
+ var topViewController = window?.rootViewController
+ while let presentedViewController = topViewController?.presentedViewController {
+ topViewController = presentedViewController
+ }
+ return topViewController
+ }
+
+ // MARK: RouterRootNavigable
+
+ @MainActor public func setRootViewController(_ viewController: UIViewController,
+ animated: Bool) {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ window.rootViewController = viewController
+ window.makeKeyAndVisible()
+
+ guard animated else {
+ return
+ }
+
+ UIView.transition(with: window, duration: 0.3,
+ options: .transitionCrossDissolve,
+ animations: nil,
+ completion: nil)
+ }
+
+ @MainActor public func prepareRootViewController(currentState: RootViewControllerState, restoration: Bool) {
+ switch currentState {
+ case .appLock:
+ showAppLock()
+ case .mainViewController(let driveFileManager):
+
+ restoreMainUIStackIfPossible(driveFileManager: driveFileManager, restoration: restoration)
+
+ showLaunchFloatingPanel()
+ Task {
+ await askForReview()
+ await askUserToRemovePicturesIfNecessary()
+ }
+ case .onboarding:
+ showOnboarding()
+ case .updateRequired:
+ showUpdateRequired()
+ case .preloading(let currentAccount):
+ showPreloading(currentAccount: currentAccount)
+ }
+ }
+
+ /// Entry point for scene restoration
+ @MainActor func restoreMainUIStackIfPossible(driveFileManager: DriveFileManager, restoration: Bool) {
+ let shouldRestoreApplicationState = appRestorationService.shouldRestoreApplicationState
+ var indexToUse: Int?
+ if shouldRestoreApplicationState,
+ let sceneUserInfo,
+ let index = sceneUserInfo[SceneRestorationKeys.selectedIndex.rawValue] as? Int {
+ indexToUse = index
+ }
+
+ let tabBarViewController = showMainViewController(driveFileManager: driveFileManager, selectedIndex: indexToUse)
+
+ guard shouldRestoreApplicationState else {
+ Log.sceneDelegate("Restoration disabled", level: .error)
+ appRestorationService.saveRestorationVersion()
+ return
+ }
+
+ Task { @MainActor in
+ guard restoration, let tabBarViewController else {
+ return
+ }
+
+ guard let sceneUserInfo,
+ let lastViewControllerString = sceneUserInfo[SceneRestorationKeys.lastViewController.rawValue] as? String,
+ let lastViewController = SceneRestorationScreens(rawValue: lastViewControllerString) else {
+ return
+ }
+
+ let selectedIndex = tabBarViewController.selectedIndex
+ let viewControllers = tabBarViewController.viewControllers
+ guard let rootNavigationController = viewControllers?[safe: selectedIndex] as? UINavigationController else {
+ Log.sceneDelegate("unable to access navigationController", level: .error)
+ return
+ }
+
+ switch lastViewController {
+ case .FileDetailViewController:
+ await restoreFileDetailViewController(
+ driveFileManager: driveFileManager,
+ navigationController: rootNavigationController,
+ sceneUserInfo: sceneUserInfo
+ )
+
+ case .FileListViewController:
+ await restoreFileListViewController(
+ driveFileManager: driveFileManager,
+ navigationController: rootNavigationController,
+ sceneUserInfo: sceneUserInfo
+ )
+
+ case .PreviewViewController:
+ await restorePreviewViewController(
+ driveFileManager: driveFileManager,
+ navigationController: rootNavigationController,
+ sceneUserInfo: sceneUserInfo
+ )
+
+ case .StoreViewController:
+ await restoreStoreViewController(
+ driveFileManager: driveFileManager,
+ navigationController: rootNavigationController,
+ sceneUserInfo: sceneUserInfo
+ )
+ }
+ }
+ }
+
+ private func restoreFileDetailViewController(driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ sceneUserInfo: [AnyHashable: Any]) async {
+ guard let fileId = sceneUserInfo[SceneRestorationValues.fileId.rawValue] else {
+ Log.sceneDelegate("unable to load file id", level: .error)
+ return
+ }
+
+ let database = driveFileManager.database
+ let frozenFile = database.fetchObject(ofType: File.self) { lazyCollection in
+ lazyCollection
+ .filter("id == %@", fileId)
+ .first?
+ .freezeIfNeeded()
+ }
+
+ guard let frozenFile else {
+ Log.sceneDelegate("unable to load file", level: .error)
+ return
+ }
+
+ await presentFileDetails(frozenFile: frozenFile,
+ driveFileManager: driveFileManager,
+ navigationController: navigationController,
+ animated: false)
+ }
+
+ private func restoreFileListViewController(driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ sceneUserInfo: [AnyHashable: Any]) async {
+ guard let driveId = sceneUserInfo[SceneRestorationValues.driveId.rawValue] as? Int,
+ driveFileManager.drive.id == driveId,
+ let fileId = sceneUserInfo[SceneRestorationValues.fileId.rawValue] else {
+ Log.sceneDelegate("metadata issue for FileList :\(sceneUserInfo)", level: .error)
+ return
+ }
+
+ let database = driveFileManager.database
+ let frozenFile = database.fetchObject(ofType: File.self) { lazyCollection in
+ lazyCollection
+ .filter("id == %@", fileId)
+ .first?
+ .freezeIfNeeded()
+ }
+
+ guard let frozenFile else {
+ Log.sceneDelegate("unable to load file", level: .error)
+ return
+ }
+
+ await presentFileList(frozenFolder: frozenFile,
+ driveFileManager: driveFileManager,
+ navigationController: navigationController)
+ }
+
+ private func restorePreviewViewController(driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ sceneUserInfo: [AnyHashable: Any]) async {
+ guard sceneUserInfo[SceneRestorationValues.driveId.rawValue] as? Int != nil,
+ let fileIds = sceneUserInfo[SceneRestorationValues.Carousel.filesIds.rawValue] as? [Int],
+ let currentIndex = sceneUserInfo[SceneRestorationValues.Carousel.currentIndex.rawValue] as? Int,
+ let normalFolderHierarchy = sceneUserInfo[SceneRestorationValues.Carousel.normalFolderHierarchy.rawValue] as? Bool,
+ let fromActivities = sceneUserInfo[SceneRestorationValues.Carousel.fromActivities.rawValue] as? Bool else {
+ Log.sceneDelegate("metadata issue for PreviewController :\(sceneUserInfo)", level: .error)
+ return
+ }
+
+ let database = driveFileManager.database
+ let frozenFetchedFiles = database.fetchResults(ofType: File.self) { lazyCollection in
+ lazyCollection
+ .filter("id IN %@", fileIds)
+ .freezeIfNeeded()
+ }
+
+ let frozenFilesToRestore = Array(frozenFetchedFiles)
+
+ await presentPreviewViewController(
+ frozenFiles: frozenFilesToRestore,
+ index: currentIndex,
+ driveFileManager: driveFileManager,
+ normalFolderHierarchy: normalFolderHierarchy,
+ fromActivities: fromActivities,
+ navigationController: navigationController,
+ animated: false
+ )
+ }
+
+ private func restoreStoreViewController(driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ sceneUserInfo: [AnyHashable: Any]) async {
+ guard let driveId = sceneUserInfo[SceneRestorationValues.driveId.rawValue] as? Int,
+ driveFileManager.drive.id == driveId else {
+ Log.sceneDelegate("unable to load drive id", level: .error)
+ return
+ }
+
+ await presentStoreViewController(
+ driveFileManager: driveFileManager,
+ navigationController: navigationController,
+ animated: false
+ )
+ }
+
+ @MainActor public func updateTheme() {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ window.overrideUserInterfaceStyle = UserDefaults.shared.theme.interfaceStyle
+ }
+
+ // MARK: RouterAppNavigable
+
+ @discardableResult
+ @MainActor public func showMainViewController(driveFileManager: DriveFileManager,
+ selectedIndex: Int?) -> UITabBarController? {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return nil
+ }
+
+ let currentDriveObjectId = (window.rootViewController as? MainTabViewController)?.driveFileManager.drive.objectId
+ guard currentDriveObjectId != driveFileManager.drive.objectId else {
+ return nil
+ }
+
+ let tabBarViewController = MainTabViewController(driveFileManager: driveFileManager,
+ selectedIndex: selectedIndex)
+
+ window.rootViewController = tabBarViewController
+ window.makeKeyAndVisible()
+
+ return tabBarViewController
+ }
+
+ @MainActor public func showPreloading(currentAccount: Account) {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ window.rootViewController = PreloadingViewController(currentAccount: currentAccount)
+ window.makeKeyAndVisible()
+ }
+
+ @MainActor public func showOnboarding() {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ defer {
+ // Clean File Provider domains on first launch in case we had some dangling
+ driveInfosManager.deleteAllFileProviderDomains()
+ }
+
+ let isNotPresentingOnboarding = window.rootViewController?.isKind(of: OnboardingViewController.self) != true
+ guard isNotPresentingOnboarding else {
+ return
+ }
+
+ keychainHelper.deleteAllTokens()
+ window.rootViewController = OnboardingViewController.instantiate()
+ window.makeKeyAndVisible()
+ }
+
+ @MainActor public func showAppLock() {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ window.rootViewController = LockedAppViewController.instantiate()
+ window.makeKeyAndVisible()
+ }
+
+ @MainActor public func showLaunchFloatingPanel() {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ let launchPanelsController = LaunchPanelsController()
+ if let viewController = window.rootViewController {
+ launchPanelsController.pickAndDisplayPanel(viewController: viewController)
+ }
+ }
+
+ @MainActor public func showUpdateRequired() {
+ guard let window else {
+ SentryDebug.captureNoWindow()
+ return
+ }
+
+ window.rootViewController = DriveUpdateRequiredViewController()
+ window.makeKeyAndVisible()
+ }
+
+ @MainActor public func showPhotoSyncSettings() {
+ guard let rootViewController = window?.rootViewController as? MainTabViewController else {
+ return
+ }
+
+ rootViewController.dismiss(animated: false)
+ rootViewController.selectedIndex = MainTabIndex.profile.rawValue
+
+ guard let navController = rootViewController.selectedViewController as? UINavigationController else {
+ return
+ }
+
+ let photoSyncSettingsViewController = PhotoSyncSettingsViewController.instantiate()
+ navController.popToRootViewController(animated: false)
+ navController.pushViewController(photoSyncSettingsViewController, animated: true)
+ }
+
+ // MARK: RouterActionable
+
+ public func askUserToRemovePicturesIfNecessary() async {
+ @InjectService var photoCleaner: PhotoLibraryCleanerServiceable
+ guard photoCleaner.hasPicturesToRemove else {
+ Log.sceneDelegate("No pictures to remove", level: .info)
+ return
+ }
+
+ Task { @MainActor in
+ let alert = AlertTextViewController(title: KDriveResourcesStrings.Localizable.modalDeletePhotosTitle,
+ message: KDriveResourcesStrings.Localizable.modalDeletePhotosDescription,
+ action: KDriveResourcesStrings.Localizable.buttonDelete,
+ destructive: true,
+ loading: false) {
+ Task {
+ @InjectService var photoCleaner: PhotoLibraryCleanerServiceable
+ await photoCleaner.removePicturesScheduledForDeletion()
+ }
+ }
+
+ window?.rootViewController?.present(alert, animated: true)
+ }
+ }
+
+ public func askForReview() async {
+ guard let presentingViewController = await window?.rootViewController,
+ !Bundle.main.isRunningInTestFlight else {
+ return
+ }
+
+ guard reviewManager.shouldRequestReview() else {
+ return
+ }
+
+ let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String
+
+ Task { @MainActor in
+ let alert = AlertTextViewController(
+ title: appName,
+ message: KDriveResourcesStrings.Localizable.reviewAlertTitle,
+ action: KDriveResourcesStrings.Localizable.buttonYes,
+ hasCancelButton: true,
+ cancelString: KDriveResourcesStrings.Localizable.buttonNo,
+ handler: requestAppStoreReview,
+ cancelHandler: openUserReport
+ )
+
+ presentingViewController.present(alert, animated: true)
+ }
+ MatomoUtils.track(eventWithCategory: .appReview, name: "alertPresented")
+ }
+
+ @MainActor private func requestAppStoreReview() {
+ MatomoUtils.track(eventWithCategory: .appReview, name: "like")
+ UserDefaults.shared.appReview = .readyForReview
+ reviewManager.requestReview()
+ }
+
+ @MainActor private func openUserReport() {
+ MatomoUtils.track(eventWithCategory: .appReview, name: "dislike")
+ guard let url = URL(string: KDriveResourcesStrings.Localizable.urlUserReportiOS),
+ let presentingViewController = window?.rootViewController else {
+ return
+ }
+ UserDefaults.shared.appReview = .feedback
+ presentingViewController.present(SFSafariViewController(url: url), animated: true)
+ }
+
+ public func refreshCacheScanLibraryAndUpload(preload: Bool, isSwitching: Bool) async {
+ Log.sceneDelegate("refreshCacheScanLibraryAndUpload preload:\(preload) isSwitching:\(preload)")
+
+ availableOfflineManager.updateAvailableOfflineFiles(status: ReachabilityListener.instance.currentStatus)
+
+ do {
+ try await refreshAccountAndShowMainView()
+ await scanLibraryAndRestartUpload()
+ } catch DriveError.NoDriveError.noDrive {
+ let driveErrorNavigationViewController = await DriveErrorViewController.instantiateInNavigationController(
+ errorType: .noDrive,
+ drive: nil
+ )
+ await setRootViewController(driveErrorNavigationViewController, animated: true)
+ } catch DriveError.NoDriveError.blocked(let drive), DriveError.NoDriveError.maintenance(let drive) {
+ let driveErrorNavigationViewController = await DriveErrorViewController.instantiateInNavigationController(
+ errorType: drive.isInTechnicalMaintenance ? .maintenance : .blocked,
+ drive: drive
+ )
+ await setRootViewController(driveErrorNavigationViewController, animated: true)
+ } catch {
+ await UIConstants.showSnackBarIfNeeded(error: DriveError.unknownError)
+ Log.sceneDelegate("Error while updating user account: \(error)", level: .error)
+ }
+ }
+
+ @MainActor private func refreshAccountAndShowMainView() async throws {
+ let oldDriveId = accountManager.currentDriveFileManager?.drive.objectId
+
+ guard let currentAccount = accountManager.currentAccount else {
+ Log.sceneDelegate("No account to refresh", level: .error)
+ return
+ }
+
+ let account = try await accountManager.updateUser(for: currentAccount, registerToken: true)
+ let rootViewController = window?.rootViewController as? UpdateAccountDelegate
+ rootViewController?.didUpdateCurrentAccountInformations(account)
+
+ if let oldDriveId,
+ let newDrive = driveInfosManager.getDrive(primaryKey: oldDriveId),
+ !newDrive.inMaintenance {
+ // The current drive is still usable, do not switch
+ await scanLibraryAndRestartUpload()
+ return
+ }
+
+ let driveFileManager = try accountManager.getFirstAvailableDriveFileManager(for: account.userId)
+ let drive = driveFileManager.drive
+ accountManager.setCurrentDriveForCurrentAccount(for: drive.id, userId: drive.userId)
+ showMainViewController(driveFileManager: driveFileManager, selectedIndex: nil)
+ }
+
+ private func scanLibraryAndRestartUpload() async {
+ backgroundUploadSessionManager.reconnectBackgroundTasks()
+
+ Log.sceneDelegate("Restart queue")
+ @InjectService var photoUploader: PhotoLibraryUploader
+ photoUploader.scheduleNewPicturesForUpload()
+
+ // Resolving an upload queue will restart it if this is the first time
+ @InjectService var uploadQueue: UploadQueue
+ uploadQueue.rebuildUploadQueueFromObjectsInRealm()
+ }
+
+ // MARK: RouterFileNavigable
+
+ @MainActor public func present(file: File, driveFileManager: DriveFileManager) {
+ present(file: file, driveFileManager: driveFileManager, office: false)
+ }
+
+ @MainActor public func present(file: File, driveFileManager: DriveFileManager, office: Bool) {
+ guard let rootViewController = window?.rootViewController as? MainTabViewController else {
+ return
+ }
+
+ rootViewController.dismiss(animated: false) {
+ rootViewController.selectedIndex = MainTabIndex.files.rawValue
+
+ guard let navController = rootViewController.selectedViewController as? UINavigationController,
+ let viewController = navController.topViewController as? FileListViewController else {
+ return
+ }
+
+ guard !file.isRoot && viewController.viewModel.currentDirectory.id != file.id else {
+ return
+ }
+
+ navController.popToRootViewController(animated: false)
+
+ guard let fileListViewController = navController.topViewController as? FileListViewController else {
+ return
+ }
+
+ if office {
+ OnlyOfficeViewController.open(driveFileManager: driveFileManager,
+ file: file,
+ viewController: fileListViewController)
+ } else {
+ let filePresenter = FilePresenter(viewController: fileListViewController)
+ filePresenter.present(for: file,
+ files: [file],
+ driveFileManager: driveFileManager,
+ normalFolderHierarchy: false)
+ }
+ }
+ }
+
+ @MainActor public func presentFileList(
+ frozenFolder: File,
+ driveFileManager: DriveFileManager,
+ navigationController: UINavigationController
+ ) {
+ assert(frozenFolder.realm == nil || frozenFolder.isFrozen, "expecting this realm object to be thread safe")
+ assert(frozenFolder.isDirectory, "This will only work for folders")
+
+ guard let topViewController = navigationController.topViewController else {
+ Log.sceneDelegate("unable to presentFileList, no topViewController", level: .error)
+ return
+ }
+
+ FilePresenter(viewController: topViewController)
+ .presentDirectory(for: frozenFolder,
+ driveFileManager: driveFileManager,
+ animated: false,
+ completion: nil)
+ }
+
+ @MainActor public func presentPreviewViewController(
+ frozenFiles: [File],
+ index: Int,
+ driveFileManager: DriveFileManager,
+ normalFolderHierarchy: Bool,
+ fromActivities: Bool,
+ navigationController: UINavigationController,
+ animated: Bool
+ ) {
+ let previewViewController = PreviewViewController.instantiate(files: frozenFiles,
+ index: index,
+ driveFileManager: driveFileManager,
+ normalFolderHierarchy: normalFolderHierarchy,
+ fromActivities: fromActivities)
+ navigationController.pushViewController(previewViewController, animated: animated)
+ }
+
+ @MainActor public func presentFileDetails(
+ frozenFile: File,
+ driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ animated: Bool
+ ) {
+ assert(frozenFile.realm == nil || frozenFile.isFrozen, "expecting this realm object to be thread safe")
+
+ let fileDetailViewController = FileDetailViewController.instantiate(
+ driveFileManager: driveFileManager,
+ file: frozenFile
+ )
+
+ navigationController.pushViewController(fileDetailViewController, animated: animated)
+ }
+
+ @MainActor public func presentStoreViewController(
+ driveFileManager: DriveFileManager,
+ navigationController: UINavigationController,
+ animated: Bool
+ ) {
+ let storeViewController = StoreViewController.instantiate(driveFileManager: driveFileManager)
+ navigationController.pushViewController(storeViewController, animated: false)
+ }
+}
diff --git a/kDrive/Resources/Info.plist b/kDrive/Resources/Info.plist
index 1de821357..d34031e6b 100644
--- a/kDrive/Resources/Info.plist
+++ b/kDrive/Resources/Info.plist
@@ -91,6 +91,31 @@
To be able to record movies you must allow the use of the microphone
NSPhotoLibraryAddUsageDescription
Save picture
+ NSUserActivityTypes
+
+ $(PRODUCT_BUNDLE_IDENTIFIER).mainActivity
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UISceneClassName
+ UIWindowScene
+ UISceneConfigurationName
+ Main Scene
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).SceneDelegate
+
+
+
+
NSPhotoLibraryUsageDescription
Auto upload
PHPhotoLibraryPreventAutomaticLimitedAccessAlert
diff --git a/kDrive/SceneDelegate.swift b/kDrive/SceneDelegate.swift
new file mode 100644
index 000000000..ec8dbb699
--- /dev/null
+++ b/kDrive/SceneDelegate.swift
@@ -0,0 +1,354 @@
+/*
+ Infomaniak kDrive - iOS App
+ Copyright (C) 2023 Infomaniak Network SA
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+
+import InfomaniakCore
+import InfomaniakCoreUI
+import InfomaniakDI
+import kDriveCore
+import kDriveResources
+import SafariServices
+import UIKit
+import VersionChecker
+
+final class SceneDelegate: UIResponder, UIWindowSceneDelegate, AccountManagerDelegate {
+ @LazyInjectService var lockHelper: AppLockHelper
+ @LazyInjectService var accountManager: AccountManageable
+ @LazyInjectService var driveInfosManager: DriveInfosManager
+ @LazyInjectService var backgroundTasksService: BackgroundTasksServiceable
+ @LazyInjectService var appNavigable: AppNavigable
+ @LazyInjectService var appRestorationService: AppRestorationServiceable
+
+ var shortcutItemToProcess: UIApplicationShortcutItem?
+
+ var window: UIWindow?
+
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ Log.sceneDelegate("scene session options")
+ guard let windowScene = (scene as? UIWindowScene) else {
+ return
+ }
+
+ if let shortcutItem = connectionOptions.shortcutItem {
+ shortcutItemToProcess = shortcutItem
+ }
+
+ prepareWindowScene(windowScene)
+
+ accountManager.delegate = self
+
+ NotificationCenter.default.addObserver(self, selector: #selector(reloadDrive), name: .reloadDrive, object: nil)
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(handleLocateUploadNotification),
+ name: .locateUploadActionTapped,
+ object: nil
+ )
+
+ let isRestoration: Bool = session.stateRestorationActivity != nil
+ Log.sceneDelegate("user activity isRestoration:\(isRestoration) \(session.stateRestorationActivity)")
+
+ guard let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity else {
+ Log.sceneDelegate("no user activity")
+ return
+ }
+
+ guard userActivity.activityType == SceneActivityIdentifier.mainSceneActivityType else {
+ Log.sceneDelegate("unsupported user activity type:\(userActivity.activityType)")
+ return
+ }
+
+ scene.userActivity = userActivity
+
+ guard let userInfo = userActivity.userInfo else {
+ Log.sceneDelegate("activity has no metadata to process")
+ return
+ }
+
+ Log.sceneDelegate("restore from \(userActivity.activityType)")
+ Log.sceneDelegate("selectedIndex:\(userInfo[SceneRestorationKeys.selectedIndex.rawValue])")
+ }
+
+ private func prepareWindowScene(_ windowScene: UIWindowScene) {
+ let newWindow = UIWindow(windowScene: windowScene)
+
+ window = newWindow
+ newWindow.makeKeyAndVisible()
+
+ setGlobalWindowTint()
+ appNavigable.updateTheme()
+ }
+
+ func configure(window: UIWindow?, session: UISceneSession, with activity: NSUserActivity) -> Bool {
+ Log.sceneDelegate("configure session with")
+ return true
+ }
+
+ func sceneDidDisconnect(_ scene: UIScene) {
+ Log.sceneDelegate("sceneDidDisconnect \(scene)")
+ }
+
+ func sceneWillResignActive(_ scene: UIScene) {
+ Log.sceneDelegate("sceneWillResignActive \(scene)")
+ }
+
+ func sceneWillEnterForeground(_ scene: UIScene) {
+ Log.sceneDelegate("sceneWillEnterForeground \(scene) \(window)")
+ @InjectService var uploadQueue: UploadQueue
+ uploadQueue.pausedNotificationSent = false
+
+ let currentState = RootViewControllerState.getCurrentState()
+ let session = scene.session
+ let isRestoration: Bool = session.stateRestorationActivity != nil
+ Log.sceneDelegate("user activity isRestoration:\(isRestoration) \(session.stateRestorationActivity)")
+ appNavigable.prepareRootViewController(currentState: currentState, restoration: isRestoration)
+
+ switch currentState {
+ case .mainViewController, .appLock:
+ UserDefaults.shared.numberOfConnections += 1
+ UserDefaults.shared.openingUntilReview -= 1
+ Task {
+ await appNavigable.refreshCacheScanLibraryAndUpload(preload: false, isSwitching: false)
+ }
+ uploadEditedFiles()
+ case .onboarding, .updateRequired, .preloading: break
+ }
+
+ UNUserNotificationCenter.current().removeAllDeliveredNotifications()
+
+ Task {
+ if try await VersionChecker.standard.checkAppVersionStatus() == .updateIsRequired {
+ appNavigable.prepareRootViewController(currentState: .updateRequired, restoration: false)
+ }
+ }
+ }
+
+ func sceneDidBecomeActive(_ scene: UIScene) {
+ Log.sceneDelegate("sceneDidBecomeActive \(scene)")
+ guard let shortcutItem = shortcutItemToProcess else {
+ return
+ }
+
+ guard let rootViewController = window?.rootViewController as? MainTabViewController else {
+ return
+ }
+
+ rootViewController.dismiss(animated: false)
+
+ guard let navController = rootViewController.selectedViewController as? UINavigationController,
+ let viewController = navController.topViewController,
+ let driveFileManager = accountManager.currentDriveFileManager else {
+ return
+ }
+
+ switch shortcutItem.type {
+ case Constants.applicationShortcutScan:
+ let openMediaHelper = OpenMediaHelper(driveFileManager: driveFileManager)
+ openMediaHelper.openScan(rootViewController, false)
+ MatomoUtils.track(eventWithCategory: .shortcuts, name: "scan")
+ case Constants.applicationShortcutSearch:
+ let viewModel = SearchFilesViewModel(driveFileManager: driveFileManager)
+ viewController.present(
+ SearchViewController.instantiateInNavigationController(viewModel: viewModel),
+ animated: true
+ )
+ MatomoUtils.track(eventWithCategory: .shortcuts, name: "search")
+ case Constants.applicationShortcutUpload:
+ let openMediaHelper = OpenMediaHelper(driveFileManager: driveFileManager)
+ openMediaHelper.openMedia(rootViewController, .library)
+ MatomoUtils.track(eventWithCategory: .shortcuts, name: "upload")
+ case Constants.applicationShortcutSupport:
+ UIApplication.shared.open(URLConstants.support.url)
+ MatomoUtils.track(eventWithCategory: .shortcuts, name: "support")
+ default:
+ break
+ }
+
+ shortcutItemToProcess = nil
+ }
+
+ func sceneDidEnterBackground(_ scene: UIScene) {
+ Log.sceneDelegate("sceneDidEnterBackground \(scene)")
+
+ backgroundTasksService.scheduleBackgroundRefresh()
+
+ if UserDefaults.shared.isAppLockEnabled,
+ !(window?.rootViewController?.isKind(of: LockedAppViewController.self) ?? false) {
+ lockHelper.setTime()
+ }
+ }
+
+ // MARK: - Window Scene
+
+ func windowScene(_ windowScene: UIWindowScene,
+ didUpdate previousCoordinateSpace: UICoordinateSpace,
+ interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation,
+ traitCollection previousTraitCollection: UITraitCollection) {
+ Log.sceneDelegate("windowScene didUpdate")
+ }
+
+ func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem) async -> Bool {
+ Log.sceneDelegate("windowScene performActionFor :\(shortcutItem)")
+ shortcutItemToProcess = shortcutItem
+ return true
+ }
+
+ // MARK: - Handoff support
+
+ func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
+ Log.sceneDelegate("scene willContinueUserActivityWithType")
+ }
+
+ func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
+ Log.sceneDelegate("scene continue userActivity")
+ }
+
+ func scene(_ scene: UIScene, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
+ Log.sceneDelegate("scene didFailToContinueUserActivityWithType")
+ }
+
+ // MARK: - Account manager delegate
+
+ func currentAccountNeedsAuthentication() {
+ Task { @MainActor in
+ let switchUser = SwitchUserViewController.instantiateInNavigationController()
+ appNavigable.setRootViewController(switchUser, animated: true)
+ }
+ }
+
+ // MARK: - Reload drive notification
+
+ @objc func reloadDrive(_ notification: Notification) {
+ Task {
+ await self.appNavigable.refreshCacheScanLibraryAndUpload(preload: false, isSwitching: false)
+ }
+ }
+
+ @objc func handleLocateUploadNotification(_ notification: Notification) {
+ if let parentId = notification.userInfo?["parentId"] as? Int,
+ let driveFileManager = accountManager.currentDriveFileManager,
+ let folder = driveFileManager.getCachedFile(id: parentId) {
+ appNavigable.present(file: folder, driveFileManager: driveFileManager)
+ }
+ }
+}
+
+// TODO: Refactor with router like pattern and split code away from this class
+extension SceneDelegate {
+ func uploadEditedFiles() {
+ Log.sceneDelegate("uploadEditedFiles")
+ guard let folderURL = DriveFileManager.constants.openInPlaceDirectoryURL,
+ FileManager.default.fileExists(atPath: folderURL.path) else {
+ return
+ }
+
+ let group = DispatchGroup()
+ var shouldCleanFolder = false
+ let driveFolders = (try? FileManager.default.contentsOfDirectory(atPath: folderURL.path)) ?? []
+ // Hierarchy inside folderURL should be /driveId/fileId/fileName.extension
+ for driveFolder in driveFolders {
+ let driveFolderURL = folderURL.appendingPathComponent(driveFolder)
+ guard let driveId = Int(driveFolder),
+ let drive = driveInfosManager.getDrive(id: driveId, userId: accountManager.currentUserId),
+ let fileFolders = try? FileManager.default.contentsOfDirectory(atPath: driveFolderURL.path) else {
+ Log.sceneDelegate("[OPEN-IN-PLACE UPLOAD] Could not infer drive from \(driveFolderURL)")
+ continue
+ }
+
+ for fileFolder in fileFolders {
+ let fileFolderURL = driveFolderURL.appendingPathComponent(fileFolder)
+ guard let fileId = Int(fileFolder),
+ let driveFileManager = accountManager.getDriveFileManager(for: drive.id, userId: drive.userId),
+ let file = driveFileManager.getCachedFile(id: fileId) else {
+ Log.sceneDelegate("[OPEN-IN-PLACE UPLOAD] Could not infer file from \(fileFolderURL)")
+ continue
+ }
+
+ let fileURL = fileFolderURL.appendingPathComponent(file.name)
+ guard FileManager.default.fileExists(atPath: fileURL.path) else {
+ continue
+ }
+
+ let attributes = try? FileManager.default.attributesOfItem(atPath: fileURL.path)
+ let modificationDate = attributes?[.modificationDate] as? Date ?? Date(timeIntervalSince1970: 0)
+
+ guard modificationDate > file.lastModifiedAt else {
+ continue
+ }
+
+ let uploadFile = UploadFile(parentDirectoryId: file.parentId,
+ userId: accountManager.currentUserId,
+ driveId: file.driveId,
+ url: fileURL,
+ name: file.name,
+ conflictOption: .version,
+ shouldRemoveAfterUpload: false)
+ group.enter()
+ shouldCleanFolder = true
+ @InjectService var uploadQueue: UploadQueue
+ var observationToken: ObservationToken?
+ observationToken = uploadQueue
+ .observeFileUploaded(self, fileId: uploadFile.id) { [fileId = file.id] uploadFile, _ in
+ observationToken?.cancel()
+ if let error = uploadFile.error {
+ shouldCleanFolder = false
+ Log.sceneDelegate("[OPEN-IN-PLACE UPLOAD] Error while uploading: \(error)", level: .error)
+ } else {
+ // Update file to get the new modification date
+ Task {
+ let file = try await driveFileManager.file(id: fileId, forceRefresh: true)
+ try? FileManager.default.setAttributes([.modificationDate: file.lastModifiedAt],
+ ofItemAtPath: file.localUrl.path)
+ driveFileManager.notifyObserversWith(file: file)
+ }
+ }
+ group.leave()
+ }
+ uploadQueue.saveToRealm(uploadFile, itemIdentifier: nil)
+ }
+ }
+
+ group.notify(queue: DispatchQueue.global(qos: .utility)) {
+ if shouldCleanFolder {
+ Log.sceneDelegate("[OPEN-IN-PLACE UPLOAD] Cleaning folder")
+ try? FileManager.default.removeItem(at: folderURL)
+ }
+ }
+ }
+
+ private func setGlobalWindowTint() {
+ window?.tintColor = KDriveResourcesAsset.infomaniakColor.color
+ UITabBar.appearance().unselectedItemTintColor = KDriveResourcesAsset.iconColor.color
+
+ // Migration from old UserDefaults
+ if UserDefaults.shared.legacyIsFirstLaunch {
+ UserDefaults.shared.legacyIsFirstLaunch = UserDefaults.standard.legacyIsFirstLaunch
+ }
+ }
+}
+
+extension SceneDelegate {
+ func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
+ Log.sceneDelegate("stateRestorationActivity for:\(scene)")
+ guard appRestorationService.shouldRestoreApplicationState else {
+ return nil
+ }
+
+ return scene.userActivity
+ }
+}
diff --git a/kDrive/UI/Controller/Base.lproj/Main.storyboard b/kDrive/UI/Controller/Base.lproj/Main.storyboard
index 02ee1e3fe..ecfcd3e51 100644
--- a/kDrive/UI/Controller/Base.lproj/Main.storyboard
+++ b/kDrive/UI/Controller/Base.lproj/Main.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -30,7 +30,7 @@
-
+
@@ -505,7 +505,7 @@
-
+
@@ -589,13 +589,13 @@
-
+
-
+
@@ -604,7 +604,7 @@
-
+
diff --git a/kDrive/UI/Controller/Files/Categories/EditCategoryViewController.swift b/kDrive/UI/Controller/Files/Categories/EditCategoryViewController.swift
index 970a7d753..ed6584845 100644
--- a/kDrive/UI/Controller/Files/Categories/EditCategoryViewController.swift
+++ b/kDrive/UI/Controller/Files/Categories/EditCategoryViewController.swift
@@ -93,46 +93,6 @@ final class EditCategoryViewController: UITableViewController {
return viewController
}
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- if let categoryId = category?.id {
- coder.encode(categoryId, forKey: "CategoryId")
- }
- if let filesIdToAdd = filesToAdd?.map(\.id) {
- coder.encode(filesIdToAdd, forKey: "FilesId")
- }
- coder.encode(name, forKey: "Name")
- coder.encode(color, forKey: "Color")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let categoryId = coder.decodeInteger(forKey: "CategoryId")
- let filesId = coder.decodeObject(of: [NSNumber.self], forKey: "FilesId") as? [NSNumber]
- if let name = coder.decodeObject(of: NSString.self, forKey: "Name") {
- self.name = name as String
- }
- if let color = coder.decodeObject(of: NSString.self, forKey: "Color") {
- self.color = color as String
- }
-
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId, userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
- category = driveFileManager.drive.categories.first { $0.id == categoryId }
- filesToAdd = filesId?.compactMap { driveFileManager.getCachedFile(id: Int(truncating: $0)) }
- // Reload view
- updateTitle()
- setRows()
- }
-
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
diff --git a/kDrive/UI/Controller/Files/Categories/ManageCategoriesViewController.swift b/kDrive/UI/Controller/Files/Categories/ManageCategoriesViewController.swift
index 1f08da97b..4dbe36f03 100644
--- a/kDrive/UI/Controller/Files/Categories/ManageCategoriesViewController.swift
+++ b/kDrive/UI/Controller/Files/Categories/ManageCategoriesViewController.swift
@@ -230,41 +230,6 @@ final class ManageCategoriesViewController: UITableViewController {
return UINavigationController(rootViewController: viewController)
}
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- if let files {
- coder.encode(files.map(\.id), forKey: "FilesId")
- }
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let filesId = coder.decodeObject(forKey: "FilesId") as! [Int]
-
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId,
- userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
-
- let matchedFiles = driveFileManager.database.fetchResults(ofType: File.self) { lazyCollection in
- lazyCollection.filter("id IN %@", filesId)
- }
-
- files = Array(matchedFiles)
-
- // Reload view
- updateTitle()
- updateNavigationItem()
- setUpObserver()
- }
-
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
diff --git a/kDrive/UI/Controller/Files/DropBox/ManageDropBoxViewController.swift b/kDrive/UI/Controller/Files/DropBox/ManageDropBoxViewController.swift
index 437bf98e1..9a191ebae 100644
--- a/kDrive/UI/Controller/Files/DropBox/ManageDropBoxViewController.swift
+++ b/kDrive/UI/Controller/Files/DropBox/ManageDropBoxViewController.swift
@@ -287,31 +287,6 @@ class ManageDropBoxViewController: UIViewController, UITableViewDelegate, UITabl
}
}
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- coder.encode(directory.id, forKey: "FolderId")
- coder.encode(convertingFolder, forKey: "ConvertingFolder")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let folderId = coder.decodeInteger(forKey: "FolderId")
- let convertingFolder = coder.decodeBool(forKey: "ConvertingFolder")
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId,
- userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
- self.convertingFolder = convertingFolder
- directory = driveFileManager.getCachedFile(id: folderId)
- }
}
// MARK: - NewFolderSettingsDelegate
diff --git a/kDrive/UI/Controller/Files/File List/FileListViewController.swift b/kDrive/UI/Controller/Files/File List/FileListViewController.swift
index 104cadd55..bb04f202d 100644
--- a/kDrive/UI/Controller/Files/File List/FileListViewController.swift
+++ b/kDrive/UI/Controller/Files/File List/FileListViewController.swift
@@ -132,7 +132,7 @@ class ConcreteFileListViewModel: FileListViewModel {
}
class FileListViewController: UIViewController, UICollectionViewDataSource, SwipeActionCollectionViewDelegate,
- SwipeActionCollectionViewDataSource, FilesHeaderViewDelegate {
+ SwipeActionCollectionViewDataSource, FilesHeaderViewDelegate, SceneStateRestorable {
class var storyboard: UIStoryboard { Storyboard.files }
class var storyboardIdentifier: String { "FileListViewController" }
@@ -232,6 +232,8 @@ class FileListViewController: UIViewController, UICollectionViewDataSource, Swip
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
MatomoUtils.track(view: viewModel.configuration.matomoViewPath)
+
+ saveSceneState()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
@@ -796,58 +798,12 @@ class FileListViewController: UIViewController, UICollectionViewDataSource, Swip
// MARK: - State restoration
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(viewModel.driveFileManager.drive.id, forKey: "DriveID")
- coder.encode(viewModel.currentDirectory.id, forKey: "DirectoryID")
- if let viewModel {
- coder.encode(String(describing: type(of: viewModel)), forKey: "ViewModel")
- }
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveID")
- let directoryId = coder.decodeInteger(forKey: "DirectoryID")
- let viewModelName = coder.decodeObject(of: NSString.self, forKey: "ViewModel") as String?
-
- // Drive File Manager should be consistent
- let maybeDriveFileManager: DriveFileManager?
- #if ISEXTENSION
- maybeDriveFileManager = accountManager.getDriveFileManager(for: driveId, userId: accountManager.currentUserId)
- #else
- if viewModelName == String(describing: SharedWithMeViewModel.self) {
- maybeDriveFileManager = accountManager.getDriveFileManager(for: driveId, userId: accountManager.currentUserId)
- } else {
- maybeDriveFileManager = (tabBarController as? MainTabViewController)?.driveFileManager
- }
- #endif
- guard let driveFileManager = maybeDriveFileManager else {
- // Handle error?
- return
- }
- let maybeCurrentDirectory = driveFileManager.getCachedFile(id: directoryId)
-
- if !(maybeCurrentDirectory == nil && directoryId > DriveFileManager.constants.rootID),
- let viewModelName,
- let viewModel = getViewModel(
- viewModelName: viewModelName,
- driveFileManager: driveFileManager,
- currentDirectory: maybeCurrentDirectory
- ) {
- self.viewModel = viewModel
- setupViewModel()
- tryLoadingFilesOrDisplayError()
- } else {
- // We need some view model to restore the view controller and pop it...
- viewModel = ConcreteFileListViewModel(
- driveFileManager: driveFileManager,
- currentDirectory: driveFileManager.getCachedRootFile()
- )
- navigationController?.popViewController(animated: true)
- }
+ var currentSceneMetadata: [AnyHashable: Any] {
+ [
+ SceneRestorationKeys.lastViewController.rawValue: SceneRestorationScreens.FileListViewController.rawValue,
+ SceneRestorationValues.driveId.rawValue: driveFileManager.drive.id,
+ SceneRestorationValues.fileId.rawValue: viewModel.currentDirectory.id
+ ]
}
// MARK: - Files header view delegate
diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift
index 271984fa4..be9607cb4 100644
--- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift
+++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift
@@ -107,7 +107,7 @@ class FileListViewModel: SelectDelegate {
/// Internal realm collection of Files observed
///
/// They should be frozen by convention.
- #if DEBUG || TEST
+ #if DEBUG
var _frozenFiles = AnyRealmCollection(List()) {
willSet {
for item in newValue {
diff --git a/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController+Actions.swift b/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController+Actions.swift
index 05c92a17e..61d2d5340 100644
--- a/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController+Actions.swift
+++ b/kDrive/UI/Controller/Files/FileActionsFloatingPanelViewController+Actions.swift
@@ -152,7 +152,6 @@ extension FileActionsFloatingPanelViewController {
private func informationsAction() {
let fileDetailViewController = FileDetailViewController.instantiate(driveFileManager: driveFileManager, file: file)
- fileDetailViewController.file = file
presentingParent?.navigationController?.pushViewController(fileDetailViewController, animated: true)
dismiss(animated: true)
}
diff --git a/kDrive/UI/Controller/Files/FileDetailViewController.swift b/kDrive/UI/Controller/Files/FileDetailViewController.swift
index 1ad1148fd..c265e3bd1 100644
--- a/kDrive/UI/Controller/Files/FileDetailViewController.swift
+++ b/kDrive/UI/Controller/Files/FileDetailViewController.swift
@@ -22,7 +22,7 @@ import kDriveCore
import kDriveResources
import UIKit
-class FileDetailViewController: UIViewController {
+class FileDetailViewController: UIViewController, SceneStateRestorable {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var commentButton: UIButton!
@@ -157,6 +157,8 @@ class FileDetailViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
MatomoUtils.track(view: ["FileDetail"])
+
+ saveSceneState()
}
override func viewWillDisappear(_ animated: Bool) {
@@ -206,20 +208,20 @@ class FileDetailViewController: UIViewController {
tableView.separatorColor = .clear
- // Set initial rows
fileInformationRows = FileInformationRow.getRows(for: file,
fileAccess: fileAccess,
contentCount: contentCount,
categoryRights: driveFileManager.drive.categoryRights)
- // Load file informations
loadFileInformation()
- // Observe file changes
driveFileManager.observeFileUpdated(self, fileId: file.id) { newFile in
Task { @MainActor [weak self] in
- self?.file = newFile
- self?.reloadTableView()
+ guard let self else {
+ return
+ }
+ self.file = newFile
+ self.reloadTableView()
}
}
}
@@ -480,45 +482,12 @@ class FileDetailViewController: UIViewController {
// MARK: - State restoration
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- coder.encode(file.id, forKey: "FileId")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let fileId = coder.decodeInteger(forKey: "FileId")
-
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId, userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
- file = driveFileManager.getCachedFile(id: fileId)
- guard file != nil else {
- // If file doesn't exist anymore, pop view controller
- navigationController?.popViewController(animated: true)
- return
- }
- Task { [proxyFile = file.proxify(), isDirectory = file.isDirectory] in
- async let currentFileAccess = driveFileManager.apiFetcher.access(for: proxyFile)
- async let folderContentCount = isDirectory ? driveFileManager.apiFetcher.count(of: proxyFile) : nil
-
- fileInformationRows = try await FileInformationRow.getRows(for: file,
- fileAccess: currentFileAccess,
- contentCount: folderContentCount,
- categoryRights: driveFileManager.drive
- .categoryRights)
- fileAccess = try await currentFileAccess
- contentCount = try await folderContentCount
-
- if tableView.window != nil && currentTab == .informations {
- reloadTableView()
- }
- }
+ var currentSceneMetadata: [AnyHashable: Any] {
+ [
+ SceneRestorationKeys.lastViewController.rawValue: SceneRestorationScreens.FileDetailViewController.rawValue,
+ SceneRestorationValues.driveId.rawValue: driveFileManager.drive.id,
+ SceneRestorationValues.fileId.rawValue: file.id
+ ]
}
}
diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift
index 5b8b64156..f0b8914aa 100644
--- a/kDrive/UI/Controller/Files/FilePresenter.swift
+++ b/kDrive/UI/Controller/Files/FilePresenter.swift
@@ -41,21 +41,21 @@ final class FilePresenter {
return
}
- // Pop current navigation stack
viewController.navigationController?.popToRootViewController(animated: false)
- // Dismiss all view controllers presented
+
rootViewController.dismiss(animated: false) {
- // Select Files tab
- rootViewController.selectedIndex = 1
+ rootViewController.selectedIndex = MainTabIndex.files.rawValue
guard let navigationController = rootViewController.selectedViewController as? UINavigationController else {
return
}
- // Pop to root
navigationController.popToRootViewController(animated: false)
- // Present file
- guard let fileListViewController = navigationController.topViewController as? FileListViewController else { return }
+
+ guard let fileListViewController = navigationController.topViewController as? FileListViewController else {
+ return
+ }
+
let filePresenter = FilePresenter(viewController: fileListViewController)
filePresenter.presentParent(of: file, driveFileManager: driveFileManager, animated: false)
}
@@ -132,13 +132,16 @@ final class FilePresenter {
}
}
- private func presentDirectory(
+ public func presentDirectory(
for file: File,
driveFileManager: DriveFileManager,
animated: Bool,
completion: ((Bool) -> Void)?
) {
- // Show files list
+ defer {
+ completion?(true)
+ }
+
let viewModel: FileListViewModel
if driveFileManager.drive.sharedWithMe {
viewModel = SharedWithMeViewModel(driveFileManager: driveFileManager, currentDirectory: file)
@@ -147,37 +150,40 @@ final class FilePresenter {
} else {
viewModel = ConcreteFileListViewModel(driveFileManager: driveFileManager, currentDirectory: file)
}
+
let nextVC = FileListViewController.instantiate(viewModel: viewModel)
- if file.isDisabled {
- if driveFileManager.drive.isUserAdmin {
- let accessFileDriveFloatingPanelController = AccessFileFloatingPanelViewController.instantiatePanel()
- let floatingPanelViewController = accessFileDriveFloatingPanelController
- .contentViewController as? AccessFileFloatingPanelViewController
- floatingPanelViewController?.actionHandler = { [weak self] _ in
- guard let self else { return }
- floatingPanelViewController?.rightButton.setLoading(true)
- Task { [proxyFile = file.proxify()] in
- do {
- let response = try await driveFileManager.apiFetcher.forceAccess(to: proxyFile)
- if response {
- accessFileDriveFloatingPanelController.dismiss(animated: true)
- self.navigationController?.pushViewController(nextVC, animated: true)
- } else {
- UIConstants.showSnackBar(message: KDriveResourcesStrings.Localizable.errorRightModification)
- }
- } catch {
- UIConstants.showSnackBarIfNeeded(error: error)
- }
+ guard file.isDisabled else {
+ navigationController?.pushViewController(nextVC, animated: animated)
+ return
+ }
+
+ guard driveFileManager.drive.isUserAdmin else {
+ viewController?.present(NoAccessFloatingPanelViewController.instantiatePanel(), animated: true)
+ return
+ }
+
+ let accessFileDriveFloatingPanelController = AccessFileFloatingPanelViewController.instantiatePanel()
+ let floatingPanelViewController = accessFileDriveFloatingPanelController
+ .contentViewController as? AccessFileFloatingPanelViewController
+ floatingPanelViewController?.actionHandler = { [weak self] _ in
+ guard let self else { return }
+ floatingPanelViewController?.rightButton.setLoading(true)
+ Task { [proxyFile = file.proxify()] in
+ do {
+ let response = try await driveFileManager.apiFetcher.forceAccess(to: proxyFile)
+ if response {
+ accessFileDriveFloatingPanelController.dismiss(animated: true)
+ self.navigationController?.pushViewController(nextVC, animated: true)
+ } else {
+ UIConstants.showSnackBar(message: KDriveResourcesStrings.Localizable.errorRightModification)
}
+ } catch {
+ UIConstants.showSnackBarIfNeeded(error: error)
}
- viewController?.present(accessFileDriveFloatingPanelController, animated: true)
- } else {
- viewController?.present(NoAccessFloatingPanelViewController.instantiatePanel(), animated: true)
}
- } else {
- navigationController?.pushViewController(nextVC, animated: animated)
}
- completion?(true)
+
+ viewController?.present(accessFileDriveFloatingPanelController, animated: true)
}
private func downloadAndPresentBookmark(for file: File, completion: ((Bool) -> Void)?) {
diff --git a/kDrive/UI/Controller/Files/Files.storyboard b/kDrive/UI/Controller/Files/Files.storyboard
index c77db9c6f..0cd81c1e4 100644
--- a/kDrive/UI/Controller/Files/Files.storyboard
+++ b/kDrive/UI/Controller/Files/Files.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -13,7 +13,7 @@
-
+
@@ -48,7 +48,7 @@
-
+
@@ -71,7 +71,7 @@
-
+
@@ -127,7 +127,7 @@
-
+
@@ -184,7 +184,7 @@
-
+
@@ -217,7 +217,7 @@
-
+
@@ -247,7 +247,7 @@
-
+
@@ -298,7 +298,7 @@
-
+
@@ -349,7 +349,7 @@
-
+
@@ -371,7 +371,7 @@
-
+
@@ -423,7 +423,7 @@
-
+
@@ -481,7 +481,7 @@
-
+
@@ -539,7 +539,7 @@
-
+
@@ -578,7 +578,7 @@
-
+
@@ -646,7 +646,7 @@
-
+
diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift
index 494d74596..4cefe6aaf 100644
--- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift
+++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController+Actions.swift
@@ -257,7 +257,7 @@ extension MultipleSelectionFloatingPanelViewController {
// Present from root view controller if the panel is no longer presented
let viewController = self.view.window != nil
? self
- : (UIApplication.shared.delegate as! AppDelegate).topMostViewController
+ : self.appNavigable.topMostViewController
guard viewController as? UIDocumentPickerViewController == nil else { return }
let documentExportViewController = UIDocumentPickerViewController(
url: downloadedArchiveUrl,
diff --git a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift
index 7f2678b87..639678c3a 100644
--- a/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift
+++ b/kDrive/UI/Controller/Files/MultipleSelectionFloatingPanelViewController.swift
@@ -25,6 +25,7 @@ import UIKit
final class MultipleSelectionFloatingPanelViewController: UICollectionViewController {
@LazyInjectService var accountManager: AccountManageable
+ @LazyInjectService var appNavigable: AppNavigable
var driveFileManager: DriveFileManager!
var files = [File]()
diff --git a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift
index 8fcffb114..89db71e23 100644
--- a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift
+++ b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift
@@ -33,7 +33,7 @@ protocol PreviewContentCellDelegate: AnyObject {
func openWith(from: UIView)
}
-class PreviewViewController: UIViewController, PreviewContentCellDelegate {
+class PreviewViewController: UIViewController, PreviewContentCellDelegate, SceneStateRestorable {
@LazyInjectService var accountManager: AccountManageable
class PreviewError {
@@ -80,6 +80,7 @@ class PreviewViewController: UIViewController, PreviewContentCellDelegate {
private var currentIndex = IndexPath(row: 0, section: 0) {
didSet {
setTitle()
+ saveSceneState()
}
}
@@ -260,6 +261,8 @@ class PreviewViewController: UIViewController, PreviewContentCellDelegate {
heightToHide = backButton.frame.minY
MatomoUtils.track(view: [MatomoUtils.Views.preview.displayName, "File"])
+
+ saveSceneState()
}
override func viewWillDisappear(_ animated: Bool) {
@@ -580,58 +583,15 @@ class PreviewViewController: UIViewController, PreviewContentCellDelegate {
// MARK: - State restoration
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- coder.encode(previewFiles.map(\.id), forKey: "Files")
- coder.encode(currentIndex.row, forKey: "CurrentIndex")
- coder.encode(initialLoading, forKey: "InitialLoading")
- coder.encode(normalFolderHierarchy, forKey: "NormalFolderHierarchy")
- coder.encode(fromActivities, forKey: "FromActivities")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- initialLoading = coder.decodeBool(forKey: "InitialLoading")
- normalFolderHierarchy = coder.decodeBool(forKey: "NormalFolderHierarchy")
- fileInformationsViewController.normalFolderHierarchy = normalFolderHierarchy
- fromActivities = coder.decodeBool(forKey: "FromActivities")
- if fromActivities {
- floatingPanelViewController.surfaceView.grabberHandle.isHidden = true
- }
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId,
- userId: accountManager.currentUserId) else {
- navigationController?.popViewController(animated: true)
- return
- }
- self.driveFileManager = driveFileManager
- let previewFileIds = coder.decodeObject(forKey: "Files") as? [Int] ?? []
-
- let matchedFiles = driveFileManager.database.fetchResults(ofType: File.self) { lazyCollection in
- lazyCollection.filter("id IN %@", previewFileIds)
- }
-
- previewFiles = Array(matchedFiles)
-
- let decodedIndex = coder.decodeInteger(forKey: "CurrentIndex")
- if decodedIndex >= previewFiles.count {
- navigationController?.popViewController(animated: true)
- return
- }
- currentIndex = IndexPath(row: decodedIndex, section: 0)
-
- // Update UI
- Task { @MainActor [self] in
- collectionView.reloadData()
- updateFileForCurrentIndex()
- collectionView.scrollToItem(at: currentIndex, at: .centeredVertically, animated: false)
- updateNavigationBar()
- downloadFileIfNeeded(at: currentIndex)
- }
- observeFileUpdated()
+ var currentSceneMetadata: [AnyHashable: Any] {
+ [
+ SceneRestorationKeys.lastViewController.rawValue: SceneRestorationScreens.PreviewViewController.rawValue,
+ SceneRestorationValues.driveId.rawValue: driveFileManager.drive.id,
+ SceneRestorationValues.Carousel.filesIds.rawValue: previewFiles.map(\.id),
+ SceneRestorationValues.Carousel.currentIndex.rawValue: currentIndex.row,
+ SceneRestorationValues.Carousel.normalFolderHierarchy.rawValue: normalFolderHierarchy,
+ SceneRestorationValues.Carousel.fromActivities.rawValue: fromActivities
+ ]
}
}
diff --git a/kDrive/UI/Controller/Files/RecentActivityFilesViewController.swift b/kDrive/UI/Controller/Files/RecentActivityFilesViewController.swift
index 399fae997..94cb44443 100644
--- a/kDrive/UI/Controller/Files/RecentActivityFilesViewController.swift
+++ b/kDrive/UI/Controller/Files/RecentActivityFilesViewController.swift
@@ -44,25 +44,6 @@ class RecentActivityFilesViewModel: InMemoryFileListViewModel {
currentDirectory: DriveFileManager.homeRootFile
)
}
-
- func encodeRestorableState(with coder: NSCoder) {
- coder.encode(activity?.id ?? 0, forKey: "ActivityId")
- coder.encode(getAllFiles().map(\.id), forKey: "Files")
- }
-
- func decodeRestorableState(with coder: NSCoder) {
- let activityId = coder.decodeInteger(forKey: "ActivityId")
- let fileIds = coder.decodeObject(forKey: "Files") as? [Int] ?? []
-
- // TODO: fixme with proper fetch transaction + pred
- try? driveFileManager.database.writeTransaction { realm in
- activity = realm.object(ofType: FileActivity.self, forPrimaryKey: activityId)?.freeze()
- let cachedFiles = fileIds.compactMap { driveFileManager.getCachedFile(id: $0, using: realm) }.map { $0.detached() }
- addPage(files: cachedFiles, fullyDownloaded: true, cursor: nil)
- }
-
- forceRefresh()
- }
}
class RecentActivityFilesViewController: FileListViewController {
@@ -139,18 +120,4 @@ class RecentActivityFilesViewController: FileListViewController {
}
return super.collectionView(collectionView, actionsFor: cell, at: indexPath)
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- activityViewModel?.encodeRestorableState(with: coder)
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- activityViewModel?.decodeRestorableState(with: coder)
- }
}
diff --git a/kDrive/UI/Controller/Files/Rights and Share/InviteUserViewController.swift b/kDrive/UI/Controller/Files/Rights and Share/InviteUserViewController.swift
index 3f20f6d83..708dc37f4 100644
--- a/kDrive/UI/Controller/Files/Rights and Share/InviteUserViewController.swift
+++ b/kDrive/UI/Controller/Files/Rights and Share/InviteUserViewController.swift
@@ -190,45 +190,6 @@ class InviteUserViewController: UIViewController {
class func instantiate() -> InviteUserViewController {
return Storyboard.files.instantiateViewController(withIdentifier: "InviteUserViewController") as! InviteUserViewController
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- coder.encode(file.id, forKey: "FileId")
- coder.encode(emails, forKey: "Emails")
- coder.encode(userIds, forKey: "UserIds")
- coder.encode(teamIds, forKey: "TeamIds")
- coder.encode(newPermission.rawValue, forKey: "NewPermission")
- coder.encode(message, forKey: "Message")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let fileId = coder.decodeInteger(forKey: "FileId")
- emails = coder.decodeObject(forKey: "Emails") as? [String] ?? []
- let restoredUserIds = coder.decodeObject(forKey: "UserIds") as? [Int] ?? []
- let restoredTeamIds = coder.decodeObject(forKey: "TeamIds") as? [Int] ?? []
- newPermission = UserPermission(rawValue: coder.decodeObject(forKey: "NewPermission") as? String ?? "") ?? .read
- message = coder.decodeObject(forKey: "Message") as? String ?? ""
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId,
- userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
- file = driveFileManager.getCachedFile(id: fileId)
-
- shareables = restoredUserIds.compactMap { driveInfosManager.getUser(primaryKey: $0) }
- + restoredTeamIds.compactMap { driveInfosManager.getTeam(primaryKey: $0) }
-
- // Update UI
- setTitle()
- reloadInvited()
- }
}
// MARK: - UITableViewDelegate, UITableViewDataSource
diff --git a/kDrive/UI/Controller/Files/Rights and Share/ShareAndRightsViewController.swift b/kDrive/UI/Controller/Files/Rights and Share/ShareAndRightsViewController.swift
index e2ceee542..32dc54268 100644
--- a/kDrive/UI/Controller/Files/Rights and Share/ShareAndRightsViewController.swift
+++ b/kDrive/UI/Controller/Files/Rights and Share/ShareAndRightsViewController.swift
@@ -143,30 +143,6 @@ class ShareAndRightsViewController: UIViewController {
viewController.file = file
return viewController
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- coder.encode(file.id, forKey: "FileId")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let fileId = coder.decodeInteger(forKey: "FileId")
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId,
- userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
- file = driveFileManager.getCachedFile(id: fileId)
- setTitle()
- updateShareList()
- }
}
// MARK: - Table view delegate & data source
diff --git a/kDrive/UI/Controller/Files/Rights and Share/ShareLinkSettingsViewController.swift b/kDrive/UI/Controller/Files/Rights and Share/ShareLinkSettingsViewController.swift
index 6ead2e180..ed18c8677 100644
--- a/kDrive/UI/Controller/Files/Rights and Share/ShareLinkSettingsViewController.swift
+++ b/kDrive/UI/Controller/Files/Rights and Share/ShareLinkSettingsViewController.swift
@@ -220,32 +220,6 @@ class ShareLinkSettingsViewController: UIViewController {
return Storyboard.files
.instantiateViewController(withIdentifier: "ShareLinkSettingsViewController") as! ShareLinkSettingsViewController
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(driveFileManager.drive.id, forKey: "DriveId")
- coder.encode(file.id, forKey: "FileId")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveId")
- let fileId = coder.decodeInteger(forKey: "FileId")
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId,
- userId: accountManager.currentUserId) else {
- return
- }
- self.driveFileManager = driveFileManager
- file = driveFileManager.getCachedFile(id: fileId)
- // Update UI
- initOptions()
- updateButton()
- tableView.reloadData()
- }
}
// MARK: - UITableViewDelegate, UITableViewDataSource
diff --git a/kDrive/UI/Controller/Files/RootMenuViewController.swift b/kDrive/UI/Controller/Files/RootMenuViewController.swift
index 10f07562d..39343b87f 100644
--- a/kDrive/UI/Controller/Files/RootMenuViewController.swift
+++ b/kDrive/UI/Controller/Files/RootMenuViewController.swift
@@ -148,6 +148,12 @@ class RootMenuViewController: CustomLargeTitleCollectionViewController, SelectSw
(tabBarController as? PlusButtonObserver)?.updateCenterButton()
}
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ saveSceneState()
+ }
+
@objc func presentSearch() {
let viewModel = SearchFilesViewModel(driveFileManager: driveFileManager)
let searchViewController = SearchViewController.instantiateInNavigationController(viewModel: viewModel)
@@ -268,4 +274,10 @@ class RootMenuViewController: CustomLargeTitleCollectionViewController, SelectSw
let destinationViewController = FileListViewController.instantiate(viewModel: destinationViewModel)
navigationController?.pushViewController(destinationViewController, animated: true)
}
+
+ // MARK: - State restoration
+
+ var currentSceneMetadata: [AnyHashable: Any] {
+ [:]
+ }
}
diff --git a/kDrive/UI/Controller/Files/Save File/SaveFile.storyboard b/kDrive/UI/Controller/Files/Save File/SaveFile.storyboard
index cc9d8a4a4..43d6edcd0 100644
--- a/kDrive/UI/Controller/Files/Save File/SaveFile.storyboard
+++ b/kDrive/UI/Controller/Files/Save File/SaveFile.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -14,7 +14,7 @@
-
+
@@ -28,7 +28,7 @@
-
+
@@ -67,7 +67,7 @@
-
+
@@ -167,7 +167,7 @@
-
+
@@ -212,7 +212,7 @@
-
+
diff --git a/kDrive/UI/Controller/Files/Save File/SaveFileViewController.swift b/kDrive/UI/Controller/Files/Save File/SaveFileViewController.swift
index c6560d416..e4b3e06b4 100644
--- a/kDrive/UI/Controller/Files/Save File/SaveFileViewController.swift
+++ b/kDrive/UI/Controller/Files/Save File/SaveFileViewController.swift
@@ -463,7 +463,7 @@ extension SaveFileViewController: SelectFolderDelegate {
extension SaveFileViewController: SelectDriveDelegate {
func didSelectDrive(_ drive: Drive) {
- if let selectedDriveFileManager = accountManager.getDriveFileManager(for: drive) {
+ if let selectedDriveFileManager = accountManager.getDriveFileManager(for: drive.id, userId: drive.userId) {
self.selectedDriveFileManager = selectedDriveFileManager
selectedDirectory = selectedDriveFileManager.getCachedRootFile()
sections = [.fileName, .driveSelection, .directorySelection]
diff --git a/kDrive/UI/Controller/Files/Save File/SelectFolderViewController.swift b/kDrive/UI/Controller/Files/Save File/SelectFolderViewController.swift
index 538458f54..c64097a64 100644
--- a/kDrive/UI/Controller/Files/Save File/SelectFolderViewController.swift
+++ b/kDrive/UI/Controller/Files/Save File/SelectFolderViewController.swift
@@ -183,19 +183,4 @@ class SelectFolderViewController: FileListViewController {
navigationController?.pushViewController(nextVC, animated: true)
}
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(disabledDirectoriesSelection, forKey: "DisabledDirectories")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- disabledDirectoriesSelection = coder.decodeObject(forKey: "DisabledDirectories") as? [Int] ?? []
- setUpDirectory()
- }
}
diff --git a/kDrive/UI/Controller/Files/Search/Search.storyboard b/kDrive/UI/Controller/Files/Search/Search.storyboard
index 19c66dae5..e20d6e549 100644
--- a/kDrive/UI/Controller/Files/Search/Search.storyboard
+++ b/kDrive/UI/Controller/Files/Search/Search.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
@@ -65,9 +65,9 @@
-
+
-
+
diff --git a/kDrive/UI/Controller/Files/Upload/UploadQueueViewController.swift b/kDrive/UI/Controller/Files/Upload/UploadQueueViewController.swift
index f40bd60b3..855f0b81f 100644
--- a/kDrive/UI/Controller/Files/Upload/UploadQueueViewController.swift
+++ b/kDrive/UI/Controller/Files/Upload/UploadQueueViewController.swift
@@ -124,30 +124,6 @@ final class UploadQueueViewController: UIViewController {
return Storyboard.files
.instantiateViewController(withIdentifier: "UploadQueueViewController") as! UploadQueueViewController
}
-
- // MARK: - State restoration
-
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
-
- coder.encode(currentDirectory.driveId, forKey: "DriveID")
- coder.encode(currentDirectory.id, forKey: "DirectoryID")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
-
- let driveId = coder.decodeInteger(forKey: "DriveID")
- let directoryId = coder.decodeInteger(forKey: "DirectoryID")
-
- guard let driveFileManager = accountManager.getDriveFileManager(for: driveId, userId: accountManager.currentUserId),
- let directory = driveFileManager.getCachedFile(id: directoryId) else {
- // Handle error?
- return
- }
- currentDirectory = directory
- setUpObserver()
- }
}
// MARK: - Table view data source
diff --git a/kDrive/UI/Controller/Floating Panel Information/InformationFloatingPanel.storyboard b/kDrive/UI/Controller/Floating Panel Information/InformationFloatingPanel.storyboard
index 5b36f83df..32797857c 100644
--- a/kDrive/UI/Controller/Floating Panel Information/InformationFloatingPanel.storyboard
+++ b/kDrive/UI/Controller/Floating Panel Information/InformationFloatingPanel.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -18,7 +18,7 @@
-
+
@@ -44,7 +44,7 @@
-
+
@@ -86,7 +86,7 @@
-
+
-
+
-
+
diff --git a/kDrive/UI/Controller/Home/Home.storyboard b/kDrive/UI/Controller/Home/Home.storyboard
index 3dfeebd75..6724d7c7c 100644
--- a/kDrive/UI/Controller/Home/Home.storyboard
+++ b/kDrive/UI/Controller/Home/Home.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -11,7 +11,7 @@
-
+
@@ -37,7 +37,7 @@
-
+
diff --git a/kDrive/UI/Controller/Home/HomeViewController.swift b/kDrive/UI/Controller/Home/HomeViewController.swift
index 2e6ace2b2..fc1af6642 100644
--- a/kDrive/UI/Controller/Home/HomeViewController.swift
+++ b/kDrive/UI/Controller/Home/HomeViewController.swift
@@ -175,6 +175,8 @@ class HomeViewController: CustomLargeTitleCollectionViewController, UpdateAccoun
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
MatomoUtils.track(view: ["Home"])
+
+ saveSceneState()
}
@objc func searchButtonPressed() {
@@ -324,7 +326,7 @@ class HomeViewController: CustomLargeTitleCollectionViewController, UpdateAccoun
// MARK: - Switch account delegate
- func didUpdateCurrentAccountInformations(_ currentAccount: Account) {
+ @MainActor func didUpdateCurrentAccountInformations(_ currentAccount: Account) {
if isViewLoaded {
reloadTopRows()
}
@@ -335,6 +337,12 @@ class HomeViewController: CustomLargeTitleCollectionViewController, UpdateAccoun
func scrollToTop() {
collectionView?.scrollToTop(animated: true, navigationController: nil)
}
+
+ // MARK: - State restoration
+
+ var currentSceneMetadata: [AnyHashable: Any] {
+ [:]
+ }
}
// MARK: - UICollectionViewDataSource
diff --git a/kDrive/UI/Controller/Home/SelectSwitchDriveDelegate.swift b/kDrive/UI/Controller/Home/SelectSwitchDriveDelegate.swift
index 2380e90be..a734e1372 100644
--- a/kDrive/UI/Controller/Home/SelectSwitchDriveDelegate.swift
+++ b/kDrive/UI/Controller/Home/SelectSwitchDriveDelegate.swift
@@ -32,8 +32,13 @@ extension SelectSwitchDriveDelegate {
} else {
MatomoUtils.track(eventWithCategory: .drive, name: "switch")
- @InjectService var appRestorationService: AppRestorationService
- appRestorationService.reloadAppUI(for: drive)
+ let driveId = drive.id
+ let driveUserId = drive.userId
+
+ Task {
+ @InjectService var appRestorationService: AppRestorationServiceable
+ await appRestorationService.reloadAppUI(for: driveId, userId: driveUserId)
+ }
}
}
}
diff --git a/kDrive/UI/Controller/LockedAppViewController.swift b/kDrive/UI/Controller/LockedAppViewController.swift
index aa879cc67..9806cb222 100644
--- a/kDrive/UI/Controller/LockedAppViewController.swift
+++ b/kDrive/UI/Controller/LockedAppViewController.swift
@@ -24,6 +24,7 @@ import UIKit
class LockedAppViewController: UIViewController {
@LazyInjectService private var appLockHelper: AppLockHelper
+ @LazyInjectService private var appNavigable: AppNavigable
@IBOutlet weak var unlockAppButton: IKLargeButton!
@@ -45,7 +46,7 @@ class LockedAppViewController: UIViewController {
func unlockApp() {
appLockHelper.setTime()
- (UIApplication.shared.delegate as? AppDelegate)?.updateRootViewControllerState()
+ appNavigable.prepareRootViewController(currentState: RootViewControllerState.getCurrentState(), restoration: true)
}
@IBAction func unlockAppButtonClicked(_ sender: UIButton) {
diff --git a/kDrive/UI/Controller/MainTabViewController.swift b/kDrive/UI/Controller/MainTabViewController.swift
index 1bcd6fda7..ec7232b48 100644
--- a/kDrive/UI/Controller/MainTabViewController.swift
+++ b/kDrive/UI/Controller/MainTabViewController.swift
@@ -24,6 +24,14 @@ import kDriveCore
import kDriveResources
import UIKit
+/// Enum to explicit tab names
+enum MainTabIndex: Int {
+ case home = 0
+ case files = 1
+ case gallery = 3
+ case profile = 4
+}
+
class MainTabViewController: UITabBarController, Restorable, PlusButtonObserver {
// swiftlint:disable:next weak_delegate
var photoPickerDelegate = PhotoPickerDelegate()
@@ -85,16 +93,6 @@ class MainTabViewController: UITabBarController, Restorable, PlusButtonObserver
updateTabBarProfilePicture()
}
- override func encodeRestorableState(with coder: NSCoder) {
- super.encodeRestorableState(with: coder)
- coder.encode(selectedIndex, forKey: "SelectedIndex")
- }
-
- override func decodeRestorableState(with coder: NSCoder) {
- super.decodeRestorableState(with: coder)
- selectedIndex = coder.decodeInteger(forKey: "SelectedIndex")
- }
-
private static func initHomeViewController(driveFileManager: DriveFileManager) -> UIViewController {
let homeViewController = HomeViewController(driveFileManager: driveFileManager)
let navigationViewController = TitleSizeAdjustingNavigationController(rootViewController: homeViewController)
@@ -264,15 +262,29 @@ extension MainTabViewController: UITabBarControllerDelegate {
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
- UserDefaults.shared.lastSelectedTab = tabBarController.selectedIndex
+ let selectedIndex = tabBarController.selectedIndex
+
+ UserDefaults.shared.lastSelectedTab = selectedIndex
+ saveSelectedTabUserActivity(selectedIndex)
+
updateCenterButton()
}
+
+ // MARK: - State restoration
+
+ private func saveSelectedTabUserActivity(_ index: Int) {
+ let metadata = [SceneRestorationKeys.selectedIndex.rawValue: index]
+ let userActivity = currentUserActivity
+ userActivity.userInfo = metadata
+
+ view.window?.windowScene?.userActivity = userActivity
+ }
}
// MARK: - SwitchAccountDelegate, SwitchDriveDelegate
extension MainTabViewController: UpdateAccountDelegate {
- func didUpdateCurrentAccountInformations(_ currentAccount: Account) {
+ @MainActor func didUpdateCurrentAccountInformations(_ currentAccount: Account) {
updateTabBarProfilePicture()
for viewController in viewControllers ?? [] where viewController.isViewLoaded {
((viewController as? UINavigationController)?.viewControllers.first as? UpdateAccountDelegate)?
diff --git a/kDrive/UI/Controller/Menu/Menu.storyboard b/kDrive/UI/Controller/Menu/Menu.storyboard
index f0f9b27bd..c23dd8aac 100644
--- a/kDrive/UI/Controller/Menu/Menu.storyboard
+++ b/kDrive/UI/Controller/Menu/Menu.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -13,7 +13,7 @@
-
+
@@ -32,7 +32,7 @@
-
+
@@ -42,7 +42,7 @@