Kurdtvs-Live-Kurdish-TV-Kur.../Mastodon/Scene/Compose/ComposeViewModel+DataSource...

319 lines
15 KiB
Swift
Raw Normal View History

2021-03-11 08:41:27 +01:00
//
// ComposeViewModel+Diffable.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-3-11.
//
2021-06-29 10:41:58 +02:00
import os.log
2021-03-11 08:41:27 +01:00
import UIKit
2021-03-25 08:56:17 +01:00
import Combine
import CoreDataStack
2021-03-25 08:56:17 +01:00
import MastodonSDK
import MastodonMeta
2021-07-22 13:34:24 +02:00
import MetaTextKit
2021-03-11 08:41:27 +01:00
extension ComposeViewModel {
2021-07-19 11:12:45 +02:00
func setupDataSource(
tableView: UITableView,
metaTextDelegate: MetaTextDelegate,
metaTextViewDelegate: UITextViewDelegate,
2021-03-25 08:56:17 +01:00
customEmojiPickerInputViewModel: CustomEmojiPickerInputViewModel,
2021-06-29 10:41:58 +02:00
composeStatusAttachmentCollectionViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
2021-03-23 11:47:21 +01:00
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
2021-06-29 10:41:58 +02:00
composeStatusPollOptionAppendEntryCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate,
composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate
) {
2021-06-29 10:41:58 +02:00
// content
composeStatusContentTableViewCell.metaText.delegate = metaTextDelegate
composeStatusContentTableViewCell.metaText.textView.delegate = metaTextViewDelegate
// attachment
composeStatusAttachmentTableViewCell.composeStatusAttachmentCollectionViewCellDelegate = composeStatusAttachmentCollectionViewCellDelegate
// poll
composeStatusPollTableViewCell.delegate = self
composeStatusPollTableViewCell.customEmojiPickerInputViewModel = customEmojiPickerInputViewModel
composeStatusPollTableViewCell.composeStatusPollOptionCollectionViewCellDelegate = composeStatusPollOptionCollectionViewCellDelegate
composeStatusPollTableViewCell.composeStatusPollOptionAppendEntryCollectionViewCellDelegate = composeStatusPollOptionAppendEntryCollectionViewCellDelegate
composeStatusPollTableViewCell.composeStatusPollExpiresOptionCollectionViewCellDelegate = composeStatusPollExpiresOptionCollectionViewCellDelegate
// setup data source
tableView.dataSource = self
2021-09-29 10:27:35 +02:00
composeStatusAttachmentTableViewCell.collectionViewHeightDidUpdate
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let _ = self else { return }
tableView.beginUpdates()
tableView.endUpdates()
}
.store(in: &disposeBag)
2021-06-29 10:41:58 +02:00
attachmentServices
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] attachmentServices in
guard let self = self else { return }
guard self.isViewAppeared else { return }
let cell = self.composeStatusAttachmentTableViewCell
guard let dataSource = cell.dataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusAttachmentSection, ComposeStatusAttachmentItem>()
snapshot.appendSections([.main])
let items = attachmentServices.map { ComposeStatusAttachmentItem.attachment(attachmentService: $0) }
snapshot.appendItems(items, toSection: .main)
2021-09-29 10:27:35 +02:00
if #available(iOS 15.0, *) {
dataSource.applySnapshotUsingReloadData(snapshot)
} else {
dataSource.apply(snapshot, animatingDifferences: false)
2021-06-29 10:41:58 +02:00
}
2021-03-24 08:46:40 +01:00
}
2021-06-29 10:41:58 +02:00
.store(in: &disposeBag)
Publishers.CombineLatest(
isPollComposing,
pollOptionAttributes
)
.receive(on: DispatchQueue.main)
.sink { [weak self] isPollComposing, pollOptionAttributes in
2021-03-24 08:46:40 +01:00
guard let self = self else { return }
2021-06-29 10:41:58 +02:00
guard self.isViewAppeared else { return }
let cell = self.composeStatusPollTableViewCell
guard let dataSource = cell.dataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusPollSection, ComposeStatusPollItem>()
snapshot.appendSections([.main])
var items: [ComposeStatusPollItem] = []
if isPollComposing {
for attribute in pollOptionAttributes {
items.append(.pollOption(attribute: attribute))
}
if pollOptionAttributes.count < self.maxPollOptions {
2021-06-29 10:41:58 +02:00
items.append(.pollOptionAppendEntry)
}
items.append(.pollExpiresOption(attribute: self.pollExpiresOptionAttribute))
}
snapshot.appendItems(items, toSection: .main)
tableView.performBatchUpdates {
2021-09-29 10:27:35 +02:00
if #available(iOS 15.0, *) {
dataSource.apply(snapshot, animatingDifferences: false)
} else {
dataSource.apply(snapshot, animatingDifferences: true)
}
2021-03-24 08:46:40 +01:00
}
2021-03-11 08:41:27 +01:00
}
2021-06-29 10:41:58 +02:00
.store(in: &disposeBag)
2021-03-11 08:41:27 +01:00
}
2021-03-25 08:56:17 +01:00
func setupCustomEmojiPickerDiffableDataSource(
for collectionView: UICollectionView,
dependency: NeedsDependency
) {
let diffableDataSource = CustomEmojiPickerSection.collectionViewDiffableDataSource(
for: collectionView,
dependency: dependency
)
self.customEmojiPickerDiffableDataSource = diffableDataSource
customEmojiViewModel
.sink { [weak self, weak diffableDataSource] customEmojiViewModel in
guard let self = self else { return }
guard let diffableDataSource = diffableDataSource else { return }
guard let customEmojiViewModel = customEmojiViewModel else {
self.customEmojiViewModelSubscription = nil
let snapshot = NSDiffableDataSourceSnapshot<CustomEmojiPickerSection, CustomEmojiPickerItem>()
diffableDataSource.apply(snapshot)
return
}
self.customEmojiViewModelSubscription = customEmojiViewModel.emojis
.receive(on: DispatchQueue.main)
.sink { [weak self, weak diffableDataSource] emojis in
guard let _ = self else { return }
guard let diffableDataSource = diffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<CustomEmojiPickerSection, CustomEmojiPickerItem>()
let customEmojiSection = CustomEmojiPickerSection.emoji(name: customEmojiViewModel.domain.uppercased())
snapshot.appendSections([customEmojiSection])
let items: [CustomEmojiPickerItem] = {
var items = [CustomEmojiPickerItem]()
for emoji in emojis where emoji.visibleInPicker {
let attribute = CustomEmojiPickerItem.CustomEmojiAttribute(emoji: emoji)
let item = CustomEmojiPickerItem.emoji(attribute: attribute)
items.append(item)
}
return items
}()
snapshot.appendItems(items, toSection: customEmojiSection)
diffableDataSource.apply(snapshot)
}
}
.store(in: &disposeBag)
}
2021-03-11 08:41:27 +01:00
}
2021-06-29 10:41:58 +02:00
// MARK: - UITableViewDataSource
extension ComposeViewModel: UITableViewDataSource {
enum Section: CaseIterable {
case repliedTo
case status
case attachment
case poll
}
func numberOfSections(in tableView: UITableView) -> Int {
return Section.allCases.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Section.allCases[section] {
case .repliedTo:
switch composeKind {
case .reply: return 1
default: return 0
}
case .status: return 1
case .attachment:
return 1
case .poll:
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch Section.allCases[indexPath.section] {
case .repliedTo:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToStatusContentTableViewCell.self), for: indexPath) as! ComposeRepliedToStatusContentTableViewCell
guard case let .reply(statusObjectID) = composeKind else { return cell }
cell.framePublisher
.receive(on: DispatchQueue.main)
.assign(to: \.value, on: self.repliedToCellFrame)
.store(in: &cell.disposeBag)
2021-06-29 10:41:58 +02:00
let managedObjectContext = context.managedObjectContext
managedObjectContext.performAndWait {
guard let replyTo = managedObjectContext.object(with: statusObjectID) as? Status else {
return
}
let status = replyTo.reblog ?? replyTo
// set avatar
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
2021-07-23 13:10:27 +02:00
// set name, username
do {
let mastodonContent = MastodonContent(content: status.author.displayNameWithFallback, emojis: status.author.emojiMeta)
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
cell.statusView.nameLabel.configure(content: metaContent)
} catch {
let metaContent = PlaintextMetaContent(string: status.author.displayNameWithFallback)
cell.statusView.nameLabel.configure(content: metaContent)
}
cell.statusView.usernameLabel.text = "@" + status.author.acct
2021-06-29 10:41:58 +02:00
// set text
let content = MastodonContent(content: status.content, emojis: status.emojiMeta)
do {
let metaContent = try MastodonMetaContent.convert(document: content)
cell.statusView.contentMetaText.configure(content: metaContent)
} catch {
cell.statusView.contentMetaText.textView.text = " "
assertionFailure()
}
// set date
2021-08-09 09:14:08 +02:00
cell.statusView.dateLabel.text = status.createdAt.localizedSlowedTimeAgoSinceNow
2021-06-29 10:41:58 +02:00
}
return cell
case .status:
let cell = self.composeStatusContentTableViewCell
// configure header
let managedObjectContext = context.managedObjectContext
managedObjectContext.performAndWait {
guard case let .reply(replyToStatusObjectID) = self.composeKind,
let replyTo = managedObjectContext.object(with: replyToStatusObjectID) as? Status else {
cell.statusView.headerContainerView.isHidden = true
return
}
cell.statusView.headerContainerView.isHidden = false
cell.statusView.headerIconLabel.configure(attributedString: StatusView.iconAttributedString(image: StatusView.replyIconImage))
let headerText: String = {
let author = replyTo.author
let name = author.displayName.isEmpty ? author.username : author.displayName
return L10n.Scene.Compose.replyingToUser(name)
}()
2021-07-23 13:10:27 +02:00
do {
let mastodonContent = MastodonContent(content: headerText, emojis: replyTo.author.emojiMeta)
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
cell.statusView.headerInfoLabel.configure(content: metaContent)
} catch {
let metaContent = PlaintextMetaContent(string: headerText)
cell.statusView.headerInfoLabel.configure(content: metaContent)
}
2021-06-29 10:41:58 +02:00
}
// configure author
ComposeStatusSection.configureStatusContent(cell: cell, attribute: composeStatusAttribute)
// configure content. bind text in UITextViewDelegate
if let composeContent = composeStatusAttribute.composeContent.value {
cell.metaText.textView.text = composeContent
}
// configure content warning
cell.statusContentWarningEditorView.textView.text = composeStatusAttribute.contentWarningContent.value
2021-06-29 10:41:58 +02:00
// bind content warning
composeStatusAttribute.isContentWarningComposing
.receive(on: DispatchQueue.main)
.sink { [weak cell, weak tableView] isContentWarningComposing in
guard let cell = cell else { return }
guard let tableView = tableView else { return }
// self size input cell
cell.statusContentWarningEditorView.isHidden = !isContentWarningComposing
cell.statusContentWarningEditorView.alpha = 0
UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) {
cell.statusContentWarningEditorView.alpha = 1
2021-07-06 10:38:14 +02:00
tableView.beginUpdates()
tableView.endUpdates()
2021-06-29 10:41:58 +02:00
} completion: { _ in
// do nothing
}
}
.store(in: &cell.disposeBag)
cell.contentWarningContent
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak tableView, weak self] text in
guard let self = self else { return }
// bind input data
self.composeStatusAttribute.contentWarningContent.value = text
2021-06-29 10:41:58 +02:00
// self size input cell
guard let tableView = tableView else { return }
2021-06-29 10:41:58 +02:00
UIView.performWithoutAnimation {
tableView.beginUpdates()
tableView.endUpdates()
}
}
.store(in: &cell.disposeBag)
// configure custom emoji picker
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.metaText.textView, disposeBag: &cell.disposeBag)
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.statusContentWarningEditorView.textView, disposeBag: &cell.disposeBag)
return cell
case .attachment:
let cell = self.composeStatusAttachmentTableViewCell
return cell
case .poll:
let cell = self.composeStatusPollTableViewCell
return cell
}
}
}
// MARK: - ComposeStatusPollTableViewCellDelegate
extension ComposeViewModel: ComposeStatusPollTableViewCellDelegate {
func composeStatusPollTableViewCell(_ cell: ComposeStatusPollTableViewCell, pollOptionAttributesDidReorder options: [ComposeStatusPollItem.PollOptionAttribute]) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
self.pollOptionAttributes.value = options
}
}