2021-03-11 08:41:27 +01:00
|
|
|
//
|
|
|
|
// ComposeStatusSection.swift
|
|
|
|
// Mastodon
|
|
|
|
//
|
|
|
|
// Created by MainasuK Cirno on 2021-3-11.
|
|
|
|
//
|
|
|
|
|
2021-03-12 07:18:07 +01:00
|
|
|
import UIKit
|
|
|
|
import Combine
|
|
|
|
import CoreData
|
|
|
|
import CoreDataStack
|
2021-03-12 12:25:28 +01:00
|
|
|
import TwitterTextEditor
|
2021-03-18 08:16:35 +01:00
|
|
|
import AlamofireImage
|
2021-03-11 08:41:27 +01:00
|
|
|
|
|
|
|
enum ComposeStatusSection: Equatable, Hashable {
|
|
|
|
case repliedTo
|
|
|
|
case status
|
2021-03-17 11:09:38 +01:00
|
|
|
case attachment
|
2021-03-23 11:47:21 +01:00
|
|
|
case poll
|
2021-03-11 08:41:27 +01:00
|
|
|
}
|
2021-03-12 07:18:07 +01:00
|
|
|
|
|
|
|
extension ComposeStatusSection {
|
|
|
|
enum ComposeKind {
|
2021-03-15 06:42:46 +01:00
|
|
|
case post
|
|
|
|
case reply(repliedToStatusObjectID: NSManagedObjectID)
|
2021-03-12 07:18:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ComposeStatusSection {
|
2021-03-22 10:48:35 +01:00
|
|
|
|
|
|
|
static func collectionViewDiffableDataSource(
|
|
|
|
for collectionView: UICollectionView,
|
2021-03-12 07:18:07 +01:00
|
|
|
dependency: NeedsDependency,
|
|
|
|
managedObjectContext: NSManagedObjectContext,
|
2021-03-12 12:25:28 +01:00
|
|
|
composeKind: ComposeKind,
|
2021-03-18 08:16:35 +01:00
|
|
|
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
|
2021-03-23 11:47:21 +01:00
|
|
|
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
|
|
|
|
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
|
|
|
|
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusNewPollOptionCollectionViewCellDelegate
|
2021-03-22 10:48:35 +01:00
|
|
|
) -> UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem> {
|
|
|
|
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
|
2021-03-12 07:18:07 +01:00
|
|
|
switch item {
|
2021-03-15 06:42:46 +01:00
|
|
|
case .replyTo(let repliedToStatusObjectID):
|
2021-03-22 10:48:35 +01:00
|
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeRepliedToTootContentCollectionViewCell.self), for: indexPath) as! ComposeRepliedToTootContentCollectionViewCell
|
2021-03-12 07:18:07 +01:00
|
|
|
return cell
|
2021-03-16 04:23:19 +01:00
|
|
|
case .input(let replyToTootObjectID, let attribute):
|
2021-03-22 10:48:35 +01:00
|
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusContentCollectionViewCell.self), for: indexPath) as! ComposeStatusContentCollectionViewCell
|
2021-03-18 10:33:07 +01:00
|
|
|
cell.textEditorView.text = attribute.composeContent.value ?? ""
|
2021-03-12 07:18:07 +01:00
|
|
|
managedObjectContext.perform {
|
|
|
|
guard let replyToTootObjectID = replyToTootObjectID,
|
|
|
|
let replyTo = managedObjectContext.object(with: replyToTootObjectID) as? Toot else {
|
|
|
|
cell.statusView.headerContainerStackView.isHidden = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cell.statusView.headerContainerStackView.isHidden = false
|
|
|
|
cell.statusView.headerInfoLabel.text = "[TODO] \(replyTo.author.displayName)"
|
|
|
|
}
|
2021-03-12 08:57:58 +01:00
|
|
|
ComposeStatusSection.configure(cell: cell, attribute: attribute)
|
2021-03-12 12:25:28 +01:00
|
|
|
cell.textEditorView.textAttributesDelegate = textEditorViewTextAttributesDelegate
|
2021-03-12 07:18:07 +01:00
|
|
|
cell.composeContent
|
2021-03-18 10:33:07 +01:00
|
|
|
.removeDuplicates()
|
2021-03-12 07:18:07 +01:00
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { text in
|
2021-03-23 11:47:21 +01:00
|
|
|
// self size input cell
|
2021-03-22 10:48:35 +01:00
|
|
|
collectionView.collectionViewLayout.invalidateLayout()
|
2021-03-18 10:33:07 +01:00
|
|
|
// bind input data
|
|
|
|
attribute.composeContent.value = text
|
2021-03-12 07:18:07 +01:00
|
|
|
}
|
|
|
|
.store(in: &cell.disposeBag)
|
|
|
|
return cell
|
2021-03-17 11:09:38 +01:00
|
|
|
case .attachment(let attachmentService):
|
2021-03-22 10:48:35 +01:00
|
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell
|
2021-03-18 10:33:07 +01:00
|
|
|
cell.attachmentContainerView.descriptionTextView.text = attachmentService.description.value
|
2021-03-18 08:16:35 +01:00
|
|
|
cell.delegate = composeStatusAttachmentTableViewCellDelegate
|
|
|
|
attachmentService.imageData
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { imageData in
|
2021-03-22 10:48:35 +01:00
|
|
|
let size = cell.attachmentContainerView.previewImageView.frame.size != .zero ? cell.attachmentContainerView.previewImageView.frame.size : CGSize(width: 1, height: 1)
|
2021-03-18 08:16:35 +01:00
|
|
|
guard let imageData = imageData,
|
|
|
|
let image = UIImage(data: imageData) else {
|
|
|
|
let placeholder = UIImage.placeholder(
|
2021-03-22 10:48:35 +01:00
|
|
|
size: size,
|
2021-03-18 08:16:35 +01:00
|
|
|
color: Asset.Colors.Background.systemGroupedBackground.color
|
|
|
|
)
|
|
|
|
.af.imageRounded(
|
|
|
|
withCornerRadius: AttachmentContainerView.containerViewCornerRadius
|
|
|
|
)
|
|
|
|
cell.attachmentContainerView.previewImageView.image = placeholder
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cell.attachmentContainerView.previewImageView.image = image
|
2021-03-22 10:48:35 +01:00
|
|
|
.af.imageAspectScaled(toFill: size)
|
2021-03-18 08:16:35 +01:00
|
|
|
.af.imageRounded(withCornerRadius: AttachmentContainerView.containerViewCornerRadius)
|
|
|
|
}
|
|
|
|
.store(in: &cell.disposeBag)
|
2021-03-19 12:49:48 +01:00
|
|
|
Publishers.CombineLatest(
|
|
|
|
attachmentService.uploadStateMachineSubject.eraseToAnyPublisher(),
|
|
|
|
attachmentService.error.eraseToAnyPublisher()
|
|
|
|
)
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { uploadState, error in
|
|
|
|
cell.attachmentContainerView.emptyStateView.isHidden = error == nil
|
2021-03-22 10:48:35 +01:00
|
|
|
cell.attachmentContainerView.descriptionBackgroundView.isHidden = error != nil
|
2021-03-19 12:49:48 +01:00
|
|
|
if let _ = error {
|
2021-03-18 08:16:35 +01:00
|
|
|
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
2021-03-19 12:49:48 +01:00
|
|
|
} else {
|
|
|
|
guard let uploadState = uploadState else { return }
|
|
|
|
switch uploadState {
|
|
|
|
case is MastodonAttachmentService.UploadState.Finish,
|
|
|
|
is MastodonAttachmentService.UploadState.Fail:
|
|
|
|
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2021-03-18 08:16:35 +01:00
|
|
|
}
|
2021-03-19 12:49:48 +01:00
|
|
|
}
|
|
|
|
.store(in: &cell.disposeBag)
|
2021-03-18 10:33:07 +01:00
|
|
|
NotificationCenter.default.publisher(
|
|
|
|
for: UITextView.textDidChangeNotification,
|
|
|
|
object: cell.attachmentContainerView.descriptionTextView
|
|
|
|
)
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { notification in
|
|
|
|
guard let textField = notification.object as? UITextView else { return }
|
|
|
|
let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
attachmentService.description.value = text
|
|
|
|
}
|
|
|
|
.store(in: &cell.disposeBag)
|
2021-03-17 11:09:38 +01:00
|
|
|
return cell
|
2021-03-23 11:47:21 +01:00
|
|
|
case .poll(let attribute):
|
|
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionCollectionViewCell
|
|
|
|
cell.pollOptionView.optionTextField.text = attribute.option.value
|
|
|
|
cell.pollOption
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.assign(to: \.value, on: attribute.option)
|
|
|
|
.store(in: &cell.disposeBag)
|
|
|
|
cell.delegate = composeStatusPollOptionCollectionViewCellDelegate
|
|
|
|
return cell
|
|
|
|
case .newPoll:
|
|
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusNewPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusNewPollOptionCollectionViewCell
|
|
|
|
cell.delegate = composeStatusNewPollOptionCollectionViewCellDelegate
|
|
|
|
return cell
|
2021-03-12 07:18:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ComposeStatusSection {
|
2021-03-12 08:57:58 +01:00
|
|
|
static func configure(
|
2021-03-22 10:48:35 +01:00
|
|
|
cell: ComposeStatusContentCollectionViewCell,
|
2021-03-16 04:23:19 +01:00
|
|
|
attribute: ComposeStatusItem.ComposeStatusAttribute
|
2021-03-12 07:18:07 +01:00
|
|
|
) {
|
2021-03-12 08:57:58 +01:00
|
|
|
// set avatar
|
2021-03-12 07:18:07 +01:00
|
|
|
attribute.avatarURL
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { avatarURL in
|
|
|
|
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: avatarURL))
|
|
|
|
}
|
|
|
|
.store(in: &cell.disposeBag)
|
2021-03-12 08:57:58 +01:00
|
|
|
// set display name and username
|
2021-03-12 07:18:07 +01:00
|
|
|
Publishers.CombineLatest(
|
|
|
|
attribute.displayName.eraseToAnyPublisher(),
|
|
|
|
attribute.username.eraseToAnyPublisher()
|
|
|
|
)
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { displayName, username in
|
|
|
|
cell.statusView.nameLabel.text = displayName
|
|
|
|
cell.statusView.usernameLabel.text = username
|
|
|
|
}
|
|
|
|
.store(in: &cell.disposeBag)
|
2021-03-12 08:57:58 +01:00
|
|
|
|
|
|
|
// bind compose content
|
|
|
|
cell.composeContent
|
|
|
|
.map { $0 as String? }
|
|
|
|
.assign(to: \.value, on: attribute.composeContent)
|
|
|
|
.store(in: &cell.disposeBag)
|
2021-03-12 07:18:07 +01:00
|
|
|
}
|
|
|
|
}
|