// // ShareHostingController.swift // ShareExtension // // Created by Shadowfacts on 4/17/23. // Copyright © 2023 Shadowfacts. All rights reserved. // import SwiftUI import ComposeUI import TuskerComponents 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 } #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 } private let controller: ComposeController private var mastodonContextPublisher: CurrentValueSubject private var cancellables = Set() 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: $0.url) { $0 .resizable() .aspectRatio(contentMode: .fit) } placeholder: { Image(systemName: "smiley.fill") }) } ) super.init(rootView: View(controller: controller)) 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) { 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) } 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 } } }