// Copyright © 2020 Metabolist. All rights reserved. import AVFoundation import Combine import Mastodon import UIKit import ViewModels final class CompositionInputAccessoryView: UIToolbar { let tagForInputView = UUID().hashValue private let viewModel: CompositionViewModel private let parentViewModel: NewStatusViewModel private var cancellables = Set() init(viewModel: CompositionViewModel, parentViewModel: NewStatusViewModel) { self.viewModel = viewModel self.parentViewModel = parentViewModel super.init( frame: .init( origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: .minimumButtonDimension))) initialSetup() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } private extension CompositionInputAccessoryView { // swiftlint:disable:next function_body_length func initialSetup() { autoresizingMask = .flexibleHeight heightAnchor.constraint(equalToConstant: .minimumButtonDimension).isActive = true var attachmentActions = [ UIAction( title: NSLocalizedString("compose.browse", comment: ""), image: UIImage(systemName: "ellipsis")) { [weak self] _ in guard let self = self else { return } self.parentViewModel.presentDocumentPicker(viewModel: self.viewModel) }, UIAction( title: NSLocalizedString("compose.photo-library", comment: ""), image: UIImage(systemName: "rectangle.on.rectangle")) { [weak self] _ in guard let self = self else { return } self.parentViewModel.presentMediaPicker(viewModel: self.viewModel) } ] #if !IS_SHARE_EXTENSION attachmentActions.insert(UIAction( title: NSLocalizedString("compose.take-photo-or-video", comment: ""), image: UIImage(systemName: "camera.fill")) { [weak self] _ in guard let self = self else { return } self.parentViewModel.presentCamera(viewModel: self.viewModel) }, at: 1) #endif let attachmentButton = UIBarButtonItem( image: UIImage(systemName: "paperclip"), menu: UIMenu(children: attachmentActions)) attachmentButton.accessibilityLabel = NSLocalizedString("compose.attachments-button.accessibility-label", comment: "") let pollButton = UIBarButtonItem( image: UIImage(systemName: "chart.bar.xaxis"), primaryAction: UIAction { [weak self] _ in self?.viewModel.displayPoll.toggle() }) pollButton.accessibilityLabel = NSLocalizedString("compose.poll-button.accessibility-label", comment: "") let visibilityButton = UIBarButtonItem( image: UIImage(systemName: parentViewModel.visibility.systemImageName), menu: visibilityMenu(selectedVisibility: parentViewModel.visibility)) let contentWarningButton = UIBarButtonItem( title: NSLocalizedString("status.content-warning-abbreviation", comment: ""), primaryAction: UIAction { [weak self] _ in self?.viewModel.displayContentWarning.toggle() }) viewModel.$displayContentWarning.sink { if $0 { contentWarningButton.accessibilityHint = NSLocalizedString("compose.content-warning-button.remove", comment: "") } else { contentWarningButton.accessibilityHint = NSLocalizedString("compose.content-warning-button.add", comment: "") } } .store(in: &cancellables) let emojiButton = UIBarButtonItem( image: UIImage(systemName: "face.smiling"), primaryAction: UIAction { [weak self] _ in guard let self = self else { return } self.parentViewModel.presentEmojiPicker(tag: self.tagForInputView) }) emojiButton.accessibilityLabel = NSLocalizedString("compose.emoji-button", comment: "") let addButton = UIBarButtonItem( image: UIImage(systemName: "plus.circle.fill"), primaryAction: UIAction { [weak self] _ in guard let self = self else { return } self.parentViewModel.insert(after: self.viewModel) }) switch parentViewModel.identityContext.appPreferences.statusWord { case .toot: addButton.accessibilityLabel = NSLocalizedString("compose.add-button-accessibility-label.toot", comment: "") case .post: addButton.accessibilityLabel = NSLocalizedString("compose.add-button-accessibility-label.post", comment: "") } let charactersLabel = UILabel() charactersLabel.font = .preferredFont(forTextStyle: .callout) charactersLabel.adjustsFontForContentSizeCategory = true charactersLabel.adjustsFontSizeToFitWidth = true let charactersBarItem = UIBarButtonItem(customView: charactersLabel) items = [ attachmentButton, UIBarButtonItem.fixedSpace(.defaultSpacing), pollButton, UIBarButtonItem.fixedSpace(.defaultSpacing), visibilityButton, UIBarButtonItem.fixedSpace(.defaultSpacing), contentWarningButton, UIBarButtonItem.fixedSpace(.defaultSpacing), emojiButton, UIBarButtonItem.flexibleSpace(), charactersBarItem, UIBarButtonItem.fixedSpace(.defaultSpacing), addButton] viewModel.$canAddAttachment .sink { attachmentButton.isEnabled = $0 } .store(in: &cancellables) viewModel.$attachmentViewModels .combineLatest(viewModel.$attachmentUpload) .sink { pollButton.isEnabled = $0.isEmpty && $1 == nil } .store(in: &cancellables) viewModel.$remainingCharacters.sink { charactersLabel.text = String($0) charactersLabel.textColor = $0 < 0 ? .systemRed : .label charactersLabel.accessibilityLabel = String.localizedStringWithFormat( NSLocalizedString("compose.characters-remaining-accessibility-label-%ld", comment: ""), $0) } .store(in: &cancellables) viewModel.$isPostable .sink { addButton.isEnabled = $0 } .store(in: &cancellables) parentViewModel.$visibility .sink { [weak self] in visibilityButton.image = UIImage(systemName: $0.systemImageName) visibilityButton.menu = self?.visibilityMenu(selectedVisibility: $0) visibilityButton.accessibilityLabel = String.localizedStringWithFormat( NSLocalizedString("compose.visibility-button.accessibility-label-%@", comment: ""), $0.title ?? "") } .store(in: &cancellables) } } private extension CompositionInputAccessoryView { func visibilityMenu(selectedVisibility: Status.Visibility) -> UIMenu { UIMenu(children: Status.Visibility.allCasesExceptUnknown.reversed().map { visibility in UIAction( title: visibility.title ?? "", image: UIImage(systemName: visibility.systemImageName), discoverabilityTitle: visibility.description, state: visibility == selectedVisibility ? .on : .off) { [weak self] _ in self?.parentViewModel.visibility = visibility } }) } }