2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00
Nathan Mattes 6b32e5e4aa
Re-enable option to post unlisted/"Quiet public" (#1271)
* Add unlisted as option to post (#250)

* Replace menu-icons with SF Symbols to match other platforms (#250)

* Replace earth with SF-symbol (#250)

* Use SF Symbols for visibility (#250)
2024-04-12 10:58:18 +02:00

224 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ComposeContentToolbarView.swift
//
//
// Created by MainasuK on 22/10/18.
//
import SwiftUI
import UIKit
import MastodonAsset
import MastodonLocalization
import MastodonSDK
protocol ComposeContentToolbarViewDelegate: AnyObject {
func composeContentToolbarView(_ viewModel: ComposeContentToolbarView.ViewModel, toolbarItemDidPressed action: ComposeContentToolbarView.ViewModel.Action)
func composeContentToolbarView(_ viewModel: ComposeContentToolbarView.ViewModel, attachmentMenuDidPressed action: ComposeContentToolbarView.ViewModel.AttachmentAction)
}
struct ComposeContentToolbarView: View {
static var toolbarHeight: CGFloat { 48 }
@ObservedObject var viewModel: ViewModel
let isZoomed = (UIScreen.main.scale != UIScreen.main.nativeScale)
@State private var showingLanguagePicker = false
@State private var didChangeLanguage = false
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.verticalSizeClass) var verticalSizeClass
var body: some View {
HStack(spacing: .zero) {
ScrollView(isZoomed ? .horizontal : []) {
HStack(spacing: .zero) {
ForEach(ComposeContentToolbarView.ViewModel.Action.allCases, id: \.self) { action in
switch action {
case .attachment:
Menu {
ForEach(ComposeContentToolbarView.ViewModel.AttachmentAction.allCases, id: \.self) { attachmentAction in
Button {
viewModel.delegate?.composeContentToolbarView(viewModel, attachmentMenuDidPressed: attachmentAction)
} label: {
Label {
Text(attachmentAction.title)
} icon: {
Image(uiImage: attachmentAction.image)
}
}
}
} label: {
label(for: action)
.opacity(viewModel.isAttachmentButtonEnabled ? 1.0 : 0.5)
}
.disabled(!viewModel.isAttachmentButtonEnabled)
.frame(width: 48, height: 48)
case .visibility:
Menu {
Picker(selection: $viewModel.visibility) {
ForEach(viewModel.allVisibilities, id: \.self) { visibility in
Label {
Text(visibility.title)
} icon: {
Image(uiImage: visibility.image)
}
}
} label: {
Text(viewModel.visibility.title)
}
} label: {
label(for: viewModel.visibility.image)
.accessibilityLabel(L10n.Scene.Compose.Keyboard.selectVisibilityEntry(viewModel.visibility.title))
.opacity(viewModel.isVisibilityButtonEnabled ? 1.0 : 0.5)
}
.disabled(!viewModel.isVisibilityButtonEnabled)
.frame(width: 48, height: 48)
case .poll:
Button {
viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action)
} label: {
label(for: action)
.opacity(viewModel.isPollButtonEnabled ? 1.0 : 0.5)
}
.disabled(!viewModel.isPollButtonEnabled)
.frame(width: 48, height: 48)
case .language:
Menu {
Section {} // workaround a bug where the Suggested section doesnt appear
if !viewModel.suggestedLanguages.isEmpty {
Section(L10n.Scene.Compose.Language.suggested) {
ForEach(viewModel.suggestedLanguages.compactMap(Language.init(id:))) { lang in
Toggle(isOn: languageBinding(for: lang.id)) {
Text(lang.label)
}
}
}
}
let recent = viewModel.recentLanguages.filter { !viewModel.suggestedLanguages.contains($0) }
if !recent.isEmpty {
Section(L10n.Scene.Compose.Language.recent) {
ForEach(recent.compactMap(Language.init(id:))) { lang in
Toggle(isOn: languageBinding(for: lang.id)) {
Text(lang.label)
}
}
}
}
if !(recent + viewModel.suggestedLanguages).contains(viewModel.language) {
Toggle(isOn: languageBinding(for: viewModel.language)) {
Text(Language(id: viewModel.language)?.label ?? AttributedString("\(viewModel.language)"))
}
}
Button(L10n.Scene.Compose.Language.other) {
showingLanguagePicker = true
}
} label: {
let font = SwiftUI.Font.system(size: 11, weight: .semibold).width(viewModel.language.count == 3 ? .compressed : .standard)
Text(viewModel.language)
.font(font)
.textCase(.uppercase)
.padding(.horizontal, 4)
.minimumScaleFactor(0.5)
.frame(width: 24, height: 24, alignment: .center)
.overlay { RoundedRectangle(cornerRadius: 7).inset(by: 3).stroke(lineWidth: 1.5) }
.accessibilityLabel(L10n.Scene.Compose.Language.title)
.accessibilityValue(Text(Language(id: viewModel.language)?.label ?? AttributedString("\(viewModel.language)")))
.foregroundColor(Color(Asset.Scene.Compose.buttonTint.color))
.overlay(alignment: .topTrailing) {
Group {
if let suggested = viewModel.highConfidenceSuggestedLanguage,
suggested != viewModel.language,
!didChangeLanguage {
Circle().fill(.blue)
.frame(width: 8, height: 8)
}
}
.transition(.opacity)
.animation(.default, value: [viewModel.highConfidenceSuggestedLanguage, viewModel.language])
}
// fixes weird appearance when drawing at low opacity (eg when pressed)
.drawingGroup()
}
.frame(width: 48, height: 48)
.popover(isPresented: $showingLanguagePicker) {
let picker = LanguagePickerNavigationView(selectedLanguage: viewModel.language) { newLanguage in
viewModel.language = newLanguage
didChangeLanguage = true
showingLanguagePicker = false
}
if verticalSizeClass == .regular && horizontalSizeClass == .regular {
// explicitly size picker when its a popover
picker.frame(width: 400, height: 500)
} else {
picker
}
}
default:
Button {
viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action)
} label: {
label(for: action)
}
.frame(width: 48, height: 48)
}
}
}
}
Spacer()
let count: Int = {
if viewModel.isContentWarningActive {
return viewModel.contentWeightedLength + viewModel.contentWarningWeightedLength
} else {
return viewModel.contentWeightedLength
}
}()
let remains = viewModel.maxTextInputLimit - count
let isOverflow = remains < 0
Text("\(remains)")
.foregroundColor(Color(isOverflow ? UIColor.systemRed : UIColor.secondaryLabel))
.font(.system(size: isOverflow ? 18 : 16, weight: isOverflow ? .medium : .regular))
.accessibilityLabel(L10n.A11y.Plural.Count.charactersLeft(remains))
}
.padding(.leading, 4) // 4 + 12 = 16
.padding(.trailing, 16)
.frame(height: ComposeContentToolbarView.toolbarHeight)
.background(Color(viewModel.backgroundColor))
.accessibilityElement(children: .contain)
.accessibilityLabel(L10n.Scene.Compose.Accessibility.postOptions)
}
}
extension ComposeContentToolbarView {
func label(for action: ComposeContentToolbarView.ViewModel.Action) -> some View {
Image(uiImage: viewModel.image(for: action))
.foregroundColor(Color(Asset.Scene.Compose.buttonTint.color))
.frame(width: 24, height: 24, alignment: .center)
.accessibilityLabel(viewModel.label(for: action))
}
func label(for image: UIImage) -> some View {
Image(uiImage: image)
.foregroundColor(Color(Asset.Scene.Compose.buttonTint.color))
.frame(width: 24, height: 24, alignment: .center)
}
private func languageBinding(for code: String) -> Binding<Bool> {
Binding {
code == viewModel.language
} set: { newValue in
if newValue {
viewModel.language = code
}
didChangeLanguage = true
}
}
}
extension Mastodon.Entity.Status.Visibility {
}