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

Honor requested minID when fetching notifications

Contributes to #399 [BUG] Multiple interactions do not collapse into a single notification
This commit is contained in:
shannon 2025-03-24 11:15:38 -04:00
parent c746507cc4
commit 7502daecd0
4 changed files with 26 additions and 23 deletions

View File

@ -206,13 +206,13 @@ final public class GroupedNotificationFeedLoader {
switch kind {
case .notificationsAll:
return try await loadNotifications(
withScope: .everything, olderThan: request.olderThan)
withScope: .everything, olderThan: request.olderThan, newerThan: request.newerThan)
case .notificationsMentionsOnly:
return try await loadNotifications(
withScope: .mentions, olderThan: request.olderThan)
withScope: .mentions, olderThan: request.olderThan, newerThan: request.newerThan)
case .notificationsWithAccount(let accountID):
return try await loadNotifications(
withAccountID: accountID, olderThan: request.olderThan)
withAccountID: accountID, olderThan: request.olderThan, newerThan: request.newerThan)
}
}
}
@ -257,27 +257,28 @@ extension GroupedNotificationFeedLoader {
extension GroupedNotificationFeedLoader {
private func loadNotifications(
withScope scope: APIService.MastodonNotificationScope,
olderThan maxID: String? = nil
olderThan maxID: String? = nil,
newerThan minID: String?
) async throws -> NotificationsResultType {
if useGroupedNotificationsApi {
do {
return try await getGroupedNotifications(
withScope: scope, olderThan: maxID)
withScope: scope, olderThan: maxID, newerThan: minID)
} catch {
}
}
return try await getUngroupedNotifications(withScope: scope, olderThan: maxID)
return try await getUngroupedNotifications(withScope: scope, olderThan: maxID, newerThan: minID)
}
private func loadNotifications(
withAccountID accountID: String, olderThan maxID: String? = nil
withAccountID accountID: String, olderThan maxID: String? = nil, newerThan minID: String?
) async throws -> [Mastodon.Entity.Notification] {
return try await getUngroupedNotifications(
accountID: accountID, olderThan: maxID)
accountID: accountID, olderThan: maxID, newerThan: minID)
}
private func getGroupedNotifications(
withScope scope: APIService.MastodonNotificationScope, olderThan maxID: String? = nil
withScope scope: APIService.MastodonNotificationScope, olderThan maxID: String? = nil, newerThan minID: String?
) async throws -> Mastodon.Entity.GroupedNotificationsResults {
guard
let authenticationBox = AuthenticationServiceProvider.shared
@ -285,7 +286,7 @@ extension GroupedNotificationFeedLoader {
else { throw APIService.APIError.implicit(.authenticationMissing) }
let results = try await APIService.shared.groupedNotifications(
olderThan: maxID, fromAccount: nil, scope: scope,
olderThan: maxID, newerThan: minID, fromAccount: nil, scope: scope,
authenticationBox: authenticationBox
)
@ -294,7 +295,7 @@ extension GroupedNotificationFeedLoader {
private func getUngroupedNotifications(
withScope scope: APIService.MastodonNotificationScope? = nil,
accountID: String? = nil, olderThan maxID: String? = nil
accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?
) async throws -> [Mastodon.Entity.Notification] {
assert(scope != nil || accountID != nil, "need a scope or an accountID")

View File

@ -97,7 +97,7 @@ class UngroupedNotificationCacheManager: NotificationsCacheManager {
updatedMostRecentChunk = newlyFetched
}
if let staleResults {
let (dedupedNewer, stale) = dedupeAndCombine(newer: updatedMostRecentChunk, older: staleResults)
let (dedupedNewer, stale) = merge(newer: updatedMostRecentChunk, older: staleResults)
mostRecentlyFetchedResults = Array(dedupedNewer)
if stale == nil {
self.staleResults = nil
@ -214,7 +214,7 @@ class GroupedNotificationCacheManager: NotificationsCacheManager {
let allStatuses: [Mastodon.Entity.Status]
if let staleResults {
let (dedupedNewer, dedupedStale) = dedupeAndCombine(newer: updatedNewerChunk, older: staleResults.notificationGroups)
let (dedupedNewer, dedupedStale) = merge(newer: updatedNewerChunk, older: staleResults.notificationGroups)
truncatedGroups = truncate(notificationGroups: dedupedNewer)
if dedupedStale == nil {
// the lists were combined, so we don't have to keep track of the stale one anymore
@ -346,14 +346,14 @@ class GroupedNotificationCacheManager: NotificationsCacheManager {
}
}
fileprivate func dedupeAndCombine<T: Overlappable>(newer: [T], older: [T]) -> ([T], [T]?) {
fileprivate func merge<T: Overlappable>(newer: [T], older: [T], assumeOverlap: Bool = true) -> ([T], [T]?) {
// There can be multiple matches between the older and newer feeds, with no guarantee of order. The newer version of a duplicate is always the one that should be used.
// Note that the check here is not fully sufficient to test for a gap between freshly fetched notifications and cached notifications (this check could miss a gap that was skipped over by a group that got promoted far enough up the list).
// Note that the check here is not fully sufficient to test for a gap between freshly fetched notifications and cached notifications (this check could miss a gap that was skipped over by a group that got promoted far enough up the list), which is why for now we fetch with a minID to avoid gaps and always assume there is an overlap.
var dedupedNewer = [T]()
var dedupedOlder = [T]()
var alreadyAdded = Set<T.ID>()
var canCombine = false
var hasOverlap = false
for element in newer {
guard !alreadyAdded.contains(element.id) else { continue }
@ -362,12 +362,12 @@ fileprivate func dedupeAndCombine<T: Overlappable>(newer: [T], older: [T]) -> ([
}
for element in older {
guard !alreadyAdded.contains(element.id) else { canCombine = true; continue }
guard !alreadyAdded.contains(element.id) else { hasOverlap = true; continue }
dedupedOlder.append(element)
alreadyAdded.insert(element.id)
}
if canCombine {
if hasOverlap || assumeOverlap {
return (dedupedNewer + dedupedOlder, nil)
} else {
return (dedupedNewer, dedupedOlder)

View File

@ -290,13 +290,13 @@ private extension MastodonFeedLoader {
}
}
private func _getGroupedNotifications(withScope scope: APIService.MastodonNotificationScope? = nil, accountID: String? = nil, olderThan maxID: String? = nil) async throws -> [MastodonFeedItemIdentifier] {
private func _getGroupedNotifications(withScope scope: APIService.MastodonNotificationScope? = nil, accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?) async throws -> [MastodonFeedItemIdentifier] {
assert(scope != nil || accountID != nil, "need a scope or an accountID")
guard let authenticationBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { throw APIService.APIError.implicit(.authenticationMissing) }
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, fromAccount: accountID, scope: scope, authenticationBox: authenticationBox)
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, newerThan: minID, fromAccount: accountID, scope: scope, authenticationBox: authenticationBox)
for account in results.accounts {
MastodonFeedItemCacheManager.shared.addToCache(account)
@ -319,13 +319,13 @@ private extension MastodonFeedLoader {
}
}
private func _getGroupedNotificationResults(withScope scope: APIService.MastodonNotificationScope? = nil, accountID: String? = nil, olderThan maxID: String? = nil) async throws -> Mastodon.Entity.GroupedNotificationsResults {
private func _getGroupedNotificationResults(withScope scope: APIService.MastodonNotificationScope? = nil, accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?) async throws -> Mastodon.Entity.GroupedNotificationsResults {
assert(scope != nil || accountID != nil, "need a scope or an accountID")
guard let authenticationBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { throw APIService.APIError.implicit(.authenticationMissing) }
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, fromAccount: accountID, scope: scope, authenticationBox: authenticationBox)
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, newerThan: minID, fromAccount: accountID, scope: scope, authenticationBox: authenticationBox)
return results
}

View File

@ -59,7 +59,8 @@ extension APIService {
}
public func groupedNotifications(
olderThan maxID: Mastodon.Entity.Status.ID?,
olderThan maxID: Mastodon.Entity.Notification.ID?,
newerThan minID: Mastodon.Entity.Notification.ID?,
fromAccount accountID: String? = nil,
scope: MastodonNotificationScope?,
authenticationBox: MastodonAuthenticationBox
@ -83,6 +84,7 @@ extension APIService {
let query = Mastodon.API.Notifications.GroupedQuery(
maxID: maxID,
minID: minID,
types: types,
excludeTypes: excludedTypes,
accountID: accountID