feat: add poll option reorder supports

This commit is contained in:
CMK 2021-03-24 15:46:40 +08:00
parent 0e84b4c164
commit 135e88c650
6 changed files with 93 additions and 44 deletions

View File

@ -15,9 +15,18 @@ protocol ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate: class {
final class ComposeStatusPollOptionAppendEntryCollectionViewCell: UICollectionViewCell {
let pollOptionView = PollOptionView()
let reorderBarImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "line.horizontal.3")?.withConfiguration(UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)).withRenderingMode(.alwaysTemplate)
imageView.tintColor = Asset.Colors.Label.secondary.color
return imageView
}()
let singleTagGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
weak var delegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate?
override var isHighlighted: Bool {
didSet {
pollOptionView.roundedBackgroundView.backgroundColor = isHighlighted ? Asset.Colors.Background.secondarySystemBackground.color : Asset.Colors.Background.systemBackground.color
@ -25,7 +34,9 @@ final class ComposeStatusPollOptionAppendEntryCollectionViewCell: UICollectionVi
}
}
weak var delegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate?
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return pollOptionView.frame.contains(point)
}
override func prepareForReuse() {
super.prepareForReuse()
@ -53,10 +64,18 @@ extension ComposeStatusPollOptionAppendEntryCollectionViewCell {
NSLayoutConstraint.activate([
pollOptionView.topAnchor.constraint(equalTo: contentView.topAnchor),
pollOptionView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
pollOptionView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
pollOptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
reorderBarImageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(reorderBarImageView)
NSLayoutConstraint.activate([
reorderBarImageView.topAnchor.constraint(equalTo: contentView.topAnchor),
reorderBarImageView.leadingAnchor.constraint(equalTo: pollOptionView.trailingAnchor, constant: ComposeStatusPollOptionCollectionViewCell.reorderHandlerImageLeadingMargin),
reorderBarImageView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
reorderBarImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
pollOptionView.checkmarkImageView.isHidden = true
pollOptionView.checkmarkBackgroundView.isHidden = true
pollOptionView.optionPercentageLabel.isHidden = true
@ -68,6 +87,8 @@ extension ComposeStatusPollOptionAppendEntryCollectionViewCell {
pollOptionView.addGestureRecognizer(singleTagGestureRecognizer)
singleTagGestureRecognizer.addTarget(self, action: #selector(ComposeStatusPollOptionAppendEntryCollectionViewCell.singleTagGestureRecognizerHandler(_:)))
reorderBarImageView.isHidden = true
}
private func setupBorderColor() {

View File

@ -16,16 +16,29 @@ protocol ComposeStatusPollOptionCollectionViewCellDelegate: class {
final class ComposeStatusPollOptionCollectionViewCell: UICollectionViewCell {
static let reorderHandlerImageLeadingMargin: CGFloat = 11
var disposeBag = Set<AnyCancellable>()
weak var delegate: ComposeStatusPollOptionCollectionViewCellDelegate?
let pollOptionView = PollOptionView()
let reorderBarImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "line.horizontal.3")?.withConfiguration(UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)).withRenderingMode(.alwaysTemplate)
imageView.tintColor = Asset.Colors.Label.secondary.color
return imageView
}()
let singleTagGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
private var pollOptionSubscription: AnyCancellable?
let pollOption = PassthroughSubject<String, Never>()
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return pollOptionView.frame.contains(point)
}
override func prepareForReuse() {
super.prepareForReuse()
@ -53,10 +66,18 @@ extension ComposeStatusPollOptionCollectionViewCell {
NSLayoutConstraint.activate([
pollOptionView.topAnchor.constraint(equalTo: contentView.topAnchor),
pollOptionView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
pollOptionView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
pollOptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
reorderBarImageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(reorderBarImageView)
NSLayoutConstraint.activate([
reorderBarImageView.topAnchor.constraint(equalTo: contentView.topAnchor),
reorderBarImageView.leadingAnchor.constraint(equalTo: pollOptionView.trailingAnchor, constant: ComposeStatusPollOptionCollectionViewCell.reorderHandlerImageLeadingMargin),
reorderBarImageView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
reorderBarImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
pollOptionView.checkmarkImageView.isHidden = true
pollOptionView.optionPercentageLabel.isHidden = true
pollOptionView.optionTextField.text = nil

View File

@ -150,9 +150,6 @@ extension ComposeViewController {
])
collectionView.delegate = self
// Note: do not allow reorder due to the images display order following the upload time
// let longPressReorderGesture = UILongPressGestureRecognizer(target: self, action: #selector(ComposeViewController.longPressReorderGestureHandler(_:)))
// collectionView.addGestureRecognizer(longPressReorderGesture)
viewModel.setupDiffableDataSource(
for: collectionView,
dependency: self,
@ -162,6 +159,8 @@ extension ComposeViewController {
composeStatusNewPollOptionCollectionViewCellDelegate: self,
composeStatusPollExpiresOptionCollectionViewCellDelegate: self
)
let longPressReorderGesture = UILongPressGestureRecognizer(target: self, action: #selector(ComposeViewController.longPressReorderGestureHandler(_:)))
collectionView.addGestureRecognizer(longPressReorderGesture)
// respond scrollView overlap change
view.layoutIfNeeded()
@ -389,13 +388,20 @@ extension ComposeViewController {
dismiss(animated: true, completion: nil)
}
/* Do not allow reorder image due to image display order following the update time
// seealso: ComposeViewModel.setupDiffableDataSource()
@objc private func longPressReorderGestureHandler(_ sender: UILongPressGestureRecognizer) {
switch(sender.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: sender.location(in: collectionView)) else {
guard let selectedIndexPath = collectionView.indexPathForItem(at: sender.location(in: collectionView)),
let cell = collectionView.cellForItem(at: selectedIndexPath) as? ComposeStatusPollOptionCollectionViewCell else {
break
}
// check if pressing reorder bar no not
let locationInCell = sender.location(in: cell)
guard cell.reorderBarImageView.frame.contains(locationInCell) else {
return
}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
guard let selectedIndexPath = collectionView.indexPathForItem(at: sender.location(in: collectionView)),
@ -403,19 +409,20 @@ extension ComposeViewController {
break
}
guard let item = diffableDataSource.itemIdentifier(for: selectedIndexPath),
case .attachment = item else {
case .pollOption = item else {
collectionView.cancelInteractiveMovement()
return
}
collectionView.updateInteractiveMovementTargetPosition(sender.location(in: collectionView))
var position = sender.location(in: collectionView)
position.x = collectionView.frame.width * 0.5
collectionView.updateInteractiveMovementTargetPosition(position)
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
*/
}
@ -571,8 +578,8 @@ extension ComposeViewController: ComposeToolbarViewDelegate {
viewModel.isPollComposing.value.toggle()
// setup initial poll option if needs
if viewModel.isPollComposing.value, viewModel.pollAttributes.value.isEmpty {
viewModel.pollAttributes.value = [ComposeStatusItem.ComposePollOptionAttribute(), ComposeStatusItem.ComposePollOptionAttribute()]
if viewModel.isPollComposing.value, viewModel.pollOptionAttributes.value.isEmpty {
viewModel.pollOptionAttributes.value = [ComposeStatusItem.ComposePollOptionAttribute(), ComposeStatusItem.ComposePollOptionAttribute()]
}
if viewModel.isPollComposing.value {
@ -708,7 +715,7 @@ extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelega
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
guard case let .pollOption(attribute) = item else { return }
var pollAttributes = viewModel.pollAttributes.value
var pollAttributes = viewModel.pollOptionAttributes.value
guard let index = pollAttributes.firstIndex(of: attribute) else { return }
// mark previous (fallback to next) item of removed middle poll option become first responder
@ -741,7 +748,7 @@ extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelega
pollAttributes.remove(at: index)
// update data source
viewModel.pollAttributes.value = pollAttributes
viewModel.pollOptionAttributes.value = pollAttributes
}
// handle keyboard return event for poll option input

View File

@ -31,26 +31,26 @@ extension ComposeViewModel {
composeStatusPollExpiresOptionCollectionViewCellDelegate: composeStatusPollExpiresOptionCollectionViewCellDelegate
)
// Note: do not allow reorder due to the images display order following the upload time
// diffableDataSource.reorderingHandlers.canReorderItem = { item in
// switch item {
// case .attachment: return true
// default: return false
// }
//
// }
// diffableDataSource.reorderingHandlers.didReorder = { [weak self] transaction in
// guard let self = self else { return }
//
// let items = transaction.finalSnapshot.itemIdentifiers
// var attachmentServices: [MastodonAttachmentService] = []
// for item in items {
// guard case let .attachment(attachmentService) = item else { continue }
// attachmentServices.append(attachmentService)
// }
// self.attachmentServices.value = attachmentServices
// }
//
diffableDataSource.reorderingHandlers.canReorderItem = { item in
switch item {
case .pollOption: return true
default: return false
}
}
// update reordered data source
diffableDataSource.reorderingHandlers.didReorder = { [weak self] transaction in
guard let self = self else { return }
let items = transaction.finalSnapshot.itemIdentifiers
var pollOptionAttributes: [ComposeStatusItem.ComposePollOptionAttribute] = []
for item in items {
guard case let .pollOption(attribute) = item else { continue }
pollOptionAttributes.append(attribute)
}
self.pollOptionAttributes.value = pollOptionAttributes
}
self.diffableDataSource = diffableDataSource
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusSection, ComposeStatusItem>()

View File

@ -55,7 +55,7 @@ extension ComposeViewModel.PublishState {
}
let pollOptions: [String]? = {
guard viewModel.isPollComposing.value else { return nil }
return viewModel.pollAttributes.value.map { attribute in attribute.option.value }
return viewModel.pollOptionAttributes.value.map { attribute in attribute.option.value }
}()
let pollExpiresIn: Int? = {
guard viewModel.isPollComposing.value else { return nil }

View File

@ -52,7 +52,7 @@ final class ComposeViewModel {
let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([])
// polls
let pollAttributes = CurrentValueSubject<[ComposeStatusItem.ComposePollOptionAttribute], Never>([])
let pollOptionAttributes = CurrentValueSubject<[ComposeStatusItem.ComposePollOptionAttribute], Never>([])
let pollExpiresOptionAttribute = ComposeStatusItem.ComposePollExpiresOptionAttribute()
init(
@ -105,7 +105,7 @@ final class ComposeViewModel {
.map { services in
services.allSatisfy { $0.uploadStateMachineSubject.value is MastodonAttachmentService.UploadState.Finish }
}
let isPollAttributeAllValid = pollAttributes
let isPollAttributeAllValid = pollOptionAttributes
.map { pollAttributes in
pollAttributes.allSatisfy { attribute -> Bool in
!attribute.option.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
@ -177,7 +177,7 @@ final class ComposeViewModel {
Publishers.CombineLatest3(
attachmentServices.eraseToAnyPublisher(),
isPollComposing.eraseToAnyPublisher(),
pollAttributes.eraseToAnyPublisher()
pollOptionAttributes.eraseToAnyPublisher()
)
.receive(on: DispatchQueue.main)
.sink { [weak self] attachmentServices, isPollComposing, pollAttributes in
@ -240,7 +240,7 @@ final class ComposeViewModel {
}
.store(in: &disposeBag)
pollAttributes
pollOptionAttributes
.sink { [weak self] pollAttributes in
guard let self = self else { return }
pollAttributes.forEach { $0.delegate = self }
@ -268,10 +268,10 @@ final class ComposeViewModel {
extension ComposeViewModel {
func createNewPollOptionIfPossible() {
guard pollAttributes.value.count < 4 else { return }
guard pollOptionAttributes.value.count < 4 else { return }
let attribute = ComposeStatusItem.ComposePollOptionAttribute()
pollAttributes.value = pollAttributes.value + [attribute]
pollOptionAttributes.value = pollOptionAttributes.value + [attribute]
}
}
@ -287,6 +287,6 @@ extension ComposeViewModel: MastodonAttachmentServiceDelegate {
extension ComposeViewModel: ComposePollAttributeDelegate {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?) {
// trigger update
pollAttributes.value = pollAttributes.value
pollOptionAttributes.value = pollOptionAttributes.value
}
}