2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00
mastodon-ios/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift
shannon d2b4d7b6b4 Refactor: Filter and Content Warning display and management
A large amount of change primarily to the view model layer, to make reasoning about the content reveal/hide state easier.

To prevent terrible scrolling jags while allowing the cells to be shorter when hiding content, the layout changes for content display state now happen before the cell is returned by the datasource provider and the tableview is reloaded when a status’s display mode changes.
2024-11-28 13:08:01 -05:00

177 lines
6.4 KiB
Swift

//
// ComposeContentViewModel+DataSource.swift
//
//
// Created by MainasuK on 22/10/10.
//
import UIKit
import MastodonCore
import MastodonSDK
import CoreDataStack
import UIHostingConfigurationBackport
extension ComposeContentViewModel {
func setupDataSource(
tableView: UITableView
) {
tableView.dataSource = self
setupTableViewCell(tableView: tableView)
}
}
extension ComposeContentViewModel {
enum Section: CaseIterable {
case replyTo
case status
}
private func setupTableViewCell(tableView: UITableView) {
composeContentTableViewCell.contentConfiguration = UIHostingConfigurationBackport {
ComposeContentView(viewModel: self)
}
$contentCellFrame
.map { $0.height }
.removeDuplicates()
.sink { [weak self] height in
guard let self = self else { return }
guard !tableView.visibleCells.isEmpty else { return }
UIView.performWithoutAnimation {
tableView.beginUpdates()
self.composeContentTableViewCell.frame.size.height = height
tableView.endUpdates()
}
}
.store(in: &disposeBag)
if case .reply(let status) = destination {
let cell = composeReplyToTableViewCell
// bind frame publisher
cell.$framePublisher
.receive(on: DispatchQueue.main)
.assign(to: \.replyToCellFrame, on: self)
.store(in: &cell.disposeBag)
// set initial width
cell.statusView.frame.size.width = tableView.frame.width
// configure status
cell.statusView.configure(status: status, contentDisplayMode: .neverConceal)
}
}
}
// MARK: - UITableViewDataSource
extension ComposeContentViewModel: UITableViewDataSource {
public func numberOfSections(in tableView: UITableView) -> Int {
return Section.allCases.count
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Section.allCases[section] {
case .replyTo:
switch destination {
case .reply: return 1
default: return 0
}
case .status: return 1
}
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch Section.allCases[indexPath.section] {
case .replyTo:
return composeReplyToTableViewCell
case .status:
return composeContentTableViewCell
}
}
}
extension ComposeContentViewModel {
func setupCustomEmojiPickerDiffableDataSource(
collectionView: UICollectionView
) {
let diffableDataSource = CustomEmojiPickerSection.collectionViewDiffableDataSource(
collectionView: collectionView,
authenticationBox: authenticationBox,
context: context
)
self.customEmojiPickerDiffableDataSource = diffableDataSource
customEmojiViewModel?.emojis
// Don't block the main queue
.receive(on: DispatchQueue.global(qos: .userInteractive))
// Sort emojis
.compactMap({ (emojis) -> [Mastodon.Entity.Emoji]? in
guard let emojis else { return nil }
return emojis.sorted { a, b in
a.shortcode.lowercased() < b.shortcode.lowercased()
}
})
// Collate emojis into categories
.map({ (emojis) -> (noCategory: [Mastodon.Entity.Emoji], categorised: [String:[Mastodon.Entity.Emoji]]) in
let emojiMap: (noCategory: [Mastodon.Entity.Emoji], categorised: [String:[Mastodon.Entity.Emoji]]) = {
var noCategory = [Mastodon.Entity.Emoji]()
var categorised = [String:[Mastodon.Entity.Emoji]]()
for emoji in emojis where emoji.visibleInPicker {
if let category = emoji.category {
var categoryArray = categorised[category] ?? [Mastodon.Entity.Emoji]()
categoryArray.append(emoji)
categorised[category] = categoryArray
} else {
noCategory.append(emoji)
}
}
return (
noCategory,
categorised
)
}()
return emojiMap
})
// Build snapshot from emoji map
.map({ (emojiMap) -> NSDiffableDataSourceSnapshot<CustomEmojiPickerSection, CustomEmojiPickerItem> in
var snapshot = NSDiffableDataSourceSnapshot<CustomEmojiPickerSection, CustomEmojiPickerItem>()
if !emojiMap.noCategory.isEmpty {
let customEmojiSection = CustomEmojiPickerSection.uncategorized
snapshot.appendSections([customEmojiSection])
snapshot.appendItems(emojiMap.noCategory.map({ emoji in
CustomEmojiPickerItem.emoji(attribute: CustomEmojiPickerItem.CustomEmojiAttribute(emoji: emoji))
}), toSection: customEmojiSection)
}
emojiMap.categorised.keys.sorted().forEach { category in
let section = CustomEmojiPickerSection.emoji(name: category)
snapshot.appendSections([section])
if let items = emojiMap.categorised[category] {
snapshot.appendItems(items.map({ emoji in
CustomEmojiPickerItem.emoji(attribute: CustomEmojiPickerItem.CustomEmojiAttribute(emoji: emoji))
}), toSection: section)
}
}
return snapshot
})
// Apply snapshot
.receive(on: DispatchQueue.main)
.sink { [weak self, weak diffableDataSource] snapshot in
guard let _ = self else { return }
guard let diffableDataSource = diffableDataSource else { return }
diffableDataSource.apply(snapshot)
}
.store(in: &disposeBag)
}
}