@@ -20,16 +20,18 @@ final public class GroupedNotificationFeedLoader {
2020 let canLoadOlder : Bool
2121 }
2222
23- struct FeedLoadRequest : Equatable {
24- let olderThan : String ?
25- let newerThan : String ?
23+ public enum FeedLoadRequest {
24+ case older
25+ case newer
26+ case reload
2627
2728 var resultsInsertionPoint : InsertLocation {
28- if olderThan != nil {
29+ switch self {
30+ case . older:
2931 return . end
30- } else if newerThan != nil {
32+ case . newer :
3133 return . start
32- } else {
34+ case . reload :
3335 return . replace
3436 }
3537 }
@@ -44,6 +46,8 @@ final public class GroupedNotificationFeedLoader {
4446 subsystem: " GroupedNotificationFeedLoader " , category: " Data " )
4547 private static let entryNotFoundMessage =
4648 " 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). "
49+
50+ private var loadRequestQueue = [ FeedLoadRequest] ( )
4751
4852 @Published private( set) var records : FeedLoadResult = FeedLoadResult (
4953 allRecords: [ ] , canLoadOlder: true )
@@ -53,7 +57,15 @@ final public class GroupedNotificationFeedLoader {
5357
5458 private let timestampUpdater = TimestampUpdater ( TimeInterval ( 30 ) )
5559
56- private var isFetching : Bool = false
60+ private var isFetching : Bool = false {
61+ didSet {
62+ if !isFetching, let waitingRequest = nextRequestThatCanBeLoadedNow ( ) {
63+ Task {
64+ await load ( waitingRequest)
65+ }
66+ }
67+ }
68+ }
5769
5870 public let useGroupedNotificationsApi : Bool
5971 private let cacheManager : ( any NotificationsCacheManager ) ?
@@ -104,21 +116,18 @@ final public class GroupedNotificationFeedLoader {
104116 . $activeFilterBox
105117 . sink { filterBox in
106118 if filterBox != nil {
107- Task { [ weak self] in
108- guard let self else { return }
109- let curAllRecords = self . records. allRecords
110- let curCanLoadOlder = self . records. canLoadOlder
111- await self . replaceRecordsAfterFiltering (
112- curAllRecords, canLoadOlder: curCanLoadOlder)
113- }
119+ let curAllRecords = self . records. allRecords
120+ let curCanLoadOlder = self . records. canLoadOlder
121+ self . replaceRecordsAfterFiltering (
122+ curAllRecords, canLoadOlder: curCanLoadOlder)
114123 }
115124 }
116125 }
117126
118127 public func doFirstLoad( ) {
119128 Task {
120129 do {
121- try await loadCached ( )
130+ try loadCached ( )
122131 } catch {
123132 }
124133 do {
@@ -128,18 +137,24 @@ final public class GroupedNotificationFeedLoader {
128137 }
129138 } catch {
130139 }
131- await asyncLoadMore ( olderThan : nil , newerThan : records . allRecords . first ? . newestID )
140+ requestLoad ( . newer )
132141 }
133142 }
134143
135144 public func commitToCache( ) async {
136145 await cacheManager? . commitToCache ( )
137146 }
147+
148+ private func noMoreResultsToFetch( ) {
149+ if records. canLoadOlder {
150+ records = FeedLoadResult ( allRecords: records. allRecords, canLoadOlder: false )
151+ }
152+ }
138153
139- private func replaceRecordsAfterFiltering( _ unfiltered: [ NotificationRowViewModel ] , canLoadOlder: Bool ? = nil ) async {
154+ private func replaceRecordsAfterFiltering( _ unfiltered: [ NotificationRowViewModel ] , canLoadOlder: Bool ? = nil ) {
140155 let filtered : [ NotificationRowViewModel ]
141156 if let filterBox = StatusFilterService . shared. activeFilterBox {
142- filtered = await filter ( unfiltered, forFeed: kind, with: filterBox)
157+ filtered = filter ( unfiltered, forFeed: kind, with: filterBox)
143158 } else {
144159 filtered = unfiltered
145160 }
@@ -170,61 +185,105 @@ final public class GroupedNotificationFeedLoader {
170185 return deduped
171186 }
172187
173-
174- public func asyncLoadMore(
175- olderThan: String ? ,
176- newerThan: String ?
177- ) async {
178- guard !isFetching else { return }
188+ private func loadCached( ) throws {
189+ guard !isFetching, let cacheManager else { return }
179190 isFetching = true
180191 defer {
181192 isFetching = false
182193 }
183- let request = FeedLoadRequest (
184- olderThan: olderThan, newerThan: newerThan)
185- do {
186- let newlyFetched = try await load ( request)
187- await updateAfterInserting ( newlyFetchedResults: newlyFetched, at: request. resultsInsertionPoint)
188- } catch {
189- presentError ? ( error)
194+ let currentResults = cacheManager. currentResults ( )
195+ try replaceRecordsAfterFiltering ( rowViewModels ( from: currentResults) , canLoadOlder: true )
196+ }
197+
198+ public func requestLoad( _ request: FeedLoadRequest ) {
199+ if !loadRequestQueue. contains ( request) {
200+ loadRequestQueue. append ( request)
201+ }
202+ if let nextDoableRequest = nextRequestThatCanBeLoadedNow ( ) {
203+ Task {
204+ await load ( nextDoableRequest)
205+ }
190206 }
191207 }
192208
193- private func loadCached( ) async throws {
194- guard !isFetching, let cacheManager else { return }
195- isFetching = true
196- defer {
197- isFetching = false
209+ public var permissionToLoadImmediately : Bool {
210+ // This is only intended for use with pull to refresh, in order to properly update the progress spinner.
211+ if isFetching {
212+ return false
213+ } else {
214+ isFetching = true
215+ return true
198216 }
199- let currentResults = await cacheManager. currentResults ( )
200- try await replaceRecordsAfterFiltering ( rowViewModels ( from: currentResults) , canLoadOlder: true )
201217 }
202-
203- private func load( _ request: FeedLoadRequest ) async throws
204- -> NotificationsResultType
218+ public func loadImmediately( _ request: FeedLoadRequest ) async {
219+ // This is only intended for use with pull to refresh, in order to properly update the progress spinner.
220+ guard isFetching else { assertionFailure ( " request permissionToLoadImmediately before calling loadImmediately " ) ; return }
221+ await load ( request)
222+ }
223+
224+ private func nextRequestThatCanBeLoadedNow( ) -> FeedLoadRequest ? {
225+ guard !isFetching else { return nil }
226+ guard !loadRequestQueue. isEmpty else { return nil }
227+ let nextRequest = loadRequestQueue. removeFirst ( )
228+ isFetching = true
229+ return nextRequest
230+ }
231+
232+ private func load( _ request: FeedLoadRequest ) async
205233 {
206- switch kind {
207- case . notificationsAll:
208- return try await loadNotifications (
209- withScope: . everything, olderThan: request. olderThan, newerThan: request. newerThan)
210- case . notificationsMentionsOnly:
211- return try await loadNotifications (
212- withScope: . mentions, olderThan: request. olderThan, newerThan: request. newerThan)
213- case . notificationsWithAccount( let accountID) :
214- return try await loadNotifications (
215- withAccountID: accountID, olderThan: request. olderThan, newerThan: request. newerThan)
234+ defer { isFetching = false }
235+ do {
236+ let olderThan : String ?
237+ let newerThan : String ?
238+ switch request {
239+ case . newer:
240+ olderThan = nil
241+ newerThan = records. allRecords. first? . newestID
242+ case . older:
243+ olderThan = records. allRecords. last? . oldestID
244+ newerThan = nil
245+ case . reload:
246+ olderThan = nil
247+ newerThan = nil
248+ }
249+ let results : NotificationsResultType
250+ switch kind {
251+ case . notificationsAll:
252+ results = try await loadNotifications (
253+ withScope: . everything, olderThan: olderThan, newerThan: newerThan)
254+ case . notificationsMentionsOnly:
255+ results = try await loadNotifications (
256+ withScope: . mentions, olderThan: olderThan, newerThan: newerThan)
257+ case . notificationsWithAccount( let accountID) :
258+ results = try await loadNotifications (
259+ withAccountID: accountID, olderThan: olderThan, newerThan: newerThan)
260+ }
261+ updateAfterInserting ( newlyFetchedResults: results, at: request. resultsInsertionPoint)
262+ } catch {
263+ presentError ? ( error)
216264 }
217265 }
218266}
219267
220268// MARK: - Filtering
221269extension GroupedNotificationFeedLoader {
222270 private func updateAfterInserting( newlyFetchedResults: NotificationsResultType ,
223- at insertionPoint: GroupedNotificationFeedLoader . FeedLoadRequest . InsertLocation ) async {
271+ at insertionPoint: GroupedNotificationFeedLoader . FeedLoadRequest . InsertLocation ) {
272+ switch insertionPoint {
273+ case . start:
274+ guard newlyFetchedResults. hasContents else { return }
275+ case . replace:
276+ break
277+ case . end:
278+ guard newlyFetchedResults. hasContents else {
279+ noMoreResultsToFetch ( )
280+ return
281+ }
282+ }
224283 guard let cacheManager else { assertionFailure ( ) ; return }
225284 do {
226285 cacheManager. updateByInserting ( newlyFetched: newlyFetchedResults, at: insertionPoint)
227- let currentResults = await cacheManager. currentResults ( )
286+ let currentResults = cacheManager. currentResults ( )
228287 let unfiltered = try rowViewModels ( from: currentResults)
229288
230289 let canLoadOlder : Bool ? = {
@@ -238,7 +297,7 @@ extension GroupedNotificationFeedLoader {
238297 }
239298 } ( )
240299
241- await replaceRecordsAfterFiltering ( unfiltered, canLoadOlder: canLoadOlder)
300+ replaceRecordsAfterFiltering ( unfiltered, canLoadOlder: canLoadOlder)
242301 } catch {
243302 presentError ? ( error)
244303 }
@@ -248,7 +307,7 @@ extension GroupedNotificationFeedLoader {
248307 _ records: [ NotificationRowViewModel ] ,
249308 forFeed feedKind: MastodonFeedKind ,
250309 with filterBox: Mastodon . Entity . FilterBox
251- ) async -> [ NotificationRowViewModel ] {
310+ ) -> [ NotificationRowViewModel ] {
252311 return records
253312 }
254313}
0 commit comments