forked from zelo72/mastodon-ios
feat: add poll option reorder supports
This commit is contained in:
parent
0e84b4c164
commit
135e88c650
|
@ -13,11 +13,20 @@ 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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue