Tusker/Tusker/Screens/Compose/ComposeToolbar.swift

150 lines
5.9 KiB
Swift

//
// ComposeToolbar.swift
// Tusker
//
// Created by Shadowfacts on 11/12/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import SwiftUI
import Pachyderm
import TuskerComponents
struct ComposeToolbar: View {
static let height: CGFloat = 44
private static let visibilityOptions: [MenuPicker.Option] = Visibility.allCases.map { vis in
.init(value: vis, title: vis.displayName, subtitle: vis.subtitle, image: UIImage(systemName: vis.unfilledImageName), accessibilityLabel: "Visibility: \(vis.displayName)")
}
@ObservedObject var draft: OldDraft
@EnvironmentObject private var uiState: ComposeUIState
@EnvironmentObject private var mastodonController: MastodonController
@ObservedObject private var preferences = Preferences.shared
@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) {
Button("CW") {
draft.contentWarningEnabled.toggle()
}
.accessibilityLabel(draft.contentWarningEnabled ? "Remove content warning" : "Add content warning")
.padding(5)
.hoverEffect()
MenuPicker(selection: $draft.visibility, options: Self.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)
if mastodonController.instanceFeatures.localOnlyPosts {
MenuPicker(selection: $draft.localOnly, options: [
.init(value: true, title: "Local-only", subtitle: "Only \(mastodonController.accountInfo!.instanceURL.host!)", image: UIImage(named: "link.broken")),
.init(value: false, title: "Federated", image: UIImage(systemName: "link"))
], buttonStyle: .iconOnly)
.padding(.horizontal, -8)
}
if let currentInput = uiState.currentInput, currentInput.toolbarElements.contains(.emojiPicker) {
Button(action: self.emojiPickerButtonPressed) {
Label("Insert custom emoji", systemImage: "face.smiling")
}
.labelStyle(.iconOnly)
.font(.system(size: imageSize))
.padding(5)
.hoverEffect()
.transition(.opacity.animation(.linear(duration: 0.2)))
}
if let currentInput = uiState.currentInput,
currentInput.toolbarElements.contains(.formattingButtons),
preferences.statusContentType != .plain {
Spacer()
ForEach(StatusFormat.allCases, id: \.rawValue) { format in
Button(action: self.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)))
}
}
Spacer()
}
.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: Self.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 func emojiPickerButtonPressed() {
guard uiState.autocompleteState == nil else {
return
}
uiState.shouldEmojiAutocompletionBeginExpanded = true
uiState.currentInput?.beginAutocompletingEmoji()
}
private func formatAction(_ format: StatusFormat) -> () -> Void {
{
uiState.currentInput?.applyFormat(format)
}
}
}
private struct ToolbarWidthPrefKey: PreferenceKey {
static var defaultValue: CGFloat? = nil
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
value = nextValue()
}
}
private extension View {
@available(iOS, obsoleted: 16.0)
@ViewBuilder
func scrollDisabledIfAvailable(_ disabled: Bool) -> some View {
if #available(iOS 16.0, *) {
self.scrollDisabled(disabled)
} else {
self
}
}
}
struct ComposeToolbar_Previews: PreviewProvider {
static var previews: some View {
ComposeToolbar(draft: OldDraft(accountID: ""))
}
}