From b296b21ef08ed35bb37e8159e5f13b2e02327290 Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 22 Mar 2021 17:48:35 +0800 Subject: [PATCH] feat: add image attachments reorder support for status compose scene --- Mastodon.xcodeproj/project.pbxproj | 22 ++-- .../Section/ComposeStatusSection.swift | 29 ++--- ...pliedToTootContentCollectionViewCell.swift | 31 +++++ ...ComposeStatusAttachmentTableViewCell.swift | 37 +++--- ...poseStatusContentCollectionViewCell.swift} | 19 ++- .../Scene/Compose/ComposeViewController.swift | 116 ++++++++++++------ .../Compose/ComposeViewModel+Diffable.swift | 28 ++++- Mastodon/Scene/Compose/ComposeViewModel.swift | 3 +- ...oseRepliedToTootContentTableViewCell.swift | 31 ----- ...astodonAttachmentService+UploadState.swift | 2 +- 10 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToTootContentCollectionViewCell.swift rename Mastodon/Scene/Compose/{TableViewCell => CollectionViewCell}/ComposeStatusAttachmentTableViewCell.swift (67%) rename Mastodon/Scene/Compose/{TableViewCell/ComposeStatusContentTableViewCell.swift => CollectionViewCell/ComposeStatusContentCollectionViewCell.swift} (86%) delete mode 100644 Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToTootContentTableViewCell.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 684141b1b..d6e489b2a 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -179,8 +179,8 @@ DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; }; DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */; }; DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; }; - DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentTableViewCell.swift */; }; - DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToTootContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentTableViewCell.swift */; }; + DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */; }; + DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */; }; DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; }; DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; }; DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; }; @@ -468,8 +468,8 @@ DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = ""; }; DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = ""; }; DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; }; - DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; - DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToTootContentTableViewCell.swift; sourceTree = ""; }; + DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentCollectionViewCell.swift; sourceTree = ""; }; + DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToTootContentCollectionViewCell.swift; sourceTree = ""; }; DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = ""; }; DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB89B9F025C10FD0008580ED /* CoreDataStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataStack.h; sourceTree = ""; }; @@ -1122,7 +1122,7 @@ isa = PBXGroup; children = ( DB55D32225FB4D320002F825 /* View */, - DB789A2125F9F76D0071ACA0 /* TableViewCell */, + DB789A2125F9F76D0071ACA0 /* CollectionViewCell */, DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */, DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */, DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */, @@ -1131,14 +1131,14 @@ path = Compose; sourceTree = ""; }; - DB789A2125F9F76D0071ACA0 /* TableViewCell */ = { + DB789A2125F9F76D0071ACA0 /* CollectionViewCell */ = { isa = PBXGroup; children = ( - DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentTableViewCell.swift */, - DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentTableViewCell.swift */, + DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */, + DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */, DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentTableViewCell.swift */, ); - path = TableViewCell; + path = CollectionViewCell; sourceTree = ""; }; DB89B9EF25C10FD0008580ED /* CoreDataStack */ = { @@ -1842,7 +1842,7 @@ DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */, 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */, DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */, - DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentTableViewCell.swift in Sources */, + DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift in Sources */, DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */, DB9A488426034BD7008B817C /* APIService+Status.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, @@ -1922,7 +1922,7 @@ 5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */, DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */, DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */, - DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToTootContentTableViewCell.swift in Sources */, + DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Mastodon/Diffiable/Section/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/ComposeStatusSection.swift index b82b1f8de..33ef0f268 100644 --- a/Mastodon/Diffiable/Section/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/ComposeStatusSection.swift @@ -26,22 +26,22 @@ extension ComposeStatusSection { } extension ComposeStatusSection { - static func tableViewDiffableDataSource( - for tableView: UITableView, + + static func collectionViewDiffableDataSource( + for collectionView: UICollectionView, dependency: NeedsDependency, managedObjectContext: NSManagedObjectContext, composeKind: ComposeKind, textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate, - composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentTableViewCellDelegate - ) -> UITableViewDiffableDataSource { - UITableViewDiffableDataSource(tableView: tableView) { [weak textEditorViewTextAttributesDelegate, weak composeStatusAttachmentTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in + composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate + ) -> UICollectionViewDiffableDataSource { + UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in switch item { case .replyTo(let repliedToStatusObjectID): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self), for: indexPath) as! ComposeRepliedToTootContentTableViewCell - // TODO: + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeRepliedToTootContentCollectionViewCell.self), for: indexPath) as! ComposeRepliedToTootContentCollectionViewCell return cell case .input(let replyToTootObjectID, let attribute): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeStatusContentTableViewCell.self), for: indexPath) as! ComposeStatusContentTableViewCell + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusContentCollectionViewCell.self), for: indexPath) as! ComposeStatusContentCollectionViewCell cell.textEditorView.text = attribute.composeContent.value ?? "" managedObjectContext.perform { guard let replyToTootObjectID = replyToTootObjectID, @@ -59,24 +59,24 @@ extension ComposeStatusSection { .removeDuplicates() .receive(on: DispatchQueue.main) .sink { text in - tableView.beginUpdates() - tableView.endUpdates() + collectionView.collectionViewLayout.invalidateLayout() // bind input data attribute.composeContent.value = text } .store(in: &cell.disposeBag) return cell case .attachment(let attachmentService): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self), for: indexPath) as! ComposeStatusAttachmentTableViewCell + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell cell.attachmentContainerView.descriptionTextView.text = attachmentService.description.value cell.delegate = composeStatusAttachmentTableViewCellDelegate attachmentService.imageData .receive(on: DispatchQueue.main) .sink { imageData in + let size = cell.attachmentContainerView.previewImageView.frame.size != .zero ? cell.attachmentContainerView.previewImageView.frame.size : CGSize(width: 1, height: 1) guard let imageData = imageData, let image = UIImage(data: imageData) else { let placeholder = UIImage.placeholder( - size: cell.attachmentContainerView.previewImageView.frame.size, + size: size, color: Asset.Colors.Background.systemGroupedBackground.color ) .af.imageRounded( @@ -86,7 +86,7 @@ extension ComposeStatusSection { return } cell.attachmentContainerView.previewImageView.image = image - .af.imageAspectScaled(toFill: cell.attachmentContainerView.previewImageView.frame.size) + .af.imageAspectScaled(toFill: size) .af.imageRounded(withCornerRadius: AttachmentContainerView.containerViewCornerRadius) } .store(in: &cell.disposeBag) @@ -97,6 +97,7 @@ extension ComposeStatusSection { .receive(on: DispatchQueue.main) .sink { uploadState, error in cell.attachmentContainerView.emptyStateView.isHidden = error == nil + cell.attachmentContainerView.descriptionBackgroundView.isHidden = error != nil if let _ = error { cell.attachmentContainerView.activityIndicatorView.stopAnimating() } else { @@ -130,7 +131,7 @@ extension ComposeStatusSection { extension ComposeStatusSection { static func configure( - cell: ComposeStatusContentTableViewCell, + cell: ComposeStatusContentCollectionViewCell, attribute: ComposeStatusItem.ComposeStatusAttribute ) { // set avatar diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToTootContentCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToTootContentCollectionViewCell.swift new file mode 100644 index 000000000..fe00563df --- /dev/null +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeRepliedToTootContentCollectionViewCell.swift @@ -0,0 +1,31 @@ +// +// ComposeRepliedToTootContentCollectionViewCell.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-3-11. +// + +import UIKit + +final class ComposeRepliedToTootContentCollectionViewCell: UICollectionViewCell { + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ComposeRepliedToTootContentCollectionViewCell { + + private func _init() { + + } + +} + diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentTableViewCell.swift similarity index 67% rename from Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift rename to Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentTableViewCell.swift index 88ae255fc..bc087c990 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentTableViewCell.swift @@ -9,18 +9,18 @@ import os.log import UIKit import Combine -protocol ComposeStatusAttachmentTableViewCellDelegate: class { - func composeStatusAttachmentTableViewCell(_ cell: ComposeStatusAttachmentTableViewCell, removeButtonDidPressed button: UIButton) +protocol ComposeStatusAttachmentCollectionViewCellDelegate: class { + func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) } -final class ComposeStatusAttachmentTableViewCell: UITableViewCell { +final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell { var disposeBag = Set() - static let verticalMarginHeight: CGFloat = ComposeStatusAttachmentTableViewCell.removeButtonSize.height * 0.5 + static let verticalMarginHeight: CGFloat = ComposeStatusAttachmentCollectionViewCell.removeButtonSize.height * 0.5 static let removeButtonSize = CGSize(width: 22, height: 22) - weak var delegate: ComposeStatusAttachmentTableViewCellDelegate? + weak var delegate: ComposeStatusAttachmentCollectionViewCellDelegate? let attachmentContainerView = AttachmentContainerView() let removeButton: UIButton = { @@ -31,7 +31,7 @@ final class ComposeStatusAttachmentTableViewCell: UITableViewCell { button.setImage(image, for: .normal) button.setBackgroundImage(.placeholder(color: Asset.Colors.Background.danger.color), for: .normal) button.layer.masksToBounds = true - button.layer.cornerRadius = ComposeStatusAttachmentTableViewCell.removeButtonSize.width * 0.5 + button.layer.cornerRadius = ComposeStatusAttachmentCollectionViewCell.removeButtonSize.width * 0.5 button.layer.borderColor = Asset.Colors.Background.dangerBorder.color.cgColor button.layer.borderWidth = 1 return button @@ -41,11 +41,14 @@ final class ComposeStatusAttachmentTableViewCell: UITableViewCell { super.prepareForReuse() attachmentContainerView.activityIndicatorView.startAnimating() + attachmentContainerView.previewImageView.af.cancelImageRequest() + attachmentContainerView.previewImageView.image = .placeholder(color: .systemFill) delegate = nil + disposeBag.removeAll() } - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + override init(frame: CGRect) { + super.init(frame: frame) _init() } @@ -56,18 +59,18 @@ final class ComposeStatusAttachmentTableViewCell: UITableViewCell { } -extension ComposeStatusAttachmentTableViewCell { +extension ComposeStatusAttachmentCollectionViewCell { private func _init() { - selectionStyle = .none + // selectionStyle = .none attachmentContainerView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(attachmentContainerView) NSLayoutConstraint.activate([ - attachmentContainerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: ComposeStatusAttachmentTableViewCell.verticalMarginHeight), + attachmentContainerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: ComposeStatusAttachmentCollectionViewCell.verticalMarginHeight), attachmentContainerView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), attachmentContainerView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: attachmentContainerView.bottomAnchor, constant: ComposeStatusAttachmentTableViewCell.verticalMarginHeight), + contentView.bottomAnchor.constraint(equalTo: attachmentContainerView.bottomAnchor, constant: ComposeStatusAttachmentCollectionViewCell.verticalMarginHeight), attachmentContainerView.heightAnchor.constraint(equalToConstant: 205).priority(.defaultHigh), ]) @@ -76,21 +79,21 @@ extension ComposeStatusAttachmentTableViewCell { NSLayoutConstraint.activate([ removeButton.centerXAnchor.constraint(equalTo: attachmentContainerView.trailingAnchor), removeButton.centerYAnchor.constraint(equalTo: attachmentContainerView.topAnchor), - removeButton.widthAnchor.constraint(equalToConstant: ComposeStatusAttachmentTableViewCell.removeButtonSize.width).priority(.defaultHigh), - removeButton.heightAnchor.constraint(equalToConstant: ComposeStatusAttachmentTableViewCell.removeButtonSize.height).priority(.defaultHigh), + removeButton.widthAnchor.constraint(equalToConstant: ComposeStatusAttachmentCollectionViewCell.removeButtonSize.width).priority(.defaultHigh), + removeButton.heightAnchor.constraint(equalToConstant: ComposeStatusAttachmentCollectionViewCell.removeButtonSize.height).priority(.defaultHigh), ]) - removeButton.addTarget(self, action: #selector(ComposeStatusAttachmentTableViewCell.removeButtonDidPressed(_:)), for: .touchUpInside) + removeButton.addTarget(self, action: #selector(ComposeStatusAttachmentCollectionViewCell.removeButtonDidPressed(_:)), for: .touchUpInside) } } -extension ComposeStatusAttachmentTableViewCell { +extension ComposeStatusAttachmentCollectionViewCell { @objc private func removeButtonDidPressed(_ sender: UIButton) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - delegate?.composeStatusAttachmentTableViewCell(self, removeButtonDidPressed: sender) + delegate?.composeStatusAttachmentCollectionViewCell(self, removeButtonDidPressed: sender) } } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentCollectionViewCell.swift similarity index 86% rename from Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift rename to Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentCollectionViewCell.swift index f5f778946..80e8cf875 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentCollectionViewCell.swift @@ -1,5 +1,5 @@ // -// ComposeStatusContentTableViewCell.swift +// ComposeStatusContentCollectionViewCell.swift // Mastodon // // Created by MainasuK Cirno on 2021-3-11. @@ -9,7 +9,7 @@ import UIKit import Combine import TwitterTextEditor -final class ComposeStatusContentTableViewCell: UITableViewCell { +final class ComposeStatusContentCollectionViewCell: UICollectionViewCell { var disposeBag = Set() @@ -27,8 +27,8 @@ final class ComposeStatusContentTableViewCell: UITableViewCell { let composeContent = PassthroughSubject() - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + override init(frame: CGRect) { + super.init(frame: frame) _init() } @@ -39,10 +39,11 @@ final class ComposeStatusContentTableViewCell: UITableViewCell { } -extension ComposeStatusContentTableViewCell { +extension ComposeStatusContentCollectionViewCell { private func _init() { - selectionStyle = .none + // selectionStyle = .none + preservesSuperviewLayoutMargins = true statusView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(statusView) @@ -82,12 +83,8 @@ extension ComposeStatusContentTableViewCell { } -extension ComposeStatusContentTableViewCell { - -} - // MARK: - UITextViewDelegate -extension ComposeStatusContentTableViewCell: TextEditorViewChangeObserver { +extension ComposeStatusContentCollectionViewCell: TextEditorViewChangeObserver { func textEditorView(_ textEditorView: TextEditorView, didChangeWithChangeResult changeResult: TextEditorViewChangeResult) { guard changeResult.isTextChanged else { return } composeContent.send(textEditorView.text) diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index 0fbada7c4..5c7615ea0 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -39,15 +39,14 @@ final class ComposeViewController: UIViewController, NeedsDependency { return barButtonItem }() - let tableView: UITableView = { - let tableView = ControlContainableTableView() - tableView.register(ComposeRepliedToTootContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self)) - tableView.register(ComposeStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusContentTableViewCell.self)) - tableView.register(ComposeStatusAttachmentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self)) - tableView.rowHeight = UITableView.automaticDimension - tableView.separatorStyle = .none - tableView.showsVerticalScrollIndicator = false - return tableView + let collectionView: UICollectionView = { + let collectionViewLayout = ComposeViewController.createLayout() + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.register(ComposeRepliedToTootContentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeRepliedToTootContentCollectionViewCell.self)) + collectionView.register(ComposeStatusContentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusContentCollectionViewCell.self)) + collectionView.register(ComposeStatusAttachmentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self)) + collectionView.backgroundColor = Asset.Colors.Background.systemBackground.color + return collectionView }() let composeToolbarView: ComposeToolbarView = { @@ -86,6 +85,20 @@ final class ComposeViewController: UIViewController, NeedsDependency { } +extension ComposeViewController { + private static func createLayout() -> UICollectionViewLayout { + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(100)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(100)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + let section = NSCollectionLayoutSection(group: group) + section.contentInsetsReference = .readableContent + // section.interGroupSpacing = 10 + // section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10) + return UICollectionViewCompositionalLayout(section: section) + } +} + extension ComposeViewController { override func viewDidLoad() { @@ -103,13 +116,13 @@ extension ComposeViewController { navigationItem.rightBarButtonItem = publishBarButtonItem publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside) - tableView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(tableView) + collectionView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(collectionView) NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + collectionView.topAnchor.constraint(equalTo: view.topAnchor), + collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) composeToolbarView.translatesAutoresizingMaskIntoConstraints = false @@ -133,9 +146,11 @@ extension ComposeViewController { view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor), ]) - tableView.delegate = self + collectionView.delegate = self + let longPressReorderGesture = UILongPressGestureRecognizer(target: self, action: #selector(ComposeViewController.longPressReorderGestureHandler(_:))) + collectionView.addGestureRecognizer(longPressReorderGesture) viewModel.setupDiffableDataSource( - for: tableView, + for: collectionView, dependency: self, textEditorViewTextAttributesDelegate: self, composeStatusAttachmentTableViewCellDelegate: self @@ -151,45 +166,45 @@ extension ComposeViewController { ) .sink(receiveValue: { [weak self] isShow, state, endFrame in guard let self = self else { return } - + guard isShow, state == .dock else { - self.tableView.contentInset.bottom = 0.0 - self.tableView.verticalScrollIndicatorInsets.bottom = 0.0 + self.collectionView.contentInset.bottom = self.view.safeAreaInsets.bottom + self.collectionView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom UIView.animate(withDuration: 0.3) { - self.composeToolbarViewBottomLayoutConstraint.constant = 0.0 + self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom self.view.layoutIfNeeded() } return } // isShow AND dock state - let contentFrame = self.view.convert(self.tableView.frame, to: nil) + let contentFrame = self.view.convert(self.collectionView.frame, to: nil) let padding = contentFrame.maxY - endFrame.minY guard padding > 0 else { - self.tableView.contentInset.bottom = 0.0 - self.tableView.verticalScrollIndicatorInsets.bottom = 0.0 + self.collectionView.contentInset.bottom = self.view.safeAreaInsets.bottom + self.collectionView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom UIView.animate(withDuration: 0.3) { - self.composeToolbarViewBottomLayoutConstraint.constant = 0.0 + self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom self.view.layoutIfNeeded() } return } // add 16pt margin - self.tableView.contentInset.bottom = padding + 16 - self.tableView.verticalScrollIndicatorInsets.bottom = padding + 16 + self.collectionView.contentInset.bottom = padding + 16 + self.collectionView.verticalScrollIndicatorInsets.bottom = padding + 16 UIView.animate(withDuration: 0.3) { self.composeToolbarViewBottomLayoutConstraint.constant = padding self.view.layoutIfNeeded() } }) .store(in: &disposeBag) - + viewModel.isPublishBarButtonItemEnabled .receive(on: DispatchQueue.main) .assign(to: \.isEnabled, on: publishBarButtonItem) .store(in: &disposeBag) - + // bind custom emojis viewModel.customEmojiViewModel .compactMap { $0?.emojis } @@ -203,7 +218,7 @@ extension ComposeViewController { self.textEditorView()?.setNeedsUpdateTextAttributes() }) .store(in: &disposeBag) - + // bind image picker toolbar state viewModel.attachmentServices .receive(on: DispatchQueue.main) @@ -236,7 +251,7 @@ extension ComposeViewController { switch item { case .input: guard let indexPath = diffableDataSource.indexPath(for: item), - let cell = tableView.cellForRow(at: indexPath) as? ComposeStatusContentTableViewCell else { + let cell = collectionView.cellForItem(at: indexPath) as? ComposeStatusContentCollectionViewCell else { continue } return cell.textEditorView @@ -306,6 +321,33 @@ extension ComposeViewController { dismiss(animated: true, completion: nil) } + + @objc private func longPressReorderGestureHandler(_ sender: UILongPressGestureRecognizer) { + switch(sender.state) { + case .began: + guard let selectedIndexPath = collectionView.indexPathForItem(at: sender.location(in: collectionView)) else { + break + } + collectionView.beginInteractiveMovementForItem(at: selectedIndexPath) + case .changed: + guard let selectedIndexPath = collectionView.indexPathForItem(at: sender.location(in: collectionView)), + let diffableDataSource = viewModel.diffableDataSource else { + break + } + guard let item = diffableDataSource.itemIdentifier(for: selectedIndexPath), + case .attachment = item else { + collectionView.cancelInteractiveMovement() + return + } + + collectionView.updateInteractiveMovementTargetPosition(sender.location(in: collectionView)) + case .ended: + collectionView.endInteractiveMovement() + default: + collectionView.cancelInteractiveMovement() + } + } + } // MARK: - TextEditorViewTextAttributesDelegate @@ -476,10 +518,8 @@ extension ComposeViewController: ComposeToolbarViewDelegate { } // MARK: - UITableViewDelegate -extension ComposeViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } +extension ComposeViewController: UICollectionViewDelegate { + } // MARK: - UIAdaptivePresentationControllerDelegate @@ -565,11 +605,11 @@ extension ComposeViewController: UIDocumentPickerDelegate { } // MARK: - ComposeStatusAttachmentTableViewCellDelegate -extension ComposeViewController: ComposeStatusAttachmentTableViewCellDelegate { +extension ComposeViewController: ComposeStatusAttachmentCollectionViewCellDelegate { - func composeStatusAttachmentTableViewCell(_ cell: ComposeStatusAttachmentTableViewCell, removeButtonDidPressed button: UIButton) { + func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) { guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let indexPath = tableView.indexPath(for: cell) else { return } + guard let indexPath = collectionView.indexPath(for: cell) else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } guard case let .attachment(attachmentService) = item else { return } diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift index d989d6f46..17465cf01 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift @@ -11,13 +11,13 @@ import TwitterTextEditor extension ComposeViewModel { func setupDiffableDataSource( - for tableView: UITableView, + for collectionView: UICollectionView, dependency: NeedsDependency, textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate, - composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentTableViewCellDelegate + composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate ) { - diffableDataSource = ComposeStatusSection.tableViewDiffableDataSource( - for: tableView, + let diffableDataSource = ComposeStatusSection.collectionViewDiffableDataSource( + for: collectionView, dependency: dependency, managedObjectContext: context.managedObjectContext, composeKind: composeKind, @@ -25,6 +25,26 @@ extension ComposeViewModel { composeStatusAttachmentTableViewCellDelegate: composeStatusAttachmentTableViewCellDelegate ) + 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 + } + self.diffableDataSource = diffableDataSource + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.repliedTo, .status, .attachment]) switch composeKind { diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 2d6dc728d..b81306eac 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -23,7 +23,8 @@ final class ComposeViewModel { let activeAuthenticationBox: CurrentValueSubject // output - var diffableDataSource: UITableViewDiffableDataSource! + //var diffableDataSource: UITableViewDiffableDataSource! + var diffableDataSource: UICollectionViewDiffableDataSource! private(set) lazy var publishStateMachine: GKStateMachine = { // exclude timeline middle fetcher state let stateMachine = GKStateMachine(states: [ diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToTootContentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToTootContentTableViewCell.swift deleted file mode 100644 index def777caf..000000000 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToTootContentTableViewCell.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ComposeRepliedToTootContentTableViewCell.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-3-11. -// - -import UIKit - -final class ComposeRepliedToTootContentTableViewCell: UITableViewCell { - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension ComposeRepliedToTootContentTableViewCell { - - private func _init() { - - } - -} - diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift index 91f6f5ba1..8493d82a0 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift @@ -76,10 +76,10 @@ extension MastodonAttachmentService.UploadState { service.error.send(error) case .finished: os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: upload attachment success", ((#file as NSString).lastPathComponent), #line, #function) - break } } receiveValue: { response in + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: upload attachment %s success: %s", ((#file as NSString).lastPathComponent), #line, #function, response.value.id, response.value.url) service.attachment.value = response.value stateMachine.enter(Finish.self) }