2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00

Restore ability to load additional notifications beyond the first fetched batch

This commit is contained in:
shannon 2025-01-24 15:08:11 -05:00
parent f552c3af91
commit bc918ffdfc
7 changed files with 68 additions and 60 deletions

View File

@ -89,7 +89,7 @@ struct NotificationListView: View {
@ViewBuilder func rowView(_ notificationListItem: NotificationListItem) -> some View {
switch notificationListItem {
case .bottomLoader, .middleLoader:
case .bottomLoader:
Text("loader not yet implemented")
case .filteredNotificationsInfo:
Text("filtered notifications not yet implemented")

View File

@ -12,19 +12,16 @@ import MastodonSDK
enum NotificationListItem: Hashable {
case filteredNotificationsInfo(policy: Mastodon.Entity.NotificationPolicy)
case notification(MastodonFeedItemIdentifier)
case middleLoader(after: MastodonFeedItemIdentifier, before: MastodonFeedItemIdentifier)
case bottomLoader
var nextFetchAnchors: (MastodonFeedItemIdentifier?, MastodonFeedItemIdentifier?) {
var fetchAnchor: MastodonFeedItemIdentifier? {
switch self {
case .filteredNotificationsInfo:
return (nil, nil)
return nil
case .notification(let identifier):
return (identifier, nil)
case .middleLoader(let after, let before):
return (after, before)
return identifier
case .bottomLoader:
return (nil, nil)
return nil
}
}
}
@ -38,8 +35,6 @@ extension NotificationListItem: Identifiable {
return "filtered_notifications_info"
case .notification(let identifier):
return identifier.id
case let .middleLoader(afterID, beforeID):
return afterID.id+"-"+beforeID.id
case .bottomLoader:
return "bottom_loader"
}

View File

@ -56,11 +56,6 @@ extension NotificationSection {
)
return cell
}
case .middleLoader:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
cell.activityIndicatorView.startAnimating()
return cell
case .bottomLoader:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
cell.activityIndicatorView.startAnimating()

View File

@ -58,7 +58,7 @@ extension NotificationTimelineViewController: DataSourceProvider {
}
case .filteredNotificationsInfo(let policy):
return DataSourceItem.notificationBanner(policy: policy)
case .bottomLoader, .middleLoader:
case .bottomLoader:
return nil
}
}

View File

@ -187,7 +187,7 @@ extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateT
return
}
Task {
await viewModel.loadMore(item: item)
await viewModel.loadMore(olderThan: item, newerThan: nil)
}
}

View File

@ -123,22 +123,26 @@ extension NotificationTimelineViewModel {
func loadLatest() async {
isLoadingLatest = true
defer { isLoadingLatest = false }
feedLoader.loadMore(olderThan: nil, newerThan: nil)
let currentFirst = diffableDataSource?.snapshot().itemIdentifiers.first
await loadMore(olderThan: nil, newerThan: currentFirst)
didLoadLatest.send()
}
// load timeline gap
func loadMore(item: NotificationListItem) async {
let olderThan: MastodonFeedItemIdentifier?
let newerThan: MastodonFeedItemIdentifier?
switch item {
case .notification, .middleLoader:
(olderThan, newerThan) = item.nextFetchAnchors
case .bottomLoader:
(olderThan, newerThan) = diffableDataSource?.snapshot().itemIdentifiers.last(where: { $0 != .bottomLoader })?.nextFetchAnchors ?? (nil, nil)
case .filteredNotificationsInfo:
return
func loadMore(olderThan: NotificationListItem?, newerThan: NotificationListItem?) async {
func fetchAnchor(for item: NotificationListItem?) -> MastodonFeedItemIdentifier? {
switch item {
case .notification:
return item?.fetchAnchor
case .bottomLoader:
return diffableDataSource?.snapshot().itemIdentifiers.last(where: { $0.fetchAnchor != nil })?.fetchAnchor
case .filteredNotificationsInfo:
return diffableDataSource?.snapshot().itemIdentifiers.first(where: { $0.fetchAnchor != nil })?.fetchAnchor
case .none:
return nil
}
}
feedLoader.loadMore(olderThan: olderThan, newerThan: newerThan)
feedLoader.loadMore(olderThan: fetchAnchor(for: olderThan), newerThan: fetchAnchor(for: newerThan))
}
}

View File

@ -14,6 +14,28 @@ import os.log
@MainActor
final public class MastodonFeedLoader {
struct FeedLoadRequest: Equatable {
let olderThan: MastodonFeedItemIdentifier?
let newerThan: MastodonFeedItemIdentifier?
var maxID: String? { olderThan?.id }
var resultsInsertionPoint: InsertLocation {
if olderThan != nil {
return .end
} else if newerThan != nil {
return .start
} else {
return .replace
}
}
enum InsertLocation {
case start
case end
case replace
}
}
private let logger = Logger(subsystem: "MastodonFeedLoader", category: "Data")
private static let entryNotFoundMessage = "Failed to find suitable record. Depending on the context this might result in errors (data not being updated) or can be discarded (e.g. when there are mixed data sources where an entry might or might not exist)."
@ -37,43 +59,26 @@ final public class MastodonFeedLoader {
}
}
private var mostRecentLoad: FeedLoadRequest?
public func loadMore(olderThan: MastodonFeedItemIdentifier?, newerThan: MastodonFeedItemIdentifier?) {
if let olderThan {
Task {
let unfiltered = try await load(kind: kind, olderThan: olderThan.id)
await setRecordsAfterFiltering(unfiltered)
}
} else {
loadInitial(kind: kind)
}
}
private func loadInitial(kind: MastodonFeedKind) {
let request = FeedLoadRequest(olderThan: olderThan, newerThan: newerThan)
Task {
let unfilteredRecords = try await load(kind: kind)
await setRecordsAfterFiltering(unfilteredRecords)
let unfiltered = try await load(request)
await insertRecordsAfterFiltering(at: request.resultsInsertionPoint, additionalRecords:unfiltered)
}
}
private func loadNext(kind: MastodonFeedKind) {
Task {
guard let lastId = records.last?.id else {
return loadInitial(kind: kind)
}
let unfiltered = try await load(kind: kind, olderThan: lastId)
await self.appendRecordsAfterFiltering(unfiltered)
}
}
private func load(kind: MastodonFeedKind, olderThan maxID: String? = nil) async throws -> [MastodonFeedItemIdentifier] {
private func load(_ request: FeedLoadRequest) async throws -> [MastodonFeedItemIdentifier] {
guard request != mostRecentLoad else { throw AppError.badRequest }
mostRecentLoad = request
switch kind {
case .notificationsAll:
return try await loadNotifications(withScope: .everything, olderThan: maxID)
return try await loadNotifications(withScope: .everything, olderThan: request.maxID)
case .notificationsMentionsOnly:
return try await loadNotifications(withScope: .mentions, olderThan: maxID)
return try await loadNotifications(withScope: .mentions, olderThan: request.maxID)
case .notificationsWithAccount(let accountID):
return try await loadNotifications(withAccountID: accountID, olderThan: maxID)
return try await loadNotifications(withAccountID: accountID, olderThan: request.maxID)
}
}
@ -217,10 +222,19 @@ private extension MastodonFeedLoader {
self.records = filtered.removingDuplicates()
}
private func appendRecordsAfterFiltering(_ additionalRecords: [MastodonFeedItemIdentifier]) async {
private func insertRecordsAfterFiltering(at insertionPoint: FeedLoadRequest.InsertLocation, additionalRecords: [MastodonFeedItemIdentifier]) async {
guard let filterBox = StatusFilterService.shared.activeFilterBox else { self.records += additionalRecords; return }
let newRecords = await self.filter(additionalRecords, forFeed: kind, with: filterBox)
self.records = (self.records + newRecords).removingDuplicates()
var combinedRecords = self.records
switch insertionPoint {
case .start:
combinedRecords = newRecords + combinedRecords
case .end:
combinedRecords.append(contentsOf: newRecords)
case .replace:
combinedRecords = newRecords
}
self.records = combinedRecords.removingDuplicates()
}
private func filter(_ records: [MastodonFeedItemIdentifier], forFeed feedKind: MastodonFeedKind, with filterBox: Mastodon.Entity.FilterBox) async -> [MastodonFeedItemIdentifier] {