forked from shadowfacts/Tusker
168 lines
6.7 KiB
Swift
168 lines
6.7 KiB
Swift
//
|
|
// ToolbarController.swift
|
|
// ComposeUI
|
|
//
|
|
// Created by Shadowfacts on 3/7/23.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Pachyderm
|
|
import TuskerComponents
|
|
|
|
class ToolbarController: ViewController {
|
|
static let height: CGFloat = 44
|
|
private static let visibilityOptions: [MenuPicker<Pachyderm.Visibility>.Option] = Pachyderm.Visibility.allCases.map { vis in
|
|
.init(value: vis, title: vis.displayName, subtitle: vis.subtitle, image: UIImage(systemName: vis.unfilledImageName), accessibilityLabel: "Visibility: \(vis.displayName)")
|
|
}
|
|
|
|
unowned let parent: ComposeController
|
|
|
|
@Published var minWidth: CGFloat?
|
|
@Published var realWidth: CGFloat?
|
|
|
|
init(parent: ComposeController) {
|
|
self.parent = parent
|
|
}
|
|
|
|
var view: some View {
|
|
ToolbarView()
|
|
}
|
|
|
|
func showEmojiPicker() {
|
|
guard parent.currentInput?.autocompleteState == nil else {
|
|
return
|
|
}
|
|
parent.shouldEmojiAutocompletionBeginExpanded = true
|
|
parent.currentInput?.beginAutocompletingEmoji()
|
|
}
|
|
|
|
func formatAction(_ format: StatusFormat) -> () -> Void {
|
|
{ [weak self] in
|
|
self?.parent.currentInput?.applyFormat(format)
|
|
}
|
|
}
|
|
|
|
struct ToolbarView: View {
|
|
@EnvironmentObject private var draft: Draft
|
|
@EnvironmentObject private var controller: ToolbarController
|
|
@EnvironmentObject private var composeController: ComposeController
|
|
@ScaledMetric(relativeTo: .body) private var imageSize: CGFloat = 22
|
|
|
|
@State private var minWidth: CGFloat?
|
|
@State private var realWidth: CGFloat?
|
|
|
|
var body: some View {
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
HStack(spacing: 0) {
|
|
cwButton
|
|
|
|
MenuPicker(selection: $draft.visibility, options: ToolbarController.visibilityOptions, buttonStyle: .iconOnly)
|
|
// the button has a bunch of extra space by default, but combined with what we add it's too much
|
|
.padding(.horizontal, -8)
|
|
.disabled(draft.editedStatusID != nil)
|
|
|
|
if composeController.mastodonController.instanceFeatures.localOnlyPosts {
|
|
localOnlyPicker
|
|
.padding(.horizontal, -8)
|
|
.disabled(draft.editedStatusID != nil)
|
|
}
|
|
|
|
if let currentInput = composeController.currentInput,
|
|
currentInput.toolbarElements.contains(.emojiPicker) {
|
|
customEmojiButton
|
|
}
|
|
|
|
if let currentInput = composeController.currentInput,
|
|
currentInput.toolbarElements.contains(.formattingButtons),
|
|
composeController.config.contentType != .plain {
|
|
|
|
Spacer()
|
|
formatButtons
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if #available(iOS 16.0, *),
|
|
composeController.mastodonController.instanceFeatures.createStatusWithLanguage {
|
|
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $composeController.hasChangedLanguageSelection)
|
|
}
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.frame(minWidth: minWidth)
|
|
.background(GeometryReader { proxy in
|
|
Color.clear
|
|
.preference(key: ToolbarWidthPrefKey.self, value: proxy.size.width)
|
|
.onPreferenceChange(ToolbarWidthPrefKey.self) { width in
|
|
realWidth = width
|
|
}
|
|
})
|
|
}
|
|
.scrollDisabledIfAvailable(realWidth ?? 0 <= minWidth ?? 0)
|
|
.frame(height: ToolbarController.height)
|
|
.frame(maxWidth: .infinity)
|
|
.background(.regularMaterial, ignoresSafeAreaEdges: .bottom)
|
|
.overlay(alignment: .top) {
|
|
Divider()
|
|
}
|
|
.background(GeometryReader { proxy in
|
|
Color.clear
|
|
.preference(key: ToolbarWidthPrefKey.self, value: proxy.size.width)
|
|
.onPreferenceChange(ToolbarWidthPrefKey.self) { width in
|
|
minWidth = width
|
|
}
|
|
})
|
|
}
|
|
|
|
private var cwButton: some View {
|
|
Button("CW", action: controller.parent.toggleContentWarning)
|
|
.accessibilityLabel(draft.contentWarningEnabled ? "Remove content warning" : "Add content warning")
|
|
.padding(5)
|
|
.hoverEffect()
|
|
}
|
|
|
|
private var localOnlyPicker: some View {
|
|
let domain = composeController.mastodonController.accountInfo!.instanceURL.host!
|
|
return MenuPicker(selection: $draft.localOnly, options: [
|
|
.init(value: true, title: "Local-only", subtitle: "Only \(domain)", image: UIImage(named: "link.broken")),
|
|
.init(value: false, title: "Federated", image: UIImage(systemName: "link")),
|
|
], buttonStyle: .iconOnly)
|
|
}
|
|
|
|
private var customEmojiButton: some View {
|
|
Button(action: controller.showEmojiPicker) {
|
|
Label("Insert custom emoji", systemImage: "face.smiling")
|
|
}
|
|
.labelStyle(.iconOnly)
|
|
.font(.system(size: imageSize))
|
|
.padding(5)
|
|
.hoverEffect()
|
|
.transition(.opacity.animation(.linear(duration: 0.2)))
|
|
}
|
|
|
|
private var formatButtons: some View {
|
|
ForEach(StatusFormat.allCases, id: \.rawValue) { format in
|
|
Button(action: controller.formatAction(format)) {
|
|
if let imageName = format.imageName {
|
|
Image(systemName: imageName)
|
|
.font(.system(size: imageSize))
|
|
} else if let (str, attrs) = format.title {
|
|
let container = try! AttributeContainer(attrs, including: \.uiKit)
|
|
Text(AttributedString(str, attributes: container))
|
|
}
|
|
}
|
|
.accessibilityLabel(format.accessibilityLabel)
|
|
.padding(5)
|
|
.hoverEffect()
|
|
.transition(.opacity.animation(.linear(duration: 0.2)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct ToolbarWidthPrefKey: PreferenceKey {
|
|
static var defaultValue: CGFloat? = nil
|
|
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
|
|
value = nextValue()
|
|
}
|
|
}
|