✨ Pagination real impl on watchOS
This commit is contained in:
@@ -207,3 +207,9 @@ struct NotificationResponse {
|
||||
let total: Int
|
||||
let hasMore: Bool
|
||||
}
|
||||
|
||||
struct ActivityResponse {
|
||||
let activities: [SnActivity]
|
||||
let hasMore: Bool
|
||||
let nextCursor: String?
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import Foundation
|
||||
class NetworkService {
|
||||
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 {
|
||||
throw URLError(.badURL)
|
||||
}
|
||||
@@ -29,17 +29,22 @@ class NetworkService {
|
||||
var request = URLRequest(url: components.url!)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||
|
||||
|
||||
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
|
||||
|
||||
let (data, _) = try await session.data(for: request)
|
||||
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .iso8601
|
||||
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 {
|
||||
|
||||
@@ -14,12 +14,15 @@ import Combine
|
||||
class ActivityViewModel: ObservableObject {
|
||||
@Published var activities: [SnActivity] = []
|
||||
@Published var isLoading = false
|
||||
@Published var isLoadingMore = false
|
||||
@Published var errorMessage: String?
|
||||
@Published var hasMore = false
|
||||
|
||||
private let networkService = NetworkService()
|
||||
let filter: String
|
||||
private var isMock = false
|
||||
private var hasFetched = false // Add this
|
||||
private var hasFetched = false
|
||||
private var nextCursor: String?
|
||||
|
||||
init(filter: String, mockActivities: [SnActivity]? = nil) {
|
||||
self.filter = filter
|
||||
@@ -30,21 +33,41 @@ class ActivityViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func fetchActivities(token: String, serverUrl: String) async {
|
||||
if isMock || hasFetched { return } // Check hasFetched
|
||||
if isMock || hasFetched { return }
|
||||
guard !isLoading else { return }
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
hasFetched = true // Set hasFetched
|
||||
hasFetched = true
|
||||
nextCursor = nil
|
||||
|
||||
do {
|
||||
let fetchedActivities = try await networkService.fetchActivities(filter: filter, token: token, serverUrl: serverUrl)
|
||||
self.activities = fetchedActivities
|
||||
let response = try await networkService.fetchActivities(filter: filter, cursor: nil, token: token, serverUrl: serverUrl)
|
||||
self.activities = response.activities
|
||||
self.hasMore = response.hasMore
|
||||
self.nextCursor = response.nextCursor
|
||||
} catch {
|
||||
self.errorMessage = error.localizedDescription
|
||||
print("[watchOS] fetchActivities failed with error: \(error)")
|
||||
hasFetched = false // Reset on error
|
||||
hasFetched = 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import SwiftUI
|
||||
struct ActivityListView: View {
|
||||
@StateObject private var viewModel: ActivityViewModel
|
||||
@EnvironmentObject var appState: AppState
|
||||
|
||||
|
||||
init(filter: String, mockActivities: [SnActivity]? = nil) {
|
||||
_viewModel = StateObject(wrappedValue: ActivityViewModel(filter: filter, mockActivities: mockActivities))
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if viewModel.isLoading {
|
||||
@@ -33,22 +33,42 @@ struct ActivityListView: View {
|
||||
} else if viewModel.activities.isEmpty {
|
||||
Text("No activities found.")
|
||||
} else {
|
||||
List(viewModel.activities) { activity in
|
||||
switch activity.type {
|
||||
case "posts.new", "posts.new.replies":
|
||||
if case .post(let post) = activity.data {
|
||||
NavigationLink(
|
||||
destination: PostDetailView(post: post).environmentObject(appState)
|
||||
) {
|
||||
PostRowView(post: post)
|
||||
List {
|
||||
ForEach(viewModel.activities) { activity in
|
||||
switch activity.type {
|
||||
case "posts.new", "posts.new.replies":
|
||||
if case .post(let post) = activity.data {
|
||||
NavigationLink(
|
||||
destination: PostDetailView(post: post).environmentObject(appState)
|
||||
) {
|
||||
PostRowView(post: post)
|
||||
}
|
||||
}
|
||||
case "discovery":
|
||||
if case .discovery(let discoveryData) = activity.data {
|
||||
DiscoveryView(discoveryData: discoveryData)
|
||||
}
|
||||
default:
|
||||
Text("Unknown activity type: \(activity.type)")
|
||||
}
|
||||
case "discovery":
|
||||
if case .discovery(let discoveryData) = activity.data {
|
||||
DiscoveryView(discoveryData: discoveryData)
|
||||
}
|
||||
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)
|
||||
}
|
||||
default:
|
||||
Text("Unknown activity type: \(activity.type)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user