From e9e08bdadd315c09e47cb4ff69b92fb313e25f11 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 6 Feb 2025 13:29:37 -0500 Subject: [PATCH] Use new compose UI for share extension --- .../Sources/ComposeUI/ComposeUIConfig.swift | 3 +- .../ComposeUI/Views/NewMainTextView.swift | 1 - ShareExtension/ShareHostingController.swift | 94 +++++++++++-------- .../SwitchAccountContainerView.swift | 23 ++++- .../Compose/ComposeHostingController.swift | 3 +- 5 files changed, 78 insertions(+), 46 deletions(-) diff --git a/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift b/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift index cfba97fb..0a4c62f3 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/ComposeUIConfig.swift @@ -24,9 +24,10 @@ public struct ComposeUIConfig { public var groupedBackgroundColor = Color(uiColor: .systemGroupedBackground) public var groupedCellBackgroundColor = Color(uiColor: .systemBackground) public var fillColor = Color(uiColor: .systemFill) - public var avatarStyle = AvatarImageView.Style.roundRect + // TODO: remove these in favor of @PreferenceObserving // Preferences + public var avatarStyle = AvatarImageView.Style.roundRect public var useTwitterKeyboard = false public var contentType = StatusContentType.plain public var requireAttachmentDescriptions = false diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift index 10ba3c91..80a9be5e 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift @@ -36,7 +36,6 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { @Environment(\.colorScheme) private var colorScheme @Environment(\.composeUIConfig.fillColor) private var fillColor @Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard - // TODO: test textSelectionStartsAtBeginning @Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning func makeUIView(context: Context) -> UITextView { diff --git a/ShareExtension/ShareHostingController.swift b/ShareExtension/ShareHostingController.swift index dff8f3d7..906b44c2 100644 --- a/ShareExtension/ShareHostingController.swift +++ b/ShareExtension/ShareHostingController.swift @@ -12,6 +12,7 @@ import TuskerComponents import WebURLFoundationExtras import Combine import TuskerPreferences +import Pachyderm class ShareHostingController: UIHostingController { private static func fetchAvatar(_ url: URL) async -> UIImage? { @@ -27,48 +28,21 @@ class ShareHostingController: UIHostingController { return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image } - private let controller: ComposeController - - private var mastodonContextPublisher: CurrentValueSubject - private var cancellables = Set() + @ObservableObjectBox private var config = ComposeUIConfig() + private let accountSwitchingState: AccountSwitchingState + private let state: ComposeViewState init(draft: Draft, mastodonContext: ShareMastodonContext) { - let mastodonContextPublisher = CurrentValueSubject(mastodonContext) - self.mastodonContextPublisher = mastodonContextPublisher - controller = ComposeController( - draft: draft, - config: ComposeUIConfig(), - mastodonController: mastodonContext, - fetchAvatar: Self.fetchAvatar, - fetchAttachment: { _ in fatalError("edits aren't allowed in share sheet, can't fetch existing attachment") }, - fetchStatus: { _ in fatalError("replies aren't allowed in share sheet") }, - displayNameLabel: { account, style, _ in AnyView(Text(account.displayName).font(.system(style))) }, - currentAccountContainerView: { AnyView(SwitchAccountContainerView(content: $0, mastodonContextPublisher: mastodonContextPublisher)) }, - 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") - }) - } + self.accountSwitchingState = AccountSwitchingState(mastodonContext: mastodonContext) + self.state = ComposeViewState(draft: draft) + let rootView = View( + accountSwitchingState: self.accountSwitchingState, + state: state, + config: _config ) - super.init(rootView: View(controller: controller)) + super.init(rootView: rootView) updateConfig() - - mastodonContextPublisher - .sink { [unowned self] in - self.controller.mastodonController = $0 - self.controller.draft.accountID = $0.accountInfo!.id - } - .store(in: &cancellables) - mastodonContextPublisher - .flatMap { $0.$ownAccount } - .sink { [unowned self] in self.controller.currentAccount = $0 } - .store(in: &cancellables) } required init?(coder aDecoder: NSCoder) { @@ -95,8 +69,13 @@ class ShareHostingController: UIHostingController { config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions config.dismiss = { [unowned self] in self.dismiss(mode: $0) } + config.fetchAvatar = Self.fetchAvatar + config.displayNameLabel = { account, style, _ in + // TODO: move AccountDisplayNameView to TuskerComponents and use that here as well + AnyView(Text(account.displayName).font(.system(style))) + } - controller.config = config + self.config = config } private func dismiss(mode: DismissMode) { @@ -110,10 +89,35 @@ class ShareHostingController: UIHostingController { } struct View: SwiftUI.View { - let controller: ComposeController + @ObservedObject var accountSwitchingState: AccountSwitchingState + let state: ComposeViewState + @ObservedObject @ObservableObjectBox private var config: ComposeUIConfig + @State private var currentAccount: Account? + + fileprivate init( + accountSwitchingState: AccountSwitchingState, + state: ComposeViewState, + config: ObservableObjectBox + ) { + self.accountSwitchingState = accountSwitchingState + self.state = state + self._config = ObservedObject(wrappedValue: config) + self._currentAccount = State(wrappedValue: accountSwitchingState.currentAccount) + } var body: some SwiftUI.View { - ControllerView(controller: { controller }) + ComposeView( + state: state, + mastodonController: accountSwitchingState.mastodonContext, + currentAccount: currentAccount, + config: config + ) + .onReceive(accountSwitchingState.$mastodonContext) { + state.draft.accountID = $0.accountInfo!.id + } + .onReceive(accountSwitchingState.currentAccountPublisher) { + currentAccount = $0 + } } } @@ -122,6 +126,16 @@ class ShareHostingController: UIHostingController { } } +// TODO: put this somewhere instead of just copying it from ComposeHostingController +@MainActor +@propertyWrapper +private final class ObservableObjectBox: ObservableObject { + @Published var wrappedValue: T + init(wrappedValue: T) { + self.wrappedValue = wrappedValue + } +} + // todo: shouldn't just copy this from the main Colors.swift extension UIColor { static let appBackground = UIColor { traitCollection in diff --git a/ShareExtension/SwitchAccountContainerView.swift b/ShareExtension/SwitchAccountContainerView.swift index 9ac6f649..01480367 100644 --- a/ShareExtension/SwitchAccountContainerView.swift +++ b/ShareExtension/SwitchAccountContainerView.swift @@ -13,9 +13,28 @@ import Pachyderm import Combine import ComposeUI +@MainActor +class AccountSwitchingState: ObservableObject { + @Published var mastodonContext: ShareMastodonContext + + var currentAccount: Account? { + mastodonContext.ownAccount + } + + var currentAccountPublisher: some Publisher { + $mastodonContext + .flatMap { $0.$ownAccount } + .compactMap { $0 } + } + + init(mastodonContext: ShareMastodonContext) { + self.mastodonContext = mastodonContext + } +} + struct SwitchAccountContainerView: View { let content: AnyView - let mastodonContextPublisher: CurrentValueSubject + @ObservedObject var state: AccountSwitchingState var accounts: [UserAccountInfo] { UserAccountsManager.shared.accounts @@ -50,7 +69,7 @@ struct SwitchAccountContainerView: View { } private func selectAccount(_ account: UserAccountInfo) { - mastodonContextPublisher.send(ShareMastodonContext(accountInfo: account)) + state.mastodonContext = ShareMastodonContext(accountInfo: account) } } diff --git a/Tusker/Screens/Compose/ComposeHostingController.swift b/Tusker/Screens/Compose/ComposeHostingController.swift index 9177c774..a6d51583 100644 --- a/Tusker/Screens/Compose/ComposeHostingController.swift +++ b/Tusker/Screens/Compose/ComposeHostingController.swift @@ -45,8 +45,7 @@ class ComposeHostingController: UIHostingController