Hide activity indicator in case of no emojis (#1088)
This commit is contained in:
parent
e50b9e13a6
commit
cc2f7f0b8c
|
@ -395,7 +395,6 @@
|
||||||
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */; };
|
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */; };
|
||||||
DB9F58EF26EF491E00E7BBE9 /* AccountListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */; };
|
DB9F58EF26EF491E00E7BBE9 /* AccountListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */; };
|
||||||
DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */; };
|
DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */; };
|
||||||
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */; };
|
|
||||||
DBA4B0F626C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; };
|
DBA4B0F626C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; };
|
||||||
DBA4B0F726C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; };
|
DBA4B0F726C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; };
|
||||||
DBA5A53126F08EF000CACBAA /* DragIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5A53026F08EF000CACBAA /* DragIndicatorView.swift */; };
|
DBA5A53126F08EF000CACBAA /* DragIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5A53026F08EF000CACBAA /* DragIndicatorView.swift */; };
|
||||||
|
@ -1103,7 +1102,6 @@
|
||||||
DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
|
DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
|
||||||
DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListViewModel.swift; sourceTree = "<group>"; };
|
DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListViewModel.swift; sourceTree = "<group>"; };
|
||||||
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListTableViewCell.swift; sourceTree = "<group>"; };
|
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = "<group>"; };
|
|
||||||
DBA4B0D326BD10AC0077136E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Intents.strings"; sourceTree = "<group>"; };
|
DBA4B0D326BD10AC0077136E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Intents.strings"; sourceTree = "<group>"; };
|
||||||
DBA4B0D626BD10AD0077136E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
DBA4B0D626BD10AD0077136E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
DBA4B0D726BD10F40077136E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Intents.strings; sourceTree = "<group>"; };
|
DBA4B0D726BD10F40077136E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||||
|
@ -2166,7 +2164,6 @@
|
||||||
DB55D32225FB4D320002F825 /* View */ = {
|
DB55D32225FB4D320002F825 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */,
|
|
||||||
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */,
|
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */,
|
||||||
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */,
|
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */,
|
||||||
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */,
|
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */,
|
||||||
|
@ -3839,7 +3836,6 @@
|
||||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
||||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||||
DBEFCD74282A130400C0ABEA /* ReportReasonViewModel.swift in Sources */,
|
DBEFCD74282A130400C0ABEA /* ReportReasonViewModel.swift in Sources */,
|
||||||
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
|
||||||
DBFEEC96279BDC67004F81DD /* ProfileAboutViewController.swift in Sources */,
|
DBFEEC96279BDC67004F81DD /* ProfileAboutViewController.swift in Sources */,
|
||||||
DB63F74F2799405600455B82 /* SearchHistoryViewModel+Diffable.swift in Sources */,
|
DB63F74F2799405600455B82 /* SearchHistoryViewModel+Diffable.swift in Sources */,
|
||||||
DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */,
|
DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */,
|
||||||
|
|
|
@ -1,384 +0,0 @@
|
||||||
//
|
|
||||||
// ComposeToolbarView.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-3-12.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
|
||||||
import MastodonSDK
|
|
||||||
import MastodonAsset
|
|
||||||
import MastodonCore
|
|
||||||
import MastodonUI
|
|
||||||
import MastodonLocalization
|
|
||||||
|
|
||||||
protocol ComposeToolbarViewDelegate: AnyObject {
|
|
||||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, mediaButtonDidPressed sender: Any, mediaSelectionType type: ComposeToolbarView.MediaSelectionType)
|
|
||||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: Any)
|
|
||||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: Any)
|
|
||||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: Any)
|
|
||||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: Any, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType)
|
|
||||||
}
|
|
||||||
|
|
||||||
final class ComposeToolbarView: UIView {
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
static let toolbarButtonSize: CGSize = CGSize(width: 44, height: 44)
|
|
||||||
static let toolbarHeight: CGFloat = 44
|
|
||||||
|
|
||||||
weak var delegate: ComposeToolbarViewDelegate?
|
|
||||||
|
|
||||||
// barButtonItem
|
|
||||||
private(set) lazy var mediaBarButtonItem: UIBarButtonItem = {
|
|
||||||
let barButtonItem = UIBarButtonItem()
|
|
||||||
barButtonItem.image = UIImage(systemName: "photo")
|
|
||||||
barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendAttachment
|
|
||||||
return barButtonItem
|
|
||||||
}()
|
|
||||||
|
|
||||||
let pollBarButtonItem: UIBarButtonItem = {
|
|
||||||
let barButtonItem = UIBarButtonItem()
|
|
||||||
barButtonItem.image = UIImage(systemName: "list.bullet")
|
|
||||||
barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
|
|
||||||
return barButtonItem
|
|
||||||
}()
|
|
||||||
|
|
||||||
let contentWarningBarButtonItem: UIBarButtonItem = {
|
|
||||||
let barButtonItem = UIBarButtonItem()
|
|
||||||
barButtonItem.image = UIImage(systemName: "exclamationmark.shield")
|
|
||||||
barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.enableContentWarning
|
|
||||||
return barButtonItem
|
|
||||||
}()
|
|
||||||
|
|
||||||
let visibilityBarButtonItem: UIBarButtonItem = {
|
|
||||||
let barButtonItem = UIBarButtonItem()
|
|
||||||
barButtonItem.image = UIImage(systemName: "person.3")
|
|
||||||
barButtonItem.accessibilityLabel = L10n.Scene.Compose.Accessibility.postVisibilityMenu
|
|
||||||
return barButtonItem
|
|
||||||
}()
|
|
||||||
|
|
||||||
// button
|
|
||||||
|
|
||||||
let mediaButton: UIButton = {
|
|
||||||
let button = HighlightDimmableButton()
|
|
||||||
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
|
|
||||||
button.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal)
|
|
||||||
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendAttachment
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
let pollButton: UIButton = {
|
|
||||||
let button = HighlightDimmableButton(type: .custom)
|
|
||||||
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
|
|
||||||
button.setImage(UIImage(systemName: "list.bullet", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium)), for: .normal)
|
|
||||||
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
let emojiButton: UIButton = {
|
|
||||||
let button = HighlightDimmableButton()
|
|
||||||
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
|
|
||||||
let image = Asset.Human.faceSmilingAdaptive.image
|
|
||||||
.af.imageScaled(to: CGSize(width: 20, height: 20))
|
|
||||||
.withRenderingMode(.alwaysTemplate)
|
|
||||||
button.setImage(image, for: .normal)
|
|
||||||
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.customEmojiPicker
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
let contentWarningButton: UIButton = {
|
|
||||||
let button = HighlightDimmableButton()
|
|
||||||
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
|
|
||||||
button.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal)
|
|
||||||
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.enableContentWarning
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
let visibilityButton: UIButton = {
|
|
||||||
let button = HighlightDimmableButton()
|
|
||||||
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
|
|
||||||
button.setImage(UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)), for: .normal)
|
|
||||||
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.postVisibilityMenu
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
let characterCountLabel: UILabel = {
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = .systemFont(ofSize: 15, weight: .regular)
|
|
||||||
label.text = "500"
|
|
||||||
label.textColor = Asset.Colors.Label.secondary.color
|
|
||||||
label.accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(500)
|
|
||||||
return label
|
|
||||||
}()
|
|
||||||
|
|
||||||
let activeVisibilityType = CurrentValueSubject<VisibilitySelectionType, Never>(.public)
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
_init()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
_init()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ComposeToolbarView {
|
|
||||||
|
|
||||||
private func _init() {
|
|
||||||
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
|
||||||
ThemeService.shared.currentTheme
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] theme in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.setupBackgroundColor(theme: theme)
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
let stackView = UIStackView()
|
|
||||||
stackView.axis = .horizontal
|
|
||||||
stackView.spacing = 0
|
|
||||||
stackView.distribution = .fillEqually
|
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(stackView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
||||||
layoutMarginsGuide.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 8), // tweak button margin offset
|
|
||||||
])
|
|
||||||
|
|
||||||
let buttons = [
|
|
||||||
mediaButton,
|
|
||||||
pollButton,
|
|
||||||
emojiButton,
|
|
||||||
contentWarningButton,
|
|
||||||
visibilityButton,
|
|
||||||
]
|
|
||||||
buttons.forEach { button in
|
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
stackView.addArrangedSubview(button)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
button.widthAnchor.constraint(equalToConstant: 44),
|
|
||||||
button.heightAnchor.constraint(equalToConstant: 44),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
characterCountLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(characterCountLabel)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
characterCountLabel.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
characterCountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: 8),
|
|
||||||
characterCountLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
|
||||||
characterCountLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
])
|
|
||||||
characterCountLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
||||||
|
|
||||||
mediaBarButtonItem.menu = createMediaContextMenu()
|
|
||||||
mediaButton.menu = createMediaContextMenu()
|
|
||||||
mediaButton.showsMenuAsPrimaryAction = true
|
|
||||||
pollBarButtonItem.target = self
|
|
||||||
pollBarButtonItem.action = #selector(ComposeToolbarView.pollButtonDidPressed(_:))
|
|
||||||
pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside)
|
|
||||||
emojiButton.addTarget(self, action: #selector(ComposeToolbarView.emojiButtonDidPressed(_:)), for: .touchUpInside)
|
|
||||||
contentWarningBarButtonItem.target = self
|
|
||||||
contentWarningBarButtonItem.action = #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:))
|
|
||||||
contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
|
||||||
visibilityBarButtonItem.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle)
|
|
||||||
visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle)
|
|
||||||
visibilityButton.showsMenuAsPrimaryAction = true
|
|
||||||
|
|
||||||
updateToolbarButtonUserInterfaceStyle()
|
|
||||||
|
|
||||||
// update menu when selected visibility type changed
|
|
||||||
activeVisibilityType
|
|
||||||
.receive(on: RunLoop.main)
|
|
||||||
.sink { [weak self] type in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.visibilityBarButtonItem.menu = self.createVisibilityContextMenu(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
|
||||||
self.visibilityButton.menu = self.createVisibilityContextMenu(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
|
||||||
|
|
||||||
updateToolbarButtonUserInterfaceStyle()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ComposeToolbarView {
|
|
||||||
enum MediaSelectionType: String {
|
|
||||||
case camera
|
|
||||||
case photoLibrary
|
|
||||||
case browse
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VisibilitySelectionType: String, CaseIterable {
|
|
||||||
case `public`
|
|
||||||
// TODO: remove unlisted option from codebase
|
|
||||||
// case unlisted
|
|
||||||
case `private`
|
|
||||||
case direct
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func image(interfaceStyle: UIUserInterfaceStyle) -> UIImage {
|
|
||||||
switch self {
|
|
||||||
case .public: return UIImage(systemName: "globe", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .medium))!
|
|
||||||
// case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular))!
|
|
||||||
case .private:
|
|
||||||
switch interfaceStyle {
|
|
||||||
case .light: return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
|
|
||||||
default: return UIImage(systemName: "person.3.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
|
|
||||||
}
|
|
||||||
case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .regular))!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var visibility: Mastodon.Entity.Status.Visibility {
|
|
||||||
switch self {
|
|
||||||
case .public: return .public
|
|
||||||
// case .unlisted: return .unlisted
|
|
||||||
case .private: return .private
|
|
||||||
case .direct: return .direct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ComposeToolbarView {
|
|
||||||
|
|
||||||
private func setupBackgroundColor(theme: Theme) {
|
|
||||||
backgroundColor = theme.composeToolbarBackgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func configureToolbarButtonAppearance(button: UIButton) {
|
|
||||||
button.tintColor = ThemeService.tintColor
|
|
||||||
button.setBackgroundImage(.placeholder(size: ComposeToolbarView.toolbarButtonSize, color: .systemFill), for: .highlighted)
|
|
||||||
button.layer.masksToBounds = true
|
|
||||||
button.layer.cornerRadius = 5
|
|
||||||
button.layer.cornerCurve = .continuous
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateToolbarButtonUserInterfaceStyle() {
|
|
||||||
// reset emoji
|
|
||||||
let emojiButtonImage = Asset.Human.faceSmilingAdaptive.image
|
|
||||||
.af.imageScaled(to: CGSize(width: 20, height: 20))
|
|
||||||
.withRenderingMode(.alwaysTemplate)
|
|
||||||
emojiButton.setImage(emojiButtonImage, for: .normal)
|
|
||||||
|
|
||||||
switch traitCollection.userInterfaceStyle {
|
|
||||||
case .light:
|
|
||||||
mediaBarButtonItem.image = UIImage(systemName: "photo")
|
|
||||||
mediaButton.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
|
||||||
contentWarningBarButtonItem.image = UIImage(systemName: "exclamationmark.shield")
|
|
||||||
contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
|
||||||
|
|
||||||
case .dark:
|
|
||||||
mediaBarButtonItem.image = UIImage(systemName: "photo.fill")
|
|
||||||
mediaButton.setImage(UIImage(systemName: "photo.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
|
||||||
contentWarningBarButtonItem.image = UIImage(systemName: "exclamationmark.shield.fill")
|
|
||||||
contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
|
||||||
|
|
||||||
default:
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
|
|
||||||
visibilityBarButtonItem.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle)
|
|
||||||
visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func createMediaContextMenu() -> UIMenu {
|
|
||||||
var children: [UIMenuElement] = []
|
|
||||||
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .photoLibrary", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
self.delegate?.composeToolbarView(self, mediaButtonDidPressed: self.mediaButton, mediaSelectionType: .photoLibrary)
|
|
||||||
}
|
|
||||||
children.append(photoLibraryAction)
|
|
||||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
|
||||||
let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .camera", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
self.delegate?.composeToolbarView(self, mediaButtonDidPressed: self.mediaButton, mediaSelectionType: .camera)
|
|
||||||
})
|
|
||||||
children.append(cameraAction)
|
|
||||||
}
|
|
||||||
let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .browse", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
self.delegate?.composeToolbarView(self, mediaButtonDidPressed: self.mediaButton, mediaSelectionType: .browse)
|
|
||||||
}
|
|
||||||
children.append(browseAction)
|
|
||||||
|
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func createVisibilityContextMenu(interfaceStyle: UIUserInterfaceStyle) -> UIMenu {
|
|
||||||
let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in
|
|
||||||
let state: UIMenuElement.State = activeVisibilityType.value == type ? .on : .off
|
|
||||||
return UIAction(title: type.title, image: type.image(interfaceStyle: interfaceStyle), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { [weak self] action in
|
|
||||||
guard let self = self else { return }
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue)
|
|
||||||
self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ComposeToolbarView {
|
|
||||||
|
|
||||||
@objc private func pollButtonDidPressed(_ sender: Any) {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
delegate?.composeToolbarView(self, pollButtonDidPressed: sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func emojiButtonDidPressed(_ sender: Any) {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
delegate?.composeToolbarView(self, emojiButtonDidPressed: sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func contentWarningButtonDidPressed(_ sender: Any) {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
delegate?.composeToolbarView(self, contentWarningButtonDidPressed: sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#if canImport(SwiftUI) && DEBUG
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ComposeToolbarView_Previews: PreviewProvider {
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
UIViewPreview(width: 375) {
|
|
||||||
let toolbarView = ComposeToolbarView()
|
|
||||||
toolbarView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
toolbarView.widthAnchor.constraint(equalToConstant: 375).priority(.defaultHigh),
|
|
||||||
toolbarView.heightAnchor.constraint(equalToConstant: 64).priority(.defaultHigh),
|
|
||||||
])
|
|
||||||
return toolbarView
|
|
||||||
}
|
|
||||||
.previewLayout(.fixed(width: 375, height: 100))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -20,43 +20,6 @@ public enum ComposeStatusSection: Equatable, Hashable {
|
||||||
case poll
|
case poll
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeStatusSection {
|
|
||||||
|
|
||||||
// static func configure(
|
|
||||||
// cell: ComposeStatusContentTableViewCell,
|
|
||||||
// attribute: ComposeStatusItem.ComposeStatusAttribute
|
|
||||||
// ) {
|
|
||||||
// cell.prepa
|
|
||||||
// // set avatar
|
|
||||||
// attribute.avatarURL
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { avatarURL in
|
|
||||||
// cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: avatarURL))
|
|
||||||
// }
|
|
||||||
// .store(in: &cell.disposeBag)
|
|
||||||
// // set display name and username
|
|
||||||
// Publishers.CombineLatest3(
|
|
||||||
// attribute.displayName,
|
|
||||||
// attribute.emojiMeta,
|
|
||||||
// attribute.username
|
|
||||||
// )
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { displayName, emojiMeta, username in
|
|
||||||
// do {
|
|
||||||
// let mastodonContent = MastodonContent(content: displayName ?? " ", emojis: emojiMeta)
|
|
||||||
// let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
|
||||||
// cell.statusView.nameLabel.configure(content: metaContent)
|
|
||||||
// } catch {
|
|
||||||
// let metaContent = PlaintextMetaContent(string: " ")
|
|
||||||
// cell.statusView.nameLabel.configure(content: metaContent)
|
|
||||||
// }
|
|
||||||
// cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " "
|
|
||||||
// }
|
|
||||||
// .store(in: &cell.disposeBag)
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public protocol CustomEmojiReplaceableTextInput: UITextInput & UIResponder {
|
public protocol CustomEmojiReplaceableTextInput: UITextInput & UIResponder {
|
||||||
var inputView: UIView? { get set }
|
var inputView: UIView? { get set }
|
||||||
}
|
}
|
||||||
|
@ -71,24 +34,3 @@ public class CustomEmojiReplaceableTextInputReference {
|
||||||
|
|
||||||
extension UITextField: CustomEmojiReplaceableTextInput { }
|
extension UITextField: CustomEmojiReplaceableTextInput { }
|
||||||
extension UITextView: CustomEmojiReplaceableTextInput { }
|
extension UITextView: CustomEmojiReplaceableTextInput { }
|
||||||
|
|
||||||
extension ComposeStatusSection {
|
|
||||||
|
|
||||||
// static func configureCustomEmojiPicker(
|
|
||||||
// viewModel: CustomEmojiPickerInputViewModel?,
|
|
||||||
// customEmojiReplaceableTextInput: CustomEmojiReplaceableTextInput,
|
|
||||||
// disposeBag: inout Set<AnyCancellable>
|
|
||||||
// ) {
|
|
||||||
// guard let viewModel = viewModel else { return }
|
|
||||||
// viewModel.isCustomEmojiComposing
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak viewModel] isCustomEmojiComposing in
|
|
||||||
// guard let viewModel = viewModel else { return }
|
|
||||||
// customEmojiReplaceableTextInput.inputView = isCustomEmojiComposing ? viewModel.customEmojiPickerInputView : nil
|
|
||||||
// customEmojiReplaceableTextInput.reloadInputViews()
|
|
||||||
// viewModel.append(customEmojiReplaceableTextInput: customEmojiReplaceableTextInput)
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,8 +32,8 @@ extension EmojiService {
|
||||||
stateMachine.enter(LoadState.Initial.self)
|
stateMachine.enter(LoadState.Initial.self)
|
||||||
return stateMachine
|
return stateMachine
|
||||||
}()
|
}()
|
||||||
public let emojis = CurrentValueSubject<[Mastodon.Entity.Emoji], Never>([])
|
public let emojis = CurrentValueSubject<[Mastodon.Entity.Emoji]?, Never>(nil)
|
||||||
public let emojiDict = CurrentValueSubject<[String: [Mastodon.Entity.Emoji]], Never>([:])
|
public let emojiDict = CurrentValueSubject<[String: [Mastodon.Entity.Emoji]]?, Never>(nil)
|
||||||
public let emojiMapping = CurrentValueSubject<[String: String], Never>([:])
|
public let emojiMapping = CurrentValueSubject<[String: String], Never>([:])
|
||||||
public let emojiTrie = CurrentValueSubject<Trie<Character>?, Never>(nil)
|
public let emojiTrie = CurrentValueSubject<Trie<Character>?, Never>(nil)
|
||||||
|
|
||||||
|
@ -44,12 +44,17 @@ extension EmojiService {
|
||||||
self.service = service
|
self.service = service
|
||||||
|
|
||||||
emojis
|
emojis
|
||||||
.map { Dictionary(grouping: $0, by: { $0.shortcode }) }
|
.compactMap { value in
|
||||||
|
guard let value else { return nil }
|
||||||
|
return Dictionary(grouping: value, by: { value in value.shortcode })
|
||||||
|
}
|
||||||
.assign(to: \.value, on: emojiDict)
|
.assign(to: \.value, on: emojiDict)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
emojiDict
|
emojiDict
|
||||||
.map { dict in
|
.compactMap { dict in
|
||||||
|
guard let dict else { return nil }
|
||||||
|
|
||||||
var mapping: [String: String] = [:]
|
var mapping: [String: String] = [:]
|
||||||
for (key, values) in dict {
|
for (key, values) in dict {
|
||||||
guard let emoji = values.first else { continue }
|
guard let emoji = values.first else { continue }
|
||||||
|
@ -61,8 +66,8 @@ extension EmojiService {
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
emojis
|
emojis
|
||||||
.map { emojis -> Trie<Character>? in
|
.compactMap { emojis -> Trie<Character>? in
|
||||||
guard !emojis.isEmpty else { return nil }
|
guard let emojis, emojis.isEmpty == false else { return nil }
|
||||||
var trie: Trie<Character> = Trie()
|
var trie: Trie<Character> = Trie()
|
||||||
for emoji in emojis {
|
for emoji in emojis {
|
||||||
let key = emoji.shortcode.lowercased()
|
let key = emoji.shortcode.lowercased()
|
||||||
|
@ -84,7 +89,7 @@ extension EmojiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return emojiDict.value[shortcode]?.first
|
return emojiDict.value?[shortcode]?.first
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,10 +272,10 @@ extension ComposeContentViewController {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveValue: { [weak self] emojis in
|
.sink(receiveValue: { [weak self] emojis in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if emojis.isEmpty {
|
if emojis != nil {
|
||||||
self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
|
|
||||||
} else {
|
|
||||||
self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
|
self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
|
||||||
|
} else {
|
||||||
|
self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -111,7 +111,9 @@ extension ComposeContentViewModel {
|
||||||
// Don't block the main queue
|
// Don't block the main queue
|
||||||
.receive(on: DispatchQueue.global(qos: .userInteractive))
|
.receive(on: DispatchQueue.global(qos: .userInteractive))
|
||||||
// Sort emojis
|
// Sort emojis
|
||||||
.map({ (emojis) -> [Mastodon.Entity.Emoji] in
|
.compactMap({ (emojis) -> [Mastodon.Entity.Emoji]? in
|
||||||
|
guard let emojis else { return nil }
|
||||||
|
|
||||||
return emojis.sorted { a, b in
|
return emojis.sorted { a, b in
|
||||||
a.shortcode.lowercased() < b.shortcode.lowercased()
|
a.shortcode.lowercased() < b.shortcode.lowercased()
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ public struct ViewLayoutFrame {
|
||||||
extension ViewLayoutFrame {
|
extension ViewLayoutFrame {
|
||||||
public mutating func update(view: UIView) {
|
public mutating func update(view: UIView) {
|
||||||
guard view.window != nil else {
|
guard view.window != nil else {
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): layoutFrame update for a view without attached window. Skip this invalid update")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +47,5 @@ extension ViewLayoutFrame {
|
||||||
if self.readableContentLayoutFrame != readableContentLayoutFrame {
|
if self.readableContentLayoutFrame != readableContentLayoutFrame {
|
||||||
self.readableContentLayoutFrame = readableContentLayoutFrame
|
self.readableContentLayoutFrame = readableContentLayoutFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): layoutFrame: \(layoutFrame.debugDescription)")
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): safeAreaLayoutFrame: \(safeAreaLayoutFrame.debugDescription)")
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): readableContentLayoutFrame: \(readableContentLayoutFrame.debugDescription)")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue