Implement Open in Metatext action extension

This commit is contained in:
Vyr Cossont 2022-11-20 16:59:07 -08:00
parent 9bfc6c1449
commit 09da2bca1f
9 changed files with 618 additions and 4 deletions

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.metabolist.metatext</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.metabolist.metatext</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,66 @@
// Copyright © 2022 Metabolist. All rights reserved.
import MobileCoreServices
import UIKit
import UniformTypeIdentifiers
/// Let the user open a URL that might be a Mastodon profile or post in Metatext.
/// Should activate for one or more web URLs, but will only do anything with the first..
class ActionExtensionViewController: UIViewController {
/// Extensions aren't allowed to call `UIApplication.shared
/// and thus don't have direct access to its `openURL` method.
/// `self.extensionContext?.open(<#T##URL: URL##URL#>)` only works for Today extensions.
/// As a workaround, we find a parent responder that has an `openURL(_:)` method.
/// This will be `UIApplication`. It's cursed, but it uses public APIs and works.
/// See <https://liman.io/blog/open-url-share-extension-swiftui>.
private func open(url: URL) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: url)
return
}
responder = responder?.next
}
}
/// Only exists so we can create a selector from it.
@objc private func openURL(_ url: URL) {
return
}
/// This extension has no actual UI, so we act on extension input items as soon as we load.
override func viewDidLoad() {
super.viewDidLoad()
// Find the first URL or thing coerceable to a URL.
for item in self.extensionContext!.inputItems as? [NSExtensionItem] ?? [] {
for provider in item.attachments! where provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
_ = provider.loadObject(ofClass: URL.self) { (url, error) in
OperationQueue.main.addOperation {
if let error = error {
self.extensionContext!.cancelRequest(withError: error)
} else if let url = url {
// Create a `metatext:search?url=https` URL from our web URL.
var urlBuilder = URLComponents()
urlBuilder.scheme = "metatext"
urlBuilder.path = "search"
urlBuilder.queryItems = [.init(name: "url", value: url.absoluteString)]
let metatextURL = urlBuilder.url!
self.open(url: metatextURL)
self.extensionContext!.completeRequest(returningItems: [])
} else {
// Should never happen. Return a generic not-found error.
self.extensionContext!.cancelRequest(withError: CocoaError(.fileNoSuchFile))
}
}
}
// We do not attempt to handle multiple URLs.
return
}
}
// No URLs. Return a generic not-found error.
self.extensionContext!.cancelRequest(withError: CocoaError(.fileNoSuchFile))
}
}

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleDisplayName</key>
<string>Action Extension</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ActionExtensionViewController</string>
</dict>
</dict>
</plist>

View file

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
94B2C8C02929617A00087955 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94B2C8BF2929617A00087955 /* UniformTypeIdentifiers.framework */; };
94B2C8C52929617A00087955 /* ActionExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B2C8C42929617A00087955 /* ActionExtensionViewController.swift */; };
94B2C8CC2929617A00087955 /* Action Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 94B2C8BE2929617A00087955 /* Action Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0030981250C6C8500EACB32 /* URL+Extensions.swift */; };
D005A1D825EF189A008B2E63 /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1D725EF189A008B2E63 /* ReportViewController.swift */; };
D005A1E625EF3D11008B2E63 /* ReportHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */; };
@ -248,6 +251,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
94B2C8CA2929617A00087955 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D047FA8024C3E21000AF17C5 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 94B2C8BD2929617A00087955;
remoteInfo = "com.metabolist.metatext.action-extension";
};
D0666A2624C677B400F3F04B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D047FA8024C3E21000AF17C5 /* Project object */;
@ -279,6 +289,7 @@
dstSubfolderSpec = 13;
files = (
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed Foundation Extensions */,
94B2C8CC2929617A00087955 /* Action Extension.appex in Embed Foundation Extensions */,
D08E5276257C36CA00FA2C5F /* Share Extension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
@ -299,6 +310,11 @@
68865A2F292349BF001BF177 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
68865A3029234A68001BF177 /* gd */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gd; path = gd.lproj/Localizable.strings; sourceTree = "<group>"; };
68865A3129234A68001BF177 /* gd */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = gd; path = gd.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
94B2C8BE2929617A00087955 /* Action Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Action Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
94B2C8BF2929617A00087955 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
94B2C8C42929617A00087955 /* ActionExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionExtensionViewController.swift; sourceTree = "<group>"; };
94B2C8C92929617A00087955 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
94B2C8CD2929617A00087955 /* Action Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Action Extension.entitlements"; sourceTree = "<group>"; };
D0030981250C6C8500EACB32 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
D005A1D725EF189A008B2E63 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = "<group>"; };
D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportHeaderView.swift; sourceTree = "<group>"; };
@ -510,6 +526,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
94B2C8BB2929617A00087955 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
94B2C8C02929617A00087955 /* UniformTypeIdentifiers.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D047FA8924C3E21200AF17C5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -550,6 +574,16 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
94B2C8C12929617A00087955 /* Action Extension */ = {
isa = PBXGroup;
children = (
94B2C8CD2929617A00087955 /* Action Extension.entitlements */,
94B2C8C42929617A00087955 /* ActionExtensionViewController.swift */,
94B2C8C92929617A00087955 /* Info.plist */,
);
path = "Action Extension";
sourceTree = "<group>";
};
D021A66325C3E167008A0C0D /* UIKit */ = {
isa = PBXGroup;
children = (
@ -723,6 +757,7 @@
isa = PBXGroup;
children = (
D0477F2A25C6EB90005C5368 /* Activities */,
94B2C8C12929617A00087955 /* Action Extension */,
D0D4301925F08CE600BE5504 /* App Icons */,
D0C7D45224F76169001EBDBB /* Assets.xcassets */,
D0FE1C9625368A15003EF1EB /* Caches */,
@ -758,6 +793,7 @@
D0666A2124C677B400F3F04B /* Tests.xctest */,
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */,
D08E526C257C36CA00FA2C5F /* Share Extension.appex */,
94B2C8BE2929617A00087955 /* Action Extension.appex */,
);
name = Products;
sourceTree = "<group>";
@ -773,6 +809,7 @@
D0666A7924C7745A00F3F04B /* Frameworks */ = {
isa = PBXGroup;
children = (
94B2C8BF2929617A00087955 /* UniformTypeIdentifiers.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -947,6 +984,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
94B2C8BD2929617A00087955 /* Action Extension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 94B2C8D02929617A00087955 /* Build configuration list for PBXNativeTarget "Action Extension" */;
buildPhases = (
94B2C8BA2929617A00087955 /* Sources */,
94B2C8BB2929617A00087955 /* Frameworks */,
94B2C8BC2929617A00087955 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Action Extension";
productName = "com.metabolist.metatext.action-extension";
productReference = 94B2C8BE2929617A00087955 /* Action Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
D047FA8B24C3E21200AF17C5 /* Metatext */ = {
isa = PBXNativeTarget;
buildConfigurationList = D047FAB624C3E21200AF17C5 /* Build configuration list for PBXNativeTarget "Metatext" */;
@ -962,6 +1016,7 @@
dependencies = (
D0E5361F24E3EB4D00FB1CE1 /* PBXTargetDependency */,
D08E5275257C36CA00FA2C5F /* PBXTargetDependency */,
94B2C8CB2929617A00087955 /* PBXTargetDependency */,
);
name = Metatext;
packageProductDependencies = (
@ -1043,10 +1098,13 @@
D047FA8024C3E21000AF17C5 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1220;
LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1410;
ORGANIZATIONNAME = Metabolist;
TargetAttributes = {
94B2C8BD2929617A00087955 = {
CreatedOnToolsVersion = 14.1;
};
D047FA8B24C3E21200AF17C5 = {
CreatedOnToolsVersion = 12.0;
};
@ -1096,11 +1154,19 @@
D0666A2024C677B400F3F04B /* Tests */,
D0E5361824E3EB4D00FB1CE1 /* Notification Service Extension */,
D08E526B257C36CA00FA2C5F /* Share Extension */,
94B2C8BD2929617A00087955 /* Action Extension */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
94B2C8BC2929617A00087955 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D047FA8A24C3E21200AF17C5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -1170,6 +1236,14 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
94B2C8BA2929617A00087955 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
94B2C8C52929617A00087955 /* ActionExtensionViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D047FA8824C3E21200AF17C5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -1411,6 +1485,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
94B2C8CB2929617A00087955 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 94B2C8BD2929617A00087955 /* Action Extension */;
targetProxy = 94B2C8CA2929617A00087955 /* PBXContainerItemProxy */;
};
D0666A2724C677B400F3F04B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D047FA8B24C3E21200AF17C5 /* Metatext */;
@ -1472,6 +1551,69 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
94B2C8CE2929617A00087955 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 82HL67AXQ2;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Action Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Open in Metatext";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Metabolist. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 14.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.6.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.metabolist.metatext.action-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
94B2C8CF2929617A00087955 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "Action Extension/Action Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 82HL67AXQ2;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Action Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Open in Metatext";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Metabolist. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 14.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.6.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.metabolist.metatext.action-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
D047FAB424C3E21200AF17C5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1801,6 +1943,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
94B2C8D02929617A00087955 /* Build configuration list for PBXNativeTarget "Action Extension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
94B2C8CE2929617A00087955 /* Debug */,
94B2C8CF2929617A00087955 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D047FA8324C3E21000AF17C5 /* Build configuration list for PBXProject "Metatext" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "94B2C8BD2929617A00087955"
BuildableName = "Action Extension.appex"
BlueprintName = "Action Extension"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<RemoteRunnable
runnableDebuggingMode = "1"
BundleIdentifier = "com.apple.mobilesafari"
RemotePath = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/Applications/MobileSafari.app">
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E5361824E3EB4D00FB1CE1"
BuildableName = "Notification Service Extension.appex"
BlueprintName = "Notification Service Extension"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D08E526B257C36CA00FA2C5F"
BuildableName = "Share Extension.appex"
BlueprintName = "Share Extension"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D047FA8B24C3E21200AF17C5"
BuildableName = "Metatext.app"
BlueprintName = "Metatext"
ReferencedContainer = "container:Metatext.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -118,6 +118,21 @@
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLIconFile</key>
<string>AppIconClassic@3x</string>
<key>CFBundleURLName</key>
<string>com.metabolist.metatext</string>
<key>CFBundleURLSchemes</key>
<array>
<string>metatext</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
@ -147,16 +162,16 @@
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>

View file

@ -13,6 +13,8 @@ struct RootView: View {
.id(navigationViewModel.identityContext.identity.id)
.environmentObject(viewModel)
.transition(.opacity)
.handlesExternalEvents(preferring: ["*"], allowing: ["*"])
.onOpenURL(perform: { openUrl(navigationViewModel, $0) })
.edgesIgnoringSafeArea(.all)
.onReceive(navigationViewModel.identityContext.$appPreferences.map(\.colorScheme),
perform: setColorScheme)
@ -27,6 +29,37 @@ struct RootView: View {
.environmentObject(viewModel)
.navigationViewStyle(StackNavigationViewStyle())
.transition(.opacity)
.handlesExternalEvents(preferring: ["*"], allowing: ["*"])
.onOpenURL(perform: { openUrl(nil, $0) })
}
}
/// Open `metatext:` URLs from the action extension.
private func openUrl(_ navigationViewModel: NavigationViewModel?, _ metatextUrl: URL) {
guard let navigationViewModel = navigationViewModel else {
// TODO: (Vyr) We haven't logged into an instance yet. Opening a URL should show an error message.
return
}
guard
let metatextComponents = URLComponents(url: metatextUrl, resolvingAgainstBaseURL: true),
metatextComponents.scheme == "metatext"
else {
return
}
switch metatextComponents.path {
case "search":
// Expecting `metatext:search?url=https://`, which we open by searching for the wrapped URL.
guard
let searchUrlString = metatextComponents.queryItems?.first(where: { $0.name == "url" })?.value,
let searchComponents = URLComponents(string: searchUrlString),
searchComponents.scheme == "https",
let searchUrl = searchComponents.url
else {
return
}
navigationViewModel.navigateToURL(searchUrl)
default:
break
}
}
}