Browse Source

Add Open in Tusker action extension

async
Shadowfacts 5 months ago
parent
commit
18ce21c2c6
Signed by: shadowfacts GPG Key ID: 94A5AB95422746E5
  1. 29
      OpenInTusker/Action.js
  2. 100
      OpenInTusker/ActionViewController.swift
  3. 65
      OpenInTusker/Base.lproj/MainInterface.storyboard
  4. 55
      OpenInTusker/Info.plist
  5. BIN
      OpenInTusker/Media.xcassets/AppIcon.appiconset/1024x1024@1x.png
  6. BIN
      OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@2x.png
  7. BIN
      OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@3x.png
  8. BIN
      OpenInTusker/Media.xcassets/AppIcon.appiconset/76x76@2x.png
  9. BIN
      OpenInTusker/Media.xcassets/AppIcon.appiconset/83.5x83.5@2x.png
  10. 103
      OpenInTusker/Media.xcassets/AppIcon.appiconset/Contents.json
  11. 6
      OpenInTusker/Media.xcassets/Contents.json
  12. 14
      OpenInTusker/Media.xcassets/TouchBarBezel.colorset/Contents.json
  13. 10
      OpenInTusker/OpenInTusker.entitlements
  14. 177
      Tusker.xcodeproj/project.pbxproj
  15. 3
      Tusker/AppDelegate.swift
  16. 19
      Tusker/MainSceneDelegate.swift
  17. 9
      Tusker/Screens/Main/AccountSwitchingContainerViewController.swift
  18. 2
      Tusker/Screens/Main/MainSidebarViewController.swift
  19. 60
      Tusker/Screens/Main/MainSplitViewController.swift
  20. 34
      Tusker/Screens/Main/MainTabBarViewController.swift
  21. 1
      Tusker/Screens/Main/TuskerRootViewController.swift
  22. 2
      Tusker/Screens/Search/SearchViewController.swift

29
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();

100
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)
}
}

65
OpenInTusker/Base.lproj/MainInterface.storyboard

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ObA-dk-sSI">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Image-->
<scene sceneID="7MM-of-jgj">
<objects>
<viewController title="Image" id="ObA-dk-sSI" customClass="ActionViewController" customModule="OpenInTusker" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="zMn-AG-sqS">
<rect key="frame" x="0.0" y="0.0" width="320" height="528"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<navigationBar contentMode="scaleToFill" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="NOA-Dm-cuz">
<rect key="frame" x="0.0" y="44" width="320" height="44"/>
<items>
<navigationItem id="3HJ-uW-3hn">
<barButtonItem key="leftBarButtonItem" title="Done" style="done" id="WYi-yp-eM6">
<connections>
<action selector="done" destination="ObA-dk-sSI" id="Qdu-qn-U6V"/>
</connections>
</barButtonItem>
</navigationItem>
</items>
</navigationBar>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Unable to find Mastodon link on this page." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Yho-gp-VyR">
<rect key="frame" x="0.0" y="254" width="320" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="VVe-Uw-JpX"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="VVe-Uw-JpX" firstAttribute="trailing" secondItem="NOA-Dm-cuz" secondAttribute="trailing" id="A05-Pj-hrr"/>
<constraint firstItem="NOA-Dm-cuz" firstAttribute="leading" secondItem="VVe-Uw-JpX" secondAttribute="leading" id="HxO-8t-aoh"/>
<constraint firstItem="Yho-gp-VyR" firstAttribute="centerY" secondItem="zMn-AG-sqS" secondAttribute="centerY" id="R7q-OB-hhA"/>
<constraint firstItem="Yho-gp-VyR" firstAttribute="leading" secondItem="VVe-Uw-JpX" secondAttribute="leading" id="TEy-zi-dP7"/>
<constraint firstItem="Yho-gp-VyR" firstAttribute="trailing" secondItem="VVe-Uw-JpX" secondAttribute="trailing" id="Uvn-0x-Y6N"/>
<constraint firstItem="NOA-Dm-cuz" firstAttribute="top" secondItem="VVe-Uw-JpX" secondAttribute="top" id="we0-1t-bgp"/>
</constraints>
</view>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<size key="freeformSize" width="320" height="528"/>
<connections>
<outlet property="view" destination="zMn-AG-sqS" id="Qma-de-2ek"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-61" y="-57"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

55
OpenInTusker/Info.plist

@ -0,0 +1,55 @@
<?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>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Open in Tusker</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionJavaScriptPreprocessingFile</key>
<string>Action</string>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsAttachmentsWithMinCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
<key>NSExtensionServiceAllowsFinderPreviewItem</key>
<true/>
<key>NSExtensionServiceAllowsTouchBarItem</key>
<true/>
<key>NSExtensionServiceFinderPreviewIconName</key>
<string>NSActionTemplate</string>
<key>NSExtensionServiceTouchBarBezelColorName</key>
<string>TouchBarBezel</string>
<key>NSExtensionServiceTouchBarIconName</key>
<string>NSActionTemplate</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
</dict>
</dict>
</plist>

BIN
OpenInTusker/Media.xcassets/AppIcon.appiconset/1024x1024@1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
OpenInTusker/Media.xcassets/AppIcon.appiconset/60x60@3x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
OpenInTusker/Media.xcassets/AppIcon.appiconset/76x76@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
OpenInTusker/Media.xcassets/AppIcon.appiconset/83.5x83.5@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

103
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
}
}

6
OpenInTusker/Media.xcassets/Contents.json

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

14
OpenInTusker/Media.xcassets/TouchBarBezel.colorset/Contents.json

@ -0,0 +1,14 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "mac",
"color" : {
"reference" : "systemPurpleColor"
}
}
]
}

10
OpenInTusker/OpenInTusker.entitlements

@ -0,0 +1,10 @@
<?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.network.client</key>
<true/>
</dict>
</plist>

177
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 = "<group>"; };
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakArray.swift; sourceTree = "<group>"; };
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = "<group>"; };
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 = "<group>"; };
D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = "<group>"; };
D6E343AF265AAD6B00C4AA01 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
D6E343B1265AAD6B00C4AA01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D6E343B5265AAD6B00C4AA01 /* OpenInTusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenInTusker.entitlements; sourceTree = "<group>"; };
D6E343B9265AAD8C00C4AA01 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = "<group>"; };
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAutocompleteView.swift; sourceTree = "<group>"; };
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcher.swift; sourceTree = "<group>"; };
D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcherTests.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -1552,6 +1591,19 @@
path = TuskerUITests;
sourceTree = "<group>";
};
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 = "<group>";
};
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 = "<group>";
};
D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */ = {
isa = PBXVariantGroup;
children = (
D6E343AF265AAD6B00C4AA01 /* Base */,
);
name = MainInterface.storyboard;
sourceTree = "<group>";
};
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 */

3
Tusker/AppDelegate.swift

@ -76,9 +76,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
case .newPost:
return "compose"
default:
fatalError("no scene for activity type \(type)")
}
}
}

19
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)
}
}

9
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)
}
}

2
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)

60
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 {

34
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 {

1
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)
}

2
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

Loading…
Cancel
Save