From ba48adb470c4668ec53db96605e71636be5511f5 Mon Sep 17 00:00:00 2001 From: CMK Date: Thu, 8 Apr 2021 16:27:26 +0800 Subject: [PATCH] chore: make favorite and hashtag scene use next page token from response header --- ...tagTimelineViewModel+LoadOldestState.swift | 17 ++++++++-- .../Favorite/FavoriteViewModel+State.swift | 19 +++++++++-- .../Response/Mastodon+Response+Content.swift | 34 +++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+LoadOldestState.swift index d0607550e..e5c78f3d5 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+LoadOldestState.swift @@ -35,6 +35,8 @@ extension HashtagTimelineViewModel.LoadOldestState { } class Loading: HashtagTimelineViewModel.LoadOldestState { + var maxID: String? + override func isValidNextState(_ stateClass: AnyClass) -> Bool { return stateClass == Fail.self || stateClass == Idle.self || stateClass == NoMore.self } @@ -54,7 +56,7 @@ extension HashtagTimelineViewModel.LoadOldestState { } // TODO: only set large count when using Wi-Fi - let maxID = last.id + let maxID = self.maxID ?? last.id viewModel.context.apiService.hashtagTimeline( domain: activeMastodonAuthenticationBox.domain, maxID: maxID, @@ -71,10 +73,19 @@ extension HashtagTimelineViewModel.LoadOldestState { // handle isFetchingLatestTimeline in fetch controller delegate break } - } receiveValue: { response in + } receiveValue: { [weak self] response in + guard let self = self else { return } + let statuses = response.value // enter no more state when no new statuses - if statuses.isEmpty || (statuses.count == 1 && statuses[0].id == maxID) { + + let hasNextPage: Bool = { + guard let link = response.link else { return true } // assert has more when link invalid + return link.maxID != nil + }() + self.maxID = response.link?.maxID + + if !hasNextPage || statuses.isEmpty || (statuses.count == 1 && statuses[0].id == maxID) { stateMachine.enter(NoMore.self) } else { stateMachine.enter(Idle.self) diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift index 134adb0a3..c4420e88b 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift @@ -92,6 +92,9 @@ extension FavoriteViewModel.State { } class Loading: FavoriteViewModel.State { + + var maxID: String? + override func isValidNextState(_ stateClass: AnyClass) -> Bool { switch stateClass { case is Fail.Type: @@ -113,8 +116,11 @@ extension FavoriteViewModel.State { stateMachine.enter(Fail.self) return } - - let maxID = viewModel.statusFetchedResultsController.statusIDs.value.last + if previousState is Reloading { + maxID = nil + } + // prefer use `maxID` token in response header + // let maxID = viewModel.statusFetchedResultsController.statusIDs.value.last viewModel.context.apiService.favoritedStatuses( maxID: maxID, @@ -139,8 +145,15 @@ extension FavoriteViewModel.State { statusIDs.append(status.id) hasNewStatusesAppend = true } + + self.maxID = response.link?.maxID + + let hasNextPage: Bool = { + guard let link = response.link else { return true } // assert has more when link invalid + return link.maxID != nil + }() - if hasNewStatusesAppend { + if hasNewStatusesAppend && hasNextPage { stateMachine.enter(Idle.self) } else { stateMachine.enter(NoMore.self) diff --git a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift index a74d0fcaa..9c39615f9 100644 --- a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift +++ b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift @@ -18,6 +18,7 @@ extension Mastodon.Response { // application fields public let rateLimit: RateLimit? + public let link: Link? public let responseTime: Int? public var networkDate: Date { @@ -33,6 +34,11 @@ extension Mastodon.Response { }() self.rateLimit = RateLimit(response: response) + self.link = { + guard let string = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "link") else { return nil } + return Link(link: string) + }() + self.responseTime = { guard let string = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "x-response-time") else { return nil } return Int(string) @@ -43,6 +49,7 @@ extension Mastodon.Response { self.value = value self.date = old.date self.rateLimit = old.rateLimit + self.link = old.link self.responseTime = old.responseTime } @@ -90,3 +97,30 @@ extension Mastodon.Response { } } + +extension Mastodon.Response { + public struct Link { + public let maxID: Mastodon.Entity.Status.ID? + public let minID: Mastodon.Entity.Status.ID? + + init(link: String) { + self.maxID = { + guard let regex = try? NSRegularExpression(pattern: "max_id=([[:digit:]]+)", options: []) else { return nil } + let results = regex.matches(in: link, options: [], range: NSRange(link.startIndex..