mastodon-app-ufficiale-ipho.../Mastodon/Scene/Compose/ComposeViewController.swift

267 lines
11 KiB
Swift
Raw Normal View History

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
import TwitterTextEditor
2021-03-12 08:23:28 +01:00
import KeyboardGuide
2021-03-11 08:41:27 +01:00
final class ComposeViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var viewModel: ComposeViewModel!
let composeTootBarButtonItem: UIBarButtonItem = {
let button = RoundedEdgesButton(type: .custom)
button.setTitle(L10n.Scene.Compose.composeAction, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold)
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.normal.color), for: .normal)
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.normal.color.withAlphaComponent(0.5)), for: .highlighted)
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
button.setTitleColor(.white, for: .normal)
button.contentEdgeInsets = UIEdgeInsets(top: 3, left: 16, bottom: 3, right: 16)
button.adjustsImageWhenHighlighted = false
let barButtonItem = UIBarButtonItem(customView: button)
return barButtonItem
}()
2021-03-11 08:41:27 +01:00
let tableView: UITableView = {
let tableView = ControlContainableTableView()
tableView.register(ComposeRepliedToTootContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self))
tableView.register(ComposeTootContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeTootContentTableViewCell.self))
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
return tableView
}()
2021-03-12 08:23:28 +01:00
let composeToolbarView: ComposeToolbarView = {
let composeToolbarView = ComposeToolbarView()
return composeToolbarView
}()
var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint!
let composeToolbarBackgroundView: UIView = {
let backgroundView = UIView()
return backgroundView
}()
2021-03-11 08:41:27 +01:00
}
extension ComposeViewController {
override func viewDidLoad() {
super.viewDidLoad()
viewModel.title
.receive(on: DispatchQueue.main)
.sink { [weak self] title in
guard let self = self else { return }
self.title = title
}
.store(in: &disposeBag)
view.backgroundColor = Asset.Colors.Background.systemBackground.color
2021-03-11 08:41:27 +01:00
navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
navigationItem.rightBarButtonItem = composeTootBarButtonItem
2021-03-11 08:41:27 +01:00
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
2021-03-12 08:23:28 +01:00
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),
2021-03-12 08:23:28 +01:00
])
composeToolbarView.preservesSuperviewLayoutMargins = true
composeToolbarView.delegate = self
tableView.delegate = self
viewModel.setupDiffableDataSource(for: tableView, dependency: self)
2021-03-12 08:23:28 +01:00
// respond scrollView overlap change
view.layoutIfNeeded()
// update layout when keyboard show/dismiss
2021-03-12 08:23:28 +01:00
Publishers.CombineLatest3(
KeyboardResponderService.shared.isShow.eraseToAnyPublisher(),
KeyboardResponderService.shared.state.eraseToAnyPublisher(),
KeyboardResponderService.shared.endFrame.eraseToAnyPublisher()
)
.sink(receiveValue: { [weak self] isShow, state, endFrame in
guard let self = self else { return }
guard isShow, state == .dock else {
self.tableView.contentInset.bottom = 0.0
self.tableView.verticalScrollIndicatorInsets.bottom = 0.0
UIView.animate(withDuration: 0.3) {
self.composeToolbarViewBottomLayoutConstraint.constant = 0.0
self.view.layoutIfNeeded()
}
return
}
// isShow AND dock state
let contentFrame = self.view.convert(self.tableView.frame, to: nil)
let padding = contentFrame.maxY - endFrame.minY
guard padding > 0 else {
self.tableView.contentInset.bottom = 0.0
self.tableView.verticalScrollIndicatorInsets.bottom = 0.0
UIView.animate(withDuration: 0.3) {
self.composeToolbarViewBottomLayoutConstraint.constant = 0.0
self.view.layoutIfNeeded()
}
return
}
// add 16pt margin
self.tableView.contentInset.bottom = padding + 16
self.tableView.verticalScrollIndicatorInsets.bottom = padding + 16
2021-03-12 08:23:28 +01:00
UIView.animate(withDuration: 0.3) {
self.composeToolbarViewBottomLayoutConstraint.constant = padding
self.view.layoutIfNeeded()
}
})
.store(in: &disposeBag)
viewModel.isComposeTootBarButtonItemEnabled
.receive(on: DispatchQueue.main)
.assign(to: \.isEnabled, on: composeTootBarButtonItem)
.store(in: &disposeBag)
2021-03-11 08:41:27 +01:00
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Fix AutoLayout conflict issue
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.markTextViewEditorBecomeFirstResponser()
}
}
}
extension ComposeViewController {
private func markTextViewEditorBecomeFirstResponser() {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
let items = diffableDataSource.snapshot().itemIdentifiers
for item in items {
switch item {
case .toot:
guard let indexPath = diffableDataSource.indexPath(for: item),
let cell = tableView.cellForRow(at: indexPath) as? ComposeTootContentTableViewCell else {
continue
}
cell.textEditorView.isEditing = true
return
default:
continue
}
}
}
private func showDismissConfirmAlertController() {
let alertController = UIAlertController(
title: L10n.Common.Alerts.DiscardComposeContent.title,
message: L10n.Common.Alerts.DiscardComposeContent.message,
preferredStyle: .alert
)
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)
present(alertController, animated: true, completion: nil)
}
2021-03-11 08:41:27 +01:00
}
extension ComposeViewController {
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard viewModel.shouldDismiss.value else {
showDismissConfirmAlertController()
return
}
2021-03-11 08:41:27 +01:00
dismiss(animated: true, completion: nil)
}
}
// MARK: - TextEditorViewTextAttributesDelegate
extension ComposeViewController: TextEditorViewTextAttributesDelegate {
func textEditorView(_ textEditorView: TextEditorView, updateAttributedString attributedString: NSAttributedString, completion: @escaping (NSAttributedString?) -> Void) {
// TODO:
}
}
2021-03-12 08:23:28 +01:00
// MARK: - ComposeToolbarViewDelegate
extension ComposeViewController: ComposeToolbarViewDelegate {
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, gifButtonDidPressed sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, atButtonDidPressed sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, topicButtonDidPressed sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, locationButtonDidPressed sender: UIButton) {
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
// MARK: - UITableViewDelegate
extension ComposeViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
2021-03-11 08:41:27 +01:00
}
// MARK: - ComposeViewController
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
2021-03-11 08:41:27 +01:00
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return viewModel.shouldDismiss.value
}
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)
}
}