diff --git a/OpenInTusker/Action.js b/OpenInTusker/Action.js
new file mode 100644
index 0000000000..932ba21b04
--- /dev/null
+++ b/OpenInTusker/Action.js
@@ -0,0 +1,29 @@
+//
+// Action.js
+// OpenInTusker
+//
+// Created by Shadowfacts on 5/22/21.
+// Copyright © 2021 Shadowfacts. All rights reserved.
+//
+
+var Action = function() {};
+
+Action.prototype = {
+
+ run: function(arguments) {
+ const results = {
+ url: window.location.href,
+ };
+ const el = document.querySelector('link[rel=alternate][type="application/activity+json"]');
+ if (el) {
+ results.activityPubURL = el.href;
+ }
+ arguments.completionFunction(results);
+ },
+
+ finalize: function(arguments) {
+ }
+
+};
+
+var ExtensionPreprocessingJS = new Action();
diff --git a/OpenInTusker/ActionViewController.swift b/OpenInTusker/ActionViewController.swift
new file mode 100644
index 0000000000..ff89ef27fc
--- /dev/null
+++ b/OpenInTusker/ActionViewController.swift
@@ -0,0 +1,100 @@
+//
+// ActionViewController.swift
+// OpenInTusker
+//
+// Created by Shadowfacts on 5/23/21.
+// Copyright © 2021 Shadowfacts. All rights reserved.
+//
+
+import UIKit
+import MobileCoreServices
+
+class ActionViewController: UIViewController {
+
+ @IBOutlet weak var imageView: UIImageView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ findURLFromWebPage { (components) in
+ if let components = components {
+ self.searchForURLInApp(components)
+ } else {
+ self.findURLItem { (components) in
+ if let components = components {
+ self.searchForURLInApp(components)
+ }
+ }
+ }
+ }
+ }
+
+ private func findURLFromWebPage(completion: @escaping (URLComponents?) -> Void) {
+ for item in extensionContext!.inputItems as! [NSExtensionItem] {
+ for provider in item.attachments! {
+ guard provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) else {
+ continue
+ }
+ provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil) { (result, error) in
+ guard let result = result as? [String: Any],
+ let jsResult = result[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any],
+ let urlString = jsResult["activityPubURL"] as? String ?? jsResult["url"] as? String,
+ let components = URLComponents(string: urlString) else {
+ completion(nil)
+ return
+ }
+
+ completion(components)
+ }
+ return
+ }
+ }
+
+ completion(nil)
+ }
+
+ private func findURLItem(completion: @escaping (URLComponents?) -> Void) {
+ for item in extensionContext!.inputItems as! [NSExtensionItem] {
+ for provider in item.attachments! {
+ guard provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) else {
+ continue
+ }
+ provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (result, error) in
+ guard let result = result as? URL,
+ let components = URLComponents(url: result, resolvingAgainstBaseURL: false) else {
+ completion(nil)
+ return
+ }
+ completion(components)
+ }
+ return
+ }
+ }
+
+ completion(nil)
+ }
+
+ private func searchForURLInApp(_ components: URLComponents) {
+ var components = components
+ components.scheme = "tusker"
+ self.openURL(components.url!)
+ self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
+ }
+
+ @objc private func openURL(_ url: URL) {
+ var responder: UIResponder = self
+ while let parent = responder.next {
+ if let application = parent as? UIApplication {
+ application.perform(#selector(openURL(_:)), with: url)
+ break
+ } else {
+ responder = parent
+ }
+ }
+ }
+
+ @IBAction func done() {
+ extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
+ }
+
+}
diff --git a/OpenInTusker/Base.lproj/MainInterface.storyboard b/OpenInTusker/Base.lproj/MainInterface.storyboard
new file mode 100644
index 0000000000..5ba0725287
--- /dev/null
+++ b/OpenInTusker/Base.lproj/MainInterface.storyboard
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenInTusker/Info.plist b/OpenInTusker/Info.plist
new file mode 100644
index 0000000000..f974a9c348
--- /dev/null
+++ b/OpenInTusker/Info.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Open in Tusker
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSExtension
+
+ NSExtensionAttributes
+
+ NSExtensionJavaScriptPreprocessingFile
+ Action
+ NSExtensionActivationRule
+
+ NSExtensionActivationSupportsAttachmentsWithMinCount
+ 1
+ NSExtensionActivationSupportsWebPageWithMaxCount
+ 1
+ NSExtensionActivationSupportsWebURLWithMaxCount
+ 1
+
+ NSExtensionServiceAllowsFinderPreviewItem
+
+ NSExtensionServiceAllowsTouchBarItem
+
+ NSExtensionServiceFinderPreviewIconName
+ NSActionTemplate
+ NSExtensionServiceTouchBarBezelColorName
+ TouchBarBezel
+ NSExtensionServiceTouchBarIconName
+ NSActionTemplate
+
+ NSExtensionMainStoryboard
+ MainInterface
+ NSExtensionPointIdentifier
+ com.apple.ui-services
+
+
+
diff --git a/OpenInTusker/Media.xcassets/AppIcon.appiconset/1024x1024@1x.png b/OpenInTusker/Media.xcassets/AppIcon.appiconset/1024x1024@1x.png
new file mode 100644
index 0000000000..a8995cff47
Binary files /dev/null and b/OpenInTusker/Media.xcassets/AppIcon.appiconset/1024x1024@1x.png differ
diff --git a/OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@2x.png b/OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@2x.png
new file mode 100644
index 0000000000..5d0298fa25
Binary files /dev/null and b/OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@2x.png differ
diff --git a/OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@3x.png b/OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@3x.png
new file mode 100644
index 0000000000..453f43f87b
Binary files /dev/null and b/OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@3x.png differ
diff --git a/OpenInTusker/Media.xcassets/AppIcon.appiconset/76x76@2x.png b/OpenInTusker/Media.xcassets/AppIcon.appiconset/76x76@2x.png
new file mode 100644
index 0000000000..4c21b73cba
Binary files /dev/null and b/OpenInTusker/Media.xcassets/AppIcon.appiconset/76x76@2x.png differ
diff --git a/OpenInTusker/Media.xcassets/AppIcon.appiconset/83.5x83.5@2x.png b/OpenInTusker/Media.xcassets/AppIcon.appiconset/83.5x83.5@2x.png
new file mode 100644
index 0000000000..23356523b2
Binary files /dev/null and b/OpenInTusker/Media.xcassets/AppIcon.appiconset/83.5x83.5@2x.png differ
diff --git a/OpenInTusker/Media.xcassets/AppIcon.appiconset/Contents.json b/OpenInTusker/Media.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..852eaa2a21
--- /dev/null
+++ b/OpenInTusker/Media.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,103 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "60x60@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "60x60@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "76x76@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "83.5x83.5@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "1024x1024@1x.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OpenInTusker/Media.xcassets/Contents.json b/OpenInTusker/Media.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/OpenInTusker/Media.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/OpenInTusker/Media.xcassets/TouchBarBezel.colorset/Contents.json b/OpenInTusker/Media.xcassets/TouchBarBezel.colorset/Contents.json
new file mode 100644
index 0000000000..94a9fc2181
--- /dev/null
+++ b/OpenInTusker/Media.xcassets/TouchBarBezel.colorset/Contents.json
@@ -0,0 +1,14 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "colors" : [
+ {
+ "idiom" : "mac",
+ "color" : {
+ "reference" : "systemPurpleColor"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/OpenInTusker/OpenInTusker.entitlements b/OpenInTusker/OpenInTusker.entitlements
new file mode 100644
index 0000000000..ee95ab7e58
--- /dev/null
+++ b/OpenInTusker/OpenInTusker.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+
+
diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj
index 1389c16d84..036e95c8b4 100644
--- a/Tusker.xcodeproj/project.pbxproj
+++ b/Tusker.xcodeproj/project.pbxproj
@@ -307,6 +307,11 @@
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */; };
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */; };
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; };
+ D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
+ D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
+ D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
+ D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = D6E343B9265AAD8C00C4AA01 /* Action.js */; };
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */; };
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
@@ -363,9 +368,27 @@
remoteGlobalIDString = D6D4DDCB212518A000E1C4BB;
remoteInfo = Tusker;
};
+ D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = D6E343A7265AAD6B00C4AA01;
+ remoteInfo = OpenInTusker;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
+ D6E3438F2659849800C4AA01 /* Embed App Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed App Extensions */,
+ );
+ name = "Embed App Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
D6F953E52125197500CF0F2B /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -687,6 +710,13 @@
D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadScrollGestureRecognizer.swift; sourceTree = ""; };
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakArray.swift; sourceTree = ""; };
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = ""; };
+ D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInTusker.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ D6E343AA265AAD6B00C4AA01 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; };
+ D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; };
+ D6E343AF265AAD6B00C4AA01 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
+ D6E343B1265AAD6B00C4AA01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ D6E343B5265AAD6B00C4AA01 /* OpenInTusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenInTusker.entitlements; sourceTree = ""; };
+ D6E343B9265AAD8C00C4AA01 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = ""; };
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAutocompleteView.swift; sourceTree = ""; };
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcher.swift; sourceTree = ""; };
D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcherTests.swift; sourceTree = ""; };
@@ -752,6 +782,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ D6E343A5265AAD6B00C4AA01 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -1472,6 +1509,7 @@
D6D4DDCE212518A000E1C4BB /* Tusker */,
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
+ D6E343A9265AAD6B00C4AA01 /* OpenInTusker */,
D6D4DDCD212518A000E1C4BB /* Products */,
D65A37F221472F300087646E /* Frameworks */,
);
@@ -1485,6 +1523,7 @@
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
+ D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */,
);
name = Products;
sourceTree = "";
@@ -1552,6 +1591,19 @@
path = TuskerUITests;
sourceTree = "";
};
+ D6E343A9265AAD6B00C4AA01 /* OpenInTusker */ = {
+ isa = PBXGroup;
+ children = (
+ D6E343B5265AAD6B00C4AA01 /* OpenInTusker.entitlements */,
+ D6E343AA265AAD6B00C4AA01 /* Media.xcassets */,
+ D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */,
+ D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */,
+ D6E343B1265AAD6B00C4AA01 /* Info.plist */,
+ D6E343B9265AAD8C00C4AA01 /* Action.js */,
+ );
+ path = OpenInTusker;
+ sourceTree = "";
+ };
D6F1F84E2193B9BE00F5FE67 /* Caching */ = {
isa = PBXGroup;
children = (
@@ -1642,11 +1694,13 @@
D6D4DDCA212518A000E1C4BB /* Resources */,
D6F953E52125197500CF0F2B /* Embed Frameworks */,
D65F612C23AE957600F3CFD3 /* Embed debug-only frameworks */,
+ D6E3438F2659849800C4AA01 /* Embed App Extensions */,
);
buildRules = (
);
dependencies = (
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
+ D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */,
);
name = Tusker;
packageProductDependencies = (
@@ -1696,13 +1750,30 @@
productReference = D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
+ D6E343A7265AAD6B00C4AA01 /* OpenInTusker */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = D6E343B6265AAD6B00C4AA01 /* Build configuration list for PBXNativeTarget "OpenInTusker" */;
+ buildPhases = (
+ D6E343A4265AAD6B00C4AA01 /* Sources */,
+ D6E343A5265AAD6B00C4AA01 /* Frameworks */,
+ D6E343A6265AAD6B00C4AA01 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = OpenInTusker;
+ productName = OpenInTusker;
+ productReference = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
D6D4DDC4212518A000E1C4BB /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 1200;
+ LastSwiftUpdateCheck = 1250;
LastUpgradeCheck = 1250;
ORGANIZATIONNAME = Shadowfacts;
TargetAttributes = {
@@ -1729,6 +1800,9 @@
LastSwiftMigration = 1020;
TestTargetID = D6D4DDCB212518A000E1C4BB;
};
+ D6E343A7265AAD6B00C4AA01 = {
+ CreatedOnToolsVersion = 12.5;
+ };
};
};
buildConfigurationList = D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */;
@@ -1760,6 +1834,7 @@
D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
D61099AA2144B0CC00432DC2 /* Pachyderm */,
D61099B22144B0CC00432DC2 /* PachydermTests */,
+ D6E343A7265AAD6B00C4AA01 /* OpenInTusker */,
);
};
/* End PBXProject section */
@@ -1827,6 +1902,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ D6E343A6265AAD6B00C4AA01 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */,
+ D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */,
+ D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -2170,6 +2255,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ D6E343A4265AAD6B00C4AA01 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -2198,6 +2291,11 @@
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
targetProxy = D6D4DDEC212518A200E1C4BB /* PBXContainerItemProxy */;
};
+ D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = D6E343A7265AAD6B00C4AA01 /* OpenInTusker */;
+ targetProxy = D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -2209,6 +2307,14 @@
name = LaunchScreen.storyboard;
sourceTree = "";
};
+ D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ D6E343AF265AAD6B00C4AA01 /* Base */,
+ );
+ name = MainInterface.storyboard;
+ sourceTree = "";
+ };
D6E57FA525C26FAB00341037 /* Localizable.stringsdict */ = {
isa = PBXVariantGroup;
children = (
@@ -2234,6 +2340,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Pachyderm/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2264,6 +2371,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Pachyderm/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2451,7 +2559,7 @@
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
- "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.0;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2480,7 +2588,7 @@
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
- "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.0;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2578,6 +2686,60 @@
};
name = Release;
};
+ D6E343B7265AAD6B00C4AA01 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 19;
+ DEVELOPMENT_TEAM = V4WK9KR9U2;
+ INFOPLIST_FILE = OpenInTusker/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 2021.1;
+ PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.OpenInTusker;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SUPPORTS_MACCATALYST = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,6";
+ };
+ name = Debug;
+ };
+ D6E343B8265AAD6B00C4AA01 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 19;
+ DEVELOPMENT_TEAM = V4WK9KR9U2;
+ INFOPLIST_FILE = OpenInTusker/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 2021.1;
+ PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.OpenInTusker;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SUPPORTS_MACCATALYST = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,6";
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -2635,6 +2797,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ D6E343B6265AAD6B00C4AA01 /* Build configuration list for PBXNativeTarget "OpenInTusker" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ D6E343B7265AAD6B00C4AA01 /* Debug */,
+ D6E343B8265AAD6B00C4AA01 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
diff --git a/Tusker/AppDelegate.swift b/Tusker/AppDelegate.swift
index def7da5659..a3ec319465 100644
--- a/Tusker/AppDelegate.swift
+++ b/Tusker/AppDelegate.swift
@@ -76,9 +76,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
case .newPost:
return "compose"
-
- default:
- fatalError("no scene for activity type \(type)")
}
}
}
diff --git a/Tusker/MainSceneDelegate.swift b/Tusker/MainSceneDelegate.swift
index cf693152ce..4e51578398 100644
--- a/Tusker/MainSceneDelegate.swift
+++ b/Tusker/MainSceneDelegate.swift
@@ -28,6 +28,9 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
handlePendingCrashReport(report, session: session)
} else {
showAppOrOnboardingUI(session: session)
+ if connectionOptions.urlContexts.count > 0 {
+ self.scene(scene, openURLContexts: connectionOptions.urlContexts)
+ }
}
window!.makeKeyAndVisible()
@@ -50,20 +53,10 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
if url.host == "x-callback-url" {
_ = XCBManager.handle(url: url)
} else if var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
- let tabBarController = window!.rootViewController as? MainTabBarViewController,
- let exploreNavController = tabBarController.getTabController(tab: .explore) as? UINavigationController,
- let exploreController = exploreNavController.viewControllers.first as? ExploreViewController {
-
- tabBarController.select(tab: .explore)
- exploreNavController.popToRootViewController(animated: false)
-
- exploreController.loadViewIfNeeded()
- exploreController.searchController.isActive = true
-
+ let rootViewController = window!.rootViewController as? TuskerRootViewController {
components.scheme = "https"
- let query = url.absoluteString
- exploreController.searchController.searchBar.text = query
- exploreController.resultsController.performSearch(query: query)
+ let query = components.string!
+ rootViewController.performSearch(query: query)
}
}
diff --git a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift
index 9dbaf7b264..4c7a30cde5 100644
--- a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift
+++ b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift
@@ -71,14 +71,21 @@ extension AccountSwitchingContainerViewController {
extension AccountSwitchingContainerViewController: TuskerRootViewController {
func presentCompose() {
+ loadViewIfNeeded()
root.presentCompose()
}
func select(tab: MainTabBarViewController.Tab) {
+ loadViewIfNeeded()
root.select(tab: tab)
}
func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? {
- root.getTabController(tab: tab)
+ loadViewIfNeeded()
+ return root.getTabController(tab: tab)
+ }
+
+ func performSearch(query: String) {
+ root.performSearch(query: query)
}
}
diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift
index dd021d1837..539223284a 100644
--- a/Tusker/Screens/Main/MainSidebarViewController.swift
+++ b/Tusker/Screens/Main/MainSidebarViewController.swift
@@ -48,7 +48,7 @@ class MainSidebarViewController: UIViewController {
private(set) var previouslySelectedItem: Item?
var selectedItem: Item? {
- guard let indexPath = collectionView.indexPathsForSelectedItems?.first else {
+ guard let indexPath = collectionView?.indexPathsForSelectedItems?.first else {
return nil
}
return dataSource.itemIdentifier(for: indexPath)
diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift
index 49f7cd698a..5759858309 100644
--- a/Tusker/Screens/Main/MainSplitViewController.swift
+++ b/Tusker/Screens/Main/MainSplitViewController.swift
@@ -19,6 +19,10 @@ class MainSplitViewController: UISplitViewController {
private var tabBarViewController: MainTabBarViewController!
+ private var secondaryNavController: UINavigationController! {
+ viewController(for: .secondary) as? UINavigationController
+ }
+
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portrait
@@ -67,8 +71,7 @@ class MainSplitViewController: UISplitViewController {
}
func select(item: MainSidebarViewController.Item) {
- let nav = viewController(for: .secondary) as! UINavigationController
- nav.viewControllers = getOrCreateNavigationStack(item: item)
+ secondaryNavController.viewControllers = getOrCreateNavigationStack(item: item)
}
func getOrCreateNavigationStack(item: MainSidebarViewController.Item) -> [UIViewController] {
@@ -109,8 +112,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
private func transferNavigationStack(from item: MainSidebarViewController.Item, to destination: UINavigationController, dropFirst: Bool = false, append: Bool = false) {
var itemNavStack: [UIViewController]
if item == sidebar.selectedItem {
- let detailNav = viewController(for: .secondary) as! UINavigationController
- itemNavStack = detailNav.viewControllers
+ itemNavStack = secondaryNavController.viewControllers
} else {
itemNavStack = navigationStacks[item] ?? []
navigationStacks.removeValue(forKey: item)
@@ -190,8 +192,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
// Make sure viewDidLoad is called so that the searchController/resultsController have been initialized
explore.loadViewIfNeeded()
- let nav = viewController(for: .secondary) as! UINavigationController
- let search = nav.viewControllers.first as! SearchViewController
+ let search = secondaryNavController.viewControllers.first as! SearchViewController
// Copy the search query from the search VC to the Explore VC's search controller.
let query = search.searchController.searchBar.text ?? ""
explore.searchController.searchBar.text = query
@@ -318,9 +319,8 @@ extension MainSplitViewController: MainSidebarViewControllerDelegate {
}
func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item) {
- let nav = viewController(for: .secondary) as! UINavigationController
if let previous = sidebar.previouslySelectedItem {
- navigationStacks[previous] = nav.viewControllers
+ navigationStacks[previous] = secondaryNavController.viewControllers
}
select(item: item)
}
@@ -366,8 +366,15 @@ extension MainSplitViewController: TuskerRootViewController {
if tab == .compose {
presentCompose()
} else {
- select(item: .tab(tab))
- sidebar.select(item: .tab(tab), animated: false)
+ if presentedViewController != nil {
+ dismiss(animated: true) {
+ self.select(item: .tab(tab))
+ self.sidebar.select(item: .tab(tab), animated: false)
+ }
+ } else {
+ select(item: .tab(tab))
+ sidebar.select(item: .tab(tab), animated: false)
+ }
}
}
}
@@ -379,12 +386,43 @@ extension MainSplitViewController: TuskerRootViewController {
if tab == .compose {
return nil
} else if case .tab(tab) = sidebar.selectedItem {
- return viewController(for: .secondary)
+ return secondaryNavController
} else {
return nil
}
}
}
+
+ func performSearch(query: String) {
+ guard traitCollection.horizontalSizeClass != .compact else {
+ // ensure the tab bar VC is loaded
+ loadViewIfNeeded()
+ tabBarViewController.performSearch(query: query)
+ return
+ }
+
+ if sidebar.selectedItem != .search {
+ select(item: .search)
+ }
+
+ guard let searchViewController = secondaryNavController.viewControllers.first as? SearchViewController else {
+ return
+ }
+
+ secondaryNavController.popToRootViewController(animated: false)
+
+ if searchViewController.isViewLoaded {
+ DispatchQueue.main.async {
+ searchViewController.searchController.isActive = true
+ }
+ } else {
+ searchViewController.searchControllerStatusOnAppearance = true
+ searchViewController.loadViewIfNeeded()
+ }
+
+ searchViewController.searchController.searchBar.text = query
+ searchViewController.resultsController.performSearch(query: query)
+ }
}
extension MainSplitViewController: BackgroundableViewController {
diff --git a/Tusker/Screens/Main/MainTabBarViewController.swift b/Tusker/Screens/Main/MainTabBarViewController.swift
index ceebf63d53..dead5878fe 100644
--- a/Tusker/Screens/Main/MainTabBarViewController.swift
+++ b/Tusker/Screens/Main/MainTabBarViewController.swift
@@ -137,6 +137,8 @@ extension MainTabBarViewController {
if tab == .compose {
return nil
} else {
+ // viewWControllers array is setup in viewDidLoad
+ loadViewIfNeeded()
return viewControllers![tab.rawValue]
}
}
@@ -168,9 +170,39 @@ extension MainTabBarViewController: TuskerRootViewController {
if tab == .compose {
presentCompose()
} else {
- selectedIndex = tab.rawValue
+ // when switching tabs, dismiss the currently presented VC
+ // otherwise the selected tab changes behind the presented VC
+ if presentedViewController != nil {
+ dismiss(animated: true) {
+ self.selectedIndex = tab.rawValue
+ }
+ } else {
+ selectedIndex = tab.rawValue
+ }
}
}
+
+ func performSearch(query: String) {
+ guard let exploreNavController = getTabController(tab: .explore) as? UINavigationController,
+ let exploreController = exploreNavController.viewControllers.first as? ExploreViewController else {
+ return
+ }
+
+ select(tab: .explore)
+ exploreNavController.popToRootViewController(animated: false)
+
+ // setting searchController.isActive directly doesn't work until the view has loaded/appeared for the first time
+ if exploreController.isViewLoaded {
+ exploreController.searchController.isActive = true
+ } else {
+ exploreController.searchControllerStatusOnAppearance = true
+ // we still need to load the view so that we can setup the search query
+ exploreController.loadViewIfNeeded()
+ }
+
+ exploreController.searchController.searchBar.text = query
+ exploreController.resultsController.performSearch(query: query)
+ }
}
extension MainTabBarViewController: BackgroundableViewController {
diff --git a/Tusker/Screens/Main/TuskerRootViewController.swift b/Tusker/Screens/Main/TuskerRootViewController.swift
index 94f7b3f6cb..80baafe9aa 100644
--- a/Tusker/Screens/Main/TuskerRootViewController.swift
+++ b/Tusker/Screens/Main/TuskerRootViewController.swift
@@ -12,4 +12,5 @@ protocol TuskerRootViewController: UIViewController {
func presentCompose()
func select(tab: MainTabBarViewController.Tab)
func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController?
+ func performSearch(query: String)
}
diff --git a/Tusker/Screens/Search/SearchViewController.swift b/Tusker/Screens/Search/SearchViewController.swift
index cedd099b4e..73b735a767 100644
--- a/Tusker/Screens/Search/SearchViewController.swift
+++ b/Tusker/Screens/Search/SearchViewController.swift
@@ -37,7 +37,7 @@ class SearchViewController: UIViewController {
resultsController = SearchResultsViewController(mastodonController: mastodonController)
resultsController.exploreNavigationController = self.navigationController
searchController = UISearchController(searchResultsController: resultsController)
- searchController.searchResultsUpdater = resultsController
+ searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.autocapitalizationType = .none
searchController.searchBar.delegate = resultsController
searchController.hidesNavigationBarDuringPresentation = false