Filter statuses on profiles

This commit is contained in:
Shadowfacts 2022-12-03 23:11:02 -05:00
parent 8ad48784d9
commit e1886509d3
2 changed files with 38 additions and 17 deletions

View File

@ -14,6 +14,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
weak var owner: ProfileViewController? weak var owner: ProfileViewController?
let mastodonController: MastodonController let mastodonController: MastodonController
let filterer: Filterer
private(set) var accountID: String! private(set) var accountID: String!
let kind: Kind let kind: Kind
var initialHeaderMode: HeaderMode? var initialHeaderMode: HeaderMode?
@ -38,6 +39,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
self.kind = kind self.kind = kind
self.owner = owner self.owner = owner
self.mastodonController = owner.mastodonController self.mastodonController = owner.mastodonController
self.filterer = Filterer(mastodonController: mastodonController, context: .account)
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@ -67,7 +69,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
config.topSeparatorVisibility = .hidden config.topSeparatorVisibility = .hidden
config.bottomSeparatorVisibility = .hidden config.bottomSeparatorVisibility = .hidden
} }
if case .status(_, _, _) = item { if case .status(_, _, _, _) = item {
config.topSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets config.topSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
config.bottomSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets config.bottomSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
} }
@ -110,10 +112,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> { private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
collectionView.register(ProfileHeaderCollectionViewCell.self, forCellWithReuseIdentifier: "headerCell") collectionView.register(ProfileHeaderCollectionViewCell.self, forCellWithReuseIdentifier: "headerCell")
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState, Bool)> { [unowned self] cell, indexPath, item in let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState, Filterer.Result, Bool)> { [unowned self] cell, indexPath, item in
cell.delegate = self cell.delegate = self
cell.showPinned = item.2 cell.showPinned = item.3
cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow) cell.updateUI(statusID: item.0, state: item.1, filterResult: item.2)
} }
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
switch itemIdentifier { switch itemIdentifier {
@ -139,8 +141,14 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
self.headerCell = cell self.headerCell = cell
return cell return cell
} }
case .status(id: let id, state: let state, pinned: let pinned): case .status(id: let id, collapseState: let collapseState, filterState: let filterState, pinned: let pinned):
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, state, pinned)) let status = {
let status = self.mastodonController.persistentContainer.status(for: id)!
// if the status is a reblog of another one, filter based on that one
return status.reblog ?? status
}
let result = filterState.resolveFor(status: status, resolver: filterer)
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, collapseState, result, pinned))
case .loadingIndicator: case .loadingIndicator:
return loadingIndicatorCell(for: indexPath) return loadingIndicatorCell(for: indexPath)
case .confirmLoadMore: case .confirmLoadMore:
@ -225,7 +233,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
let existingPinned = snapshot.itemIdentifiers(inSection: .pinned) let existingPinned = snapshot.itemIdentifiers(inSection: .pinned)
let items = statuses.map { let items = statuses.map {
let item = Item.status(id: $0.id, state: .unknown, pinned: true) let item = Item.status(id: $0.id, collapseState: .unknown, filterState: .unknown, pinned: true)
// try to keep the existing status state // try to keep the existing status state
if let existing = existingPinned.first(where: { $0 == item }) { if let existing = existingPinned.first(where: { $0 == item }) {
return existing return existing
@ -288,19 +296,19 @@ extension ProfileStatusesViewController {
typealias TimelineItem = String typealias TimelineItem = String
case header(String) case header(String)
case status(id: String, state: CollapseState, pinned: Bool) case status(id: String, collapseState: CollapseState, filterState: FilterState, pinned: Bool)
case loadingIndicator case loadingIndicator
case confirmLoadMore case confirmLoadMore
static func fromTimelineItem(_ item: String) -> Self { static func fromTimelineItem(_ item: String) -> Self {
return .status(id: item, state: .unknown, pinned: false) return .status(id: item, collapseState: .unknown, filterState: .unknown, pinned: false)
} }
static func ==(lhs: Item, rhs: Item) -> Bool { static func ==(lhs: Item, rhs: Item) -> Bool {
switch (lhs, rhs) { switch (lhs, rhs) {
case let (.header(a), .header(b)): case let (.header(a), .header(b)):
return a == b return a == b
case let (.status(id: a, state: _, pinned: ap), .status(id: b, state: _, pinned: bp)): case let (.status(id: a, _, _, pinned: ap), .status(id: b, _, _, pinned: bp)):
return a == b && ap == bp return a == b && ap == bp
case (.loadingIndicator, .loadingIndicator): case (.loadingIndicator, .loadingIndicator):
return true return true
@ -316,7 +324,7 @@ extension ProfileStatusesViewController {
case .header(let id): case .header(let id):
hasher.combine(0) hasher.combine(0)
hasher.combine(id) hasher.combine(id)
case .status(id: let id, state: _, pinned: let pinned): case .status(id: let id, _, _, pinned: let pinned):
hasher.combine(1) hasher.combine(1)
hasher.combine(id) hasher.combine(id)
hasher.combine(pinned) hasher.combine(pinned)
@ -338,7 +346,7 @@ extension ProfileStatusesViewController {
var isSelectable: Bool { var isSelectable: Bool {
switch self { switch self {
case .status(id: _, state: _, pinned: _): case .status(_, _, _, _):
return true return true
default: default:
return false return false
@ -445,11 +453,20 @@ extension ProfileStatusesViewController: UICollectionViewDelegate {
} }
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard case .status(id: let id, state: let state, pinned: _) = dataSource.itemIdentifier(for: indexPath) else { guard let item = dataSource.itemIdentifier(for: indexPath),
case .status(id: let id, collapseState: let collapseState, filterState: let filterState, pinned: _) = item else {
return return
} }
let status = mastodonController.persistentContainer.status(for: id)! if filterState.isWarning {
selected(status: status.reblog?.id ?? id, state: state.copy()) filterState.setResult(.allow)
collectionView.deselectItem(at: indexPath, animated: true)
var snapshot = dataSource.snapshot()
snapshot.reconfigureItems([item])
dataSource.apply(snapshot, animatingDifferences: true)
} else {
let status = mastodonController.persistentContainer.status(for: id)!
selected(status: status.reblog?.id ?? id, state: collapseState.copy())
}
} }
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {

View File

@ -144,7 +144,11 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
switch itemIdentifier { switch itemIdentifier {
case .status(id: let id, collapseState: let state, filterState: let filterState): case .status(id: let id, collapseState: let state, filterState: let filterState):
let status = { self.mastodonController.persistentContainer.status(for: id)! } let status = {
let status = self.mastodonController.persistentContainer.status(for: id)!
// if the status is a reblog of another one, filter based on that one
return status.reblog ?? status
}
let result = filterState.resolveFor(status: status, resolver: filterer) let result = filterState.resolveFor(status: status, resolver: filterer)
switch result { switch result {
case .allow, .warn(_): case .allow, .warn(_):
@ -785,7 +789,6 @@ extension TimelineViewController: UICollectionViewDelegate {
case .publicTimelineDescription: case .publicTimelineDescription:
removeTimelineDescriptionCell() removeTimelineDescriptionCell()
case .status(id: let id, collapseState: let collapseState, filterState: let filterState): case .status(id: let id, collapseState: let collapseState, filterState: let filterState):
let status = mastodonController.persistentContainer.status(for: id)!
if filterState.isWarning { if filterState.isWarning {
filterState.setResult(.allow) filterState.setResult(.allow)
collectionView.deselectItem(at: indexPath, animated: true) collectionView.deselectItem(at: indexPath, animated: true)
@ -793,6 +796,7 @@ extension TimelineViewController: UICollectionViewDelegate {
snapshot.reconfigureItems([item]) snapshot.reconfigureItems([item])
dataSource.apply(snapshot, animatingDifferences: true) dataSource.apply(snapshot, animatingDifferences: true)
} else { } else {
let status = mastodonController.persistentContainer.status(for: id)!
// if the status in the timeline is a reblog, show the status that it is a reblog of // if the status in the timeline is a reblog, show the status that it is a reblog of
selected(status: status.reblog?.id ?? id, state: collapseState.copy()) selected(status: status.reblog?.id ?? id, state: collapseState.copy())
} }