Handle HTTP Link header for favourites and followers/following

This commit is contained in:
Thomas Ricouard 2022-12-24 09:21:04 +01:00
parent a90a63bf1b
commit 8de2a8192b
5 changed files with 64 additions and 16 deletions

View file

@ -47,6 +47,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
@Published var title: String = ""
@Published var relationship: Relationshionship?
@Published var favourites: [Status] = []
private var favouritesNextPage: LinkHandler?
@Published var followedTags: [Tag] = []
@Published var featuredTags: [FeaturedTag] = []
@Published var fields: [Account.Field] = []
@ -108,7 +109,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
tabState = .statuses(statusesState: .loading)
statuses = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: nil, tag: nil))
if isCurrentUser {
favourites = try await client.get(endpoint: Accounts.favourites)
(favourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nil))
}
reloadTabState()
} catch {
@ -126,7 +127,13 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
let newStatuses: [Status] = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: lastId, tag: nil))
statuses.append(contentsOf: newStatuses)
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .hasNextPage))
case .favourites, .followedTags:
case .favourites:
guard let nextPageId = favouritesNextPage?.maxId else { return }
let newFavourites: [Status]
(newFavourites, favouritesNextPage) = try await client.getWithLink(endpoint: Accounts.favourites(sinceId: nextPageId))
favourites.append(contentsOf: newFavourites)
tabState = .statuses(statusesState: .display(statuses: favourites, nextPageState: .hasNextPage))
case .followedTags:
break
}
} catch {
@ -157,7 +164,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
case .statuses:
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .hasNextPage))
case .favourites:
tabState = .statuses(statusesState: .display(statuses: favourites, nextPageState: .none))
tabState = .statuses(statusesState: .display(statuses: favourites, nextPageState: .hasNextPage))
case .followedTags:
tabState = .followedTags(tags: followedTags)
}

View file

@ -29,6 +29,8 @@ class AccountsListViewModel: ObservableObject {
@Published var state = State.loading
private var nextPageId: String?
init(accountId: String, mode: AccountsListMode) {
self.accountId = accountId
self.mode = mode
@ -38,14 +40,16 @@ class AccountsListViewModel: ObservableObject {
guard let client else { return }
do {
state = .loading
let link: LinkHandler?
switch mode {
case .followers:
accounts = try await client.get(endpoint: Accounts.followers(id: accountId,
sinceId: nil))
(accounts, link) = try await client.getWithLink(endpoint: Accounts.followers(id: accountId,
sinceId: nil))
case .following:
accounts = try await client.get(endpoint: Accounts.following(id: accountId,
sinceId: nil))
(accounts, link) = try await client.getWithLink(endpoint: Accounts.following(id: accountId,
sinceId: nil))
}
nextPageId = link?.maxId
relationships = try await client.get(endpoint:
Accounts.relationships(ids: accounts.map{ $0.id }))
state = .display(accounts: accounts,
@ -55,23 +59,25 @@ class AccountsListViewModel: ObservableObject {
}
func fetchNextPage() async {
guard let client else { return }
guard let client, let nextPageId else { return }
do {
state = .display(accounts: accounts, relationships: relationships, nextPageState: .loadingNextPage)
let newAccounts: [Account]
let link: LinkHandler?
switch mode {
case .followers:
newAccounts = try await client.get(endpoint: Accounts.followers(id: accountId,
sinceId: accounts.last?.id))
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.followers(id: accountId,
sinceId: nextPageId))
case .following:
newAccounts = try await client.get(endpoint: Accounts.following(id: accountId,
sinceId: accounts.last?.id))
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.following(id: accountId,
sinceId: nextPageId))
}
accounts.append(contentsOf: newAccounts)
let newRelationships: [Relationshionship] =
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map{ $0.id }))
relationships.append(contentsOf: newRelationships)
self.nextPageId = link?.maxId
state = .display(accounts: accounts,
relationships: relationships,
nextPageState: .hasNextPage)

View file

@ -61,15 +61,29 @@ public class Client: ObservableObject, Equatable {
}
return request
}
private func makeGet(endpoint: Endpoint) -> URLRequest {
let url = makeURL(endpoint: endpoint)
return makeURLRequest(url: url, httpMethod: "GET")
}
public func get<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
let url = makeURL(endpoint: endpoint)
let request = makeURLRequest(url: url, httpMethod: "GET")
let (data, httpResponse) = try await urlSession.data(for: request)
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
logResponseOnError(httpResponse: httpResponse, data: data)
return try decoder.decode(Entity.self, from: data)
}
public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) {
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
var linkHandler: LinkHandler?
if let response = httpResponse as? HTTPURLResponse,
let link = response.allHeaderFields["Link"] as? String{
linkHandler = .init(rawLink: link)
}
logResponseOnError(httpResponse: httpResponse, data: data)
return (try decoder.decode(Entity.self, from: data), linkHandler)
}
public func post<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
let url = makeURL(endpoint: endpoint)
let request = makeURLRequest(url: url, httpMethod: "POST")

View file

@ -2,7 +2,7 @@ import Foundation
public enum Accounts: Endpoint {
case accounts(id: String)
case favourites
case favourites(sinceId: String?)
case followedTags
case featuredTags(id: String)
case verifyCredentials
@ -69,6 +69,9 @@ public enum Accounts: Endpoint {
case let .following(_, sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]
case let .favourites(sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]
default:
return nil
}

View file

@ -0,0 +1,18 @@
import Foundation
import RegexBuilder
public struct LinkHandler {
public let rawLink: String
public var maxId: String? {
do {
let regex = try Regex("max_id=[0-9]+")
if let match = rawLink.firstMatch(of: regex) {
return match.output.first?.substring?.replacingOccurrences(of: "max_id=", with: "")
}
} catch {
return nil
}
return nil
}
}