forked from shadowfacts/Tusker
Initial notifications collection view implementatioan
This commit is contained in:
parent
25e82d828f
commit
574d1f9134
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct NotificationGroup: Identifiable, Hashable {
|
public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
||||||
public private(set) var notifications: [Notification]
|
public private(set) var notifications: [Notification]
|
||||||
public let id: String
|
public let id: String
|
||||||
public let kind: Notification.Kind
|
public let kind: Notification.Kind
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
|
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
|
||||||
D646DCAC2A06C8840059ECEB /* ProfileFieldValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCAB2A06C8840059ECEB /* ProfileFieldValueView.swift */; };
|
D646DCAC2A06C8840059ECEB /* ProfileFieldValueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCAB2A06C8840059ECEB /* ProfileFieldValueView.swift */; };
|
||||||
D646DCAE2A06C8C90059ECEB /* ProfileFieldVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCAD2A06C8C90059ECEB /* ProfileFieldVerificationView.swift */; };
|
D646DCAE2A06C8C90059ECEB /* ProfileFieldVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCAD2A06C8C90059ECEB /* ProfileFieldVerificationView.swift */; };
|
||||||
|
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646DCD12A06F2510059ECEB /* NotificationsCollectionViewController.swift */; };
|
||||||
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */; };
|
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */; };
|
||||||
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9026C80DC600FC57FB /* ToastView.swift */; };
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9026C80DC600FC57FB /* ToastView.swift */; };
|
||||||
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; };
|
D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; };
|
||||||
|
@ -515,6 +516,7 @@
|
||||||
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
||||||
D646DCAB2A06C8840059ECEB /* ProfileFieldValueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldValueView.swift; sourceTree = "<group>"; };
|
D646DCAB2A06C8840059ECEB /* ProfileFieldValueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldValueView.swift; sourceTree = "<group>"; };
|
||||||
D646DCAD2A06C8C90059ECEB /* ProfileFieldVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldVerificationView.swift; sourceTree = "<group>"; };
|
D646DCAD2A06C8C90059ECEB /* ProfileFieldVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldVerificationView.swift; sourceTree = "<group>"; };
|
||||||
|
D646DCD12A06F2510059ECEB /* NotificationsCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPreviewViewController.swift; sourceTree = "<group>"; };
|
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPreviewViewController.swift; sourceTree = "<group>"; };
|
||||||
D64AAE9026C80DC600FC57FB /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
D64AAE9026C80DC600FC57FB /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = "<group>"; };
|
||||||
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = "<group>"; };
|
D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1069,6 +1071,7 @@
|
||||||
children = (
|
children = (
|
||||||
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */,
|
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */,
|
||||||
D65234E02561AA68001AF9CF /* NotificationsTableViewController.swift */,
|
D65234E02561AA68001AF9CF /* NotificationsTableViewController.swift */,
|
||||||
|
D646DCD12A06F2510059ECEB /* NotificationsCollectionViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Notifications;
|
path = Notifications;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2030,6 +2033,7 @@
|
||||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
||||||
D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */,
|
D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */,
|
||||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||||
|
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */,
|
||||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameLabel.swift in Sources */,
|
||||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
//
|
||||||
|
// NotificationsCollectionViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/6/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
import Combine
|
||||||
|
import Sentry
|
||||||
|
|
||||||
|
class NotificationsCollectionViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController {
|
||||||
|
|
||||||
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
|
private let allowedTypes: [Pachyderm.Notification.Kind]
|
||||||
|
private let groupTypes = [Pachyderm.Notification.Kind.favourite, .reblog, .follow]
|
||||||
|
|
||||||
|
private(set) var controller: TimelineLikeController<TimelineItem>!
|
||||||
|
let confirmLoadMore = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
|
private(set) var collectionView: UICollectionView!
|
||||||
|
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||||
|
|
||||||
|
private var newer: RequestRange?
|
||||||
|
private var older: RequestRange?
|
||||||
|
|
||||||
|
init(allowedTypes: [Pachyderm.Notification.Kind], mastodonController: MastodonController) {
|
||||||
|
self.allowedTypes = allowedTypes
|
||||||
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
|
self.controller = TimelineLikeController(delegate: self)
|
||||||
|
|
||||||
|
// todo: title
|
||||||
|
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh Notifications"))
|
||||||
|
// todo: user activity
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||||
|
config.backgroundColor = .appBackground
|
||||||
|
// todo: swipe actions
|
||||||
|
// todo: separators
|
||||||
|
let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in
|
||||||
|
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
|
section.contentInsetsReference = .readableContent
|
||||||
|
}
|
||||||
|
return section
|
||||||
|
}
|
||||||
|
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
// todo: drag
|
||||||
|
//collectionView.dragDelegate = self
|
||||||
|
collectionView.allowsFocus = true
|
||||||
|
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(collectionView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
registerTimelineLikeCells()
|
||||||
|
dataSource = createDataSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, NotificationGroup> { [unowned self] cell, indexPath, itemIdentifier in
|
||||||
|
cell.delegate = self
|
||||||
|
let statusID = itemIdentifier.notifications.first!.status!.id
|
||||||
|
let statusState = itemIdentifier.statusState!
|
||||||
|
cell.updateUI(statusID: statusID, state: statusState, filterResult: .allow, precomputedContent: nil)
|
||||||
|
}
|
||||||
|
let unknownCell = UICollectionView.CellRegistration<UICollectionViewListCell, ()> { cell, indexPath, itemIdentifier in
|
||||||
|
var config = cell.defaultContentConfiguration()
|
||||||
|
config.text = "Unknown Notification"
|
||||||
|
cell.contentConfiguration = config
|
||||||
|
}
|
||||||
|
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
|
||||||
|
switch itemIdentifier {
|
||||||
|
case .group(let group):
|
||||||
|
switch group.kind {
|
||||||
|
case .status, .mention:
|
||||||
|
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: group)
|
||||||
|
default:
|
||||||
|
return collectionView.dequeueConfiguredReusableCell(using: unknownCell, for: indexPath, item: ())
|
||||||
|
}
|
||||||
|
case .loadingIndicator:
|
||||||
|
return self.loadingIndicatorCell(for: indexPath)
|
||||||
|
case .confirmLoadMore:
|
||||||
|
return self.confirmLoadMoreCell(for: indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
clearSelectionOnAppear(animated: animated)
|
||||||
|
|
||||||
|
if case .notLoadedInitial = controller.state {
|
||||||
|
Task {
|
||||||
|
await controller.loadInitial()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func refresh() {
|
||||||
|
// todo: refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationsCollectionViewController {
|
||||||
|
enum Section: TimelineLikeCollectionViewSection {
|
||||||
|
case notifications
|
||||||
|
case footer
|
||||||
|
|
||||||
|
static var entries: Self { .notifications }
|
||||||
|
}
|
||||||
|
enum Item: TimelineLikeCollectionViewItem {
|
||||||
|
case group(NotificationGroup)
|
||||||
|
case loadingIndicator
|
||||||
|
case confirmLoadMore
|
||||||
|
|
||||||
|
static func fromTimelineItem(_ item: NotificationGroup) -> Self {
|
||||||
|
return .group(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
var group: NotificationGroup? {
|
||||||
|
if case .group(let group) = self {
|
||||||
|
return group
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSelectable: Bool {
|
||||||
|
switch self {
|
||||||
|
case .group(_):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: TimelineLikeControllerDelegate
|
||||||
|
extension NotificationsCollectionViewController {
|
||||||
|
typealias TimelineItem = NotificationGroup
|
||||||
|
|
||||||
|
private static let pageSize = 40
|
||||||
|
|
||||||
|
private func request(range: RequestRange) -> Request<[Pachyderm.Notification]> {
|
||||||
|
if mastodonController.instanceFeatures.notificationsAllowedTypes {
|
||||||
|
return Client.getNotifications(allowedTypes: allowedTypes, range: range)
|
||||||
|
} else {
|
||||||
|
var types = Set(Notification.Kind.allCases)
|
||||||
|
allowedTypes.forEach { types.remove($0) }
|
||||||
|
return Client.getNotifications(excludedTypes: Array(types), range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateNotifications(_ notifications: [Pachyderm.Notification]) -> [Pachyderm.Notification] {
|
||||||
|
return notifications.compactMap { notif in
|
||||||
|
if notif.status == nil && (notif.kind == .mention || notif.kind == .reblog || notif.kind == .favourite) {
|
||||||
|
let crumb = Breadcrumb(level: .fatal, category: "notifications")
|
||||||
|
crumb.data = [
|
||||||
|
"id": notif.id,
|
||||||
|
"type": notif.kind.rawValue,
|
||||||
|
"created_at": notif.createdAt.formatted(.iso8601),
|
||||||
|
"account": notif.account.id,
|
||||||
|
]
|
||||||
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return notif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadInitial() async throws -> [NotificationGroup] {
|
||||||
|
let request = self.request(range: .count(NotificationsCollectionViewController.pageSize))
|
||||||
|
let (notifications, _) = try await mastodonController.run(request)
|
||||||
|
|
||||||
|
if !notifications.isEmpty {
|
||||||
|
self.newer = .after(id: notifications.first!.id, count: NotificationsCollectionViewController.pageSize)
|
||||||
|
self.older = .before(id: notifications.last!.id, count: NotificationsCollectionViewController.pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
let validated = validateNotifications(notifications)
|
||||||
|
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
mastodonController.persistentContainer.addAll(notifications: validated) {
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NotificationGroup.createGroups(notifications: validated, only: self.groupTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNewer() async throws -> [NotificationGroup] {
|
||||||
|
guard let newer else {
|
||||||
|
throw Error.noNewer
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = self.request(range: newer)
|
||||||
|
let (notifications, _) = try await mastodonController.run(request)
|
||||||
|
|
||||||
|
if !notifications.isEmpty {
|
||||||
|
self.newer = .after(id: notifications.first!.id, count: NotificationsCollectionViewController.pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
let validated = validateNotifications(notifications)
|
||||||
|
guard !validated.isEmpty else {
|
||||||
|
throw Error.allCaughtUp
|
||||||
|
}
|
||||||
|
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
mastodonController.persistentContainer.addAll(notifications: validated) {
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newerGroups = NotificationGroup.createGroups(notifications: validated, only: self.groupTypes)
|
||||||
|
let existingGroups = dataSource.snapshot().itemIdentifiers(inSection: .notifications).compactMap(\.group)
|
||||||
|
return NotificationGroup.mergeGroups(first: newerGroups, second: existingGroups, only: self.groupTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePrependItems(_ timelineItems: [NotificationGroup]) async {
|
||||||
|
// we always replace all, because new items are merged with existing ones
|
||||||
|
await handleReplaceAllItems(timelineItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOlder() async throws -> [NotificationGroup] {
|
||||||
|
guard let older else {
|
||||||
|
throw Error.noOlder
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = self.request(range: older)
|
||||||
|
let (notifications, _) = try await mastodonController.run(request)
|
||||||
|
|
||||||
|
if !notifications.isEmpty {
|
||||||
|
self.older = .before(id: notifications.last!.id, count: NotificationsCollectionViewController.pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
let validated = validateNotifications(notifications)
|
||||||
|
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
mastodonController.persistentContainer.addAll(notifications: validated) {
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let olderGroups = NotificationGroup.createGroups(notifications: validated, only: self.groupTypes)
|
||||||
|
let existingGroups = dataSource.snapshot().itemIdentifiers(inSection: .notifications).compactMap(\.group)
|
||||||
|
return NotificationGroup.mergeGroups(first: existingGroups, second: olderGroups, only: self.groupTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAppendItems(_ timelineItems: [NotificationGroup]) async {
|
||||||
|
await handleReplaceAllItems(timelineItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Error: TimelineLikeCollectionViewError {
|
||||||
|
case noNewer
|
||||||
|
case noOlder
|
||||||
|
case allCaughtUp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationsCollectionViewController: UICollectionViewDelegate {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||||
|
guard case .notifications = dataSource.sectionIdentifier(for: indexPath.section) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let itemsInSection = collectionView.numberOfItems(inSection: indexPath.section)
|
||||||
|
if indexPath.row == itemsInSection - 1 {
|
||||||
|
Task {
|
||||||
|
await controller.loadOlder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
||||||
|
return dataSource.itemIdentifier(for: indexPath)?.isSelectable ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
|
// todo
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
|
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationsCollectionViewController: TuskerNavigationDelegate {
|
||||||
|
var apiController: MastodonController! { mastodonController }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationsCollectionViewController: MenuActionProvider {
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationsCollectionViewController: StatusCollectionViewCellDelegate {
|
||||||
|
func statusCellNeedsReconfigure(_ cell: StatusCollectionViewCell, animated: Bool, completion: (() -> Void)?) {
|
||||||
|
if let indexPath = collectionView.indexPath(for: cell) {
|
||||||
|
var snapshot = dataSource.snapshot()
|
||||||
|
snapshot.reconfigureItems([dataSource.itemIdentifier(for: indexPath)!])
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: animated, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusCellShowFiltered(_ cell: StatusCollectionViewCell) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ class NotificationsPageViewController: SegmentedPageViewController<Notifications
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
super.init(pages: [.all, .mentions]) { page in
|
super.init(pages: [.all, .mentions]) { page in
|
||||||
let vc = NotificationsTableViewController(allowedTypes: page.allowedTypes, mastodonController: mastodonController)
|
let vc = NotificationsCollectionViewController(allowedTypes: page.allowedTypes, mastodonController: mastodonController)
|
||||||
vc.title = page.title
|
vc.title = page.title
|
||||||
vc.userActivity = page.userActivity(accountID: mastodonController.accountInfo!.id)
|
vc.userActivity = page.userActivity(accountID: mastodonController.accountInfo!.id)
|
||||||
return vc
|
return vc
|
||||||
|
|
Loading…
Reference in New Issue