From 5c179457e56f958a9f76fe64baf084a0088e127b Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Tue, 19 Jan 2021 11:59:20 -0800 Subject: [PATCH] Composition niceties --- Localizations/Localizable.strings | 1 + .../NewStatusViewController.swift | 14 ++++++-- Views/AttachmentUploadView.swift | 18 ++++++----- Views/CompositionInputAccessoryView.swift | 1 - Views/CompositionPollOptionView.swift | 4 +-- Views/CompositionPollView.swift | 13 ++++++++ Views/CompositionView.swift | 32 ++++++++++++------- Views/ViewConstants.swift | 2 ++ 8 files changed, 60 insertions(+), 25 deletions(-) diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 6d76135..0825583 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -51,6 +51,7 @@ "compose.photo-library" = "Photo Library"; "compose.poll.add-choice" = "Add a choice"; "compose.poll.allow-multiple-choices" = "Allow multiple choices"; +"compose.poll.option-%ld" = "Option %ld"; "compose.prompt" = "What's on your mind?"; "compose.take-photo-or-video" = "Take Photo or Video"; "emoji.custom" = "Custom"; diff --git a/View Controllers/NewStatusViewController.swift b/View Controllers/NewStatusViewController.swift index 8ed3c40..8e2461a 100644 --- a/View Controllers/NewStatusViewController.swift +++ b/View Controllers/NewStatusViewController.swift @@ -193,9 +193,19 @@ private extension NewStatusViewController { } for removal in diff.removals { - guard case let .remove(_, id, _) = removal else { continue } + guard case let .remove(_, id, _) = removal, + let index = stackView.arrangedSubviews.firstIndex(where: { ($0 as? CompositionView)?.id == id }) + else { continue } - stackView.arrangedSubviews.first { ($0 as? CompositionView)?.id == id }?.removeFromSuperview() + if (stackView.arrangedSubviews[index] as? CompositionView)?.textView.isFirstResponder ?? false { + if index > 0 { + (stackView.arrangedSubviews[index - 1] as? CompositionView)?.textView.becomeFirstResponder() + } else if stackView.arrangedSubviews.count > index { + (stackView.arrangedSubviews[index + 1] as? CompositionView)?.textView.becomeFirstResponder() + } + } + + stackView.arrangedSubviews[index].removeFromSuperview() } for compositionView in stackView.arrangedSubviews.compactMap({ $0 as? CompositionView }) { diff --git a/Views/AttachmentUploadView.swift b/Views/AttachmentUploadView.swift index d7ba78f..d46cb13 100644 --- a/Views/AttachmentUploadView.swift +++ b/Views/AttachmentUploadView.swift @@ -50,16 +50,18 @@ final class AttachmentUploadView: UIView { progressView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) ]) - viewModel.$attachmentUpload.sink { [weak self] in + viewModel.$attachmentUpload.sink { [weak self] attachmentUpload in guard let self = self else { return } - if let attachmentUpload = $0 { - self.progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted) - .receive(on: DispatchQueue.main) - .sink { self.progressView.progress = Float($0) } - self.isHidden = false - } else { - self.isHidden = true + UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) { + if let attachmentUpload = attachmentUpload { + self.progressCancellable = attachmentUpload.progress.publisher(for: \.fractionCompleted) + .receive(on: DispatchQueue.main) + .sink { self.progressView.progress = Float($0) } + self.isHidden = false + } else { + self.isHidden = true + } } } .store(in: &cancellables) diff --git a/Views/CompositionInputAccessoryView.swift b/Views/CompositionInputAccessoryView.swift index b83d6bb..50723fa 100644 --- a/Views/CompositionInputAccessoryView.swift +++ b/Views/CompositionInputAccessoryView.swift @@ -65,7 +65,6 @@ private extension CompositionInputAccessoryView { #endif let attachmentButton = UIBarButtonItem( - title: "hm", image: UIImage(systemName: "paperclip"), menu: UIMenu(children: attachmentActions)) let pollButton = UIBarButtonItem( diff --git a/Views/CompositionPollOptionView.swift b/Views/CompositionPollOptionView.swift index 92a102d..a8efdf6 100644 --- a/Views/CompositionPollOptionView.swift +++ b/Views/CompositionPollOptionView.swift @@ -5,6 +5,7 @@ import UIKit import ViewModels final class CompositionPollOptionView: UIView { + let textField = UITextField() let option: CompositionViewModel.PollOption let removeButton = UIButton(type: .close) private let viewModel: CompositionViewModel @@ -33,7 +34,6 @@ private extension CompositionPollOptionView { // swiftlint:disable:next function_body_length func initialSetup() { let stackView = UIStackView() - let textField = UITextField() let remainingCharactersLabel = UILabel() addSubview(stackView) @@ -51,7 +51,7 @@ private extension CompositionPollOptionView { textField.tag = textInputAccessoryView.tagForInputView textField.addAction( UIAction { [weak self] _ in - self?.option.text = textField.text ?? "" }, + self?.option.text = self?.textField.text ?? "" }, for: .editingChanged) textField.text = option.text diff --git a/Views/CompositionPollView.swift b/Views/CompositionPollView.swift index a4d480e..7a06dd3 100644 --- a/Views/CompositionPollView.swift +++ b/Views/CompositionPollView.swift @@ -62,6 +62,7 @@ private extension CompositionPollView { withConfiguration: UIImage.SymbolConfiguration(scale: .medium)), for: .normal) addChoiceButton.setTitle(NSLocalizedString("compose.poll.add-choice", comment: ""), for: .normal) + addChoiceButton.imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: .defaultSpacing) let expiresInButton = UIButton(type: .system) @@ -76,6 +77,7 @@ private extension CompositionPollView { self?.viewModel.pollExpiresIn = expiry } }) + expiresInButton.imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: .defaultSpacing) let switchStackView = UIStackView() @@ -121,6 +123,9 @@ private extension CompositionPollView { parentViewModel: self.parentViewModel, option: option) + optionView.textField.placeholder = String.localizedStringWithFormat( + NSLocalizedString("compose.poll.option-%ld", comment: ""), + index + 1) self.stackView.insertArrangedSubview(optionView, at: index) } } @@ -129,6 +134,14 @@ private extension CompositionPollView { optionView.removeButton.isHidden = index < CompositionViewModel.minPollOptionCount if !$0.contains(where: { $0 === optionView.option }) { + if optionView.textField.isFirstResponder { + if index > 0 { + self.pollOptionViews[index - 1].textField.becomeFirstResponder() + } else if self.pollOptionViews.count > index { + self.pollOptionViews[index + 1].textField.becomeFirstResponder() + } + } + optionView.removeFromSuperview() } } diff --git a/Views/CompositionView.swift b/Views/CompositionView.swift index 0226867..b35e362 100644 --- a/Views/CompositionView.swift +++ b/Views/CompositionView.swift @@ -157,17 +157,21 @@ private extension CompositionView { .store(in: &cancellables) viewModel.$displayContentWarning - .sink { [weak self] in + .sink { [weak self] displayContentWarning in guard let self = self else { return } - if self.spoilerTextField.isHidden && self.textView.isFirstResponder && $0 { + if self.spoilerTextField.isHidden && self.textView.isFirstResponder && displayContentWarning { self.spoilerTextField.becomeFirstResponder() - } else if !self.spoilerTextField.isHidden && self.spoilerTextField.isFirstResponder && !$0 { + } else if !self.spoilerTextField.isHidden + && self.spoilerTextField.isFirstResponder + && !displayContentWarning { self.textView.becomeFirstResponder() } - self.spoilerTextField.isHidden = !$0 - textViewBaselineConstraint.isActive = !$0 + UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) { + self.spoilerTextField.isHidden = !displayContentWarning + textViewBaselineConstraint.isActive = !displayContentWarning + } } .store(in: &cancellables) @@ -177,20 +181,24 @@ private extension CompositionView { viewModel.$attachmentViewModels .receive(on: RunLoop.main) - .sink { [weak self] in - self?.attachmentsView.viewModel = self?.viewModel - self?.attachmentsView.isHidden = $0.isEmpty - self?.markAttachmentsSensitiveView.isHidden = $0.isEmpty + .sink { [weak self] attachmentViewModels in + UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) { + self?.attachmentsView.viewModel = self?.viewModel + self?.attachmentsView.isHidden = attachmentViewModels.isEmpty + self?.markAttachmentsSensitiveView.isHidden = attachmentViewModels.isEmpty + } } .store(in: &cancellables) viewModel.$displayPoll - .sink { [weak self] in - if !$0 { + .sink { [weak self] displayPoll in + if !displayPoll { self?.textView.becomeFirstResponder() } - self?.pollView.isHidden = !$0 + UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) { + self?.pollView.isHidden = !displayPoll + } } .store(in: &cancellables) diff --git a/Views/ViewConstants.swift b/Views/ViewConstants.swift index d6306a3..41f2aeb 100644 --- a/Views/ViewConstants.swift +++ b/Views/ViewConstants.swift @@ -22,6 +22,8 @@ extension CGRect { extension TimeInterval { static let defaultAnimationDuration: Self = 0.5 static let shortAnimationDuration = defaultAnimationDuration / 2 + + static func zeroIfReduceMotion(_ duration: Self) -> Self { UIAccessibility.isReduceMotionEnabled ? 0 : duration } } extension UIImage {