Tusker/ShareExtension/ShareHostingController.swift

170 lines
6.1 KiB
Swift

//
// 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<ShareHostingController.View> {
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<ShareMastodonContext, Never>
private var cancellables = Set<AnyCancellable>()
init(draft: Draft, mastodonContext: ShareMastodonContext) {
let mastodonContextPublisher = CurrentValueSubject<ShareMastodonContext, Never>(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")
})
}
)
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
}
}
}