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
}