From 2b82e3b54586049fb8e8fa8e74dd926d30578d48 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 30 Sep 2021 11:06:03 -0400 Subject: [PATCH] Add Open URL intent --- Gemini-iOS/AppDelegate.swift | 10 ++ Gemini-iOS/Info.plist | 13 +- Gemini-iOS/Intents.intentdefinition | 131 ++++++++++++++++++ Gemini-iOS/Intents/OpenURLIntentHandler.swift | 32 +++++ Gemini-iOS/SceneDelegate.swift | 23 ++- Gemini-iOS/UserActivities.swift | 2 +- Gemini.xcodeproj/project.pbxproj | 20 +++ .../xcshareddata/swiftpm/Package.resolved | 2 +- 8 files changed, 225 insertions(+), 8 deletions(-) create mode 100644 Gemini-iOS/Intents.intentdefinition create mode 100644 Gemini-iOS/Intents/OpenURLIntentHandler.swift diff --git a/Gemini-iOS/AppDelegate.swift b/Gemini-iOS/AppDelegate.swift index 8aa30c0..6ffbbef 100644 --- a/Gemini-iOS/AppDelegate.swift +++ b/Gemini-iOS/AppDelegate.swift @@ -6,6 +6,7 @@ // import UIKit +import Intents @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -32,6 +33,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } + func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? { + switch intent { + case is OpenURLIntent: + return OpenURLIntentHandler() + + default: + return nil + } + } } diff --git a/Gemini-iOS/Info.plist b/Gemini-iOS/Info.plist index 5ea458b..24a1a82 100644 --- a/Gemini-iOS/Info.plist +++ b/Gemini-iOS/Info.plist @@ -2,10 +2,6 @@ - NSUserActivityTypes - - $(PRODUCE_BUNDLE_IDENTIFIER).activity.browse - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -37,8 +33,17 @@ CFBundleVersion $(CURRENT_PROJECT_VERSION) + INIntentsSupported + + OpenURLIntent + LSRequiresIPhoneOS + NSUserActivityTypes + + $(PRODUCE_BUNDLE_IDENTIFIER).activity.browse + OpenURLIntent + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Gemini-iOS/Intents.intentdefinition b/Gemini-iOS/Intents.intentdefinition new file mode 100644 index 0000000..318be23 --- /dev/null +++ b/Gemini-iOS/Intents.intentdefinition @@ -0,0 +1,131 @@ + + + + + INEnums + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + YzEWx7 + INIntentDefinitionSystemVersion + 20F71 + INIntentDefinitionToolsBuildVersion + 13A233 + INIntentDefinitionToolsVersion + 13.0 + INIntents + + + INIntentCategory + information + INIntentConfigurable + + INIntentDescription + Open a Gemini link in Rocketeer. + INIntentDescriptionID + q39SFr + INIntentInput + url + INIntentLastParameterTag + 2 + INIntentManagedParameterCombinations + + url + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Open ${url} + INIntentParameterCombinationTitleID + S2D5Se + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + OpenURL + INIntentParameterCombinations + + url + + INIntentParameterCombinationIsLinked + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Open ${url} + INIntentParameterCombinationTitleID + vxeXI3 + + + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + URL + INIntentParameterDisplayNameID + 8RER8H + INIntentParameterDisplayPriority + 1 + INIntentParameterName + url + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + What URL would you like to open? + INIntentParameterPromptDialogFormatStringID + Bssksg + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 2 + INIntentParameterType + URL + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Open URL + INIntentTitleID + yqBCLe + INIntentType + Custom + INIntentVerb + Open + + + INTypes + + + diff --git a/Gemini-iOS/Intents/OpenURLIntentHandler.swift b/Gemini-iOS/Intents/OpenURLIntentHandler.swift new file mode 100644 index 0000000..7bdfe5f --- /dev/null +++ b/Gemini-iOS/Intents/OpenURLIntentHandler.swift @@ -0,0 +1,32 @@ +// +// OpenURLIntentHandler.swift +// Gemini-iOS +// +// Created by Shadowfacts on 9/29/21. +// + +import Intents +import UIKit + +class OpenURLIntentHandler: NSObject, OpenURLIntentHandling { + + func resolveUrl(for intent: OpenURLIntent, with completion: @escaping (INURLResolutionResult) -> Void) { + guard let url = intent.url, + url.scheme?.lowercased() == "gemini" else { + completion(.unsupported()) + return + } + completion(.success(with: url)) + } + + func handle(intent: OpenURLIntent, completion: @escaping (OpenURLIntentResponse) -> Void) { + print("handling intent") + guard let url = intent.url, + url.scheme?.lowercased() == "gemini" else { + completion(OpenURLIntentResponse(code: .failure, userActivity: nil)) + return + } + completion(OpenURLIntentResponse(code: .continueInApp, userActivity: NSUserActivity(geminiURL: url))) + } + +} diff --git a/Gemini-iOS/SceneDelegate.swift b/Gemini-iOS/SceneDelegate.swift index d58f0d5..4cca168 100644 --- a/Gemini-iOS/SceneDelegate.swift +++ b/Gemini-iOS/SceneDelegate.swift @@ -10,6 +10,7 @@ import SwiftUI import BrowserCore import SafariServices import Combine +import Intents class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -42,7 +43,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } Preferences.shared.$theme - .sink { (newStyle) in + .sink { [unowned self] (newStyle) in self.window!.overrideUserInterfaceStyle = newStyle } .store(in: &cancellables) @@ -53,6 +54,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { navigationManager.changeURL(context.url) } } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if let url = userActivity.geminiURL { + navigationManager.changeURL(url) + } + } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. @@ -87,9 +94,21 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } private func createNavigationManager(for session: UISceneSession, with connectionOptions: UIScene.ConnectionOptions) -> NavigationManager { + // try to restore the existing nav manager if there is one + if let manager = session.stateRestorationActivity?.navigationManager { + // if there's a user activity with a gemini url (e.g., from OpenURLIntentHandler), + // navigate the existing manager to that URL + if let newURL = connectionOptions.userActivities.first?.geminiURL { + manager.changeURL(newURL) + } + return manager + } + + // otherwise, work out the initial URL and create a new manager + var initialURL: URL? = nil - if let activity = session.stateRestorationActivity ?? connectionOptions.userActivities.first { + if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { if let manager = activity.navigationManager { return manager } else if let url = activity.geminiURL { diff --git a/Gemini-iOS/UserActivities.swift b/Gemini-iOS/UserActivities.swift index 017a880..5c59e9e 100644 --- a/Gemini-iOS/UserActivities.swift +++ b/Gemini-iOS/UserActivities.swift @@ -8,7 +8,7 @@ import Foundation import BrowserCore -private let type = "space.vaccor.Gemini.activity.browse" +private let type = "\(Bundle.main.bundleIdentifier!).activity.browse" private let encoder = PropertyListEncoder() private let decoder = PropertyListDecoder() diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index a844de4..386e52a 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -52,6 +52,9 @@ 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 */; }; + D68C1E1927055E09002D642B /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = D68C1E1827055E09002D642B /* Intents.intentdefinition */; }; + D68C1E1C27055EB0002D642B /* OpenURLIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68C1E1B27055EB0002D642B /* OpenURLIntentHandler.swift */; }; + D68C1E1E270605A7002D642B /* BrowserHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68C1E1D270605A7002D642B /* BrowserHelper.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 */; }; @@ -323,6 +326,9 @@ 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 = ""; }; + D68C1E1827055E09002D642B /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = ""; }; + D68C1E1B27055EB0002D642B /* OpenURLIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenURLIntentHandler.swift; sourceTree = ""; }; + D68C1E1D270605A7002D642B /* BrowserHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserHelper.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 = ""; }; @@ -596,6 +602,14 @@ path = Resources; sourceTree = ""; }; + D68C1E1A27055EA0002D642B /* Intents */ = { + isa = PBXGroup; + children = ( + D68C1E1B27055EB0002D642B /* OpenURLIntentHandler.swift */, + ); + path = Intents; + sourceTree = ""; + }; D6E152A324BFFDF500FDF9D3 /* Gemini-iOS */ = { isa = PBXGroup; children = ( @@ -615,9 +629,11 @@ D653F40A267996FF004E32B1 /* ActivityItemSource.swift */, D653F40E2679A0AB004E32B1 /* SetHomepageActivity.swift */, D68C1DFF2703EA13002D642B /* UserActivities.swift */, + D68C1E1A27055EA0002D642B /* Intents */, D688F618258AD231003A0A73 /* Resources */, D6376A6E26DDAF57005AD89C /* Vendor */, D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */, + D68C1E1827055E09002D642B /* Intents.intentdefinition */, D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */, D6E152B224BFFDF600FDF9D3 /* Info.plist */, D6E152AC24BFFDF600FDF9D3 /* Preview Content */, @@ -639,6 +655,7 @@ D6E152D224C0007200FDF9D3 /* BrowserCore.h */, D6E152D324C0007200FDF9D3 /* Info.plist */, D69F00AF24BEA84D00E37622 /* NavigationManager.swift */, + D68C1E1D270605A7002D642B /* BrowserHelper.swift */, D626646024BBF1C200DF9B88 /* BrowserView.swift */, ); path = BrowserCore; @@ -1141,8 +1158,10 @@ D653F40F2679A0AB004E32B1 /* SetHomepageActivity.swift in Sources */, D688F65A258C2256003A0A73 /* BrowserNavigationController.swift in Sources */, D6BC9AD7258FC8B3008652BC /* TableOfContentsView.swift in Sources */, + D68C1E1927055E09002D642B /* Intents.intentdefinition in Sources */, D688F663258C2479003A0A73 /* UIViewController+Children.swift in Sources */, D653F40D26799F2F004E32B1 /* HomepagePrefView.swift in Sources */, + D68C1E1C27055EB0002D642B /* OpenURLIntentHandler.swift in Sources */, D691A64E25217C6F00348C4B /* Preferences.swift in Sources */, D6BC9ABC258E9862008652BC /* NavigationBarView.swift in Sources */, D68C1E002703EA13002D642B /* UserActivities.swift in Sources */, @@ -1153,6 +1172,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D68C1E1E270605A7002D642B /* BrowserHelper.swift in Sources */, D6E152F124C000A000FDF9D3 /* NavigationManager.swift in Sources */, D6E152F224C000CD00FDF9D3 /* BrowserView.swift in Sources */, ); diff --git a/Gemini.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Gemini.xcworkspace/xcshareddata/swiftpm/Package.resolved index a9b799b..14af2fa 100644 --- a/Gemini.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Gemini.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -2,7 +2,7 @@ "object": { "pins": [ { - "package": "swift-html-entities", + "package": "HTMLEntities", "repositoryURL": "https://github.com/Kitura/swift-html-entities", "state": { "branch": null,