From ccb74b08f7bb341d73285f6d72093ad1756e947b Mon Sep 17 00:00:00 2001 From: CMK Date: Thu, 15 Apr 2021 12:10:43 +0800 Subject: [PATCH] feat: make reply to foldable in compose scene --- .../Section/ComposeStatusSection.swift | 3 ++ ...iedToStatusContentCollectionViewCell.swift | 7 +++ .../Scene/Compose/ComposeViewController.swift | 51 ++++++++++++++++++- .../Compose/ComposeViewModel+Diffable.swift | 1 + Mastodon/Scene/Compose/ComposeViewModel.swift | 9 ++++ 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Mastodon/Diffiable/Section/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/ComposeStatusSection.swift index 49f4fb5b7..0e7c574b4 100644 --- a/Mastodon/Diffiable/Section/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/ComposeStatusSection.swift @@ -34,6 +34,7 @@ extension ComposeStatusSection { dependency: NeedsDependency, managedObjectContext: NSManagedObjectContext, composeKind: ComposeKind, + repliedToCellFrameSubscriber: CurrentValueSubject, customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel, textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate, composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate, @@ -70,6 +71,8 @@ extension ComposeStatusSection { cell.statusView.activeTextLabel.configure(content: status.content) // set date cell.statusView.dateLabel.text = status.createdAt.shortTimeAgoSinceNow + + cell.framePublisher.assign(to: \.value, on: repliedToCellFrameSubscriber).store(in: &cell.disposeBag) } return cell case .input(let replyToStatusObjectID, let attribute): diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToStatusContentCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToStatusContentCollectionViewCell.swift index 35af3c0ac..95e9b4f1a 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToStatusContentCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToStatusContentCollectionViewCell.swift @@ -14,6 +14,8 @@ final class ComposeRepliedToStatusContentCollectionViewCell: UICollectionViewCel let statusView = StatusView() + let framePublisher = PassthroughSubject() + override func prepareForReuse() { super.prepareForReuse() @@ -32,6 +34,11 @@ final class ComposeRepliedToStatusContentCollectionViewCell: UICollectionViewCel _init() } + override func layoutSubviews() { + super.layoutSubviews() + framePublisher.send(bounds) + } + } extension ComposeRepliedToStatusContentCollectionViewCell { diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index 636d2de63..b463f13ac 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -31,7 +31,7 @@ final class ComposeViewController: UIViewController, NeedsDependency { 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: 5.5, left: 16, bottom: 5.5, right: 16) // set 28pt height + button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height button.adjustsImageWhenHighlighted = false return button }() @@ -50,6 +50,7 @@ final class ComposeViewController: UIViewController, NeedsDependency { collectionView.register(ComposeStatusPollOptionAppendEntryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self)) collectionView.register(ComposeStatusPollExpiresOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self)) collectionView.backgroundColor = Asset.Scene.Compose.background.color + collectionView.alwaysBounceVertical = true return collectionView }() @@ -331,6 +332,24 @@ extension ComposeViewController { } }) .store(in: &disposeBag) + + // setup snap behavior + Publishers.CombineLatest( + viewModel.repliedToCellFrame.removeDuplicates().eraseToAnyPublisher(), + viewModel.collectionViewState.eraseToAnyPublisher() + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] repliedToCellFrame, collectionViewState in + guard let self = self else { return } + guard repliedToCellFrame != .zero else { return } + switch collectionViewState { + case .fold: + self.collectionView.contentInset.top = -repliedToCellFrame.height + case .expand: + self.collectionView.contentInset.top = 0 + } + } + .store(in: &disposeBag) } override func viewWillAppear(_ animated: Bool) { @@ -754,6 +773,32 @@ extension ComposeViewController: ComposeToolbarViewDelegate { } +// MARK: - UIScrollViewDelegate +extension ComposeViewController { + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + guard scrollView === collectionView else { return } + + let repliedToCellFrame = viewModel.repliedToCellFrame.value + guard repliedToCellFrame != .zero else { return } + let throttle = viewModel.repliedToCellFrame.value.height - scrollView.adjustedContentInset.top + // print("\(throttle) - \(scrollView.contentOffset.y)") + + switch viewModel.collectionViewState.value { + case .fold: + if scrollView.contentOffset.y < throttle { + viewModel.collectionViewState.value = .expand + } + os_log("%{public}s[%{public}ld], %{public}s: fold", ((#file as NSString).lastPathComponent), #line, #function) + + case .expand: + if scrollView.contentOffset.y > -44 { + viewModel.collectionViewState.value = .fold + os_log("%{public}s[%{public}ld], %{public}s: expand", ((#file as NSString).lastPathComponent), #line, #function) + } + } + } +} + // MARK: - UITableViewDelegate extension ComposeViewController: UICollectionViewDelegate { @@ -790,6 +835,10 @@ extension ComposeViewController: UICollectionViewDelegate { // MARK: - UIAdaptivePresentationControllerDelegate extension ComposeViewController: UIAdaptivePresentationControllerDelegate { + + func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + return .fullScreen + } func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { return viewModel.shouldDismiss.value diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift index 4d5a39be1..6581e1fb1 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift @@ -27,6 +27,7 @@ extension ComposeViewModel { dependency: dependency, managedObjectContext: context.managedObjectContext, composeKind: composeKind, + repliedToCellFrameSubscriber: repliedToCellFrame, customEmojiPickerInputViewModel: customEmojiPickerInputViewModel, textEditorViewTextAttributesDelegate: textEditorViewTextAttributesDelegate, composeStatusAttachmentTableViewCellDelegate: composeStatusAttachmentTableViewCellDelegate, diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 777059adb..ef744d0b3 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -30,6 +30,7 @@ final class ComposeViewModel { let activeAuthentication: CurrentValueSubject let activeAuthenticationBox: CurrentValueSubject let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make intial event emit + let repliedToCellFrame = CurrentValueSubject(.zero) // output var diffableDataSource: UICollectionViewDiffableDataSource! @@ -56,6 +57,7 @@ final class ComposeViewModel { let isMediaToolbarButtonEnabled = CurrentValueSubject(true) let isPollToolbarButtonEnabled = CurrentValueSubject(true) let characterCount = CurrentValueSubject(0) + let collectionViewState = CurrentValueSubject(.fold) // for hashtag: #' ' // for mention: @' ' @@ -377,6 +379,13 @@ final class ComposeViewModel { } +extension ComposeViewModel { + enum CollectionViewState { + case fold // snap to input + case expand // snap to reply + } +} + extension ComposeViewModel { func createNewPollOptionIfPossible() { guard pollOptionAttributes.value.count < 4 else { return }