diff --git a/ZShare/ViewModels/ExtensionViewModel.swift b/ZShare/ViewModels/ExtensionViewModel.swift index 40b07f7ab..1ffec8186 100644 --- a/ZShare/ViewModels/ExtensionViewModel.swift +++ b/ZShare/ViewModels/ExtensionViewModel.swift @@ -1457,7 +1457,13 @@ final class ExtensionViewModel { recents = recentCollections library = _library - collection = _collection + switch self.state.selectedCollectionId { + case .collection: + collection = _collection + + default: + break + } }) guard let library = library else { return } diff --git a/Zotero/Controllers/Database/Requests/ReadCollectionAndLibraryDbRequest.swift b/Zotero/Controllers/Database/Requests/ReadCollectionAndLibraryDbRequest.swift index 1af670342..a1d0f5209 100644 --- a/Zotero/Controllers/Database/Requests/ReadCollectionAndLibraryDbRequest.swift +++ b/Zotero/Controllers/Database/Requests/ReadCollectionAndLibraryDbRequest.swift @@ -26,7 +26,11 @@ struct ReadCollectionAndLibraryDbRequest: DbResponseRequest { let rCollection = try ReadCollectionDbRequest(libraryId: self.libraryId, key: key).process(in: database) let collection = Collection(object: rCollection, itemCount: 0) return (collection, library) - + + case .custom(let type): + let collection = Collection(custom: type) + return (collection, library) + default: return (nil, library) } diff --git a/Zotero/Extensions/NSUserActivity+Activities.swift b/Zotero/Extensions/NSUserActivity+Activities.swift index d1e56648b..a60cea150 100644 --- a/Zotero/Extensions/NSUserActivity+Activities.swift +++ b/Zotero/Extensions/NSUserActivity+Activities.swift @@ -11,6 +11,7 @@ import Foundation struct RestoredStateData { let key: String let libraryId: LibraryIdentifier + let collectionId: CollectionIdentifier } extension NSUserActivity { @@ -21,9 +22,13 @@ extension NSUserActivity { return NSUserActivity(activityType: self.mainId) } - static func pdfActivity(for key: String, libraryId: LibraryIdentifier) -> NSUserActivity { + static func pdfActivity(for key: String, libraryId: LibraryIdentifier, collectionId: CollectionIdentifier) -> NSUserActivity { let activity = NSUserActivity(activityType: self.pdfId) - activity.addUserInfoEntries(from: ["key": key, "libraryId": self.libraryIdToString(libraryId)]) + var pdfUserInfo: [AnyHashable: Any] = ["key": key, "libraryId": libraryIdToString(libraryId)] + if let collectionIdData = try? JSONEncoder().encode(collectionId) { + pdfUserInfo["collectionId"] = collectionIdData + } + activity.addUserInfoEntries(from: pdfUserInfo) return activity } @@ -54,7 +59,18 @@ extension NSUserActivity { var restoredStateData: RestoredStateData? { guard self.activityType == NSUserActivity.pdfId, - let key = self.userInfo?["key"] as? String, let libraryString = self.userInfo?["libraryId"] as? String, let libraryId = self.stringToLibraryId(libraryString) else { return nil } - return RestoredStateData(key: key, libraryId: libraryId) + let userInfo, + let key = userInfo["key"] as? String, + let libraryString = userInfo["libraryId"] as? String, + let libraryId = stringToLibraryId(libraryString) + else { return nil } + var collectionId: CollectionIdentifier + if let collectionIdData = userInfo["collectionId"] as? Data, + let decodedCollectionId = try? JSONDecoder().decode(CollectionIdentifier.self, from: collectionIdData) { + collectionId = decodedCollectionId + } else { + collectionId = Defaults.shared.selectedCollectionId + } + return RestoredStateData(key: key, libraryId: libraryId, collectionId: collectionId) } } diff --git a/Zotero/Scenes/AppCoordinator.swift b/Zotero/Scenes/AppCoordinator.swift index b7cba1fc4..e7c0f8cf6 100644 --- a/Zotero/Scenes/AppCoordinator.swift +++ b/Zotero/Scenes/AppCoordinator.swift @@ -98,6 +98,8 @@ final class AppCoordinator: NSObject { guard let window = self.window else { return } let viewController: UIViewController + var urlContext: UIOpenURLContext? + var data: RestoredStateData? if !isLogged { let controller = OnboardingViewController(size: window.frame.size, htmlConverter: self.controllers.htmlAttributedStringConverter) controller.coordinatorDelegate = self @@ -107,6 +109,7 @@ final class AppCoordinator: NSObject { self.conflictAlertQueueController = nil self.controllers.userControllers?.syncScheduler.syncController.set(coordinator: nil) } else { + (urlContext, data) = preprocess(connectionOptions: connectionOptions, session: session) let controller = MainViewController(controllers: self.controllers) viewController = controller @@ -117,12 +120,24 @@ final class AppCoordinator: NSObject { DDLogInfo("AppCoordinator: show main screen logged \(isLogged ? "in" : "out"); animated=\(animated)") self.show(viewController: viewController, in: window, animated: animated) - guard let options = connectionOptions, let session = session else { return } - self.process(connectionOptions: options, session: session) + process(urlContext: urlContext, data: data) } - private func process(connectionOptions: UIScene.ConnectionOptions, session: UISceneSession) { - if let urlContext = connectionOptions.urlContexts.first, let urlController = self.controllers.userControllers?.customUrlController { + private func preprocess(connectionOptions: UIScene.ConnectionOptions?, session: UISceneSession?) -> (UIOpenURLContext?, RestoredStateData?) { + let urlContext = connectionOptions?.urlContexts.first + let userActivity = connectionOptions?.userActivities.first ?? session?.stateRestorationActivity + let data = userActivity?.restoredStateData + if let data { + // If scene had state stored, check if defaults need to be updated first + DDLogInfo("AppCoordinator: Preprocessing restored state - \(data)") + Defaults.shared.selectedLibrary = data.libraryId + Defaults.shared.selectedCollectionId = data.collectionId + } + return (urlContext, data) + } + + private func process(urlContext: UIOpenURLContext?, data: RestoredStateData?) { + if let urlContext, let urlController = self.controllers.userControllers?.customUrlController { // If scene was started from custom URL let sourceApp = urlContext.options.sourceApplication ?? "unknown" DDLogInfo("AppCoordinator: App launched by \(urlContext.url.absoluteString) from \(sourceApp)") @@ -133,8 +148,8 @@ final class AppCoordinator: NSObject { } } - if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity, let data = userActivity.restoredStateData { - DDLogInfo("AppCoordinator: Restored state - \(data)") + if let data { + DDLogInfo("AppCoordinator: Processing restored state - \(data)") // If scene had state stored, restore state self.showRestoredState(for: data) } @@ -218,9 +233,17 @@ final class AppCoordinator: NSObject { private func showRestoredState(for data: RestoredStateData) { guard let mainController = self.window?.rootViewController as? MainViewController, - let (url, library) = self.loadRestoredStateData(forKey: data.key, libraryId: data.libraryId) else { return } - DDLogInfo("AppCoordinator: show restored state - \(data.key); \(data.libraryId); \(url.relativePath)") - + let (url, library, collection) = self.loadRestoredStateData(forKey: data.key, libraryId: data.libraryId, collectionId: data.collectionId) else { return } + if let collection { + DDLogInfo("AppCoordinator: show restored state - \(data.key); \(data.libraryId); \(data.collectionId); \(url.relativePath)") + mainController.showItems(for: collection, in: library, saveCollectionToDefaults: true) + } else { + DDLogWarn("AppCoordinator: show restored state using all items collection - \(data.key); \(data.libraryId); \(url.relativePath)") + // Collection is missing, show all items instead + let collection = Collection(custom: .all) + mainController.showItems(for: collection, in: library, saveCollectionToDefaults: false) + } + mainController.getDetailCoordinator { [weak self] coordinator in guard let self = self, let window = self.window else { return } let controller = self.pdfController(key: data.key, library: library, url: url, page: nil, preselectedAnnotationKey: nil, detailCoordinator: coordinator) @@ -240,11 +263,12 @@ final class AppCoordinator: NSObject { return navigationController } - private func loadRestoredStateData(forKey key: String, libraryId: LibraryIdentifier) -> (URL, Library)? { + private func loadRestoredStateData(forKey key: String, libraryId: LibraryIdentifier, collectionId: CollectionIdentifier) -> (URL, Library, Collection?)? { guard let dbStorage = self.controllers.userControllers?.dbStorage else { return nil } var url: URL? var library: Library? + var collection: Collection? do { try dbStorage.perform(on: .main, with: { coordinator in @@ -258,7 +282,9 @@ final class AppCoordinator: NSObject { case .local, .localAndChangedRemotely: let file = Files.attachmentFile(in: libraryId, key: key, filename: filename, contentType: contentType) url = file.createUrl() - library = try coordinator.perform(request: ReadLibraryDbRequest(libraryId: libraryId)) + let (_collection, _library) = try coordinator.perform(request: ReadCollectionAndLibraryDbRequest(collectionId: collectionId, libraryId: libraryId)) + collection = _collection + library = _library case .remote, .remoteMissing: break } @@ -272,7 +298,7 @@ final class AppCoordinator: NSObject { } if let url = url, let library = library { - return (url, library) + return (url, library, collection) } return nil } diff --git a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift index 18678f906..c6e3d8deb 100644 --- a/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift +++ b/Zotero/Scenes/Detail/PDF/Views/PDFReaderViewController.swift @@ -206,7 +206,7 @@ class PDFReaderViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - self.set(userActivity: .pdfActivity(for: self.viewModel.state.key, libraryId: self.viewModel.state.library.identifier)) + self.set(userActivity: .pdfActivity(for: self.viewModel.state.key, libraryId: self.viewModel.state.library.identifier, collectionId: Defaults.shared.selectedCollectionId)) self.view.backgroundColor = .systemGray6 self.setupViews() diff --git a/Zotero/Scenes/Main/Views/MainViewController.swift b/Zotero/Scenes/Main/Views/MainViewController.swift index 9ec704981..498e22d63 100644 --- a/Zotero/Scenes/Main/Views/MainViewController.swift +++ b/Zotero/Scenes/Main/Views/MainViewController.swift @@ -15,7 +15,7 @@ import CocoaLumberjackSwift import RxSwift protocol MainCoordinatorDelegate: SplitControllerDelegate { - func showItems(for collection: Collection, in library: Library, isInitial: Bool) + func showItems(for collection: Collection, in library: Library, saveCollectionToDefaults: Bool) } protocol SplitControllerDelegate: AnyObject { @@ -27,14 +27,8 @@ protocol MainCoordinatorSyncToolbarDelegate: AnyObject { } final class MainViewController: UISplitViewController { - private struct InitialLoadData { - let collection: Collection - let library: Library - } - // Constants private let controllers: Controllers - private let defaultCollection: Collection private let disposeBag: DisposeBag // Variables private var didAppear: Bool = false @@ -54,7 +48,6 @@ final class MainViewController: UISplitViewController { init(controllers: Controllers) { self.controllers = controllers - self.defaultCollection = Collection(custom: .all) self.disposeBag = DisposeBag() super.init(nibName: nil, bundle: nil) @@ -109,44 +102,6 @@ final class MainViewController: UISplitViewController { self.showDetailViewController(navigationController, sender: nil) } - private func loadInitialDetailData(collectionId: CollectionIdentifier, libraryId: LibraryIdentifier) -> InitialLoadData? { - guard let dbStorage = self.controllers.userControllers?.dbStorage else { return nil } - - DDLogInfo("MainViewController: load initial detail data collectionId=\(collectionId); libraryId=\(libraryId)") - - var collection: Collection? - var library: Library? - - do { - try dbStorage.perform(on: .main, with: { coordinator in - switch collectionId { - case .collection(let key): - let rCollection = try coordinator.perform(request: ReadCollectionDbRequest(libraryId: libraryId, key: key)) - collection = Collection(object: rCollection, itemCount: 0) - - case .search(let key): - let rSearch = try coordinator.perform(request: ReadSearchDbRequest(libraryId: libraryId, key: key)) - collection = Collection(object: rSearch) - - case .custom(let type): - collection = Collection(custom: type) - } - library = try coordinator.perform(request: ReadLibraryDbRequest(libraryId: libraryId)) - }) - } catch let error { - DDLogError("MainViewController: can't load initial data - \(error)") - return nil - } - - if let collection = collection, let library = library { - return InitialLoadData(collection: collection, library: library) - } - - DDLogWarn("MainViewController: returning default library and collection") - return InitialLoadData(collection: Collection(custom: .all), - library: Library(identifier: .custom(.myLibrary), name: "My Library", metadataEditable: true, filesEditable: true)) - } - // MARK: - Setups private func setupControllers() { @@ -177,7 +132,10 @@ extension MainViewController: UISplitViewControllerDelegate { } extension MainViewController: MainCoordinatorDelegate { - func showItems(for collection: Collection, in library: Library, isInitial: Bool) { + func showItems(for collection: Collection, in library: Library, saveCollectionToDefaults: Bool) { + if saveCollectionToDefaults { + Defaults.shared.selectedCollectionId = collection.identifier + } guard !self.isSplit || self.detailCoordinator?.library != library || self.detailCoordinator?.collection.identifier != collection.identifier else { return } self.showItems(for: collection, in: library, searchItemKeys: nil) } diff --git a/Zotero/Scenes/Master/Collections/Views/CollectionsViewController.swift b/Zotero/Scenes/Master/Collections/Views/CollectionsViewController.swift index 3ac965587..dd6f68c55 100644 --- a/Zotero/Scenes/Master/Collections/Views/CollectionsViewController.swift +++ b/Zotero/Scenes/Master/Collections/Views/CollectionsViewController.swift @@ -44,7 +44,7 @@ final class CollectionsViewController: UICollectionViewController { if self.coordinatorDelegate?.isSplit == true { if let collection = self.viewModel.state.collectionTree.collection(for: self.viewModel.state.selectedCollectionId) { - self.coordinatorDelegate?.showItems(for: collection, in: self.viewModel.state.library, isInitial: true) + self.coordinatorDelegate?.showItems(for: collection, in: self.viewModel.state.library, saveCollectionToDefaults: false) } else { self.viewModel.process(action: .select(.custom(.all))) } @@ -86,7 +86,7 @@ final class CollectionsViewController: UICollectionViewController { } if state.changes.contains(.selection), let collection = state.collectionTree.collection(for: state.selectedCollectionId) { - self.coordinatorDelegate?.showItems(for: collection, in: state.library, isInitial: false) + self.coordinatorDelegate?.showItems(for: collection, in: state.library, saveCollectionToDefaults: true) if !requiresUpdate { self.selectIfNeeded(collectionId: state.selectedCollectionId, tree: state.collectionTree, scrollToPosition: false) diff --git a/Zotero/Scenes/Master/MasterTopCoordinator.swift b/Zotero/Scenes/Master/MasterTopCoordinator.swift index f9d3ce0bd..fac39c87d 100644 --- a/Zotero/Scenes/Master/MasterTopCoordinator.swift +++ b/Zotero/Scenes/Master/MasterTopCoordinator.swift @@ -195,12 +195,9 @@ extension MasterTopCoordinator: MasterCollectionsCoordinatorDelegate { self.navigationController?.present(navigationController, animated: true, completion: nil) } - func showItems(for collection: Collection, in library: Library, isInitial: Bool) { + func showItems(for collection: Collection, in library: Library, saveCollectionToDefaults: Bool) { self.visibleLibraryId = library.identifier - if !isInitial { - Defaults.shared.selectedCollectionId = collection.identifier - } - self.mainCoordinatorDelegate.showItems(for: collection, in: library, isInitial: isInitial) + self.mainCoordinatorDelegate.showItems(for: collection, in: library, saveCollectionToDefaults: saveCollectionToDefaults) } var isSplit: Bool {