diff --git a/IceCubesActionExtension/Action.js b/IceCubesActionExtension/Action.js new file mode 100644 index 00000000..272b2a43 --- /dev/null +++ b/IceCubesActionExtension/Action.js @@ -0,0 +1,22 @@ +// +// Action.js +// IceCubesActionExtension +// +// Created by Thomas Durand on 26/01/2023. +// + +var Action = function() {}; + +Action.prototype = { + run: function(arguments) { + arguments.completionFunction({ "url" : document.URL }) + }, + finalize: function(arguments) { + var openingUrl = arguments["deeplink"] + if (openingUrl) { + document.location.href = openingUrl + } + } +}; + +var ExtensionPreprocessingJS = new Action diff --git a/IceCubesActionExtension/ActionRequestHandler.swift b/IceCubesActionExtension/ActionRequestHandler.swift new file mode 100644 index 00000000..4a04bad0 --- /dev/null +++ b/IceCubesActionExtension/ActionRequestHandler.swift @@ -0,0 +1,104 @@ +// +// ActionRequestHandler.swift +// IceCubesActionExtension +// +// Created by Thomas Durand on 26/01/2023. +// + +import UIKit +import MobileCoreServices +import UniformTypeIdentifiers + +import Models +import Network + +// Sample code was sending this from a thread to another, let asume @Sendable for this +extension NSExtensionContext: @unchecked Sendable { } + +class ActionRequestHandler: NSObject, NSExtensionRequestHandling { + enum Error: Swift.Error { + case inputProviderNotFound + case loadedItemHasWrongType + case urlNotFound + case noHost + case notMastodonInstance + } + + func beginRequest(with context: NSExtensionContext) { + // Do not call super in an Action extension with no user interface + Task { + do { + let url = try await url(from: context) + guard await url.isMastodonInstance else { + throw Error.notMastodonInstance + } + await MainActor.run { + let deeplink = url.iceCubesAppDeepLink + let output = output(wrapping: deeplink) + context.completeRequest(returningItems: output) + } + } catch { + await MainActor.run { + context.completeRequest(returningItems: []) + } + } + } + } +} + +extension URL { + var isMastodonInstance: Bool { + get async { + do { + guard let host = host() else { + throw ActionRequestHandler.Error.noHost + } + let _: Instance = try await Client(server: host).get(endpoint: Instances.instance) + return true + } catch { + return false + } + } + } + + var iceCubesAppDeepLink: URL { + var components = URLComponents(url: self, resolvingAgainstBaseURL: false)! + components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"]) + return components.url! + } +} + +extension ActionRequestHandler { + /// Will look for an input item that might provide the property list that Javascript sent us + private func url(from context: NSExtensionContext) async throws -> URL { + for item in context.inputItems as! [NSExtensionItem] { + guard let attachments = item.attachments else { + continue + } + for itemProvider in attachments { + guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else { + continue + } + guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else { + throw Error.loadedItemHasWrongType + } + let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:] + guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else { + throw Error.urlNotFound + } + return url + } + } + throw Error.inputProviderNotFound + } + + /// Wrap the output to the expected object so we send back results to JS + private func output(wrapping deeplink: URL) -> [NSExtensionItem] { + let results = ["deeplink": deeplink.absoluteString] + let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results] + let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier) + let item = NSExtensionItem() + item.attachments = [provider] + return [item] + } +} diff --git a/IceCubesActionExtension/Assets.xcassets/ActionIcon.appiconset/ActionIcon1024.png b/IceCubesActionExtension/Assets.xcassets/ActionIcon.appiconset/ActionIcon1024.png new file mode 100644 index 00000000..e93668c5 Binary files /dev/null and b/IceCubesActionExtension/Assets.xcassets/ActionIcon.appiconset/ActionIcon1024.png differ diff --git a/IceCubesActionExtension/Assets.xcassets/ActionIcon.appiconset/Contents.json b/IceCubesActionExtension/Assets.xcassets/ActionIcon.appiconset/Contents.json new file mode 100644 index 00000000..c7748b35 --- /dev/null +++ b/IceCubesActionExtension/Assets.xcassets/ActionIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "ActionIcon1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesActionExtension/Assets.xcassets/Contents.json b/IceCubesActionExtension/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/IceCubesActionExtension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IceCubesActionExtension/Info.plist b/IceCubesActionExtension/Info.plist new file mode 100644 index 00000000..ad25a5c1 --- /dev/null +++ b/IceCubesActionExtension/Info.plist @@ -0,0 +1,33 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsFileWithMaxCount + 0 + NSExtensionActivationSupportsImageWithMaxCount + 0 + NSExtensionActivationSupportsMovieWithMaxCount + 0 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + NSExtensionJavaScriptPreprocessingFile + Action + NSExtensionServiceFinderPreviewIconName + NSActionTemplate + + NSExtensionPointIdentifier + com.apple.services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ActionRequestHandler + + + diff --git a/IceCubesActionExtension/en.lproj/InfoPlist.strings b/IceCubesActionExtension/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..562aa6ea --- /dev/null +++ b/IceCubesActionExtension/en.lproj/InfoPlist.strings @@ -0,0 +1,9 @@ +/* + InfoPlist.strings + IceCubesApp + + Created by Thomas Durand on 27/01/2023. + +*/ + +"CFBundleDisplayName" = "Open in Ice Cubes"; diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 75a57525..b5d1cb17 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -74,7 +74,15 @@ 9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; }; 9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; }; C9B22677297F6C2E001F9EFE /* ContentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B22676297F6C2E001F9EFE /* ContentSettingsView.swift */; }; + E92817FA298443D600875FD1 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E92817F9298443D600875FD1 /* Models */; }; + E92817FC298443D600875FD1 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = E92817FB298443D600875FD1 /* Network */; }; + E92817FE29844DB700875FD1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E92817FD29844DB700875FD1 /* Assets.xcassets */; }; + E970C10829845A9400E88A8C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E970C10A29845A9400E88A8C /* InfoPlist.strings */; }; E9B576C329743F4C00BCE646 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; }; + E9DF41FC29830FEC0003AAD2 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */; }; + E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; }; + E9DF420329830FEC0003AAD2 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = E9DF420229830FEC0003AAD2 /* Action.js */; }; + E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -92,6 +100,13 @@ remoteGlobalIDString = 9FAD858729743F7400496AB1; remoteInfo = IceCubesShareExtension; }; + E9DF420529830FEC0003AAD2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9FBFE631292A715500C250E9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E9DF41F929830FEC0003AAD2; + remoteInfo = IceCubesActionExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -101,6 +116,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */, 9F2A541D296AB631009B2D7C /* IceCubesNotifications.appex in Embed Foundation Extensions */, 9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */, ); @@ -181,8 +197,15 @@ C465A53D297C5E0C00864FB7 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; C9B22676297F6C2E001F9EFE /* ContentSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSettingsView.swift; sourceTree = ""; }; DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = IceCubesApp.xcconfig; sourceTree = ""; }; + E92817FD29844DB700875FD1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + E970C10929845A9400E88A8C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; E9B576C429743F4C00BCE646 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E9B576CC2974AAAF00BCE646 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; + E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRequestHandler.swift; sourceTree = ""; }; + E9DF420229830FEC0003AAD2 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = ""; }; + E9DF420429830FEC0003AAD2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F355EEDA297A8BD500E362C0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -236,6 +259,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E9DF41F729830FEC0003AAD2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E92817FC298443D600875FD1 /* Network in Frameworks */, + E92817FA298443D600875FD1 /* Models in Frameworks */, + E9DF41FC29830FEC0003AAD2 /* UniformTypeIdentifiers.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -321,6 +354,7 @@ DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */, 9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */, 9FBFE63B292A715500C250E9 /* IceCubesApp */, + E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */, 9F2A5417296AB631009B2D7C /* IceCubesNotifications */, 9FAD858929743F7400496AB1 /* IceCubesShareExtension */, 9FBFE63A292A715500C250E9 /* Products */, @@ -346,6 +380,7 @@ 9FBFE639292A715500C250E9 /* IceCubesApp.app */, 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */, 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */, + E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */, ); name = Products; sourceTree = ""; @@ -370,6 +405,7 @@ 9F2A5404296995FB009B2D7C /* QuickLookUI.framework */, 9F7335EE29674F7100AFF0BA /* QuickLook.framework */, 9F7335EB2967461B00AFF0BA /* AVKit.framework */, + E9DF41FB29830FEC0003AAD2 /* UniformTypeIdentifiers.framework */, ); name = Frameworks; sourceTree = ""; @@ -399,6 +435,18 @@ path = Localization; sourceTree = ""; }; + E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */ = { + isa = PBXGroup; + children = ( + E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */, + E9DF420229830FEC0003AAD2 /* Action.js */, + E9DF420429830FEC0003AAD2 /* Info.plist */, + E92817FD29844DB700875FD1 /* Assets.xcassets */, + E970C10A29845A9400E88A8C /* InfoPlist.strings */, + ); + path = IceCubesActionExtension; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -465,6 +513,7 @@ dependencies = ( 9F2A541C296AB631009B2D7C /* PBXTargetDependency */, 9FAD859129743F7400496AB1 /* PBXTargetDependency */, + E9DF420629830FEC0003AAD2 /* PBXTargetDependency */, ); name = IceCubesApp; packageProductDependencies = ( @@ -487,6 +536,27 @@ productReference = 9FBFE639292A715500C250E9 /* IceCubesApp.app */; productType = "com.apple.product-type.application"; }; + E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = E9DF420A29830FEC0003AAD2 /* Build configuration list for PBXNativeTarget "IceCubesActionExtension" */; + buildPhases = ( + E9DF41F629830FEC0003AAD2 /* Sources */, + E9DF41F729830FEC0003AAD2 /* Frameworks */, + E9DF41F829830FEC0003AAD2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IceCubesActionExtension; + packageProductDependencies = ( + E92817F9298443D600875FD1 /* Models */, + E92817FB298443D600875FD1 /* Network */, + ); + productName = IceCubesActionExtension; + productReference = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -506,6 +576,9 @@ 9FBFE638292A715500C250E9 = { CreatedOnToolsVersion = 14.1; }; + E9DF41F929830FEC0003AAD2 = { + CreatedOnToolsVersion = 14.2; + }; }; }; buildConfigurationList = 9FBFE634292A715500C250E9 /* Build configuration list for PBXProject "IceCubesApp" */; @@ -534,6 +607,7 @@ projectRoot = ""; targets = ( 9FBFE638292A715500C250E9 /* IceCubesApp */, + E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */, 9F2A5415296AB631009B2D7C /* IceCubesNotifications */, 9FAD858729743F7400496AB1 /* IceCubesShareExtension */, ); @@ -576,6 +650,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E9DF41F829830FEC0003AAD2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E92817FE29844DB700875FD1 /* Assets.xcassets in Resources */, + E970C10829845A9400E88A8C /* InfoPlist.strings in Resources */, + E9DF420329830FEC0003AAD2 /* Action.js in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -624,6 +708,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E9DF41F629830FEC0003AAD2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -639,6 +731,11 @@ target = 9FAD858729743F7400496AB1 /* IceCubesShareExtension */; targetProxy = 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */; }; + E9DF420629830FEC0003AAD2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E9DF41F929830FEC0003AAD2 /* IceCubesActionExtension */; + targetProxy = E9DF420529830FEC0003AAD2 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -658,6 +755,14 @@ name = Localizable.stringsdict; sourceTree = ""; }; + E970C10A29845A9400E88A8C /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + E970C10929845A9400E88A8C /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; E9B576C529743F4C00BCE646 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -1018,6 +1123,69 @@ }; name = Release; }; + E9DF420829830FEC0003AAD2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcon; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 730; + DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IceCubesActionExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Open in Ice Cube"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.1; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E9DF420929830FEC0003AAD2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = ActionIcon; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 730; + DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IceCubesActionExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Open in Ice Cube"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.1; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1057,6 +1225,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + E9DF420A29830FEC0003AAD2 /* Build configuration list for PBXNativeTarget "IceCubesActionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E9DF420829830FEC0003AAD2 /* Debug */, + E9DF420929830FEC0003AAD2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -1184,6 +1361,14 @@ isa = XCSwiftPackageProductDependency; productName = AppAccount; }; + E92817F9298443D600875FD1 /* Models */ = { + isa = XCSwiftPackageProductDependency; + productName = Models; + }; + E92817FB298443D600875FD1 /* Network */ = { + isa = XCSwiftPackageProductDependency; + productName = Network; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 9FBFE631292A715500C250E9 /* Project object */; diff --git a/IceCubesApp/App/SafariRouter.swift b/IceCubesApp/App/SafariRouter.swift index 3446ee12..be0c6edc 100644 --- a/IceCubesApp/App/SafariRouter.swift +++ b/IceCubesApp/App/SafariRouter.swift @@ -19,8 +19,15 @@ private struct SafariRouter: ViewModifier { func body(content: Content) -> some View { content .environment(\.openURL, OpenURLAction { url in + // Open internal URL. routerPath.handle(url: url) }) + .onOpenURL(perform: { url in + // Open external URL (from icecubesapp://) + let urlString = url.absoluteString.replacingOccurrences(of: "icecubesapp://", with: "https://") + guard let url = URL(string: urlString) else { return } + _ = routerPath.handle(url: url) + }) .onAppear { routerPath.urlHandler = { url in if url.absoluteString.contains("@twitter.com"), url.absoluteString.hasPrefix("mailto:") { diff --git a/Packages/Env/Sources/Env/Router.swift b/Packages/Env/Sources/Env/Router.swift index 6cc1e202..bd7cabab 100644 --- a/Packages/Env/Sources/Env/Router.swift +++ b/Packages/Env/Sources/Env/Router.swift @@ -107,6 +107,16 @@ public class RouterPath: ObservableObject { await navigateToAccountFrom(acct: acct, url: url) } return .handled + } else if let client = client, + client.isAuth, + client.hasConnection(with: url), + let id = Int(url.lastPathComponent) { + if url.absoluteString.contains(client.server) { + navigate(to: .statusDetail(id: String(id))) + } else { + navigate(to: .remoteStatusDetail(url: url)) + } + return .handled } return urlHandler?(url) ?? .systemAction }