Initial share extension implementation

This commit is contained in:
Shadowfacts 2023-04-18 21:55:14 -04:00
parent 06855420da
commit 6c371f868f
10 changed files with 678 additions and 3 deletions

View File

@ -12,16 +12,24 @@ import PencilKit
import TuskerComponents import TuskerComponents
public struct ComposeUIConfig { public struct ComposeUIConfig {
// Config
public var allowSwitchingDrafts = true
public var textSelectionStartsAtBeginning = false
// Style
public var backgroundColor = Color(uiColor: .systemBackground) public var backgroundColor = Color(uiColor: .systemBackground)
public var groupedBackgroundColor = Color(uiColor: .systemGroupedBackground) public var groupedBackgroundColor = Color(uiColor: .systemGroupedBackground)
public var groupedCellBackgroundColor = Color(uiColor: .systemBackground) public var groupedCellBackgroundColor = Color(uiColor: .systemBackground)
public var fillColor = Color(uiColor: .systemFill) public var fillColor = Color(uiColor: .systemFill)
public var avatarStyle = AvatarImageView.Style.roundRect public var avatarStyle = AvatarImageView.Style.roundRect
// Preferences
public var useTwitterKeyboard = false public var useTwitterKeyboard = false
public var contentType = StatusContentType.plain public var contentType = StatusContentType.plain
public var automaticallySaveDrafts = false public var automaticallySaveDrafts = false
public var requireAttachmentDescriptions = false public var requireAttachmentDescriptions = false
// Host callbacks
public var dismiss: @MainActor (DismissMode) -> Void = { _ in } public var dismiss: @MainActor (DismissMode) -> Void = { _ in }
public var presentAssetPicker: ((@MainActor @escaping ([PHPickerResult]) -> Void) -> Void)? public var presentAssetPicker: ((@MainActor @escaping ([PHPickerResult]) -> Void) -> Void)?
public var presentDrawing: ((PKDrawing, @escaping (PKDrawing) -> Void) -> Void)? public var presentDrawing: ((PKDrawing, @escaping (PKDrawing) -> Void) -> Void)?

View File

@ -351,7 +351,7 @@ public final class ComposeController: ViewController {
@ViewBuilder @ViewBuilder
private var postButton: some View { private var postButton: some View {
if draft.hasContent { if draft.hasContent || !controller.config.allowSwitchingDrafts {
Button(action: controller.postStatus) { Button(action: controller.postStatus) {
Text("Post") Text("Post")
} }

View File

@ -15,6 +15,7 @@ struct MainTextView: View {
@State private var hasFirstAppeared = false @State private var hasFirstAppeared = false
@State private var height: CGFloat? @State private var height: CGFloat?
@State private var updateSelection: ((UITextView) -> Void)?
private let minHeight: CGFloat = 150 private let minHeight: CGFloat = 150
private var effectiveHeight: CGFloat { height ?? minHeight } private var effectiveHeight: CGFloat { height ?? minHeight }
@ -34,7 +35,7 @@ struct MainTextView: View {
.accessibilityHidden(true) .accessibilityHidden(true)
} }
MainWrappedTextViewRepresentable(text: $draft.text, becomeFirstResponder: $controller.mainComposeTextViewBecomeFirstResponder, textDidChange: textDidChange) MainWrappedTextViewRepresentable(text: $draft.text, becomeFirstResponder: $controller.mainComposeTextViewBecomeFirstResponder, updateSelection: $updateSelection, textDidChange: textDidChange)
} }
.frame(height: effectiveHeight) .frame(height: effectiveHeight)
.onAppear(perform: becomeFirstResponderOnFirstAppearance) .onAppear(perform: becomeFirstResponderOnFirstAppearance)
@ -44,6 +45,11 @@ struct MainTextView: View {
if !hasFirstAppeared { if !hasFirstAppeared {
hasFirstAppeared = true hasFirstAppeared = true
controller.mainComposeTextViewBecomeFirstResponder = true controller.mainComposeTextViewBecomeFirstResponder = true
if config.textSelectionStartsAtBeginning {
updateSelection = { textView in
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
} }
} }
@ -57,6 +63,7 @@ fileprivate struct MainWrappedTextViewRepresentable: UIViewRepresentable {
@Binding var text: String @Binding var text: String
@Binding var becomeFirstResponder: Bool @Binding var becomeFirstResponder: Bool
@Binding var updateSelection: ((UITextView) -> Void)?
let textDidChange: (UITextView) -> Void let textDidChange: (UITextView) -> Void
@EnvironmentObject private var controller: ComposeController @EnvironmentObject private var controller: ComposeController
@ -85,6 +92,11 @@ fileprivate struct MainWrappedTextViewRepresentable: UIViewRepresentable {
context.coordinator.text = $text context.coordinator.text = $text
if let updateSelection {
updateSelection(uiView)
self.updateSelection = nil
}
// wait until the next runloop iteration so that SwiftUI view updates have finished and // wait until the next runloop iteration so that SwiftUI view updates have finished and
// the text view knows its new content size // the text view knows its new content size
DispatchQueue.main.async { DispatchQueue.main.async {

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

18
ShareExtension/Info.plist Normal file
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>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>TRUEPREDICATE</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,14 @@
<?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.space.vaccor.Tusker</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,154 @@
//
// ShareHostingController.swift
// ShareExtension
//
// Created by Shadowfacts on 4/17/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import SwiftUI
import ComposeUI
import TuskerComponents
import WebURLFoundationExtras
import Combine
import TuskerPreferences
class ShareHostingController: UIHostingController<ShareHostingController.View> {
private static func fetchAvatar(_ url: URL) async -> UIImage? {
guard let (data, _) = try? await URLSession.shared.data(from: url),
let image = UIImage(data: data) else {
return nil
}
return await image.byPreparingThumbnail(ofSize: CGSize(width: 50, height: 50)) ?? image
}
private let controller: ComposeController
private var cancellables = Set<AnyCancellable>()
init(draft: Draft, mastodonContext: ShareMastodonContext) {
controller = ComposeController(
draft: draft,
config: ComposeUIConfig(),
mastodonController: mastodonContext,
fetchAvatar: Self.fetchAvatar,
fetchStatus: { _ in fatalError("replies aren't allowed in share sheet") },
displayNameLabel: { account, style, _ in AnyView(Text(account.displayName).font(.system(style))) },
replyContentView: { _, _ in fatalError("replies aren't allowed in share sheet") },
emojiImageView: {
AnyView(AsyncImage(url: URL($0.url)!) {
$0
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Image(systemName: "smiley.fill")
})
}
)
super.init(rootView: View(controller: controller))
updateConfig()
mastodonContext.$ownAccount
.sink { [unowned self] in self.controller.currentAccount = $0 }
.store(in: &cancellables)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
private func updateConfig() {
var config = ComposeUIConfig()
config.allowSwitchingDrafts = false
config.textSelectionStartsAtBeginning = true
// note: in the share sheet, we ignore this preference
config.automaticallySaveDrafts = false
config.backgroundColor = Color(uiColor: .appBackground)
config.groupedBackgroundColor = Color(uiColor: .appGroupedBackground)
config.groupedCellBackgroundColor = Color(uiColor: .appGroupedCellBackground)
config.fillColor = Color(uiColor: .appFill)
switch Preferences.shared.avatarStyle {
case .roundRect:
config.avatarStyle = .roundRect
case .circle:
config.avatarStyle = .circle
}
config.useTwitterKeyboard = Preferences.shared.useTwitterKeyboard
config.contentType = Preferences.shared.statusContentType
config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions
config.dismiss = { [unowned self] in self.dismiss(mode: $0) }
controller.config = config
}
private func dismiss(mode: DismissMode) {
guard let extensionContext else { return }
switch mode {
case .cancel:
extensionContext.cancelRequest(withError: Error.cancelled)
case .post:
extensionContext.completeRequest(returningItems: nil)
}
}
struct View: SwiftUI.View {
let controller: ComposeController
var body: some SwiftUI.View {
ControllerView(controller: { controller })
}
}
enum Error: Swift.Error {
case cancelled
}
}
// todo: shouldn't just copy this from the main Colors.swift
extension UIColor {
static let appBackground = UIColor { traitCollection in
if case .dark = traitCollection.userInterfaceStyle,
!Preferences.shared.pureBlackDarkMode {
return UIColor(hue: 230/360, saturation: 23/100, brightness: 10/100, alpha: 1)
} else {
return .systemBackground
}
}
static let appGroupedBackground = UIColor { traitCollection in
if case .dark = traitCollection.userInterfaceStyle,
!Preferences.shared.pureBlackDarkMode {
if traitCollection.userInterfaceLevel == .elevated {
return UIColor(hue: 230/360, saturation: 23/100, brightness: 10/100, alpha: 1)
} else {
return UIColor(hue: 230/360, saturation: 23/100, brightness: 5/100, alpha: 1)
}
} else {
return .systemGroupedBackground
}
}
static let appGroupedCellBackground = UIColor { traitCollection in
if case .dark = traitCollection.userInterfaceStyle {
if Preferences.shared.pureBlackDarkMode {
return .secondarySystemBackground
} else {
return .appFill
}
} else {
return .systemBackground
}
}
static let appFill = UIColor { traitCollection in
if case .dark = traitCollection.userInterfaceStyle,
!Preferences.shared.pureBlackDarkMode {
return UIColor(hue: 230/360, saturation: 20/100, brightness: 17/100, alpha: 1)
} else {
return .systemFill
}
}
}

View File

@ -0,0 +1,92 @@
//
// ShareMastodonContext.swift
// ShareExtension
//
// Created by Shadowfacts on 4/17/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import Pachyderm
import ComposeUI
import UserAccounts
import InstanceFeatures
import Combine
class ShareMastodonContext: ComposeMastodonContext, ObservableObject {
let accountInfo: UserAccountInfo?
let client: Client
let instanceFeatures: InstanceFeatures
@MainActor
private var customEmojis: [Emoji]?
@Published var ownAccount: Account?
init(accountInfo: UserAccountInfo) {
self.accountInfo = accountInfo
self.client = Client(baseURL: accountInfo.instanceURL, accessToken: accountInfo.accessToken)
self.instanceFeatures = InstanceFeatures()
Task { @MainActor in
async let instance = try? await run(Client.getInstance()).0
async let nodeInfo: NodeInfo? = await withCheckedContinuation({ continuation in
self.client.nodeInfo { response in
switch response {
case .success(let nodeInfo, _):
continuation.resume(returning: nodeInfo)
case .failure(_):
continuation.resume(returning: nil)
}
}
})
guard let instance = await instance else { return }
self.instanceFeatures.update(instance: instance, nodeInfo: await nodeInfo)
}
Task { @MainActor in
if let account = try? await run(Client.getSelfAccount()).0 {
self.ownAccount = account
}
}
}
// MARK: CompmoseMastodonContext
func run<Result: Decodable & Sendable>(_ request: Request<Result>) async throws -> (Result, Pagination?) {
return try await withCheckedThrowingContinuation({ continuation in
client.run(request) { response in
switch response {
case .success(let result, let pagination):
continuation.resume(returning: (result, pagination))
case .failure(let error):
continuation.resume(throwing: error)
}
}
})
}
@MainActor
func getCustomEmojis(completion: @escaping ([Emoji]) -> Void) {
if let customEmojis {
completion(customEmojis)
} else {
Task.detached { @MainActor in
let emojis = (try? await self.run(Client.getCustomEmoji()).0) ?? []
self.customEmojis = emojis
completion(emojis)
}
}
}
func searchCachedAccounts(query: String) -> [AccountProtocol] {
return []
}
func cachedRelationship(for accountID: String) -> RelationshipProtocol? {
return nil
}
func searchCachedHashtags(query: String) -> [Hashtag] {
return []
}
}

View File

@ -0,0 +1,124 @@
//
// ShareViewController.swift
// ShareExtension
//
// Created by Shadowfacts on 4/17/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import SwiftUI
import UserAccounts
import ComposeUI
import UniformTypeIdentifiers
import TuskerPreferences
class ShareViewController: UIViewController {
private var state: State = .loading
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
view.tintColor = Preferences.shared.accentColor.color
if let account = UserAccountsManager.shared.getMostRecentAccount() {
Task { @MainActor in
let draft = await createDraft(account: account)
state = .ok
let context = ShareMastodonContext(accountInfo: account)
let host = ShareHostingController(draft: draft, mastodonContext: context)
let nav = UINavigationController(rootViewController: host)
self.addChild(nav)
nav.view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(nav.view)
NSLayoutConstraint.activate([
nav.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
nav.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
nav.view.topAnchor.constraint(equalTo: self.view.topAnchor),
nav.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
])
nav.didMove(toParent: self)
}
} else {
state = .notLoggedIn
}
}
private func createDraft(account: UserAccountInfo) async -> Draft {
let (text, attachments) = await getDraftConfigurationFromExtensionContext()
let draft = Draft(
accountID: account.id,
text: text,
contentWarning: "",
inReplyToID: nil,
// TODO: get the default visibility from preferences
visibility: .public,
localOnly: false
)
draft.attachments = attachments
return draft
}
private func getDraftConfigurationFromExtensionContext() async -> (String, [DraftAttachment]) {
guard let extensionContext,
let inputItem = (extensionContext.inputItems as? [NSExtensionItem])?.first,
let itemProvider = inputItem.attachments?.first else {
return ("", [])
}
if let url: NSURL = await getObject(from: itemProvider) {
if let title = inputItem.attributedTitle ?? inputItem.attributedContentText {
return ("\n\n\(title.string)\n\(url.absoluteString ?? "")", [])
} else {
return ("\n\n\(url.absoluteString ?? "")", [])
}
} else if let text: NSString = await getObject(from: itemProvider) {
return ("\n\n\(text)", [])
} else if let attachment: DraftAttachment = await getObject(from: itemProvider) {
return ("", [attachment])
} else if let attributedContent = inputItem.attributedContentText {
return ("\n\n\(attributedContent.string)", [])
} else {
return ("", [])
}
}
private func getObject<T: NSItemProviderReading>(from itemProvider: NSItemProvider) async -> T? {
guard itemProvider.canLoadObject(ofClass: T.self) else {
return nil
}
return await withCheckedContinuation({ continuation in
itemProvider.loadObject(ofClass: T.self) { object, error in
continuation.resume(returning: object as? T)
}
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if case .notLoggedIn = state {
let alert = UIAlertController(title: "Not Logged In", message: "You need to log in to an account through the app before you can post.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [unowned self] _ in
self.extensionContext!.cancelRequest(withError: Error.notLoggedIn)
}))
present(alert, animated: true)
}
}
enum State {
case loading
case notLoggedIn
case ok
}
enum Error: Swift.Error {
case notLoggedIn
}
}

View File

@ -220,6 +220,16 @@
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */; }; D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */; };
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */; }; D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */; };
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */; }; D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */; };
D6A4531629EF64BA00032932 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4531529EF64BA00032932 /* ShareViewController.swift */; };
D6A4531929EF64BA00032932 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6A4531729EF64BA00032932 /* MainInterface.storyboard */; };
D6A4531D29EF64BA00032932 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D6A4531329EF64BA00032932 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D6A4532429EF665200032932 /* ComposeUI in Frameworks */ = {isa = PBXBuildFile; productRef = D6A4532329EF665200032932 /* ComposeUI */; };
D6A4532629EF665600032932 /* InstanceFeatures in Frameworks */ = {isa = PBXBuildFile; productRef = D6A4532529EF665600032932 /* InstanceFeatures */; };
D6A4532829EF665800032932 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D6A4532729EF665800032932 /* Pachyderm */; };
D6A4532A29EF665A00032932 /* TuskerPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = D6A4532929EF665A00032932 /* TuskerPreferences */; };
D6A4532C29EF665D00032932 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D6A4532B29EF665D00032932 /* UserAccounts */; };
D6A4532E29EF7DDD00032932 /* ShareHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4532D29EF7DDD00032932 /* ShareHostingController.swift */; };
D6A4533029EF7DEE00032932 /* ShareMastodonContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4532F29EF7DEE00032932 /* ShareMastodonContext.swift */; };
D6A4DCCC2553667800D9DE31 /* FastAccountSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */; }; D6A4DCCC2553667800D9DE31 /* FastAccountSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */; };
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A4DCCB2553667800D9DE31 /* FastAccountSwitcherViewController.xib */; }; D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A4DCCB2553667800D9DE31 /* FastAccountSwitcherViewController.xib */; };
D6A4DCE525537C7A00D9DE31 /* FastSwitchingAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4DCE425537C7A00D9DE31 /* FastSwitchingAccountView.swift */; }; D6A4DCE525537C7A00D9DE31 /* FastSwitchingAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4DCE425537C7A00D9DE31 /* FastSwitchingAccountView.swift */; };
@ -335,6 +345,13 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
proxyType = 1;
remoteGlobalIDString = D6A4531229EF64BA00032932;
remoteInfo = ShareExtension;
};
D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = { D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
@ -365,6 +382,7 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 13; dstSubfolderSpec = 13;
files = ( files = (
D6A4531D29EF64BA00032932 /* ShareExtension.appex in Embed Foundation Extensions */,
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */, D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */,
); );
name = "Embed Foundation Extensions"; name = "Embed Foundation Extensions";
@ -597,6 +615,13 @@
D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationGroupTableViewCell.xib; sourceTree = "<group>"; }; D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationGroupTableViewCell.xib; sourceTree = "<group>"; };
D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = "<group>"; }; D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = "<group>"; };
D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountTableViewCell.xib; sourceTree = "<group>"; }; D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountTableViewCell.xib; sourceTree = "<group>"; };
D6A4531329EF64BA00032932 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
D6A4531529EF64BA00032932 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
D6A4531829EF64BA00032932 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
D6A4531A29EF64BA00032932 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D6A4531E29EF64BA00032932 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
D6A4532D29EF7DDD00032932 /* ShareHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareHostingController.swift; sourceTree = "<group>"; };
D6A4532F29EF7DEE00032932 /* ShareMastodonContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareMastodonContext.swift; sourceTree = "<group>"; };
D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherViewController.swift; sourceTree = "<group>"; }; D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherViewController.swift; sourceTree = "<group>"; };
D6A4DCCB2553667800D9DE31 /* FastAccountSwitcherViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FastAccountSwitcherViewController.xib; sourceTree = "<group>"; }; D6A4DCCB2553667800D9DE31 /* FastAccountSwitcherViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FastAccountSwitcherViewController.xib; sourceTree = "<group>"; };
D6A4DCE425537C7A00D9DE31 /* FastSwitchingAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastSwitchingAccountView.swift; sourceTree = "<group>"; }; D6A4DCE425537C7A00D9DE31 /* FastSwitchingAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastSwitchingAccountView.swift; sourceTree = "<group>"; };
@ -723,6 +748,18 @@
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
D6A4531029EF64BA00032932 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D6A4532829EF665800032932 /* Pachyderm in Frameworks */,
D6A4532A29EF665A00032932 /* TuskerPreferences in Frameworks */,
D6A4532629EF665600032932 /* InstanceFeatures in Frameworks */,
D6A4532C29EF665D00032932 /* UserAccounts in Frameworks */,
D6A4532429EF665200032932 /* ComposeUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D6D4DDC9212518A000E1C4BB /* Frameworks */ = { D6D4DDC9212518A000E1C4BB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1264,6 +1301,19 @@
path = "Status Action Account List"; path = "Status Action Account List";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D6A4531429EF64BA00032932 /* ShareExtension */ = {
isa = PBXGroup;
children = (
D6A4531E29EF64BA00032932 /* ShareExtension.entitlements */,
D6A4532F29EF7DEE00032932 /* ShareMastodonContext.swift */,
D6A4531529EF64BA00032932 /* ShareViewController.swift */,
D6A4532D29EF7DDD00032932 /* ShareHostingController.swift */,
D6A4531729EF64BA00032932 /* MainInterface.storyboard */,
D6A4531A29EF64BA00032932 /* Info.plist */,
);
path = ShareExtension;
sourceTree = "<group>";
};
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */ = { D6A4DCC92553666600D9DE31 /* Fast Account Switcher */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1408,6 +1458,7 @@
D6D4DDE3212518A200E1C4BB /* TuskerTests */, D6D4DDE3212518A200E1C4BB /* TuskerTests */,
D6D4DDEE212518A200E1C4BB /* TuskerUITests */, D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
D6E343A9265AAD6B00C4AA01 /* OpenInTusker */, D6E343A9265AAD6B00C4AA01 /* OpenInTusker */,
D6A4531429EF64BA00032932 /* ShareExtension */,
D6D4DDCD212518A000E1C4BB /* Products */, D6D4DDCD212518A000E1C4BB /* Products */,
D65A37F221472F300087646E /* Frameworks */, D65A37F221472F300087646E /* Frameworks */,
); );
@ -1420,6 +1471,7 @@
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */, D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */, D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */, D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */,
D6A4531329EF64BA00032932 /* ShareExtension.appex */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1578,6 +1630,30 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
D6A4531229EF64BA00032932 /* ShareExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
buildPhases = (
D6A4530F29EF64BA00032932 /* Sources */,
D6A4531029EF64BA00032932 /* Frameworks */,
D6A4531129EF64BA00032932 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = ShareExtension;
packageProductDependencies = (
D6A4532329EF665200032932 /* ComposeUI */,
D6A4532529EF665600032932 /* InstanceFeatures */,
D6A4532729EF665800032932 /* Pachyderm */,
D6A4532929EF665A00032932 /* TuskerPreferences */,
D6A4532B29EF665D00032932 /* UserAccounts */,
);
productName = ShareExtension;
productReference = D6A4531329EF64BA00032932 /* ShareExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
D6D4DDCB212518A000E1C4BB /* Tusker */ = { D6D4DDCB212518A000E1C4BB /* Tusker */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */; buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
@ -1594,6 +1670,7 @@
); );
dependencies = ( dependencies = (
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */, D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */,
D6A4531C29EF64BA00032932 /* PBXTargetDependency */,
); );
name = Tusker; name = Tusker;
packageProductDependencies = ( packageProductDependencies = (
@ -1676,10 +1753,13 @@
D6D4DDC4212518A000E1C4BB /* Project object */ = { D6D4DDC4212518A000E1C4BB /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 1250; LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1400; LastUpgradeCheck = 1400;
ORGANIZATIONNAME = Shadowfacts; ORGANIZATIONNAME = Shadowfacts;
TargetAttributes = { TargetAttributes = {
D6A4531229EF64BA00032932 = {
CreatedOnToolsVersion = 14.3;
};
D6D4DDCB212518A000E1C4BB = { D6D4DDCB212518A000E1C4BB = {
CreatedOnToolsVersion = 10.0; CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1420; LastSwiftMigration = 1420;
@ -1728,11 +1808,20 @@
D6D4DDDF212518A200E1C4BB /* TuskerTests */, D6D4DDDF212518A200E1C4BB /* TuskerTests */,
D6D4DDEA212518A200E1C4BB /* TuskerUITests */, D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
D6E343A7265AAD6B00C4AA01 /* OpenInTusker */, D6E343A7265AAD6B00C4AA01 /* OpenInTusker */,
D6A4531229EF64BA00032932 /* ShareExtension */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
D6A4531129EF64BA00032932 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D6A4531929EF64BA00032932 /* MainInterface.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D6D4DDCA212518A000E1C4BB /* Resources */ = { D6D4DDCA212518A000E1C4BB /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1832,6 +1921,16 @@
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
D6A4530F29EF64BA00032932 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D6A4532E29EF7DDD00032932 /* ShareHostingController.swift in Sources */,
D6A4531629EF64BA00032932 /* ShareViewController.swift in Sources */,
D6A4533029EF7DEE00032932 /* ShareMastodonContext.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D6D4DDC8212518A000E1C4BB /* Sources */ = { D6D4DDC8212518A000E1C4BB /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -2150,6 +2249,11 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
D6A4531C29EF64BA00032932 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D6A4531229EF64BA00032932 /* ShareExtension */;
targetProxy = D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */;
};
D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = { D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = D6D4DDCB212518A000E1C4BB /* Tusker */; target = D6D4DDCB212518A000E1C4BB /* Tusker */;
@ -2169,6 +2273,14 @@
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
D6A4531729EF64BA00032932 /* MainInterface.storyboard */ = {
isa = PBXVariantGroup;
children = (
D6A4531829EF64BA00032932 /* Base */,
);
name = MainInterface.storyboard;
sourceTree = "<group>";
};
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = { D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
children = ( children = (
@ -2349,6 +2461,93 @@
}; };
name = Dist; name = Dist;
}; };
D6A4531F29EF64BA00032932 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Shadowfacts. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
D6A4532029EF64BA00032932 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Shadowfacts. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
D6A4532129EF64BA00032932 /* Dist */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Shadowfacts. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Dist;
};
D6D4DDF2212518A200E1C4BB /* Debug */ = { D6D4DDF2212518A200E1C4BB /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = D6D706A829498C82000827ED /* Tusker.xcconfig */; baseConfigurationReference = D6D706A829498C82000827ED /* Tusker.xcconfig */;
@ -2667,6 +2866,16 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D6A4531F29EF64BA00032932 /* Debug */,
D6A4532029EF64BA00032932 /* Release */,
D6A4532129EF64BA00032932 /* Dist */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = { D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@ -2791,6 +3000,26 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Pachyderm; productName = Pachyderm;
}; };
D6A4532329EF665200032932 /* ComposeUI */ = {
isa = XCSwiftPackageProductDependency;
productName = ComposeUI;
};
D6A4532529EF665600032932 /* InstanceFeatures */ = {
isa = XCSwiftPackageProductDependency;
productName = InstanceFeatures;
};
D6A4532729EF665800032932 /* Pachyderm */ = {
isa = XCSwiftPackageProductDependency;
productName = Pachyderm;
};
D6A4532929EF665A00032932 /* TuskerPreferences */ = {
isa = XCSwiftPackageProductDependency;
productName = TuskerPreferences;
};
D6A4532B29EF665D00032932 /* UserAccounts */ = {
isa = XCSwiftPackageProductDependency;
productName = UserAccounts;
};
D6B0026D29B5248800C70BE2 /* UserAccounts */ = { D6B0026D29B5248800C70BE2 /* UserAccounts */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = UserAccounts; productName = UserAccounts;