feat: implement content warning dismiss action logic
This commit is contained in:
parent
f455faa273
commit
4d2e75f3ca
|
@ -49,7 +49,7 @@
|
|||
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; };
|
||||
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; };
|
||||
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */; };
|
||||
2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* TimelineSection.swift */; };
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; };
|
||||
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; };
|
||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; };
|
||||
2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0125C7E4F2004F19B8 /* Mention.swift */; };
|
||||
|
@ -239,7 +239,7 @@
|
|||
2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewModel.swift; sourceTree = "<group>"; };
|
||||
2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; };
|
||||
2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
2D76319E25C1521200929FB9 /* TimelineSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSection.swift; sourceTree = "<group>"; };
|
||||
2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = "<group>"; };
|
||||
2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
|
||||
2D927F0125C7E4F2004F19B8 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
||||
|
@ -514,8 +514,8 @@
|
|||
2D69CFF225CA9E2200C3A1B2 /* Protocol */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */,
|
||||
2D38F1FC25CD47D900561493 /* StatusProvider */,
|
||||
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */,
|
||||
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */,
|
||||
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
|
||||
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
|
||||
|
@ -549,7 +549,7 @@
|
|||
2D76319D25C151F600929FB9 /* Section */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D76319E25C1521200929FB9 /* TimelineSection.swift */,
|
||||
2D76319E25C1521200929FB9 /* StatusSection.swift */,
|
||||
);
|
||||
path = Section;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1382,7 +1382,7 @@
|
|||
DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */,
|
||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
|
||||
2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */,
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */,
|
||||
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */,
|
||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
||||
|
|
|
@ -12,10 +12,11 @@ import MastodonSDK
|
|||
|
||||
/// Note: update Equatable when change case
|
||||
enum Item {
|
||||
case homeTimelineIndex(objectID: NSManagedObjectID, attribute: Attribute)
|
||||
// timeline
|
||||
case homeTimelineIndex(objectID: NSManagedObjectID, attribute: StatusTimelineAttribute)
|
||||
|
||||
// normal list
|
||||
case toot(objectID: NSManagedObjectID)
|
||||
case toot(objectID: NSManagedObjectID, attribute: StatusTimelineAttribute)
|
||||
|
||||
// loader
|
||||
case homeMiddleLoader(upperTimelineIndexAnchorObjectID: NSManagedObjectID)
|
||||
|
@ -23,16 +24,31 @@ enum Item {
|
|||
case bottomLoader
|
||||
}
|
||||
|
||||
extension Item {
|
||||
class Attribute: Hashable {
|
||||
var separatorLineStyle: SeparatorLineStyle = .indent
|
||||
protocol StatusContentWarningAttribute {
|
||||
var isStatusTextSensitive: Bool { get set }
|
||||
}
|
||||
|
||||
static func == (lhs: Item.Attribute, rhs: Item.Attribute) -> Bool {
|
||||
return lhs.separatorLineStyle == rhs.separatorLineStyle
|
||||
extension Item {
|
||||
class StatusTimelineAttribute: Hashable, StatusContentWarningAttribute {
|
||||
var separatorLineStyle: SeparatorLineStyle = .indent
|
||||
var isStatusTextSensitive: Bool = false
|
||||
|
||||
public init(
|
||||
separatorLineStyle: Item.StatusTimelineAttribute.SeparatorLineStyle = .indent,
|
||||
isStatusTextSensitive: Bool
|
||||
) {
|
||||
self.separatorLineStyle = separatorLineStyle
|
||||
self.isStatusTextSensitive = isStatusTextSensitive
|
||||
}
|
||||
|
||||
static func == (lhs: Item.StatusTimelineAttribute, rhs: Item.StatusTimelineAttribute) -> Bool {
|
||||
return lhs.separatorLineStyle == rhs.separatorLineStyle &&
|
||||
lhs.isStatusTextSensitive == rhs.isStatusTextSensitive
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(separatorLineStyle)
|
||||
hasher.combine(isStatusTextSensitive)
|
||||
}
|
||||
|
||||
enum SeparatorLineStyle {
|
||||
|
@ -48,7 +64,7 @@ extension Item: Equatable {
|
|||
switch (lhs, rhs) {
|
||||
case (.homeTimelineIndex(let objectIDLeft, _), .homeTimelineIndex(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.toot(let objectIDLeft), .toot(let objectIDRight)):
|
||||
case (.toot(let objectIDLeft, _), .toot(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.bottomLoader, .bottomLoader):
|
||||
return true
|
||||
|
@ -67,7 +83,7 @@ extension Item: Hashable {
|
|||
switch self {
|
||||
case .homeTimelineIndex(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .toot(let objectID):
|
||||
case .toot(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .publicMiddleLoader(let upper):
|
||||
hasher.combine(String(describing: Item.publicMiddleLoader.self))
|
||||
|
|
|
@ -11,11 +11,11 @@ import CoreDataStack
|
|||
import os.log
|
||||
import UIKit
|
||||
|
||||
enum TimelineSection: Equatable, Hashable {
|
||||
enum StatusSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension TimelineSection {
|
||||
extension StatusSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency,
|
||||
|
@ -23,29 +23,29 @@ extension TimelineSection {
|
|||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
||||
timelinePostTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||
) -> UITableViewDiffableDataSource<TimelineSection, Item> {
|
||||
) -> UITableViewDiffableDataSource<StatusSection, Item> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [weak timelinePostTableViewCellDelegate, weak timelineMiddleLoaderTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in
|
||||
guard let timelinePostTableViewCellDelegate = timelinePostTableViewCellDelegate else { return UITableViewCell() }
|
||||
|
||||
switch item {
|
||||
case .homeTimelineIndex(objectID: let objectID, attribute: _):
|
||||
case .homeTimelineIndex(objectID: let objectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
|
||||
// configure cell
|
||||
managedObjectContext.performAndWait {
|
||||
let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
||||
TimelineSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID)
|
||||
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID, statusContentWarningAttribute: attribute)
|
||||
}
|
||||
cell.delegate = timelinePostTableViewCellDelegate
|
||||
return cell
|
||||
case .toot(let objectID):
|
||||
case .toot(let objectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
let requestUserID = activeMastodonAuthenticationBox?.userID ?? ""
|
||||
// configure cell
|
||||
managedObjectContext.performAndWait {
|
||||
let toot = managedObjectContext.object(with: objectID) as! Toot
|
||||
TimelineSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID)
|
||||
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID, statusContentWarningAttribute: attribute)
|
||||
}
|
||||
cell.delegate = timelinePostTableViewCellDelegate
|
||||
return cell
|
||||
|
@ -72,7 +72,8 @@ extension TimelineSection {
|
|||
readableLayoutFrame: CGRect?,
|
||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
||||
toot: Toot,
|
||||
requestUserID: String
|
||||
requestUserID: String,
|
||||
statusContentWarningAttribute: StatusContentWarningAttribute?
|
||||
) {
|
||||
// set header
|
||||
cell.statusView.headerContainerStackView.isHidden = toot.reblog == nil
|
||||
|
@ -94,7 +95,8 @@ extension TimelineSection {
|
|||
cell.statusView.activeTextLabel.config(content: (toot.reblog ?? toot).content)
|
||||
|
||||
// set content warning
|
||||
cell.statusView.updateContentWarningDisplay(isHidden: !(toot.reblog ?? toot).sensitive)
|
||||
let isStatusTextSensitive = statusContentWarningAttribute?.isStatusTextSensitive ?? (toot.reblog ?? toot).sensitive
|
||||
cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive)
|
||||
cell.statusView.contentWarningTitle.text = (toot.reblog ?? toot).spoilerText.flatMap { spoilerText in
|
||||
return L10n.Common.Controls.Status.contentWarning + ": \(spoilerText)"
|
||||
} ?? L10n.Common.Controls.Status.contentWarning
|
||||
|
@ -146,14 +148,14 @@ extension TimelineSection {
|
|||
// toolbar
|
||||
let replyCountTitle: String = {
|
||||
let count = (toot.reblog ?? toot).repliesCount?.intValue ?? 0
|
||||
return TimelineSection.formattedNumberTitleForActionButton(count)
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.replyButton.setTitle(replyCountTitle, for: .normal)
|
||||
|
||||
let isLike = (toot.reblog ?? toot).favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||
let favoriteCountTitle: String = {
|
||||
let count = (toot.reblog ?? toot).favouritesCount.intValue
|
||||
return TimelineSection.formattedNumberTitleForActionButton(count)
|
||||
return StatusSection.formattedNumberTitleForActionButton(count)
|
||||
}()
|
||||
cell.statusView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal)
|
||||
cell.statusView.actionToolbarContainer.isStarButtonHighlight = isLike
|
||||
|
@ -179,7 +181,7 @@ extension TimelineSection {
|
|||
|
||||
let isLike = targetToot.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||
let favoriteCount = targetToot.favouritesCount.intValue
|
||||
let favoriteCountTitle = TimelineSection.formattedNumberTitleForActionButton(favoriteCount)
|
||||
let favoriteCountTitle = StatusSection.formattedNumberTitleForActionButton(favoriteCount)
|
||||
cell.statusView.actionToolbarContainer.starButton.setTitle(favoriteCountTitle, for: .normal)
|
||||
cell.statusView.actionToolbarContainer.isStarButtonHighlight = isLike
|
||||
os_log("%{public}s[%{public}ld], %{public}s: like count label for toot %s did update: %ld", (#file as NSString).lastPathComponent, #line, #function, targetToot.id, favoriteCount)
|
||||
|
@ -188,7 +190,7 @@ extension TimelineSection {
|
|||
}
|
||||
}
|
||||
|
||||
extension TimelineSection {
|
||||
extension StatusSection {
|
||||
private static func formattedNumberTitleForActionButton(_ number: Int?) -> String {
|
||||
guard let number = number, number > 0 else { return "" }
|
||||
return String(number)
|
|
@ -20,5 +20,26 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
StatusProviderFacade.responseToStatusLikeAction(provider: self, cell: cell)
|
||||
}
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton) {
|
||||
guard let diffableDataSource = self.tableViewDiffableDataSource else { return }
|
||||
item(for: cell, indexPath: nil)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] item in
|
||||
guard let _ = self else { return }
|
||||
guard let item = item else { return }
|
||||
switch item {
|
||||
case .homeTimelineIndex(_, let attribute):
|
||||
attribute.isStatusTextSensitive = false
|
||||
case .toot(_, let attribute):
|
||||
attribute.isStatusTextSensitive = false
|
||||
default:
|
||||
return
|
||||
}
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
snapshot.reloadItems([item])
|
||||
diffableDataSource.apply(snapshot)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,4 +13,7 @@ protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewControl
|
|||
func toot() -> Future<Toot?, Never>
|
||||
func toot(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Toot?, Never>
|
||||
func toot(for cell: UICollectionViewCell) -> Future<Toot?, Never>
|
||||
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? { get }
|
||||
func item(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Item?, Never>
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import CoreDataStack
|
|||
|
||||
// MARK: - StatusProvider
|
||||
extension HomeTimelineViewController: StatusProvider {
|
||||
|
||||
|
||||
func toot() -> Future<Toot?, Never> {
|
||||
return Future { promise in promise(.success(nil)) }
|
||||
}
|
||||
|
@ -47,4 +47,25 @@ extension HomeTimelineViewController: StatusProvider {
|
|||
return Future { promise in promise(.success(nil)) }
|
||||
}
|
||||
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? {
|
||||
return viewModel.diffableDataSource
|
||||
}
|
||||
|
||||
func item(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Item?, Never> {
|
||||
return Future { promise in
|
||||
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||
assertionFailure()
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
guard let indexPath = indexPath ?? self.tableView.indexPath(for: cell),
|
||||
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
|
||||
promise(.success(item))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import GameplayKit
|
|||
import MastodonSDK
|
||||
import AlamofireImage
|
||||
|
||||
final class HomeTimelineViewController: UIViewController, NeedsDependency,StatusTableViewCellDelegate {
|
||||
final class HomeTimelineViewController: UIViewController, NeedsDependency {
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
@ -313,3 +313,6 @@ extension HomeTimelineViewController: ScrollViewContainer {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - StatusTableViewCellDelegate
|
||||
extension HomeTimelineViewController: StatusTableViewCellDelegate { }
|
||||
|
|
|
@ -23,7 +23,7 @@ extension HomeTimelineViewModel {
|
|||
.share()
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
diffableDataSource = TimelineSection.tableViewDiffableDataSource(
|
||||
diffableDataSource = StatusSection.tableViewDiffableDataSource(
|
||||
for: tableView,
|
||||
dependency: dependency,
|
||||
managedObjectContext: fetchedResultsController.managedObjectContext,
|
||||
|
@ -73,7 +73,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
|
||||
// that's will be the most fastest fetch because of upstream just update and no modify needs consider
|
||||
|
||||
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.Attribute] = [:]
|
||||
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusTimelineAttribute] = [:]
|
||||
|
||||
for item in oldSnapshot.itemIdentifiers {
|
||||
guard case let .homeTimelineIndex(objectID, attribute) = item else { continue }
|
||||
|
@ -83,7 +83,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
var newTimelineItems: [Item] = []
|
||||
|
||||
for (i, timelineIndex) in timelineIndexes.enumerated() {
|
||||
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.Attribute()
|
||||
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: timelineIndex.toot.sensitive)
|
||||
|
||||
// append new item into snapshot
|
||||
newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute))
|
||||
|
@ -103,7 +103,7 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
}
|
||||
} // end for
|
||||
|
||||
var newSnapshot = NSDiffableDataSourceSnapshot<TimelineSection, Item>()
|
||||
var newSnapshot = NSDiffableDataSourceSnapshot<StatusSection, Item>()
|
||||
newSnapshot.appendSections([.main])
|
||||
newSnapshot.appendItems(newTimelineItems, toSection: .main)
|
||||
|
||||
|
@ -142,8 +142,8 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
private func calculateReloadSnapshotDifference<T: Hashable>(
|
||||
navigationBar: UINavigationBar,
|
||||
tableView: UITableView,
|
||||
oldSnapshot: NSDiffableDataSourceSnapshot<TimelineSection, T>,
|
||||
newSnapshot: NSDiffableDataSourceSnapshot<TimelineSection, T>
|
||||
oldSnapshot: NSDiffableDataSourceSnapshot<StatusSection, T>,
|
||||
newSnapshot: NSDiffableDataSourceSnapshot<StatusSection, T>
|
||||
) -> Difference<T>? {
|
||||
guard oldSnapshot.numberOfItems != 0 else { return nil }
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ final class HomeTimelineViewModel: NSObject {
|
|||
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<LoadOldestState?, Never>(nil)
|
||||
// middle loader
|
||||
let loadMiddleSateMachineList = CurrentValueSubject<[NSManagedObjectID: GKStateMachine], Never>([:]) // TimelineIndex.objectID : middle loading state machine
|
||||
var diffableDataSource: UITableViewDiffableDataSource<TimelineSection, Item>?
|
||||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
|
||||
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ extension PublicTimelineViewController: StatusProvider {
|
|||
}
|
||||
|
||||
switch item {
|
||||
case .toot(let objectID):
|
||||
case .toot(let objectID, _):
|
||||
let managedObjectContext = self.viewModel.fetchedResultsController.managedObjectContext
|
||||
managedObjectContext.perform {
|
||||
let toot = managedObjectContext.object(with: objectID) as? Toot
|
||||
|
@ -48,4 +48,25 @@ extension PublicTimelineViewController: StatusProvider {
|
|||
return Future { promise in promise(.success(nil)) }
|
||||
}
|
||||
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? {
|
||||
return viewModel.diffableDataSource
|
||||
}
|
||||
|
||||
func item(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Item?, Never> {
|
||||
return Future { promise in
|
||||
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||
assertionFailure()
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
guard let indexPath = indexPath ?? self.tableView.indexPath(for: cell),
|
||||
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
|
||||
promise(.success(item))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ extension PublicTimelineViewModel {
|
|||
.share()
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
diffableDataSource = TimelineSection.tableViewDiffableDataSource(
|
||||
diffableDataSource = StatusSection.tableViewDiffableDataSource(
|
||||
for: tableView,
|
||||
dependency: dependency,
|
||||
managedObjectContext: fetchedResultsController.managedObjectContext,
|
||||
|
@ -50,11 +50,18 @@ extension PublicTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
return indexes.firstIndex(of: toot.id).map { index in (index, toot) }
|
||||
}
|
||||
.sorted { $0.0 < $1.0 }
|
||||
var oldSnapshotAttributeDict: [NSManagedObjectID: Item.StatusTimelineAttribute] = [:]
|
||||
for item in self.items.value {
|
||||
guard case let .toot(objectID, attribute) = item else { continue }
|
||||
oldSnapshotAttributeDict[objectID] = attribute
|
||||
}
|
||||
|
||||
var items = [Item]()
|
||||
for tuple in indexTootTuples {
|
||||
items.append(Item.toot(objectID: tuple.1.objectID))
|
||||
if tootIDsWhichHasGap.contains(tuple.1.id) {
|
||||
items.append(Item.publicMiddleLoader(tootID: tuple.1.id))
|
||||
for (_, toot) in indexTootTuples {
|
||||
let attribute = oldSnapshotAttributeDict[toot.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: toot.sensitive)
|
||||
items.append(Item.toot(objectID: toot.objectID, attribute: attribute))
|
||||
if tootIDsWhichHasGap.contains(toot.id) {
|
||||
items.append(Item.publicMiddleLoader(tootID: toot.id))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class PublicTimelineViewModel: NSObject {
|
|||
//
|
||||
var tootIDsWhichHasGap = [String]()
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<TimelineSection, Item>?
|
||||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
|
||||
|
||||
lazy var stateMachine: GKStateMachine = {
|
||||
let stateMachine = GKStateMachine(states: [
|
||||
|
@ -82,7 +82,7 @@ class PublicTimelineViewModel: NSObject {
|
|||
let oldSnapshot = diffableDataSource.snapshot()
|
||||
os_log("%{public}s[%{public}ld], %{public}s: items did change", (#file as NSString).lastPathComponent, #line, #function)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, Item>()
|
||||
var snapshot = NSDiffableDataSourceSnapshot<StatusSection, Item>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(items)
|
||||
if let currentState = self.stateMachine.currentState {
|
||||
|
@ -140,8 +140,8 @@ class PublicTimelineViewModel: NSObject {
|
|||
private func calculateReloadSnapshotDifference<T: Hashable>(
|
||||
navigationBar: UINavigationBar,
|
||||
tableView: UITableView,
|
||||
oldSnapshot: NSDiffableDataSourceSnapshot<TimelineSection, T>,
|
||||
newSnapshot: NSDiffableDataSourceSnapshot<TimelineSection, T>
|
||||
oldSnapshot: NSDiffableDataSourceSnapshot<StatusSection, T>,
|
||||
newSnapshot: NSDiffableDataSourceSnapshot<StatusSection, T>
|
||||
) -> Difference<T>? {
|
||||
guard oldSnapshot.numberOfItems != 0 else { return nil }
|
||||
|
||||
|
|
|
@ -5,17 +5,24 @@
|
|||
// Created by sxiaojian on 2021/1/28.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import AVKit
|
||||
import ActiveLabel
|
||||
import AlamofireImage
|
||||
|
||||
protocol StatusViewDelegate: class {
|
||||
func statusView(_ statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
}
|
||||
|
||||
final class StatusView: UIView {
|
||||
|
||||
static let avatarImageSize = CGSize(width: 42, height: 42)
|
||||
static let avatarImageCornerRadius: CGFloat = 4
|
||||
static let contentWarningBlurRadius: CGFloat = 12
|
||||
|
||||
weak var delegate: StatusViewDelegate?
|
||||
|
||||
let headerContainerStackView = UIStackView()
|
||||
|
||||
let headerIconLabel: UILabel = {
|
||||
|
@ -231,7 +238,7 @@ extension StatusView {
|
|||
statusContentWarningContainerStackView.distribution = .fill
|
||||
statusContentWarningContainerStackView.alignment = .center
|
||||
statusTextContainerView.addSubview(statusContentWarningContainerStackView)
|
||||
statusContentWarningContainerStackViewBottomLayoutConstraint = statusTextContainerView.bottomAnchor.constraint(greaterThanOrEqualTo: statusContentWarningContainerStackView.bottomAnchor, constant: 8)
|
||||
statusContentWarningContainerStackViewBottomLayoutConstraint = statusTextContainerView.bottomAnchor.constraint(greaterThanOrEqualTo: statusContentWarningContainerStackView.bottomAnchor)
|
||||
NSLayoutConstraint.activate([
|
||||
statusContentWarningContainerStackView.topAnchor.constraint(equalTo: statusTextContainerView.topAnchor),
|
||||
statusContentWarningContainerStackView.leadingAnchor.constraint(equalTo: statusTextContainerView.leadingAnchor),
|
||||
|
@ -252,6 +259,8 @@ extension StatusView {
|
|||
contentWarningBlurContentImageView.isHidden = true
|
||||
statusContentWarningContainerStackView.isHidden = true
|
||||
statusContentWarningContainerStackViewBottomLayoutConstraint.isActive = false
|
||||
|
||||
contentWarningActionButton.addTarget(self, action: #selector(StatusView.contentWarningActionButtonPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -284,6 +293,13 @@ extension StatusView {
|
|||
|
||||
}
|
||||
|
||||
extension StatusView {
|
||||
@objc private func contentWarningActionButtonPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.statusView(self, contentWarningActionButtonPressed: sender)
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusView: AvatarConfigurableView {
|
||||
static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize }
|
||||
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
||||
|
|
|
@ -13,6 +13,7 @@ import Combine
|
|||
|
||||
protocol StatusTableViewCellDelegate: class {
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
}
|
||||
|
||||
final class StatusTableViewCell: UITableViewCell {
|
||||
|
@ -69,11 +70,20 @@ extension StatusTableViewCell {
|
|||
bottomPaddingView.heightAnchor.constraint(equalToConstant: 10).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
statusView.delegate = self
|
||||
statusView.actionToolbarContainer.delegate = self
|
||||
bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - StatusViewDelegate
|
||||
extension StatusTableViewCell: StatusViewDelegate {
|
||||
func statusView(_ statusView: StatusView, contentWarningActionButtonPressed button: UIButton) {
|
||||
delegate?.statusTableViewCell(self, statusView: statusView, contentWarningActionButtonPressed: button)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ActionToolbarContainerDelegate
|
||||
extension StatusTableViewCell: ActionToolbarContainerDelegate {
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) {
|
||||
|
|
Loading…
Reference in New Issue