2021-03-11 08:41:27 +01:00
|
|
|
//
|
|
|
|
// ComposeViewController.swift
|
|
|
|
// Mastodon
|
|
|
|
//
|
|
|
|
// Created by MainasuK Cirno on 2021-3-11.
|
|
|
|
//
|
|
|
|
|
|
|
|
import os.log
|
|
|
|
import UIKit
|
|
|
|
import Combine
|
2021-03-18 08:16:35 +01:00
|
|
|
import PhotosUI
|
2022-10-08 07:43:06 +02:00
|
|
|
import Meta
|
2021-07-22 13:34:24 +02:00
|
|
|
import MetaTextKit
|
2021-06-28 13:41:41 +02:00
|
|
|
import MastodonMeta
|
2022-01-27 14:23:39 +01:00
|
|
|
import MastodonAsset
|
2022-10-08 07:43:06 +02:00
|
|
|
import MastodonCore
|
|
|
|
import MastodonUI
|
2022-01-27 14:23:39 +01:00
|
|
|
import MastodonLocalization
|
|
|
|
import MastodonSDK
|
2021-03-11 08:41:27 +01:00
|
|
|
|
|
|
|
final class ComposeViewController: UIViewController, NeedsDependency {
|
|
|
|
|
2021-05-18 08:25:32 +02:00
|
|
|
static let minAutoCompleteVisibleHeight: CGFloat = 100
|
2021-09-28 13:58:14 +02:00
|
|
|
|
2021-03-11 08:41:27 +01:00
|
|
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
|
|
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
|
|
|
|
|
|
|
var disposeBag = Set<AnyCancellable>()
|
|
|
|
var viewModel: ComposeViewModel!
|
2021-06-28 13:41:41 +02:00
|
|
|
|
|
|
|
let logger = Logger(subsystem: "ComposeViewController", category: "logic")
|
2021-03-16 07:19:12 +01:00
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
lazy var composeContentViewModel: ComposeContentViewModel = {
|
2022-10-11 12:31:40 +02:00
|
|
|
return ComposeContentViewModel(
|
|
|
|
context: context,
|
|
|
|
authContext: viewModel.authContext,
|
|
|
|
kind: viewModel.kind
|
|
|
|
)
|
2021-09-29 10:27:35 +02:00
|
|
|
}()
|
2022-10-10 13:14:52 +02:00
|
|
|
private(set) lazy var composeContentViewController: ComposeContentViewController = {
|
|
|
|
let composeContentViewController = ComposeContentViewController()
|
|
|
|
composeContentViewController.viewModel = composeContentViewModel
|
|
|
|
return composeContentViewController
|
2021-09-29 10:27:35 +02:00
|
|
|
}()
|
2022-01-27 14:23:39 +01:00
|
|
|
|
2022-10-11 12:31:40 +02:00
|
|
|
private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
|
|
|
|
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
|
|
|
|
}()
|
|
|
|
private(set) lazy var characterCountBarButtonItem: UIBarButtonItem = {
|
|
|
|
let barButtonItem = UIBarButtonItem(customView: characterCountLabel)
|
|
|
|
return barButtonItem
|
|
|
|
}()
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// let publishButton: UIButton = {
|
|
|
|
// let button = RoundedEdgesButton(type: .custom)
|
|
|
|
// button.cornerRadius = 10
|
|
|
|
// button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height
|
|
|
|
// button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold)
|
|
|
|
// button.setTitle(L10n.Scene.Compose.composeAction, for: .normal)
|
|
|
|
// return button
|
|
|
|
// }()
|
|
|
|
// private(set) lazy var publishBarButtonItem: UIBarButtonItem = {
|
|
|
|
// configurePublishButtonApperance()
|
|
|
|
// let shadowBackgroundContainer = ShadowBackgroundContainer()
|
|
|
|
// publishButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// shadowBackgroundContainer.addSubview(publishButton)
|
|
|
|
// NSLayoutConstraint.activate([
|
|
|
|
// publishButton.topAnchor.constraint(equalTo: shadowBackgroundContainer.topAnchor),
|
|
|
|
// publishButton.leadingAnchor.constraint(equalTo: shadowBackgroundContainer.leadingAnchor),
|
|
|
|
// publishButton.trailingAnchor.constraint(equalTo: shadowBackgroundContainer.trailingAnchor),
|
|
|
|
// publishButton.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor),
|
|
|
|
// ])
|
|
|
|
// let barButtonItem = UIBarButtonItem(customView: shadowBackgroundContainer)
|
|
|
|
// return barButtonItem
|
|
|
|
// }()
|
|
|
|
//
|
|
|
|
// private func configurePublishButtonApperance() {
|
|
|
|
// publishButton.adjustsImageWhenHighlighted = false
|
|
|
|
// publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Label.primary.color), for: .normal)
|
|
|
|
// publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Label.primary.color.withAlphaComponent(0.5)), for: .highlighted)
|
|
|
|
// publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
|
|
|
|
// publishButton.setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
|
|
|
|
// }
|
2022-09-30 13:28:09 +02:00
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// var systemKeyboardHeight: CGFloat = .zero {
|
|
|
|
// didSet {
|
|
|
|
// // note: some system AutoLayout warning here
|
|
|
|
// let height = max(300, systemKeyboardHeight)
|
|
|
|
// customEmojiPickerInputView.frame.size.height = height
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // CustomEmojiPickerView
|
|
|
|
// let customEmojiPickerInputView: CustomEmojiPickerInputView = {
|
|
|
|
// let view = CustomEmojiPickerInputView(frame: CGRect(x: 0, y: 0, width: 0, height: 300), inputViewStyle: .keyboard)
|
|
|
|
// return view
|
|
|
|
// }()
|
|
|
|
//
|
|
|
|
// let composeToolbarView = ComposeToolbarView()
|
|
|
|
// var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint!
|
|
|
|
// let composeToolbarBackgroundView = UIView()
|
|
|
|
//
|
|
|
|
// static func createPhotoLibraryPickerConfiguration(selectionLimit: Int = 4) -> PHPickerConfiguration {
|
|
|
|
// var configuration = PHPickerConfiguration()
|
|
|
|
// configuration.filter = .any(of: [.images, .videos])
|
|
|
|
// configuration.selectionLimit = selectionLimit
|
|
|
|
// return configuration
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private(set) lazy var photoLibraryPicker: PHPickerViewController = {
|
|
|
|
// let imagePicker = PHPickerViewController(configuration: ComposeViewController.createPhotoLibraryPickerConfiguration())
|
|
|
|
// imagePicker.delegate = self
|
|
|
|
// return imagePicker
|
|
|
|
// }()
|
|
|
|
// private(set) lazy var imagePickerController: UIImagePickerController = {
|
|
|
|
// let imagePickerController = UIImagePickerController()
|
|
|
|
// imagePickerController.sourceType = .camera
|
|
|
|
// imagePickerController.delegate = self
|
|
|
|
// return imagePickerController
|
|
|
|
// }()
|
|
|
|
//
|
|
|
|
// private(set) lazy var documentPickerController: UIDocumentPickerViewController = {
|
|
|
|
// let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image, .movie])
|
|
|
|
// documentPickerController.delegate = self
|
|
|
|
// return documentPickerController
|
|
|
|
// }()
|
|
|
|
//
|
|
|
|
// private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
|
|
|
|
// let viewController = AutoCompleteViewController()
|
|
|
|
// viewController.viewModel = AutoCompleteViewModel(context: context, authContext: viewModel.authContext)
|
|
|
|
// viewController.delegate = self
|
|
|
|
// viewController.viewModel.customEmojiViewModel.value = viewModel.customEmojiViewModel
|
|
|
|
// return viewController
|
|
|
|
// }()
|
2021-05-14 14:02:59 +02:00
|
|
|
|
2021-03-26 12:16:19 +01:00
|
|
|
deinit {
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
}
|
|
|
|
|
2021-03-11 08:41:27 +01:00
|
|
|
}
|
|
|
|
|
2021-03-22 10:48:35 +01:00
|
|
|
extension ComposeViewController {
|
|
|
|
private static func createLayout() -> UICollectionViewLayout {
|
2021-03-26 12:16:19 +01:00
|
|
|
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
2021-03-22 10:48:35 +01:00
|
|
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
2021-03-26 12:16:19 +01:00
|
|
|
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
|
|
|
|
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
|
2021-03-22 10:48:35 +01:00
|
|
|
let section = NSCollectionLayoutSection(group: group)
|
|
|
|
section.contentInsetsReference = .readableContent
|
|
|
|
// section.interGroupSpacing = 10
|
|
|
|
// section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
|
|
|
|
return UICollectionViewCompositionalLayout(section: section)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-11 08:41:27 +01:00
|
|
|
extension ComposeViewController {
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
2022-10-11 12:31:40 +02:00
|
|
|
navigationItem.leftBarButtonItem = cancelBarButtonItem
|
|
|
|
// navigationItem.rightBarButtonItem = publishBarButtonItem
|
|
|
|
// viewModel.traitCollectionDidChangePublisher
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] _ in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// guard self.traitCollection.userInterfaceIdiom == .pad else { return }
|
|
|
|
// var items = [self.publishBarButtonItem]
|
|
|
|
// if self.traitCollection.horizontalSizeClass == .regular {
|
|
|
|
// items.append(self.characterCountBarButtonItem)
|
|
|
|
// }
|
|
|
|
// self.navigationItem.rightBarButtonItems = items
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
// publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
addChild(composeContentViewController)
|
|
|
|
composeContentViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
view.addSubview(composeContentViewController.view)
|
2021-03-12 08:23:28 +01:00
|
|
|
NSLayoutConstraint.activate([
|
2022-10-10 13:14:52 +02:00
|
|
|
composeContentViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
|
composeContentViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
|
composeContentViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
|
composeContentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
2021-03-12 12:25:28 +01:00
|
|
|
])
|
2022-10-10 13:14:52 +02:00
|
|
|
composeContentViewController.didMove(toParent: self)
|
|
|
|
|
|
|
|
// configureNavigationBarTitleStyle()
|
|
|
|
// viewModel.traitCollectionDidChangePublisher
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] _ in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.configureNavigationBarTitleStyle()
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// viewModel.$title
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] title in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.title = title
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
// self.setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
|
|
|
// ThemeService.shared.currentTheme
|
|
|
|
// .receive(on: RunLoop.main)
|
|
|
|
// .sink { [weak self] theme in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.setupBackgroundColor(theme: theme)
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// view.addSubview(scrollView)
|
|
|
|
// NSLayoutConstraint.activate([
|
|
|
|
// scrollView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
|
// scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
|
// scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
|
// scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
|
// ])
|
|
|
|
//
|
|
|
|
// composeToolbarView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// view.addSubview(composeToolbarView)
|
|
|
|
// composeToolbarViewBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: composeToolbarView.bottomAnchor)
|
|
|
|
// NSLayoutConstraint.activate([
|
|
|
|
// composeToolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
|
// composeToolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
|
// composeToolbarViewBottomLayoutConstraint,
|
|
|
|
// composeToolbarView.heightAnchor.constraint(equalToConstant: ComposeToolbarView.toolbarHeight),
|
|
|
|
// ])
|
|
|
|
// composeToolbarView.preservesSuperviewLayoutMargins = true
|
|
|
|
// composeToolbarView.delegate = self
|
|
|
|
//
|
|
|
|
// composeToolbarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// view.insertSubview(composeToolbarBackgroundView, belowSubview: composeToolbarView)
|
|
|
|
// NSLayoutConstraint.activate([
|
|
|
|
// composeToolbarBackgroundView.topAnchor.constraint(equalTo: composeToolbarView.topAnchor),
|
|
|
|
// composeToolbarBackgroundView.leadingAnchor.constraint(equalTo: composeToolbarView.leadingAnchor),
|
|
|
|
// composeToolbarBackgroundView.trailingAnchor.constraint(equalTo: composeToolbarView.trailingAnchor),
|
|
|
|
// view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor),
|
|
|
|
// ])
|
2021-06-28 13:41:41 +02:00
|
|
|
|
2022-09-30 13:28:09 +02:00
|
|
|
// tableView.delegate = self
|
|
|
|
// viewModel.setupDataSource(
|
|
|
|
// tableView: tableView,
|
|
|
|
// metaTextDelegate: self,
|
|
|
|
// metaTextViewDelegate: self,
|
|
|
|
// customEmojiPickerInputViewModel: viewModel.customEmojiPickerInputViewModel,
|
|
|
|
// composeStatusAttachmentCollectionViewCellDelegate: self,
|
|
|
|
// composeStatusPollOptionCollectionViewCellDelegate: self,
|
|
|
|
// composeStatusPollOptionAppendEntryCollectionViewCellDelegate: self,
|
|
|
|
// composeStatusPollExpiresOptionCollectionViewCellDelegate: self
|
|
|
|
// )
|
2021-03-26 12:16:19 +01:00
|
|
|
|
2022-09-30 13:28:09 +02:00
|
|
|
// viewModel.composeStatusAttribute.$composeContent
|
|
|
|
// .removeDuplicates()
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] _ in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// guard self.view.window != nil else { return }
|
|
|
|
// UIView.performWithoutAnimation {
|
|
|
|
// self.tableView.beginUpdates()
|
|
|
|
// self.tableView.setNeedsLayout()
|
|
|
|
// self.tableView.layoutIfNeeded()
|
|
|
|
// self.tableView.endUpdates()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
2021-04-15 06:10:43 +02:00
|
|
|
|
2022-09-30 13:28:09 +02:00
|
|
|
// customEmojiPickerInputView.collectionView.delegate = self
|
|
|
|
// viewModel.customEmojiPickerInputViewModel.customEmojiPickerInputView = customEmojiPickerInputView
|
|
|
|
// viewModel.setupCustomEmojiPickerDiffableDataSource(
|
|
|
|
// for: customEmojiPickerInputView.collectionView,
|
|
|
|
// dependency: self
|
|
|
|
// )
|
2021-09-29 10:27:35 +02:00
|
|
|
|
2022-09-30 13:28:09 +02:00
|
|
|
// viewModel.composeStatusContentTableViewCell.delegate = self
|
|
|
|
//
|
|
|
|
// // update layout when keyboard show/dismiss
|
|
|
|
// view.layoutIfNeeded()
|
|
|
|
//
|
|
|
|
// let keyboardHasShortcutBar = CurrentValueSubject<Bool, Never>(traitCollection.userInterfaceIdiom == .pad) // update default value later
|
|
|
|
// let keyboardEventPublishers = Publishers.CombineLatest3(
|
|
|
|
// KeyboardResponderService.shared.isShow,
|
|
|
|
// KeyboardResponderService.shared.state,
|
|
|
|
// KeyboardResponderService.shared.endFrame
|
|
|
|
// )
|
|
|
|
// Publishers.CombineLatest3(
|
|
|
|
// keyboardEventPublishers,
|
|
|
|
// viewModel.$isCustomEmojiComposing,
|
|
|
|
// viewModel.$autoCompleteInfo
|
|
|
|
// )
|
|
|
|
// .sink(receiveValue: { [weak self] keyboardEvents, isCustomEmojiComposing, autoCompleteInfo in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
//
|
|
|
|
// let (isShow, state, endFrame) = keyboardEvents
|
|
|
|
//
|
|
|
|
// switch self.traitCollection.userInterfaceIdiom {
|
|
|
|
// case .pad:
|
|
|
|
// keyboardHasShortcutBar.value = state != .floating
|
|
|
|
// default:
|
|
|
|
// keyboardHasShortcutBar.value = false
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// let extraMargin: CGFloat = {
|
|
|
|
// var margin = self.composeToolbarView.frame.height
|
|
|
|
// if autoCompleteInfo != nil {
|
|
|
|
// margin += ComposeViewController.minAutoCompleteVisibleHeight
|
|
|
|
// }
|
|
|
|
// return margin
|
|
|
|
// }()
|
|
|
|
//
|
|
|
|
// guard isShow, state == .dock else {
|
|
|
|
// self.tableView.contentInset.bottom = extraMargin
|
|
|
|
// self.tableView.verticalScrollIndicatorInsets.bottom = extraMargin
|
|
|
|
//
|
|
|
|
// if let superView = self.autoCompleteViewController.tableView.superview {
|
|
|
|
// let autoCompleteTableViewBottomInset: CGFloat = {
|
|
|
|
// let tableViewFrameInWindow = superView.convert(self.autoCompleteViewController.tableView.frame, to: nil)
|
|
|
|
// let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - self.view.frame.maxY
|
|
|
|
// return max(0, padding)
|
|
|
|
// }()
|
|
|
|
// self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset
|
|
|
|
// self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// UIView.animate(withDuration: 0.3) {
|
|
|
|
// self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom
|
|
|
|
// if self.view.window != nil {
|
|
|
|
// self.view.layoutIfNeeded()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// // isShow AND dock state
|
|
|
|
// self.systemKeyboardHeight = endFrame.height
|
|
|
|
//
|
|
|
|
// // adjust inset for auto-complete
|
|
|
|
// let autoCompleteTableViewBottomInset: CGFloat = {
|
|
|
|
// guard let superview = self.autoCompleteViewController.tableView.superview else { return .zero }
|
|
|
|
// let tableViewFrameInWindow = superview.convert(self.autoCompleteViewController.tableView.frame, to: nil)
|
|
|
|
// let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - endFrame.minY
|
|
|
|
// return max(0, padding)
|
|
|
|
// }()
|
|
|
|
// self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset
|
|
|
|
// self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset
|
|
|
|
//
|
|
|
|
// // adjust inset for tableView
|
|
|
|
// let contentFrame = self.view.convert(self.tableView.frame, to: nil)
|
|
|
|
// let padding = contentFrame.maxY + extraMargin - endFrame.minY
|
|
|
|
// guard padding > 0 else {
|
|
|
|
// self.tableView.contentInset.bottom = self.view.safeAreaInsets.bottom + extraMargin
|
|
|
|
// self.tableView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom + extraMargin
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// self.tableView.contentInset.bottom = padding - self.view.safeAreaInsets.bottom
|
|
|
|
// self.tableView.verticalScrollIndicatorInsets.bottom = padding - self.view.safeAreaInsets.bottom
|
|
|
|
// UIView.animate(withDuration: 0.3) {
|
|
|
|
// self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height
|
|
|
|
// self.view.layoutIfNeeded()
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind auto-complete
|
|
|
|
// viewModel.$autoCompleteInfo
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] info in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// let textEditorView = self.textEditorView
|
|
|
|
// if self.autoCompleteViewController.view.superview == nil {
|
|
|
|
// self.autoCompleteViewController.view.frame = self.view.bounds
|
|
|
|
// // add to container view. seealso: `viewDidLayoutSubviews()`
|
|
|
|
// self.viewModel.composeStatusContentTableViewCell.textEditorViewContainerView.addSubview(self.autoCompleteViewController.view)
|
|
|
|
// self.addChild(self.autoCompleteViewController)
|
|
|
|
// self.autoCompleteViewController.didMove(toParent: self)
|
|
|
|
// self.autoCompleteViewController.view.isHidden = true
|
|
|
|
// self.tableView.autoCompleteViewController = self.autoCompleteViewController
|
|
|
|
// }
|
|
|
|
// self.updateAutoCompleteViewControllerLayout()
|
|
|
|
// self.autoCompleteViewController.view.isHidden = info == nil
|
|
|
|
// guard let info = info else { return }
|
|
|
|
// let symbolBoundingRectInContainer = textEditorView.textView.convert(info.symbolBoundingRect, to: self.autoCompleteViewController.chevronView)
|
|
|
|
// self.autoCompleteViewController.view.frame.origin.y = info.textBoundingRect.maxY
|
|
|
|
// self.autoCompleteViewController.viewModel.symbolBoundingRect.value = symbolBoundingRectInContainer
|
|
|
|
// self.autoCompleteViewController.viewModel.inputText.value = String(info.inputText)
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind publish bar button state
|
|
|
|
// viewModel.$isPublishBarButtonItemEnabled
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .assign(to: \.isEnabled, on: publishButton)
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind media button toolbar state
|
|
|
|
// viewModel.$isMediaToolbarButtonEnabled
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] isMediaToolbarButtonEnabled in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.composeToolbarView.mediaBarButtonItem.isEnabled = isMediaToolbarButtonEnabled
|
|
|
|
// self.composeToolbarView.mediaButton.isEnabled = isMediaToolbarButtonEnabled
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind poll button toolbar state
|
|
|
|
// viewModel.$isPollToolbarButtonEnabled
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] isPollToolbarButtonEnabled in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.composeToolbarView.pollBarButtonItem.isEnabled = isPollToolbarButtonEnabled
|
|
|
|
// self.composeToolbarView.pollButton.isEnabled = isPollToolbarButtonEnabled
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// Publishers.CombineLatest(
|
|
|
|
// viewModel.$isPollComposing,
|
|
|
|
// viewModel.$isPollToolbarButtonEnabled
|
|
|
|
// )
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] isPollComposing, isPollToolbarButtonEnabled in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// guard isPollToolbarButtonEnabled else {
|
|
|
|
// let accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
|
|
|
|
// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
|
|
|
|
// self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// let accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll
|
|
|
|
// self.composeToolbarView.pollBarButtonItem.accessibilityLabel = accessibilityLabel
|
|
|
|
// self.composeToolbarView.pollButton.accessibilityLabel = accessibilityLabel
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind image picker toolbar state
|
|
|
|
// viewModel.$attachmentServices
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] attachmentServices in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// let isEnabled = attachmentServices.count < self.viewModel.maxMediaAttachments
|
|
|
|
// self.composeToolbarView.mediaBarButtonItem.isEnabled = isEnabled
|
|
|
|
// self.composeToolbarView.mediaButton.isEnabled = isEnabled
|
|
|
|
// self.resetImagePicker()
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind content warning button state
|
|
|
|
// viewModel.$isContentWarningComposing
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] isContentWarningComposing in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// let accessibilityLabel = isContentWarningComposing ? L10n.Scene.Compose.Accessibility.disableContentWarning : L10n.Scene.Compose.Accessibility.enableContentWarning
|
|
|
|
// self.composeToolbarView.contentWarningBarButtonItem.accessibilityLabel = accessibilityLabel
|
|
|
|
// self.composeToolbarView.contentWarningButton.accessibilityLabel = accessibilityLabel
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind visibility toolbar UI
|
|
|
|
// Publishers.CombineLatest(
|
|
|
|
// viewModel.$selectedStatusVisibility,
|
|
|
|
// viewModel.traitCollectionDidChangePublisher
|
|
|
|
// )
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] type, _ in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
|
|
|
// self.composeToolbarView.visibilityBarButtonItem.image = image
|
|
|
|
// self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
|
|
|
|
// self.composeToolbarView.activeVisibilityType.value = type
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// viewModel.$characterCount
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] characterCount in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// let count = self.viewModel.composeContentLimit - characterCount
|
|
|
|
// self.composeToolbarView.characterCountLabel.text = "\(count)"
|
|
|
|
// self.characterCountLabel.text = "\(count)"
|
|
|
|
// let font: UIFont
|
|
|
|
// let textColor: UIColor
|
|
|
|
// let accessibilityLabel: String
|
|
|
|
// switch count {
|
|
|
|
// case _ where count < 0:
|
|
|
|
// font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold)
|
|
|
|
// textColor = Asset.Colors.danger.color
|
|
|
|
// accessibilityLabel = L10n.A11y.Plural.Count.inputLimitExceeds(abs(count))
|
|
|
|
// default:
|
|
|
|
// font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular)
|
|
|
|
// textColor = Asset.Colors.Label.secondary.color
|
|
|
|
// accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(count)
|
|
|
|
// }
|
|
|
|
// self.composeToolbarView.characterCountLabel.font = font
|
|
|
|
// self.composeToolbarView.characterCountLabel.textColor = textColor
|
|
|
|
// self.composeToolbarView.characterCountLabel.accessibilityLabel = accessibilityLabel
|
|
|
|
// self.characterCountLabel.font = font
|
|
|
|
// self.characterCountLabel.textColor = textColor
|
|
|
|
// self.characterCountLabel.accessibilityLabel = accessibilityLabel
|
|
|
|
// self.characterCountLabel.sizeToFit()
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// // bind custom emoji picker UI
|
|
|
|
// viewModel.customEmojiViewModel?.emojis
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink(receiveValue: { [weak self] emojis in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// if emojis.isEmpty {
|
|
|
|
// self.customEmojiPickerInputView.activityIndicatorView.startAnimating()
|
|
|
|
// } else {
|
|
|
|
// self.customEmojiPickerInputView.activityIndicatorView.stopAnimating()
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// .store(in: &disposeBag)
|
|
|
|
//
|
|
|
|
// configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar.value)
|
|
|
|
// Publishers.CombineLatest(
|
|
|
|
// keyboardHasShortcutBar,
|
|
|
|
// viewModel.traitCollectionDidChangePublisher
|
|
|
|
// )
|
|
|
|
// .receive(on: DispatchQueue.main)
|
|
|
|
// .sink { [weak self] keyboardHasShortcutBar, _ in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.configureToolbarDisplay(keyboardHasShortcutBar: keyboardHasShortcutBar)
|
|
|
|
// }
|
|
|
|
// .store(in: &disposeBag)
|
2021-03-11 08:41:27 +01:00
|
|
|
}
|
|
|
|
|
2021-03-11 12:26:10 +01:00
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
|
|
super.viewWillAppear(animated)
|
2022-01-27 14:23:39 +01:00
|
|
|
|
2022-09-30 13:28:09 +02:00
|
|
|
// // update MetaText without trigger call underlaying `UITextStorage.processEditing`
|
|
|
|
// _ = textEditorView.processEditing(textEditorView.textStorage)
|
2022-01-27 14:23:39 +01:00
|
|
|
|
2022-09-30 13:28:09 +02:00
|
|
|
// markTextEditorViewBecomeFirstResponser()
|
2021-03-11 12:26:10 +01:00
|
|
|
}
|
2021-06-29 10:41:58 +02:00
|
|
|
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
|
|
super.viewDidAppear(animated)
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// viewModel.isViewAppeared = true
|
2021-06-29 10:41:58 +02:00
|
|
|
}
|
2021-03-11 12:26:10 +01:00
|
|
|
|
2021-04-14 09:24:54 +02:00
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// configurePublishButtonApperance()
|
|
|
|
// viewModel.traitCollectionDidChangePublisher.send()
|
2021-04-14 09:24:54 +02:00
|
|
|
}
|
|
|
|
|
2021-05-14 14:02:59 +02:00
|
|
|
override func viewDidLayoutSubviews() {
|
|
|
|
super.viewDidLayoutSubviews()
|
2022-01-27 14:23:39 +01:00
|
|
|
|
2021-06-28 13:41:41 +02:00
|
|
|
updateAutoCompleteViewControllerLayout()
|
|
|
|
}
|
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
private func updateAutoCompleteViewControllerLayout() {
|
2021-06-28 13:41:41 +02:00
|
|
|
// pin autoCompleteViewController frame to current view
|
2022-10-10 13:14:52 +02:00
|
|
|
// if let containerView = autoCompleteViewController.view.superview {
|
|
|
|
// let viewFrameInWindow = containerView.convert(autoCompleteViewController.view.frame, to: view)
|
|
|
|
// if viewFrameInWindow.origin.x != 0 {
|
|
|
|
// autoCompleteViewController.view.frame.origin.x = -viewFrameInWindow.origin.x
|
|
|
|
// }
|
|
|
|
// autoCompleteViewController.view.frame.size.width = view.frame.width
|
|
|
|
// }
|
2021-03-12 08:23:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
//extension ComposeViewController {
|
|
|
|
//
|
|
|
|
// private var textEditorView: MetaText {
|
|
|
|
// return viewModel.composeStatusContentTableViewCell.metaText
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func markTextEditorViewBecomeFirstResponser() {
|
|
|
|
// textEditorView.textView.becomeFirstResponder()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func contentWarningEditorTextView() -> UITextView? {
|
|
|
|
// viewModel.composeStatusContentTableViewCell.statusContentWarningEditorView.textView
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func pollOptionCollectionViewCell(of item: ComposeStatusPollItem) -> ComposeStatusPollOptionCollectionViewCell? {
|
|
|
|
// guard case .pollOption = item else { return nil }
|
|
|
|
// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return nil }
|
|
|
|
// guard let indexPath = dataSource.indexPath(for: item),
|
|
|
|
// let cell = viewModel.composeStatusPollTableViewCell.collectionView.cellForItem(at: indexPath) as? ComposeStatusPollOptionCollectionViewCell else {
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return cell
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func firstPollOptionCollectionViewCell() -> ComposeStatusPollOptionCollectionViewCell? {
|
|
|
|
// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return nil }
|
|
|
|
// let items = dataSource.snapshot().itemIdentifiers(inSection: .main)
|
|
|
|
// let firstPollItem = items.first { item -> Bool in
|
|
|
|
// guard case .pollOption = item else { return false }
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// guard let item = firstPollItem else {
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return pollOptionCollectionViewCell(of: item)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func lastPollOptionCollectionViewCell() -> ComposeStatusPollOptionCollectionViewCell? {
|
|
|
|
// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return nil }
|
|
|
|
// let items = dataSource.snapshot().itemIdentifiers(inSection: .main)
|
|
|
|
// let lastPollItem = items.last { item -> Bool in
|
|
|
|
// guard case .pollOption = item else { return false }
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// guard let item = lastPollItem else {
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return pollOptionCollectionViewCell(of: item)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func markFirstPollOptionCollectionViewCellBecomeFirstResponser() {
|
|
|
|
// guard let cell = firstPollOptionCollectionViewCell() else { return }
|
|
|
|
// cell.pollOptionView.optionTextField.becomeFirstResponder()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func markLastPollOptionCollectionViewCellBecomeFirstResponser() {
|
|
|
|
// guard let cell = lastPollOptionCollectionViewCell() else { return }
|
|
|
|
// cell.pollOptionView.optionTextField.becomeFirstResponder()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func showDismissConfirmAlertController() {
|
|
|
|
// let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
|
|
// let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { [weak self] _ in
|
|
|
|
// guard let self = self else { return }
|
|
|
|
// self.dismiss(animated: true, completion: nil)
|
|
|
|
// }
|
|
|
|
// alertController.addAction(discardAction)
|
|
|
|
// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
|
|
|
// alertController.addAction(cancelAction)
|
|
|
|
// alertController.popoverPresentationController?.barButtonItem = cancelBarButtonItem
|
|
|
|
// present(alertController, animated: true, completion: nil)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func resetImagePicker() {
|
|
|
|
// let selectionLimit = max(1, viewModel.maxMediaAttachments - viewModel.attachmentServices.count)
|
|
|
|
// let configuration = ComposeViewController.createPhotoLibraryPickerConfiguration(selectionLimit: selectionLimit)
|
|
|
|
// photoLibraryPicker = createImagePicker(configuration: configuration)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func createImagePicker(configuration: PHPickerConfiguration) -> PHPickerViewController {
|
|
|
|
// let imagePicker = PHPickerViewController(configuration: configuration)
|
|
|
|
// imagePicker.delegate = self
|
|
|
|
// return imagePicker
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func setupBackgroundColor(theme: Theme) {
|
|
|
|
// let backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
|
|
|
// switch traitCollection.userInterfaceStyle {
|
|
|
|
// case .light:
|
|
|
|
// return .systemBackground
|
|
|
|
// default:
|
|
|
|
// return theme.systemElevatedBackgroundColor
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// view.backgroundColor = backgroundColor
|
|
|
|
//// tableView.backgroundColor = backgroundColor
|
|
|
|
//// composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // keyboard shortcutBar
|
|
|
|
// private func setupInputAssistantItem(item: UITextInputAssistantItem) {
|
|
|
|
// let barButtonItems = [
|
|
|
|
// composeToolbarView.mediaBarButtonItem,
|
|
|
|
// composeToolbarView.pollBarButtonItem,
|
|
|
|
// composeToolbarView.contentWarningBarButtonItem,
|
|
|
|
// composeToolbarView.visibilityBarButtonItem,
|
|
|
|
// ]
|
|
|
|
// let group = UIBarButtonItemGroup(barButtonItems: barButtonItems, representativeItem: nil)
|
|
|
|
//
|
|
|
|
// item.trailingBarButtonGroups = [group]
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func configureToolbarDisplay(keyboardHasShortcutBar: Bool) {
|
|
|
|
// switch self.traitCollection.userInterfaceIdiom {
|
|
|
|
// case .pad:
|
|
|
|
// let shouldHideToolbar = keyboardHasShortcutBar && self.traitCollection.horizontalSizeClass == .regular
|
|
|
|
// self.composeToolbarView.alpha = shouldHideToolbar ? 0 : 1
|
|
|
|
// self.composeToolbarBackgroundView.alpha = shouldHideToolbar ? 0 : 1
|
|
|
|
// default:
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func configureNavigationBarTitleStyle() {
|
|
|
|
// switch traitCollection.userInterfaceIdiom {
|
|
|
|
// case .pad:
|
|
|
|
// navigationController?.navigationBar.prefersLargeTitles = traitCollection.horizontalSizeClass == .regular
|
|
|
|
// default:
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//}
|
|
|
|
//
|
2022-10-11 12:31:40 +02:00
|
|
|
extension ComposeViewController {
|
|
|
|
|
|
|
|
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
|
|
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
2022-10-10 13:14:52 +02:00
|
|
|
// guard viewModel.shouldDismiss else {
|
|
|
|
// showDismissConfirmAlertController()
|
|
|
|
// return
|
|
|
|
// }
|
2022-10-11 12:31:40 +02:00
|
|
|
dismiss(animated: true, completion: nil)
|
|
|
|
}
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
|
|
|
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
// do {
|
|
|
|
// try viewModel.checkAttachmentPrecondition()
|
|
|
|
// } catch {
|
|
|
|
// let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
|
|
|
|
// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
|
|
|
// alertController.addAction(okAction)
|
|
|
|
// coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// guard viewModel.publishStateMachine.enter(ComposeViewModel.PublishState.Publishing.self) else {
|
|
|
|
// // TODO: handle error
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // context.statusPublishService.publish(composeViewModel: viewModel)
|
|
|
|
// assertionFailure()
|
|
|
|
//
|
|
|
|
// dismiss(animated: true, completion: nil)
|
|
|
|
// }
|
2022-10-11 12:31:40 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
//// MARK: - MetaTextDelegate
|
|
|
|
//extension ComposeViewController: MetaTextDelegate {
|
|
|
|
// func metaText(_ metaText: MetaText, processEditing textStorage: MetaTextStorage) -> MetaContent? {
|
|
|
|
// let string = metaText.textStorage.string
|
|
|
|
// let content = MastodonContent(
|
|
|
|
// content: string,
|
|
|
|
// emojis: viewModel.customEmojiViewModel?.emojiMapping.value ?? [:]
|
|
|
|
// )
|
|
|
|
// let metaContent = MastodonMetaContent.convert(text: content)
|
|
|
|
// return metaContent
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - UITextViewDelegate
|
|
|
|
//extension ComposeViewController: UITextViewDelegate {
|
|
|
|
//
|
|
|
|
// func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
|
|
|
// setupInputAssistantItem(item: textView.inputAssistantItem)
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func textViewDidChange(_ textView: UITextView) {
|
|
|
|
// switch textView {
|
|
|
|
// case textEditorView.textView:
|
|
|
|
// // update model
|
|
|
|
// let metaText = self.textEditorView
|
|
|
|
// let backedString = metaText.backedString
|
|
|
|
// viewModel.composeStatusAttribute.composeContent = backedString
|
|
|
|
// logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(backedString)")
|
|
|
|
//
|
|
|
|
// // configure auto completion
|
|
|
|
// setupAutoComplete(for: textView)
|
|
|
|
// default:
|
|
|
|
// assertionFailure()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// struct AutoCompleteInfo {
|
|
|
|
// // model
|
|
|
|
// let inputText: Substring
|
|
|
|
// // range
|
|
|
|
// let symbolRange: Range<String.Index>
|
|
|
|
// let symbolString: Substring
|
|
|
|
// let toCursorRange: Range<String.Index>
|
|
|
|
// let toCursorString: Substring
|
|
|
|
// let toHighlightEndRange: Range<String.Index>
|
|
|
|
// let toHighlightEndString: Substring
|
|
|
|
// // geometry
|
|
|
|
// var textBoundingRect: CGRect = .zero
|
|
|
|
// var symbolBoundingRect: CGRect = .zero
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private func setupAutoComplete(for textView: UITextView) {
|
|
|
|
// guard var autoCompletion = ComposeViewController.scanAutoCompleteInfo(textView: textView) else {
|
|
|
|
// viewModel.autoCompleteInfo = nil
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: auto complete %s (%s)", ((#file as NSString).lastPathComponent), #line, #function, String(autoCompletion.toHighlightEndString), String(autoCompletion.toCursorString))
|
|
|
|
//
|
|
|
|
// // get layout text bounding rect
|
|
|
|
// var glyphRange = NSRange()
|
|
|
|
// textView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompletion.toCursorRange, in: textView.text), actualGlyphRange: &glyphRange)
|
|
|
|
// let textContainer = textView.layoutManager.textContainers[0]
|
|
|
|
// let textBoundingRect = textView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
|
|
|
//
|
|
|
|
// let retryLayoutTimes = viewModel.autoCompleteRetryLayoutTimes
|
|
|
|
// guard textBoundingRect.size != .zero else {
|
|
|
|
// viewModel.autoCompleteRetryLayoutTimes += 1
|
|
|
|
// // avoid infinite loop
|
|
|
|
// guard retryLayoutTimes < 3 else { return }
|
|
|
|
// // needs retry calculate layout when the rect position changing
|
|
|
|
// DispatchQueue.main.async {
|
|
|
|
// self.setupAutoComplete(for: textView)
|
2022-09-30 13:28:09 +02:00
|
|
|
// }
|
2022-10-10 13:14:52 +02:00
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// viewModel.autoCompleteRetryLayoutTimes = 0
|
|
|
|
//
|
|
|
|
// // get symbol bounding rect
|
|
|
|
// textView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompletion.symbolRange, in: textView.text), actualGlyphRange: &glyphRange)
|
|
|
|
// let symbolBoundingRect = textView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
2022-09-30 13:28:09 +02:00
|
|
|
//
|
2022-10-10 13:14:52 +02:00
|
|
|
// // set bounding rect and trigger layout
|
|
|
|
// autoCompletion.textBoundingRect = textBoundingRect
|
|
|
|
// autoCompletion.symbolBoundingRect = symbolBoundingRect
|
|
|
|
// viewModel.autoCompleteInfo = autoCompletion
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// private static func scanAutoCompleteInfo(textView: UITextView) -> AutoCompleteInfo? {
|
|
|
|
// guard let text = textView.text,
|
|
|
|
// textView.selectedRange.location > 0, !text.isEmpty,
|
|
|
|
// let selectedRange = Range(textView.selectedRange, in: text) else {
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
// let cursorIndex = selectedRange.upperBound
|
|
|
|
// let _highlightStartIndex: String.Index? = {
|
|
|
|
// var index = text.index(before: cursorIndex)
|
|
|
|
// while index > text.startIndex {
|
|
|
|
// let char = text[index]
|
|
|
|
// if char == "@" || char == "#" || char == ":" {
|
|
|
|
// return index
|
|
|
|
// }
|
|
|
|
// index = text.index(before: index)
|
|
|
|
// }
|
|
|
|
// assert(index == text.startIndex)
|
|
|
|
// let char = text[index]
|
|
|
|
// if char == "@" || char == "#" || char == ":" {
|
|
|
|
// return index
|
|
|
|
// } else {
|
|
|
|
// return nil
|
2022-09-30 13:28:09 +02:00
|
|
|
// }
|
2022-10-10 13:14:52 +02:00
|
|
|
// }()
|
|
|
|
//
|
|
|
|
// guard let highlightStartIndex = _highlightStartIndex else { return nil }
|
|
|
|
// let scanRange = NSRange(highlightStartIndex..<text.endIndex, in: text)
|
|
|
|
//
|
|
|
|
// guard let match = text.firstMatch(pattern: MastodonRegex.autoCompletePattern, options: [], range: scanRange) else { return nil }
|
|
|
|
// guard let matchRange = Range(match.range(at: 0), in: text) else { return nil }
|
|
|
|
// let matchStartIndex = matchRange.lowerBound
|
|
|
|
// let matchEndIndex = matchRange.upperBound
|
|
|
|
//
|
|
|
|
// guard matchStartIndex == highlightStartIndex, matchEndIndex >= cursorIndex else { return nil }
|
|
|
|
// let symbolRange = highlightStartIndex..<text.index(after: highlightStartIndex)
|
|
|
|
// let symbolString = text[symbolRange]
|
|
|
|
// let toCursorRange = highlightStartIndex..<cursorIndex
|
|
|
|
// let toCursorString = text[toCursorRange]
|
|
|
|
// let toHighlightEndRange = matchStartIndex..<matchEndIndex
|
|
|
|
// let toHighlightEndString = text[toHighlightEndRange]
|
|
|
|
//
|
|
|
|
// let inputText = toHighlightEndString
|
|
|
|
// let autoCompleteInfo = AutoCompleteInfo(
|
|
|
|
// inputText: inputText,
|
|
|
|
// symbolRange: symbolRange,
|
|
|
|
// symbolString: symbolString,
|
|
|
|
// toCursorRange: toCursorRange,
|
|
|
|
// toCursorString: toCursorString,
|
|
|
|
// toHighlightEndRange: toHighlightEndRange,
|
|
|
|
// toHighlightEndString: toHighlightEndString
|
|
|
|
// )
|
|
|
|
// return autoCompleteInfo
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
|
|
|
// switch textView {
|
|
|
|
// case textEditorView.textView:
|
|
|
|
// return false
|
|
|
|
// default:
|
|
|
|
// return true
|
2022-09-30 13:28:09 +02:00
|
|
|
// }
|
|
|
|
// }
|
2022-10-10 13:14:52 +02:00
|
|
|
//
|
|
|
|
// func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
|
|
|
// switch textView {
|
|
|
|
// case textEditorView.textView:
|
|
|
|
// return false
|
|
|
|
// default:
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - ComposeToolbarViewDelegate
|
|
|
|
//extension ComposeViewController: ComposeToolbarViewDelegate {
|
|
|
|
//
|
|
|
|
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, mediaButtonDidPressed sender: Any, mediaSelectionType type: ComposeToolbarView.MediaSelectionType) {
|
|
|
|
// switch type {
|
|
|
|
// case .photoLibrary:
|
|
|
|
// present(photoLibraryPicker, animated: true, completion: nil)
|
|
|
|
// case .camera:
|
|
|
|
// present(imagePickerController, animated: true, completion: nil)
|
|
|
|
// case .browse:
|
|
|
|
// #if SNAPSHOT
|
|
|
|
// guard let image = UIImage(named: "Athens") else { return }
|
|
|
|
//
|
|
|
|
// let attachmentService = MastodonAttachmentService(
|
|
|
|
// context: context,
|
|
|
|
// image: image,
|
|
|
|
// initialAuthenticationBox: viewModel.authenticationBox
|
|
|
|
// )
|
|
|
|
// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService]
|
|
|
|
// #else
|
|
|
|
// present(documentPickerController, animated: true, completion: nil)
|
|
|
|
// #endif
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: Any) {
|
|
|
|
// // toggle poll composing state
|
|
|
|
// viewModel.isPollComposing.toggle()
|
|
|
|
//
|
|
|
|
// // cancel custom picker input
|
|
|
|
// viewModel.isCustomEmojiComposing = false
|
|
|
|
//
|
|
|
|
// // setup initial poll option if needs
|
|
|
|
// if viewModel.isPollComposing, viewModel.pollOptionAttributes.isEmpty {
|
|
|
|
// viewModel.pollOptionAttributes = [ComposeStatusPollItem.PollOptionAttribute(), ComposeStatusPollItem.PollOptionAttribute()]
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// if viewModel.isPollComposing {
|
|
|
|
// // Magic RunLoop
|
|
|
|
// DispatchQueue.main.async {
|
|
|
|
// self.markFirstPollOptionCollectionViewCellBecomeFirstResponser()
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// markTextEditorViewBecomeFirstResponser()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: Any) {
|
|
|
|
// viewModel.isCustomEmojiComposing.toggle()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: Any) {
|
|
|
|
// // cancel custom picker input
|
|
|
|
// viewModel.isCustomEmojiComposing = false
|
|
|
|
//
|
|
|
|
// // restore first responder for text editor when content warning dismiss
|
|
|
|
// if viewModel.isContentWarningComposing {
|
|
|
|
// if contentWarningEditorTextView()?.isFirstResponder == true {
|
|
|
|
// markTextEditorViewBecomeFirstResponser()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // toggle composing status
|
|
|
|
// viewModel.isContentWarningComposing.toggle()
|
|
|
|
//
|
|
|
|
// // active content warning after toggled
|
|
|
|
// if viewModel.isContentWarningComposing {
|
|
|
|
// contentWarningEditorTextView()?.becomeFirstResponder()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: Any, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) {
|
|
|
|
// viewModel.selectedStatusVisibility = type
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//}
|
2022-10-11 12:31:40 +02:00
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
//// MARK: - UITableViewDelegate
|
|
|
|
//extension ComposeViewController: UITableViewDelegate { }
|
|
|
|
//
|
|
|
|
//// MARK: - UICollectionViewDelegate
|
|
|
|
//extension ComposeViewController: UICollectionViewDelegate {
|
|
|
|
//
|
|
|
|
// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
|
|
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
|
|
|
//
|
|
|
|
// if collectionView === customEmojiPickerInputView.collectionView {
|
|
|
|
// guard let diffableDataSource = viewModel.customEmojiPickerDiffableDataSource else { return }
|
|
|
|
// let item = diffableDataSource.itemIdentifier(for: indexPath)
|
|
|
|
// guard case let .emoji(attribute) = item else { return }
|
|
|
|
// let emoji = attribute.emoji
|
|
|
|
//
|
|
|
|
// // make click sound
|
|
|
|
// UIDevice.current.playInputClick()
|
|
|
|
//
|
|
|
|
// // retrieve active text input and insert emoji
|
|
|
|
// // the trailing space is REQUIRED to make regex happy
|
|
|
|
// _ = viewModel.customEmojiPickerInputViewModel.insertText(":\(emoji.shortcode): ")
|
|
|
|
// } else {
|
|
|
|
// // do nothing
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
2021-03-11 08:41:27 +01:00
|
|
|
|
2021-03-15 07:40:10 +01:00
|
|
|
// MARK: - UIAdaptivePresentationControllerDelegate
|
2021-03-11 08:41:27 +01:00
|
|
|
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
|
2021-04-15 06:10:43 +02:00
|
|
|
|
|
|
|
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
2021-09-24 13:58:50 +02:00
|
|
|
switch traitCollection.horizontalSizeClass {
|
|
|
|
case .compact:
|
|
|
|
return .overFullScreen
|
|
|
|
default:
|
|
|
|
return .pageSheet
|
|
|
|
}
|
2021-04-15 06:10:43 +02:00
|
|
|
}
|
2021-03-12 08:57:58 +01:00
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
|
|
|
// return viewModel.shouldDismiss
|
|
|
|
// }
|
2021-03-11 08:41:27 +01:00
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
// func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
|
|
|
|
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
// showDismissConfirmAlertController()
|
|
|
|
// }
|
2021-03-11 08:41:27 +01:00
|
|
|
|
|
|
|
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
|
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2021-03-18 08:16:35 +01:00
|
|
|
|
2022-10-10 13:14:52 +02:00
|
|
|
//// MARK: - PHPickerViewControllerDelegate
|
|
|
|
//extension ComposeViewController: PHPickerViewControllerDelegate {
|
|
|
|
// func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
|
|
|
// picker.dismiss(animated: true, completion: nil)
|
|
|
|
//
|
|
|
|
// let attachmentServices: [MastodonAttachmentService] = results.map { result in
|
|
|
|
// let service = MastodonAttachmentService(
|
|
|
|
// context: context,
|
|
|
|
// pickerResult: result,
|
|
|
|
// initialAuthenticationBox: viewModel.authenticationBox
|
|
|
|
// )
|
|
|
|
// return service
|
|
|
|
// }
|
|
|
|
// viewModel.attachmentServices = viewModel.attachmentServices + attachmentServices
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - UIImagePickerControllerDelegate
|
|
|
|
//extension ComposeViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
|
|
|
|
//
|
|
|
|
// func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
|
|
// picker.dismiss(animated: true, completion: nil)
|
|
|
|
//
|
|
|
|
// guard let image = info[.originalImage] as? UIImage else { return }
|
|
|
|
//
|
|
|
|
// let attachmentService = MastodonAttachmentService(
|
|
|
|
// context: context,
|
|
|
|
// image: image,
|
|
|
|
// initialAuthenticationBox: viewModel.authenticationBox
|
|
|
|
// )
|
|
|
|
// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService]
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
|
|
|
// os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
|
|
// picker.dismiss(animated: true, completion: nil)
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - UIDocumentPickerDelegate
|
|
|
|
//extension ComposeViewController: UIDocumentPickerDelegate {
|
|
|
|
// func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
|
|
|
// guard let url = urls.first else { return }
|
|
|
|
//
|
|
|
|
// let attachmentService = MastodonAttachmentService(
|
|
|
|
// context: context,
|
|
|
|
// documentURL: url,
|
|
|
|
// initialAuthenticationBox: viewModel.authenticationBox
|
|
|
|
// )
|
|
|
|
// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService]
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - ComposeStatusAttachmentTableViewCellDelegate
|
|
|
|
//extension ComposeViewController: ComposeStatusAttachmentCollectionViewCellDelegate {
|
|
|
|
//
|
|
|
|
// func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) {
|
|
|
|
// guard let diffableDataSource = viewModel.composeStatusAttachmentTableViewCell.dataSource else { return }
|
|
|
|
// guard let indexPath = viewModel.composeStatusAttachmentTableViewCell.collectionView.indexPath(for: cell) else { return }
|
|
|
|
// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
|
|
|
// guard case let .attachment(attachmentService) = item else { return }
|
|
|
|
//
|
|
|
|
// var attachmentServices = viewModel.attachmentServices
|
|
|
|
// guard let index = attachmentServices.firstIndex(of: attachmentService) else { return }
|
|
|
|
// let removedItem = attachmentServices[index]
|
|
|
|
// attachmentServices.remove(at: index)
|
|
|
|
// viewModel.attachmentServices = attachmentServices
|
|
|
|
//
|
|
|
|
// // cancel task
|
|
|
|
// removedItem.disposeBag.removeAll()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - ComposeStatusPollOptionCollectionViewCellDelegate
|
|
|
|
//extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelegate {
|
|
|
|
//
|
|
|
|
// func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField) {
|
|
|
|
//
|
|
|
|
// setupInputAssistantItem(item: textField.inputAssistantItem)
|
|
|
|
//
|
|
|
|
// // FIXME: make poll section visible
|
|
|
|
// // DispatchQueue.main.async {
|
|
|
|
// // self.collectionView.scroll(to: .bottom, animated: true)
|
|
|
|
// // }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// // handle delete backward event for poll option input
|
|
|
|
// func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) {
|
|
|
|
// guard (text ?? "").isEmpty else { return }
|
|
|
|
// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return }
|
|
|
|
// guard let indexPath = viewModel.composeStatusPollTableViewCell.collectionView.indexPath(for: cell) else { return }
|
|
|
|
// guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
|
|
|
// guard case let .pollOption(attribute) = item else { return }
|
|
|
|
//
|
|
|
|
// var pollAttributes = viewModel.pollOptionAttributes
|
|
|
|
// guard let index = pollAttributes.firstIndex(of: attribute) else { return }
|
|
|
|
//
|
|
|
|
// // mark previous (fallback to next) item of removed middle poll option become first responder
|
|
|
|
// let pollItems = dataSource.snapshot().itemIdentifiers(inSection: .main)
|
|
|
|
// if let indexOfItem = pollItems.firstIndex(of: item), index > 0 {
|
|
|
|
// func cellBeforeRemoved() -> ComposeStatusPollOptionCollectionViewCell? {
|
|
|
|
// guard index > 0 else { return nil }
|
|
|
|
// let indexBeforeRemoved = pollItems.index(before: indexOfItem)
|
|
|
|
// let itemBeforeRemoved = pollItems[indexBeforeRemoved]
|
|
|
|
// return pollOptionCollectionViewCell(of: itemBeforeRemoved)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func cellAfterRemoved() -> ComposeStatusPollOptionCollectionViewCell? {
|
|
|
|
// guard index < pollItems.count - 1 else { return nil }
|
|
|
|
// let indexAfterRemoved = pollItems.index(after: index)
|
|
|
|
// let itemAfterRemoved = pollItems[indexAfterRemoved]
|
|
|
|
// return pollOptionCollectionViewCell(of: itemAfterRemoved)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// var cell: ComposeStatusPollOptionCollectionViewCell? = cellBeforeRemoved()
|
|
|
|
// if cell == nil {
|
|
|
|
// cell = cellAfterRemoved()
|
|
|
|
// }
|
|
|
|
// cell?.pollOptionView.optionTextField.becomeFirstResponder()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// guard pollAttributes.count > 2 else {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// pollAttributes.remove(at: index)
|
|
|
|
//
|
|
|
|
// // update data source
|
|
|
|
// viewModel.pollOptionAttributes = pollAttributes
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // handle keyboard return event for poll option input
|
|
|
|
// func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField) {
|
|
|
|
// guard let dataSource = viewModel.composeStatusPollTableViewCell.dataSource else { return }
|
|
|
|
// guard let indexPath = viewModel.composeStatusPollTableViewCell.collectionView.indexPath(for: cell) else { return }
|
|
|
|
// let pollItems = dataSource.snapshot().itemIdentifiers(inSection: .main).filter { item in
|
|
|
|
// guard case .pollOption = item else { return false }
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
// guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
|
|
|
// guard let index = pollItems.firstIndex(of: item) else { return }
|
|
|
|
//
|
|
|
|
// if index == pollItems.count - 1 {
|
|
|
|
// // is the last
|
|
|
|
// viewModel.createNewPollOptionIfPossible()
|
|
|
|
// DispatchQueue.main.async {
|
|
|
|
// self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// // not the last
|
|
|
|
// let indexAfter = pollItems.index(after: index)
|
|
|
|
// let itemAfter = pollItems[indexAfter]
|
|
|
|
// let cell = pollOptionCollectionViewCell(of: itemAfter)
|
|
|
|
// cell?.pollOptionView.optionTextField.becomeFirstResponder()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate
|
|
|
|
//extension ComposeViewController: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate {
|
|
|
|
// func composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(_ cell: ComposeStatusPollOptionAppendEntryCollectionViewCell) {
|
|
|
|
// viewModel.createNewPollOptionIfPossible()
|
|
|
|
// DispatchQueue.main.async {
|
|
|
|
// self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - ComposeStatusPollExpiresOptionCollectionViewCellDelegate
|
|
|
|
//extension ComposeViewController: ComposeStatusPollExpiresOptionCollectionViewCellDelegate {
|
|
|
|
// func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusPollItem.PollExpiresOptionAttribute.ExpiresOption) {
|
|
|
|
// viewModel.pollExpiresOptionAttribute.expiresOption.value = expiresOption
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - ComposeStatusContentTableViewCellDelegate
|
|
|
|
//extension ComposeViewController: ComposeStatusContentTableViewCellDelegate {
|
|
|
|
// func composeStatusContentTableViewCell(_ cell: ComposeStatusContentTableViewCell, textViewShouldBeginEditing textView: UITextView) -> Bool {
|
|
|
|
// setupInputAssistantItem(item: textView.inputAssistantItem)
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//// MARK: - AutoCompleteViewControllerDelegate
|
|
|
|
//extension ComposeViewController: AutoCompleteViewControllerDelegate {
|
|
|
|
// func autoCompleteViewController(_ viewController: AutoCompleteViewController, didSelectItem item: AutoCompleteItem) {
|
|
|
|
// guard let info = viewModel.autoCompleteInfo else { return }
|
|
|
|
// let _replacedText: String? = {
|
|
|
|
// var text: String
|
|
|
|
// switch item {
|
|
|
|
// case .hashtag(let hashtag):
|
|
|
|
// text = "#" + hashtag.name
|
|
|
|
// case .hashtagV1(let hashtagName):
|
|
|
|
// text = "#" + hashtagName
|
|
|
|
// case .account(let account):
|
|
|
|
// text = "@" + account.acct
|
|
|
|
// case .emoji(let emoji):
|
|
|
|
// text = ":" + emoji.shortcode + ":"
|
|
|
|
// case .bottomLoader:
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
// return text
|
|
|
|
// }()
|
|
|
|
// guard let replacedText = _replacedText else { return }
|
|
|
|
// guard let text = textEditorView.textView.text else { return }
|
|
|
|
//
|
|
|
|
// let range = NSRange(info.toHighlightEndRange, in: text)
|
|
|
|
// textEditorView.textStorage.replaceCharacters(in: range, with: replacedText)
|
|
|
|
// DispatchQueue.main.async {
|
|
|
|
// self.textEditorView.textView.insertText(" ") // trigger textView delegate update
|
|
|
|
// }
|
|
|
|
// viewModel.autoCompleteInfo = nil
|
|
|
|
//
|
|
|
|
// switch item {
|
|
|
|
// case .emoji, .bottomLoader:
|
|
|
|
// break
|
|
|
|
// default:
|
|
|
|
// // set selected range except emoji
|
|
|
|
// let newRange = NSRange(location: range.location + (replacedText as NSString).length, length: 0)
|
|
|
|
// guard textEditorView.textStorage.length <= newRange.location else { return }
|
|
|
|
// textEditorView.textView.selectedRange = newRange
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//extension ComposeViewController {
|
|
|
|
// override var keyCommands: [UIKeyCommand]? {
|
|
|
|
// composeKeyCommands
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//extension ComposeViewController {
|
|
|
|
//
|
|
|
|
// enum ComposeKeyCommand: String, CaseIterable {
|
|
|
|
// case discardPost
|
|
|
|
// case publishPost
|
|
|
|
// case mediaBrowse
|
|
|
|
// case mediaPhotoLibrary
|
|
|
|
// case mediaCamera
|
|
|
|
// case togglePoll
|
|
|
|
// case toggleContentWarning
|
|
|
|
// case selectVisibilityPublic
|
|
|
|
// // TODO: remove selectVisibilityUnlisted from codebase
|
|
|
|
// // case selectVisibilityUnlisted
|
|
|
|
// case selectVisibilityPrivate
|
|
|
|
// case selectVisibilityDirect
|
|
|
|
//
|
|
|
|
// var title: String {
|
|
|
|
// switch self {
|
|
|
|
// case .discardPost: return L10n.Scene.Compose.Keyboard.discardPost
|
|
|
|
// case .publishPost: return L10n.Scene.Compose.Keyboard.publishPost
|
|
|
|
// case .mediaBrowse: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.browse)
|
|
|
|
// case .mediaPhotoLibrary: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.photoLibrary)
|
|
|
|
// case .mediaCamera: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.camera)
|
|
|
|
// case .togglePoll: return L10n.Scene.Compose.Keyboard.togglePoll
|
|
|
|
// case .toggleContentWarning: return L10n.Scene.Compose.Keyboard.toggleContentWarning
|
|
|
|
// case .selectVisibilityPublic: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.public)
|
|
|
|
// // case .selectVisibilityUnlisted: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.unlisted)
|
|
|
|
// case .selectVisibilityPrivate: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.private)
|
|
|
|
// case .selectVisibilityDirect: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.direct)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // UIKeyCommand input
|
|
|
|
// var input: String {
|
|
|
|
// switch self {
|
|
|
|
// case .discardPost: return "w" // + command
|
|
|
|
// case .publishPost: return "\r" // (enter) + command
|
|
|
|
// case .mediaBrowse: return "b" // + option + command
|
|
|
|
// case .mediaPhotoLibrary: return "p" // + option + command
|
|
|
|
// case .mediaCamera: return "c" // + option + command
|
|
|
|
// case .togglePoll: return "p" // + shift + command
|
|
|
|
// case .toggleContentWarning: return "c" // + shift + command
|
|
|
|
// case .selectVisibilityPublic: return "1" // + command
|
|
|
|
// // case .selectVisibilityUnlisted: return "2" // + command
|
|
|
|
// case .selectVisibilityPrivate: return "2" // + command
|
|
|
|
// case .selectVisibilityDirect: return "3" // + command
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// var modifierFlags: UIKeyModifierFlags {
|
|
|
|
// switch self {
|
|
|
|
// case .discardPost: return [.command]
|
|
|
|
// case .publishPost: return [.command]
|
|
|
|
// case .mediaBrowse: return [.alternate, .command]
|
|
|
|
// case .mediaPhotoLibrary: return [.alternate, .command]
|
|
|
|
// case .mediaCamera: return [.alternate, .command]
|
|
|
|
// case .togglePoll: return [.shift, .command]
|
|
|
|
// case .toggleContentWarning: return [.shift, .command]
|
|
|
|
// case .selectVisibilityPublic: return [.command]
|
|
|
|
// // case .selectVisibilityUnlisted: return [.command]
|
|
|
|
// case .selectVisibilityPrivate: return [.command]
|
|
|
|
// case .selectVisibilityDirect: return [.command]
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// var propertyList: Any {
|
|
|
|
// return rawValue
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// var composeKeyCommands: [UIKeyCommand]? {
|
|
|
|
// ComposeKeyCommand.allCases.map { command in
|
|
|
|
// UIKeyCommand(
|
|
|
|
// title: command.title,
|
|
|
|
// image: nil,
|
|
|
|
// action: #selector(Self.composeKeyCommandHandler(_:)),
|
|
|
|
// input: command.input,
|
|
|
|
// modifierFlags: command.modifierFlags,
|
|
|
|
// propertyList: command.propertyList,
|
|
|
|
// alternates: [],
|
|
|
|
// discoverabilityTitle: nil,
|
|
|
|
// attributes: [],
|
|
|
|
// state: .off
|
|
|
|
// )
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// @objc private func composeKeyCommandHandler(_ sender: UIKeyCommand) {
|
|
|
|
// guard let rawValue = sender.propertyList as? String,
|
|
|
|
// let command = ComposeKeyCommand(rawValue: rawValue) else { return }
|
|
|
|
//
|
|
|
|
// switch command {
|
|
|
|
// case .discardPost:
|
|
|
|
// cancelBarButtonItemPressed(cancelBarButtonItem)
|
|
|
|
// case .publishPost:
|
|
|
|
// publishBarButtonItemPressed(publishBarButtonItem)
|
|
|
|
// case .mediaBrowse:
|
|
|
|
// present(documentPickerController, animated: true, completion: nil)
|
|
|
|
// case .mediaPhotoLibrary:
|
|
|
|
// present(photoLibraryPicker, animated: true, completion: nil)
|
|
|
|
// case .mediaCamera:
|
|
|
|
// guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// present(imagePickerController, animated: true, completion: nil)
|
|
|
|
// case .togglePoll:
|
|
|
|
// composeToolbarView.pollButton.sendActions(for: .touchUpInside)
|
|
|
|
// case .toggleContentWarning:
|
|
|
|
// composeToolbarView.contentWarningButton.sendActions(for: .touchUpInside)
|
|
|
|
// case .selectVisibilityPublic:
|
|
|
|
// viewModel.selectedStatusVisibility = .public
|
|
|
|
// // case .selectVisibilityUnlisted:
|
|
|
|
// // viewModel.selectedStatusVisibility.value = .unlisted
|
|
|
|
// case .selectVisibilityPrivate:
|
|
|
|
// viewModel.selectedStatusVisibility = .private
|
|
|
|
// case .selectVisibilityDirect:
|
|
|
|
// viewModel.selectedStatusVisibility = .direct
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//}
|