mastodon-ios/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView.s...

242 lines
13 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 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 {
fileprivate var title: String {
switch self {
case .public: return L10n.Scene.Compose.Visibility.public
case .unlisted: return L10n.Scene.Compose.Visibility.unlisted
case .private: return L10n.Scene.Compose.Visibility.private
case .direct: return L10n.Scene.Compose.Visibility.direct
case ._other(let value): return value
}
}
fileprivate var image: UIImage {
switch self {
case .public: return Asset.Scene.Compose.earth.image.withRenderingMode(.alwaysTemplate)
case .unlisted: return Asset.Scene.Compose.people.image.withRenderingMode(.alwaysTemplate)
case .private: return Asset.Scene.Compose.peopleAdd.image.withRenderingMode(.alwaysTemplate)
case .direct: return Asset.Scene.Compose.mention.image.withRenderingMode(.alwaysTemplate)
case ._other: return Asset.Scene.Compose.more.image.withRenderingMode(.alwaysTemplate)
}
}
}