diff --git a/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift b/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift
index 0cc3694a..fe51bb1f 100644
--- a/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift
+++ b/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift
@@ -12,16 +12,24 @@ import PencilKit
import TuskerComponents
public struct ComposeUIConfig {
+ // Config
+ public var allowSwitchingDrafts = true
+ public var textSelectionStartsAtBeginning = false
+
+ // Style
public var backgroundColor = Color(uiColor: .systemBackground)
public var groupedBackgroundColor = Color(uiColor: .systemGroupedBackground)
public var groupedCellBackgroundColor = Color(uiColor: .systemBackground)
public var fillColor = Color(uiColor: .systemFill)
public var avatarStyle = AvatarImageView.Style.roundRect
+
+ // Preferences
public var useTwitterKeyboard = false
public var contentType = StatusContentType.plain
public var automaticallySaveDrafts = false
public var requireAttachmentDescriptions = false
+ // Host callbacks
public var dismiss: @MainActor (DismissMode) -> Void = { _ in }
public var presentAssetPicker: ((@MainActor @escaping ([PHPickerResult]) -> Void) -> Void)?
public var presentDrawing: ((PKDrawing, @escaping (PKDrawing) -> Void) -> Void)?
diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift
index df2db563..365417c6 100644
--- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift
+++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift
@@ -351,7 +351,7 @@ public final class ComposeController: ViewController {
@ViewBuilder
private var postButton: some View {
- if draft.hasContent {
+ if draft.hasContent || !controller.config.allowSwitchingDrafts {
Button(action: controller.postStatus) {
Text("Post")
}
diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/MainTextView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/MainTextView.swift
index 079584c6..f44c3d59 100644
--- a/Packages/ComposeUI/Sources/ComposeUI/Views/MainTextView.swift
+++ b/Packages/ComposeUI/Sources/ComposeUI/Views/MainTextView.swift
@@ -15,6 +15,7 @@ struct MainTextView: View {
@State private var hasFirstAppeared = false
@State private var height: CGFloat?
+ @State private var updateSelection: ((UITextView) -> Void)?
private let minHeight: CGFloat = 150
private var effectiveHeight: CGFloat { height ?? minHeight }
@@ -34,7 +35,7 @@ struct MainTextView: View {
.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)
.onAppear(perform: becomeFirstResponderOnFirstAppearance)
@@ -44,6 +45,11 @@ struct MainTextView: View {
if !hasFirstAppeared {
hasFirstAppeared = 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 becomeFirstResponder: Bool
+ @Binding var updateSelection: ((UITextView) -> Void)?
let textDidChange: (UITextView) -> Void
@EnvironmentObject private var controller: ComposeController
@@ -85,6 +92,11 @@ fileprivate struct MainWrappedTextViewRepresentable: UIViewRepresentable {
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
// the text view knows its new content size
DispatchQueue.main.async {
diff --git a/ShareExtension/Base.lproj/MainInterface.storyboard b/ShareExtension/Base.lproj/MainInterface.storyboard
new file mode 100644
index 00000000..286a5089
--- /dev/null
+++ b/ShareExtension/Base.lproj/MainInterface.storyboard
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ShareExtension/Info.plist b/ShareExtension/Info.plist
new file mode 100644
index 00000000..4b1f7e70
--- /dev/null
+++ b/ShareExtension/Info.plist
@@ -0,0 +1,18 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionAttributes
+
+ NSExtensionActivationRule
+ TRUEPREDICATE
+
+ NSExtensionMainStoryboard
+ MainInterface
+ NSExtensionPointIdentifier
+ com.apple.share-services
+
+
+
diff --git a/ShareExtension/ShareExtension.entitlements b/ShareExtension/ShareExtension.entitlements
new file mode 100644
index 00000000..d5e9f9a9
--- /dev/null
+++ b/ShareExtension/ShareExtension.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.application-groups
+
+ group.space.vaccor.Tusker
+
+ com.apple.security.network.client
+
+
+
diff --git a/ShareExtension/ShareHostingController.swift b/ShareExtension/ShareHostingController.swift
new file mode 100644
index 00000000..11836d2b
--- /dev/null
+++ b/ShareExtension/ShareHostingController.swift
@@ -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 {
+ 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()
+
+ 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
+ }
+ }
+}
diff --git a/ShareExtension/ShareMastodonContext.swift b/ShareExtension/ShareMastodonContext.swift
new file mode 100644
index 00000000..a4adbc7b
--- /dev/null
+++ b/ShareExtension/ShareMastodonContext.swift
@@ -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(_ request: Request) 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 []
+ }
+}
diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift
new file mode 100644
index 00000000..4270ad67
--- /dev/null
+++ b/ShareExtension/ShareViewController.swift
@@ -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(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
+ }
+
+}
diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj
index aab086d6..53242ada 100644
--- a/Tusker.xcodeproj/project.pbxproj
+++ b/Tusker.xcodeproj/project.pbxproj
@@ -220,6 +220,16 @@
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */; };
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */; };
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 */; };
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A4DCCB2553667800D9DE31 /* FastAccountSwitcherViewController.xib */; };
D6A4DCE525537C7A00D9DE31 /* FastSwitchingAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4DCE425537C7A00D9DE31 /* FastSwitchingAccountView.swift */; };
@@ -335,6 +345,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = D6A4531229EF64BA00032932;
+ remoteInfo = ShareExtension;
+ };
D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
@@ -365,6 +382,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
+ D6A4531D29EF64BA00032932 /* ShareExtension.appex in Embed Foundation Extensions */,
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
@@ -597,6 +615,13 @@
D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationGroupTableViewCell.xib; sourceTree = ""; };
D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = ""; };
D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountTableViewCell.xib; sourceTree = ""; };
+ 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 = ""; };
+ D6A4531829EF64BA00032932 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
+ D6A4531A29EF64BA00032932 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ D6A4531E29EF64BA00032932 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; };
+ D6A4532D29EF7DDD00032932 /* ShareHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareHostingController.swift; sourceTree = ""; };
+ D6A4532F29EF7DEE00032932 /* ShareMastodonContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareMastodonContext.swift; sourceTree = ""; };
D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherViewController.swift; sourceTree = ""; };
D6A4DCCB2553667800D9DE31 /* FastAccountSwitcherViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FastAccountSwitcherViewController.xib; sourceTree = ""; };
D6A4DCE425537C7A00D9DE31 /* FastSwitchingAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastSwitchingAccountView.swift; sourceTree = ""; };
@@ -723,6 +748,18 @@
/* End PBXFileReference 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 */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -1264,6 +1301,19 @@
path = "Status Action Account List";
sourceTree = "";
};
+ 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 = "";
+ };
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */ = {
isa = PBXGroup;
children = (
@@ -1408,6 +1458,7 @@
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
D6E343A9265AAD6B00C4AA01 /* OpenInTusker */,
+ D6A4531429EF64BA00032932 /* ShareExtension */,
D6D4DDCD212518A000E1C4BB /* Products */,
D65A37F221472F300087646E /* Frameworks */,
);
@@ -1420,6 +1471,7 @@
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */,
+ D6A4531329EF64BA00032932 /* ShareExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -1578,6 +1630,30 @@
/* End PBXGroup 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 */ = {
isa = PBXNativeTarget;
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
@@ -1594,6 +1670,7 @@
);
dependencies = (
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */,
+ D6A4531C29EF64BA00032932 /* PBXTargetDependency */,
);
name = Tusker;
packageProductDependencies = (
@@ -1676,10 +1753,13 @@
D6D4DDC4212518A000E1C4BB /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 1250;
+ LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1400;
ORGANIZATIONNAME = Shadowfacts;
TargetAttributes = {
+ D6A4531229EF64BA00032932 = {
+ CreatedOnToolsVersion = 14.3;
+ };
D6D4DDCB212518A000E1C4BB = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1420;
@@ -1728,11 +1808,20 @@
D6D4DDDF212518A200E1C4BB /* TuskerTests */,
D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
D6E343A7265AAD6B00C4AA01 /* OpenInTusker */,
+ D6A4531229EF64BA00032932 /* ShareExtension */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ D6A4531129EF64BA00032932 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D6A4531929EF64BA00032932 /* MainInterface.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
D6D4DDCA212518A000E1C4BB /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1832,6 +1921,16 @@
/* End PBXShellScriptBuildPhase 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 */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -2150,6 +2249,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ D6A4531C29EF64BA00032932 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = D6A4531229EF64BA00032932 /* ShareExtension */;
+ targetProxy = D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */;
+ };
D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
@@ -2169,6 +2273,14 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
+ D6A4531729EF64BA00032932 /* MainInterface.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ D6A4531829EF64BA00032932 /* Base */,
+ );
+ name = MainInterface.storyboard;
+ sourceTree = "";
+ };
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
@@ -2349,6 +2461,93 @@
};
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 */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D6D706A829498C82000827ED /* Tusker.xcconfig */;
@@ -2667,6 +2866,16 @@
/* End XCBuildConfiguration 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" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -2791,6 +3000,26 @@
isa = XCSwiftPackageProductDependency;
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 */ = {
isa = XCSwiftPackageProductDependency;
productName = UserAccounts;