// // 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 import Pachyderm 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 } #if os(visionOS) let size: CGFloat = 50 * 2 #else let size = 50 * UIScreen.main.scale #endif return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image } @ObservableObjectBox private var config = ComposeUIConfig() private let accountSwitchingState: AccountSwitchingState private let state: ComposeViewState init(draft: Draft, mastodonContext: ShareMastodonContext) { self.accountSwitchingState = AccountSwitchingState(mastodonContext: mastodonContext) self.state = ComposeViewState(draft: draft) let rootView = View( accountSwitchingState: self.accountSwitchingState, state: state, config: _config ) super.init(rootView: rootView) updateConfig() } required init?(coder aDecoder: NSCoder) { fatalError() } private func updateConfig() { var config = ComposeUIConfig() config.allowSwitchingDrafts = false config.textSelectionStartsAtBeginning = true 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) } 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))) } self.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 { @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 { ComposeView( state: state, mastodonController: accountSwitchingState.mastodonContext, currentAccount: currentAccount, config: config ) .onReceive(accountSwitchingState.$mastodonContext) { state.draft.accountID = $0.accountInfo!.id } .onReceive(accountSwitchingState.currentAccountPublisher) { currentAccount = $0 } } } enum Error: Swift.Error { case cancelled } } // 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 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 } } }