feat: add image attachments reorder support for status compose scene

This commit is contained in:
CMK 2021-03-22 17:48:35 +08:00
parent 36b42ba3e7
commit b296b21ef0
10 changed files with 190 additions and 128 deletions

View File

@ -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 = "<group>"; };
DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = "<group>"; };
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = "<group>"; };
DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = "<group>"; };
DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToTootContentTableViewCell.swift; sourceTree = "<group>"; };
DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentCollectionViewCell.swift; sourceTree = "<group>"; };
DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToTootContentCollectionViewCell.swift; sourceTree = "<group>"; };
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -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 = "<group>";
};
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 = "<group>";
};
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;
};

View File

@ -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<ComposeStatusSection, ComposeStatusItem> {
UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>(tableView: tableView) { [weak textEditorViewTextAttributesDelegate, weak composeStatusAttachmentTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate
) -> UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem> {
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

View File

@ -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() {
}
}

View File

@ -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<AnyCancellable>()
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)
}
}

View File

@ -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<AnyCancellable>()
@ -27,8 +27,8 @@ final class ComposeStatusContentTableViewCell: UITableViewCell {
let composeContent = PassthroughSubject<String, Never>()
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)

View File

@ -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 }

View File

@ -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<ComposeStatusSection, ComposeStatusItem>()
snapshot.appendSections([.repliedTo, .status, .attachment])
switch composeKind {

View File

@ -23,7 +23,8 @@ final class ComposeViewModel {
let activeAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
// output
var diffableDataSource: UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>!
//var diffableDataSource: UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>!
var diffableDataSource: UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>!
private(set) lazy var publishStateMachine: GKStateMachine = {
// exclude timeline middle fetcher state
let stateMachine = GKStateMachine(states: [

View File

@ -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() {
}
}

View File

@ -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)
}