Pagination real impl on watchOS

This commit is contained in:
2025-10-29 22:21:11 +08:00
parent 82682cae9a
commit 9c3b228d02
4 changed files with 80 additions and 26 deletions

View File

@@ -207,3 +207,9 @@ struct NotificationResponse {
let total: Int let total: Int
let hasMore: Bool let hasMore: Bool
} }
struct ActivityResponse {
let activities: [SnActivity]
let hasMore: Bool
let nextCursor: String?
}

View File

@@ -12,7 +12,7 @@ import Foundation
class NetworkService { class NetworkService {
private let session = URLSession.shared private let session = URLSession.shared
func fetchActivities(filter: String, cursor: String? = nil, token: String, serverUrl: String) async throws -> [SnActivity] { func fetchActivities(filter: String, cursor: String? = nil, token: String, serverUrl: String) async throws -> ActivityResponse {
guard let baseURL = URL(string: serverUrl) else { guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL) throw URLError(.badURL)
} }
@@ -39,7 +39,12 @@ class NetworkService {
decoder.dateDecodingStrategy = .iso8601 decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode([SnActivity].self, from: data) let activities = try decoder.decode([SnActivity].self, from: data)
let hasMore = (activities.first?.type ?? "empty") != "empty"
let nextCursor = activities.isEmpty ? nil : activities.map { $0.createdAt }.min()?.ISO8601Format()
return ActivityResponse(activities: activities, hasMore: hasMore, nextCursor: nextCursor)
} }
func createPost(title: String, content: String, token: String, serverUrl: String) async throws { func createPost(title: String, content: String, token: String, serverUrl: String) async throws {

View File

@@ -14,12 +14,15 @@ import Combine
class ActivityViewModel: ObservableObject { class ActivityViewModel: ObservableObject {
@Published var activities: [SnActivity] = [] @Published var activities: [SnActivity] = []
@Published var isLoading = false @Published var isLoading = false
@Published var isLoadingMore = false
@Published var errorMessage: String? @Published var errorMessage: String?
@Published var hasMore = false
private let networkService = NetworkService() private let networkService = NetworkService()
let filter: String let filter: String
private var isMock = false private var isMock = false
private var hasFetched = false // Add this private var hasFetched = false
private var nextCursor: String?
init(filter: String, mockActivities: [SnActivity]? = nil) { init(filter: String, mockActivities: [SnActivity]? = nil) {
self.filter = filter self.filter = filter
@@ -30,21 +33,41 @@ class ActivityViewModel: ObservableObject {
} }
func fetchActivities(token: String, serverUrl: String) async { func fetchActivities(token: String, serverUrl: String) async {
if isMock || hasFetched { return } // Check hasFetched if isMock || hasFetched { return }
guard !isLoading else { return } guard !isLoading else { return }
isLoading = true isLoading = true
errorMessage = nil errorMessage = nil
hasFetched = true // Set hasFetched hasFetched = true
nextCursor = nil
do { do {
let fetchedActivities = try await networkService.fetchActivities(filter: filter, token: token, serverUrl: serverUrl) let response = try await networkService.fetchActivities(filter: filter, cursor: nil, token: token, serverUrl: serverUrl)
self.activities = fetchedActivities self.activities = response.activities
self.hasMore = response.hasMore
self.nextCursor = response.nextCursor
} catch { } catch {
self.errorMessage = error.localizedDescription self.errorMessage = error.localizedDescription
print("[watchOS] fetchActivities failed with error: \(error)") print("[watchOS] fetchActivities failed with error: \(error)")
hasFetched = false // Reset on error hasFetched = false
} }
isLoading = false isLoading = false
} }
func loadMoreActivities(token: String, serverUrl: String) async {
guard !isLoadingMore && hasMore && nextCursor != nil else { return }
isLoadingMore = true
do {
let response = try await networkService.fetchActivities(filter: filter, cursor: nextCursor, token: token, serverUrl: serverUrl)
self.activities.append(contentsOf: response.activities)
self.hasMore = response.hasMore
self.nextCursor = response.nextCursor
} catch {
self.errorMessage = error.localizedDescription
print("[watchOS] loadMoreActivities failed with error: \(error)")
}
isLoadingMore = false
}
} }

View File

@@ -33,7 +33,8 @@ struct ActivityListView: View {
} else if viewModel.activities.isEmpty { } else if viewModel.activities.isEmpty {
Text("No activities found.") Text("No activities found.")
} else { } else {
List(viewModel.activities) { activity in List {
ForEach(viewModel.activities) { activity in
switch activity.type { switch activity.type {
case "posts.new", "posts.new.replies": case "posts.new", "posts.new.replies":
if case .post(let post) = activity.data { if case .post(let post) = activity.data {
@@ -51,6 +52,25 @@ struct ActivityListView: View {
Text("Unknown activity type: \(activity.type)") Text("Unknown activity type: \(activity.type)")
} }
} }
if viewModel.hasMore {
if viewModel.isLoadingMore {
HStack {
Spacer()
ProgressView()
Spacer()
}
} else {
Button("Load More") {
Task {
if let token = appState.token, let serverUrl = appState.serverUrl {
await viewModel.loadMoreActivities(token: token, serverUrl: serverUrl)
}
}
}
.frame(maxWidth: .infinity)
}
}
}
} }
} }
.onAppear { .onAppear {