feat: 实现动态内容的分页加载与刷新功能

- 在FeedListFeature中新增分页相关状态管理,支持上拉加载更多和下拉刷新功能,提升用户体验。
- 在FeedListView中实现上拉加载更多的触发逻辑和加载指示器,优化动态内容展示。
This commit is contained in:
edwinQQQ
2025-07-22 17:43:24 +08:00
parent 60b3f824be
commit 4eb01bde7c
2 changed files with 77 additions and 6 deletions

View File

@@ -13,12 +13,17 @@ struct FeedListFeature {
var moments: [MomentsInfo] = [] var moments: [MomentsInfo] = []
// //
var isLoaded: Bool = false var isLoaded: Bool = false
//
var currentPage: Int = 1
var hasMore: Bool = true
var isLoadingMore: Bool = false
} }
enum Action: Equatable { enum Action: Equatable {
case onAppear case onAppear
case reload case reload
case loadMore case loadMore
case loadMoreResponse(TaskResult<MomentsLatestResponse>)
case editFeedButtonTapped // add case editFeedButtonTapped // add
case editFeedDismissed // case editFeedDismissed //
// //
@@ -34,6 +39,57 @@ struct FeedListFeature {
guard !state.isLoaded else { return .none } guard !state.isLoaded else { return .none }
state.isLoaded = true state.isLoaded = true
return .send(.fetchFeeds) return .send(.fetchFeeds)
case .reload:
//
state.isLoading = true
state.error = nil
state.currentPage = 1
state.hasMore = true
state.isLoaded = true
return .run { [apiService] send in
await send(.fetchFeedsResponse(TaskResult {
let request = LatestDynamicsRequest(dynamicId: "", pageSize: 20, types: [.text, .picture])
return try await apiService.request(request)
}))
}
case .loadMore:
//
guard state.hasMore, !state.isLoadingMore, !state.isLoading else { return .none }
state.isLoadingMore = true
let lastDynamicId: String = {
if let last = state.moments.last {
return String(last.dynamicId)
} else {
return ""
}
}()
return .run { [apiService] send in
await send(.loadMoreResponse(TaskResult {
let request = LatestDynamicsRequest(dynamicId: lastDynamicId, pageSize: 20, types: [.text, .picture])
return try await apiService.request(request)
}))
}
case let .loadMoreResponse(.success(response)):
state.isLoadingMore = false
if let list = response.data?.dynamicList {
if list.isEmpty {
state.hasMore = false
} else {
state.moments.append(contentsOf: list)
state.currentPage += 1
state.hasMore = (list.count >= 20)
}
state.error = nil
} else {
state.hasMore = false
state.error = response.message
}
return .none
case let .loadMoreResponse(.failure(error)):
state.isLoadingMore = false
state.hasMore = false
state.error = error.localizedDescription
return .none
case .fetchFeeds: case .fetchFeeds:
state.isLoading = true state.isLoading = true
state.error = nil state.error = nil
@@ -49,21 +105,19 @@ struct FeedListFeature {
if let list = response.data?.dynamicList { if let list = response.data?.dynamicList {
state.moments = list state.moments = list
state.error = nil state.error = nil
state.currentPage = 1
state.hasMore = (list.count >= 20)
} else { } else {
state.moments = [] state.moments = []
state.error = response.message state.error = response.message
state.hasMore = false
} }
return .none return .none
case let .fetchFeedsResponse(.failure(error)): case let .fetchFeedsResponse(.failure(error)):
state.isLoading = false state.isLoading = false
state.moments = [] state.moments = []
state.error = error.localizedDescription state.error = error.localizedDescription
return .none state.hasMore = false
case .reload:
//
return .none
case .loadMore:
//
return .none return .none
case .editFeedButtonTapped: case .editFeedButtonTapped:
state.isEditFeedPresented = true state.isEditFeedPresented = true

View File

@@ -69,12 +69,29 @@ struct FeedListView: View {
LazyVStack(spacing: 16) { LazyVStack(spacing: 16) {
ForEach(Array(viewStore.moments.enumerated()), id: \ .element.dynamicId) { index, moment in ForEach(Array(viewStore.moments.enumerated()), id: \ .element.dynamicId) { index, moment in
OptimizedDynamicCardView(moment: moment, allMoments: viewStore.moments, currentIndex: index) OptimizedDynamicCardView(moment: moment, allMoments: viewStore.moments, currentIndex: index)
//
if index == viewStore.moments.count - 1, viewStore.hasMore, !viewStore.isLoadingMore {
Color.clear
.frame(height: 1)
.onAppear {
viewStore.send(.loadMore)
}
}
}
//
if viewStore.isLoadingMore {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.padding(.vertical, 8)
} }
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.top, 10) .padding(.top, 10)
.padding(.bottom, 20) .padding(.bottom, 20)
} }
.refreshable {
viewStore.send(.reload)
}
} }
Spacer() Spacer()
} }