feat: show discard alert when user cancel toot composing

This commit is contained in:
CMK 2021-03-12 15:57:58 +08:00
parent 1746c1fc77
commit 36604d150f
7 changed files with 82 additions and 17 deletions

View File

@ -14,6 +14,10 @@
"vote_failure": {
"title": "Vote Failure",
"poll_expired": "The poll has expired"
},
"discard_compose_content": {
"title": "Discard Toot",
"message": "Confirm discard composed toot content."
}
},
"controls": {
@ -27,6 +31,7 @@
"confirm": "Confirm",
"continue": "Continue",
"cancel": "Cancel",
"discard": "Discard",
"take_photo": "Take photo",
"save_photo": "Save photo",
"sign_in": "Sign In",

View File

@ -46,7 +46,7 @@ extension ComposeStatusSection {
cell.statusView.headerContainerStackView.isHidden = false
cell.statusView.headerInfoLabel.text = "[TODO] \(replyTo.author.displayName)"
}
ComposeStatusSection.configureComposeTootContent(cell: cell, attribute: attribute)
ComposeStatusSection.configure(cell: cell, attribute: attribute)
// self size input cell
cell.composeContent
.receive(on: DispatchQueue.main)
@ -62,16 +62,18 @@ extension ComposeStatusSection {
}
extension ComposeStatusSection {
static func configureComposeTootContent(
static func configure(
cell: ComposeTootContentTableViewCell,
attribute: ComposeStatusItem.ComposeTootAttribute
) {
// 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.CombineLatest(
attribute.displayName.eraseToAnyPublisher(),
attribute.username.eraseToAnyPublisher()
@ -82,5 +84,11 @@ extension ComposeStatusSection {
cell.statusView.usernameLabel.text = username
}
.store(in: &cell.disposeBag)
// bind compose content
cell.composeContent
.map { $0 as String? }
.assign(to: \.value, on: attribute.composeContent)
.store(in: &cell.disposeBag)
}
}

View File

@ -19,6 +19,12 @@ internal enum L10n {
/// Please try again later.
internal static let pleaseTryAgainLater = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgainLater")
}
internal enum DiscardComposeContent {
/// Confirm discard composed toot content.
internal static let message = L10n.tr("Localizable", "Common.Alerts.DiscardComposeContent.Message")
/// Discard Toot
internal static let title = L10n.tr("Localizable", "Common.Alerts.DiscardComposeContent.Title")
}
internal enum ServerError {
/// Server Error
internal static let title = L10n.tr("Localizable", "Common.Alerts.ServerError.Title")
@ -46,6 +52,8 @@ internal enum L10n {
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
/// Continue
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
/// Discard
internal static let discard = L10n.tr("Localizable", "Common.Controls.Actions.Discard")
/// Edit
internal static let edit = L10n.tr("Localizable", "Common.Controls.Actions.Edit")
/// OK

View File

@ -1,5 +1,7 @@
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
"Common.Alerts.DiscardComposeContent.Message" = "Confirm discard composed toot content.";
"Common.Alerts.DiscardComposeContent.Title" = "Discard Toot";
"Common.Alerts.ServerError.Title" = "Server Error";
"Common.Alerts.SignUpFailure.Title" = "Sign Up Failure";
"Common.Alerts.VoteFailure.PollExpired" = "The poll has expired";
@ -9,6 +11,7 @@
"Common.Controls.Actions.Cancel" = "Cancel";
"Common.Controls.Actions.Confirm" = "Confirm";
"Common.Controls.Actions.Continue" = "Continue";
"Common.Controls.Actions.Discard" = "Discard";
"Common.Controls.Actions.Edit" = "Edit";
"Common.Controls.Actions.Ok" = "OK";
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";

View File

@ -70,7 +70,6 @@ extension ComposeViewController {
navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
navigationItem.rightBarButtonItem = composeTootBarButtonItem
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
@ -87,13 +86,17 @@ extension ComposeViewController {
composeToolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
composeToolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
composeToolbarViewBottomLayoutConstraint,
composeToolbarView.heightAnchor.constraint(equalToConstant: 44),
composeToolbarView.heightAnchor.constraint(equalToConstant: ComposeToolbarView.toolbarHeight),
])
composeToolbarView.preservesSuperviewLayoutMargins = true
composeToolbarView.delegate = self
tableView.delegate = self
viewModel.setupDiffableDataSource(for: tableView, dependency: self)
// respond scrollView overlap change
view.layoutIfNeeded()
// update layout when keyboard show/dismiss
Publishers.CombineLatest3(
KeyboardResponderService.shared.isShow.eraseToAnyPublisher(),
KeyboardResponderService.shared.state.eraseToAnyPublisher(),
@ -125,8 +128,9 @@ extension ComposeViewController {
return
}
self.tableView.contentInset.bottom = padding
self.tableView.verticalScrollIndicatorInsets.bottom = padding
// add 16pt margin
self.tableView.contentInset.bottom = padding + 16
self.tableView.verticalScrollIndicatorInsets.bottom = padding + 16
UIView.animate(withDuration: 0.3) {
self.composeToolbarViewBottomLayoutConstraint.constant = padding
self.view.layoutIfNeeded()
@ -134,8 +138,10 @@ extension ComposeViewController {
})
.store(in: &disposeBag)
tableView.delegate = self
viewModel.setupDiffableDataSource(for: tableView, dependency: self)
viewModel.isComposeTootBarButtonItemEnabled
.receive(on: DispatchQueue.main)
.assign(to: \.isEnabled, on: composeTootBarButtonItem)
.store(in: &disposeBag)
}
override func viewWillAppear(_ animated: Bool) {
@ -168,12 +174,32 @@ extension ComposeViewController {
}
}
}
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)
}
}
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
}
dismiss(animated: true, completion: nil)
}
@ -222,21 +248,15 @@ extension ComposeViewController: UITableViewDelegate {
// MARK: - ComposeViewController
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
// func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// switch traitCollection.userInterfaceIdiom {
// case .phone:
// return .fullScreen
// default:
// return .pageSheet
// }
// }
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()
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {

View File

@ -27,6 +27,7 @@ final class ComposeViewModel {
// UI & UX
let title: CurrentValueSubject<String, Never>
let shouldDismiss = CurrentValueSubject<Bool, Never>(true)
let isComposeTootBarButtonItemEnabled = CurrentValueSubject<Bool, Never>(false)
init(
context: AppContext,
@ -62,6 +63,24 @@ final class ComposeViewModel {
self.composeTootAttribute.username.value = username
}
.store(in: &disposeBag)
composeTootAttribute.composeContent
.receive(on: DispatchQueue.main)
.map { content in
let content = content?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return !content.isEmpty
}
.assign(to: \.value, on: isComposeTootBarButtonItemEnabled)
.store(in: &disposeBag)
composeTootAttribute.composeContent
.receive(on: DispatchQueue.main)
.map { content in
let content = content ?? ""
return content.isEmpty
}
.assign(to: \.value, on: shouldDismiss)
.store(in: &disposeBag)
}
}

View File

@ -17,6 +17,8 @@ protocol ComposeToolbarViewDelegate: class {
final class ComposeToolbarView: UIView {
static let toolbarHeight: CGFloat = 44
weak var delegate: ComposeToolbarViewDelegate?
let mediaButton: UIButton = {