Use new compose UI for share extension

This commit is contained in:
Shadowfacts 2025-02-06 13:29:37 -05:00
parent c7c363782b
commit e9e08bdadd
5 changed files with 78 additions and 46 deletions

View File

@ -24,9 +24,10 @@ public struct ComposeUIConfig {
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
// TODO: remove these in favor of @PreferenceObserving
// Preferences // Preferences
public var avatarStyle = AvatarImageView.Style.roundRect
public var useTwitterKeyboard = false public var useTwitterKeyboard = false
public var contentType = StatusContentType.plain public var contentType = StatusContentType.plain
public var requireAttachmentDescriptions = false public var requireAttachmentDescriptions = false

View File

@ -36,7 +36,6 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.composeUIConfig.fillColor) private var fillColor @Environment(\.composeUIConfig.fillColor) private var fillColor
@Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard @Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard
// TODO: test textSelectionStartsAtBeginning
@Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning @Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning
func makeUIView(context: Context) -> UITextView { func makeUIView(context: Context) -> UITextView {

View File

@ -12,6 +12,7 @@ import TuskerComponents
import WebURLFoundationExtras import WebURLFoundationExtras
import Combine import Combine
import TuskerPreferences import TuskerPreferences
import Pachyderm
class ShareHostingController: UIHostingController<ShareHostingController.View> { class ShareHostingController: UIHostingController<ShareHostingController.View> {
private static func fetchAvatar(_ url: URL) async -> UIImage? { private static func fetchAvatar(_ url: URL) async -> UIImage? {
@ -27,48 +28,21 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image
} }
private let controller: ComposeController @ObservableObjectBox private var config = ComposeUIConfig()
private let accountSwitchingState: AccountSwitchingState
private var mastodonContextPublisher: CurrentValueSubject<ShareMastodonContext, Never> private let state: ComposeViewState
private var cancellables = Set<AnyCancellable>()
init(draft: Draft, mastodonContext: ShareMastodonContext) { init(draft: Draft, mastodonContext: ShareMastodonContext) {
let mastodonContextPublisher = CurrentValueSubject<ShareMastodonContext, Never>(mastodonContext) self.accountSwitchingState = AccountSwitchingState(mastodonContext: mastodonContext)
self.mastodonContextPublisher = mastodonContextPublisher self.state = ComposeViewState(draft: draft)
controller = ComposeController( let rootView = View(
draft: draft, accountSwitchingState: self.accountSwitchingState,
config: ComposeUIConfig(), state: state,
mastodonController: mastodonContext, config: _config
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")
})
}
) )
super.init(rootView: View(controller: controller)) super.init(rootView: rootView)
updateConfig() 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) { required init?(coder aDecoder: NSCoder) {
@ -95,8 +69,13 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions
config.dismiss = { [unowned self] in self.dismiss(mode: $0) } 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) { private func dismiss(mode: DismissMode) {
@ -110,10 +89,35 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
} }
struct View: SwiftUI.View { 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<ComposeUIConfig>
) {
self.accountSwitchingState = accountSwitchingState
self.state = state
self._config = ObservedObject(wrappedValue: config)
self._currentAccount = State(wrappedValue: accountSwitchingState.currentAccount)
}
var body: some SwiftUI.View { 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<ShareHostingController.View> {
} }
} }
// TODO: put this somewhere instead of just copying it from ComposeHostingController
@MainActor
@propertyWrapper
private final class ObservableObjectBox<T>: ObservableObject {
@Published var wrappedValue: T
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
}
// todo: shouldn't just copy this from the main Colors.swift // todo: shouldn't just copy this from the main Colors.swift
extension UIColor { extension UIColor {
static let appBackground = UIColor { traitCollection in static let appBackground = UIColor { traitCollection in

View File

@ -13,9 +13,28 @@ import Pachyderm
import Combine import Combine
import ComposeUI import ComposeUI
@MainActor
class AccountSwitchingState: ObservableObject {
@Published var mastodonContext: ShareMastodonContext
var currentAccount: Account? {
mastodonContext.ownAccount
}
var currentAccountPublisher: some Publisher<Account, Never> {
$mastodonContext
.flatMap { $0.$ownAccount }
.compactMap { $0 }
}
init(mastodonContext: ShareMastodonContext) {
self.mastodonContext = mastodonContext
}
}
struct SwitchAccountContainerView: View { struct SwitchAccountContainerView: View {
let content: AnyView let content: AnyView
let mastodonContextPublisher: CurrentValueSubject<ShareMastodonContext, Never> @ObservedObject var state: AccountSwitchingState
var accounts: [UserAccountInfo] { var accounts: [UserAccountInfo] {
UserAccountsManager.shared.accounts UserAccountsManager.shared.accounts
@ -50,7 +69,7 @@ struct SwitchAccountContainerView: View {
} }
private func selectAccount(_ account: UserAccountInfo) { private func selectAccount(_ account: UserAccountInfo) {
mastodonContextPublisher.send(ShareMastodonContext(accountInfo: account)) state.mastodonContext = ShareMastodonContext(accountInfo: account)
} }
} }

View File

@ -45,8 +45,7 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
self.mastodonController = mastodonController self.mastodonController = mastodonController
self.config = ComposeUIConfig() self.config = ComposeUIConfig()
self.currentAccount = mastodonController.account self.currentAccount = mastodonController.account
let state = ComposeViewState(draft: draft) self.state = ComposeViewState(draft: draft)
self.state = state
let rootView = View( let rootView = View(
state: state, state: state,