diff --git a/Mlem/API/APIClient/APIClient+Post.swift b/Mlem/API/APIClient/APIClient+Post.swift index 105294b11..3b07a05c1 100644 --- a/Mlem/API/APIClient/APIClient+Post.swift +++ b/Mlem/API/APIClient/APIClient+Post.swift @@ -12,16 +12,18 @@ extension APIClient { func loadPosts( communityId: Int?, page: Int, + cursor: String?, sort: PostSortType?, type: FeedType, limit: Int?, savedOnly: Bool?, communityName: String? - ) async throws -> [APIPostView] { + ) async throws -> GetPostsResponse { let request = try GetPostsRequest( session: session, communityId: communityId, page: page, + cursor: cursor, sort: sort, type: type, limit: limit, @@ -29,7 +31,7 @@ extension APIClient { communityName: communityName ) - return try await perform(request: request).posts + return try await perform(request: request) } // swiftlint:enable function_parameter_count diff --git a/Mlem/API/Models/Posts/APIPostAggregates.swift b/Mlem/API/Models/Posts/APIPostAggregates.swift index 169f79eba..756b4eeec 100644 --- a/Mlem/API/Models/Posts/APIPostAggregates.swift +++ b/Mlem/API/Models/Posts/APIPostAggregates.swift @@ -16,8 +16,9 @@ struct APIPostAggregates: Decodable, APIContentAggregatesProtocol { let upvotes: Int let downvotes: Int let published: Date - let newestCommentTime: Date - let newestCommentTimeNecro: Date - let featuredCommunity: Bool - let featuredLocal: Bool + // TODO: 0.18 Deprecation remove these fields + let newestCommentTime: Date? + let newestCommentTimeNecro: Date? + let featuredCommunity: Bool? + let featuredLocal: Bool? } diff --git a/Mlem/API/Models/Posts/APIPostView.swift b/Mlem/API/Models/Posts/APIPostView.swift index 16c696e54..be2267265 100644 --- a/Mlem/API/Models/Posts/APIPostView.swift +++ b/Mlem/API/Models/Posts/APIPostView.swift @@ -13,6 +13,8 @@ struct APIPostView: Decodable, APIContentViewProtocol { let creator: APIPerson let community: APICommunity let creatorBannedFromCommunity: Bool + // TODO: 0.18 Deprecation make this field non-optional + let creatorIsModerator: Bool? var counts: APIPostAggregates let subscribed: APISubscribedStatus let saved: Bool diff --git a/Mlem/API/Requests/Post/GetPosts.swift b/Mlem/API/Requests/Post/GetPosts.swift index 8c6618a84..f1e6650fb 100644 --- a/Mlem/API/Requests/Post/GetPosts.swift +++ b/Mlem/API/Requests/Post/GetPosts.swift @@ -19,6 +19,7 @@ struct GetPostsRequest: APIGetRequest { session: APISession, communityId: Int?, page: Int, + cursor: String?, sort: PostSortType?, type: FeedType, limit: Int? = nil, @@ -27,7 +28,6 @@ struct GetPostsRequest: APIGetRequest { ) throws { self.instanceURL = try session.instanceUrl var queryItems: [URLQueryItem] = [ - .init(name: "page", value: "\(page)"), .init(name: "type_", value: type.rawValue), .init(name: "sort", value: sort.map(\.rawValue)), .init(name: "community_id", value: communityId.map(String.init)), @@ -36,6 +36,15 @@ struct GetPostsRequest: APIGetRequest { .init(name: "saved_only", value: savedOnly.map(String.init)) ] + let paginationParameter: URLQueryItem + if let cursor { + paginationParameter = .init(name: "page_v2", value: cursor) + } else { + paginationParameter = .init(name: "page", value: "\(page)") + } + + queryItems.append(paginationParameter) + if let token = try? session.token { queryItems.append( .init(name: "auth", value: token) @@ -49,6 +58,7 @@ struct GetPostsRequest: APIGetRequest { // lemmy_api_common::post::GetPostsResponse struct GetPostsResponse: Decodable { let posts: [APIPostView] + let nextPage: String? } // MARK: - FeedTrackerItemProviding diff --git a/Mlem/Models/Trackers/Post Tracker.swift b/Mlem/Models/Trackers/Post Tracker.swift index fed245cbf..45a42c389 100644 --- a/Mlem/Models/Trackers/Post Tracker.swift +++ b/Mlem/Models/Trackers/Post Tracker.swift @@ -37,6 +37,7 @@ class PostTracker: ObservableObject { private(set) var isLoading: Bool = false // accessible but not published because it causes lots of bad view redraws private(set) var page: Int = 1 private(set) var hiddenItems: [PostFilterReason: Int] = .init() + private(set) var currentCursor: String? private var hasReachedEnd: Bool = false @@ -75,19 +76,25 @@ class PostTracker: ObservableObject { var newPosts: [PostModel] = .init() let numItems = items.count repeat { - newPosts = try await postRepository.loadPage( + let (posts, cursor) = try await postRepository.loadPage( communityId: communityId, page: page, + cursor: currentCursor, sort: sort, type: type, limit: internetSpeed.pageSize ) + newPosts = posts + if newPosts.isEmpty { hasReachedEnd = true + } else if let currentCursor, cursor == currentCursor { + hasReachedEnd = true } else { await add(newPosts, filtering: filtering) page += 1 + currentCursor = cursor } } while !hasReachedEnd && numItems > items.count + AppConstants.infiniteLoadThresholdOffset @@ -130,15 +137,17 @@ class PostTracker: ObservableObject { page = 1 - let newPosts = try await postRepository.loadPage( + let (newPosts, cursor) = try await postRepository.loadPage( communityId: communityId, page: page, + cursor: currentCursor, sort: sort, type: feedType, limit: internetSpeed.pageSize ) - await reset(with: newPosts, filteredWith: filtering) + currentCursor = cursor + await reset(with: newPosts, cursor: cursor, filteredWith: filtering) } @MainActor @@ -172,10 +181,12 @@ class PostTracker: ObservableObject { @MainActor func reset( with newItems: [PostModel] = .init(), + cursor: String? = nil, filteredWith filter: @escaping (_: PostModel) -> PostFilterReason? = { _ in nil } ) { hasReachedEnd = false page = newItems.isEmpty ? 1 : 2 + currentCursor = cursor if page == 1 { hiddenItems.removeAll() } @@ -434,11 +445,11 @@ class PostTracker: ObservableObject { // preload user and community avatars--fetching both because we don't know which we'll need, but these are super tiny // so it's probably not an API crime, right? if let communityAvatarLink = post.community.avatar { - imageRequests.append(ImageRequest(url: communityAvatarLink.withIconSize(Int(AppConstants.smallAvatarSize*2)))) + imageRequests.append(ImageRequest(url: communityAvatarLink.withIconSize(Int(AppConstants.smallAvatarSize * 2)))) } if let userAvatarLink = post.creator.avatar { - imageRequests.append(ImageRequest(url: userAvatarLink.withIconSize(Int(AppConstants.largeAvatarSize*2)))) + imageRequests.append(ImageRequest(url: userAvatarLink.withIconSize(Int(AppConstants.largeAvatarSize * 2)))) } switch post.postType { diff --git a/Mlem/Repositories/PostRepository.swift b/Mlem/Repositories/PostRepository.swift index 4eca82ec3..b5919ceb2 100644 --- a/Mlem/Repositories/PostRepository.swift +++ b/Mlem/Repositories/PostRepository.swift @@ -11,26 +11,33 @@ import Foundation class PostRepository { @Dependency(\.apiClient) private var apiClient + // swiftlint:disable function_parameter_count func loadPage( communityId: Int?, page: Int, + cursor: String?, sort: PostSortType?, type: FeedType, limit: Int, savedOnly: Bool? = nil, communityName: String? = nil - ) async throws -> [PostModel] { - try await apiClient.loadPosts( + ) async throws -> (posts: [PostModel], cursor: String?) { + let response = try await apiClient.loadPosts( communityId: communityId, page: page, + cursor: cursor, sort: sort, type: type, limit: limit, savedOnly: savedOnly, communityName: communityName ) - .map { PostModel(from: $0) } + + let posts = response.posts.map { PostModel(from: $0) } + return (posts, response.nextPage) } + + // swiftlint:enable function_parameter_count /// Loads a single post /// - Parameter postId: id of the post to load diff --git a/Mlem/Views/Shared/Links/User/UserLabelView.swift b/Mlem/Views/Shared/Links/User/UserLabelView.swift index 4a6526a8a..1137f0bb8 100644 --- a/Mlem/Views/Shared/Links/User/UserLabelView.swift +++ b/Mlem/Views/Shared/Links/User/UserLabelView.swift @@ -267,6 +267,7 @@ struct UserLinkViewPreview: PreviewProvider { creator: creator, community: community, creatorBannedFromCommunity: false, + creatorIsModerator: false, counts: postVotes, subscribed: .notSubscribed, saved: false, diff --git a/Mlem/Views/Tabs/Profile/User View.swift b/Mlem/Views/Tabs/Profile/User View.swift index f30e22c51..2c20e17f9 100644 --- a/Mlem/Views/Tabs/Profile/User View.swift +++ b/Mlem/Views/Tabs/Profile/User View.swift @@ -371,6 +371,7 @@ struct UserViewPreview: PreviewProvider { creator: creator, community: community, creatorBannedFromCommunity: false, + creatorIsModerator: false, counts: postVotes, subscribed: .notSubscribed, saved: false,