From 36604d150f0de764fda3510a81e6dcba6a6940ee Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 12 Mar 2021 15:57:58 +0800 Subject: [PATCH] feat: show discard alert when user cancel toot composing --- Localization/app.json | 5 ++ .../Section/ComposeStatusSection.swift | 12 ++++- Mastodon/Generated/Strings.swift | 8 +++ .../Resources/en.lproj/Localizable.strings | 3 ++ .../Scene/Compose/ComposeViewController.swift | 50 +++++++++++++------ Mastodon/Scene/Compose/ComposeViewModel.swift | 19 +++++++ .../Compose/View/ComposeToolbarView.swift | 2 + 7 files changed, 82 insertions(+), 17 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 8734ea00..17994bb3 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -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", diff --git a/Mastodon/Diffiable/Section/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/ComposeStatusSection.swift index e1405309..be3608ef 100644 --- a/Mastodon/Diffiable/Section/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/ComposeStatusSection.swift @@ -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) } } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 49e4cd7c..105e98ef 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -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 diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index f9a1ffe6..5605683d 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -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"; diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index 492a6985..b1a1cf5b 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -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) { diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 7aaadcb7..a6228099 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -27,6 +27,7 @@ final class ComposeViewModel { // UI & UX let title: CurrentValueSubject let shouldDismiss = CurrentValueSubject(true) + let isComposeTootBarButtonItemEnabled = CurrentValueSubject(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) } } diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index 7b501bf7..7eb3ae82 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -17,6 +17,8 @@ protocol ComposeToolbarViewDelegate: class { final class ComposeToolbarView: UIView { + static let toolbarHeight: CGFloat = 44 + weak var delegate: ComposeToolbarViewDelegate? let mediaButton: UIButton = {