From ebdd06738a6f5221535309288fd14c05379cf015 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 28 Sep 2021 21:03:37 -0400 Subject: [PATCH] State restoration --- Gemini-iOS/BrowserNavigationController.swift | 10 +++++ Gemini-iOS/BrowserWebViewController.swift | 16 ++++++- Gemini-iOS/Info.plist | 4 ++ Gemini-iOS/SceneDelegate.swift | 44 +++++++++++++------ Gemini-iOS/UserActivities.swift | 46 ++++++++++++++++++++ Gemini.xcodeproj/project.pbxproj | 4 ++ 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 Gemini-iOS/UserActivities.swift diff --git a/Gemini-iOS/BrowserNavigationController.swift b/Gemini-iOS/BrowserNavigationController.swift index 0cc458a..c036600 100644 --- a/Gemini-iOS/BrowserNavigationController.swift +++ b/Gemini-iOS/BrowserNavigationController.swift @@ -50,6 +50,13 @@ class BrowserNavigationController: UIViewController { .slide } + override var userActivity: NSUserActivity? { + get { + currentBrowserVC?.userActivity + } + set {} + } + private var cancellables = [AnyCancellable]() init(navigator: NavigationManager) { @@ -75,6 +82,9 @@ class BrowserNavigationController: UIViewController { currentBrowserVC.scrollViewDelegate = self embedChild(currentBrowserVC, in: browserContainer) + backBrowserVCs = navigator.backStack.map(createBrowserVC(url:)) + forwardBrowserVCs = navigator.forwardStack.map(createBrowserVC(url:)) + navBarView = NavigationBarView(navigator: navigator) navBarView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navBarView) diff --git a/Gemini-iOS/BrowserWebViewController.swift b/Gemini-iOS/BrowserWebViewController.swift index 395b3a3..ca157ce 100644 --- a/Gemini-iOS/BrowserWebViewController.swift +++ b/Gemini-iOS/BrowserWebViewController.swift @@ -45,6 +45,9 @@ class BrowserWebViewController: UIViewController { self.url = url super.init(nibName: nil, bundle: nil) + + userActivity = NSUserActivity(geminiURL: url) + userActivity!.isEligibleForPrediction = true } required init?(coder: NSCoder) { @@ -358,7 +361,18 @@ extension BrowserWebViewController: WKUIDelegate { return SFSafariViewController(url: url) } } actionProvider: { (_) in - return nil + guard #available(iOS 15.0, *), + url.scheme == "gemini" else { + return nil + } + return UIMenu(children: [ + UIWindowScene.ActivationAction({ (_) in + let options = UIWindowScene.ActivationRequestOptions() + // automatic presents in the prominent style even when a fullscreen window is the only existing one + options.preferredPresentationStyle = .standard + return UIWindowScene.ActivationConfiguration(userActivity: NSUserActivity(geminiURL: url), options: options, preview: nil) + }) + ]) } completionHandler(config) } diff --git a/Gemini-iOS/Info.plist b/Gemini-iOS/Info.plist index dbf15cb..5ea458b 100644 --- a/Gemini-iOS/Info.plist +++ b/Gemini-iOS/Info.plist @@ -2,6 +2,10 @@ + NSUserActivityTypes + + $(PRODUCE_BUNDLE_IDENTIFIER).activity.browse + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/Gemini-iOS/SceneDelegate.swift b/Gemini-iOS/SceneDelegate.swift index 906919d..d58f0d5 100644 --- a/Gemini-iOS/SceneDelegate.swift +++ b/Gemini-iOS/SceneDelegate.swift @@ -23,20 +23,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - - let initialURL: URL - if let context = connectionOptions.urlContexts.first { - initialURL = context.url - } else { - if ProcessInfo.processInfo.environment.keys.contains("DEFAULT_URL") { - initialURL = URL(string: ProcessInfo.processInfo.environment["DEFAULT_URL"]!)! - } else { - initialURL = Preferences.shared.homepage - } - } - - navigationManager = NavigationManager(url: initialURL) + navigationManager = createNavigationManager(for: session, with: connectionOptions) navigationManager.delegate = self // Create the SwiftUI view that provides the window contents. @@ -93,6 +81,36 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } + + func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { + return NSUserActivity(navigationManager: navigationManager) + } + + private func createNavigationManager(for session: UISceneSession, with connectionOptions: UIScene.ConnectionOptions) -> NavigationManager { + var initialURL: URL? = nil + + if let activity = session.stateRestorationActivity ?? connectionOptions.userActivities.first { + if let manager = activity.navigationManager { + return manager + } else if let url = activity.geminiURL { + initialURL = url + } + } + + if initialURL == nil { + initialURL = connectionOptions.urlContexts.first?.url + } + + if initialURL == nil { + if ProcessInfo.processInfo.environment.keys.contains("DEFAULT_URL") { + initialURL = URL(string: ProcessInfo.processInfo.environment["DEFAULT_URL"]!)! + } else { + initialURL = Preferences.shared.homepage + } + } + + return NavigationManager(url: initialURL!) + } } diff --git a/Gemini-iOS/UserActivities.swift b/Gemini-iOS/UserActivities.swift new file mode 100644 index 0000000..2da987d --- /dev/null +++ b/Gemini-iOS/UserActivities.swift @@ -0,0 +1,46 @@ +// +// UserActivities.swift +// Gemini-iOS +// +// Created by Shadowfacts on 9/28/21. +// + +import Foundation +import BrowserCore + +private let type = "space.vaccor.Gemini.activity.browse" + +extension NSUserActivity { + convenience init(geminiURL url: URL) { + self.init(activityType: type) + self.userInfo = [ + "url": url, + ] + } + + convenience init(navigationManager manager: NavigationManager) { + self.init(activityType: type) + self.userInfo = [ + "url": manager.currentURL, + "back": manager.backStack, + "forward": manager.forwardStack, + ] + } + + var navigationManager: NavigationManager? { + guard activityType == type, + let url = userInfo?["url"] as? URL else { return nil } + let back = userInfo?["back"] as? [URL] ?? [] + let forward = userInfo?["forward"] as? [URL] ?? [] + let manager = NavigationManager(url: url) + manager.backStack = back + manager.forwardStack = forward + return manager + } + + var geminiURL: URL? { + guard activityType == type, + let url = userInfo?["url"] as? URL else { return nil } + return url + } +} diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index dab0fd0..a844de4 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ D688F64A258C17F3003A0A73 /* SymbolCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F649258C17F3003A0A73 /* SymbolCache.swift */; }; D688F65A258C2256003A0A73 /* BrowserNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F659258C2256003A0A73 /* BrowserNavigationController.swift */; }; D688F663258C2479003A0A73 /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F662258C2479003A0A73 /* UIViewController+Children.swift */; }; + D68C1E002703EA13002D642B /* UserActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68C1DFF2703EA13002D642B /* UserActivities.swift */; }; D691A64E25217C6F00348C4B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A64D25217C6F00348C4B /* Preferences.swift */; }; D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.swift */; }; D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; }; @@ -321,6 +322,7 @@ D688F649258C17F3003A0A73 /* SymbolCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolCache.swift; sourceTree = ""; }; D688F659258C2256003A0A73 /* BrowserNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationController.swift; sourceTree = ""; }; D688F662258C2479003A0A73 /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = ""; }; + D68C1DFF2703EA13002D642B /* UserActivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivities.swift; sourceTree = ""; }; D691A64D25217C6F00348C4B /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; D691A66625217FD800348C4B /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiDataTask.swift; sourceTree = ""; }; @@ -612,6 +614,7 @@ D653F40C26799F2F004E32B1 /* HomepagePrefView.swift */, D653F40A267996FF004E32B1 /* ActivityItemSource.swift */, D653F40E2679A0AB004E32B1 /* SetHomepageActivity.swift */, + D68C1DFF2703EA13002D642B /* UserActivities.swift */, D688F618258AD231003A0A73 /* Resources */, D6376A6E26DDAF57005AD89C /* Vendor */, D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */, @@ -1142,6 +1145,7 @@ D653F40D26799F2F004E32B1 /* HomepagePrefView.swift in Sources */, D691A64E25217C6F00348C4B /* Preferences.swift in Sources */, D6BC9ABC258E9862008652BC /* NavigationBarView.swift in Sources */, + D68C1E002703EA13002D642B /* UserActivities.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };