
- 在FeedListFeature中添加返回语句,确保认证信息准备好后立即获取动态。 - 移除FeedListView中的冗余注释,提升代码整洁性。 - 更新MainView中的方法名称,从contentView更改为mainContentView,增强代码可读性。
314 lines
14 KiB
Swift
314 lines
14 KiB
Swift
import Foundation
|
||
import ComposableArchitecture
|
||
|
||
@Reducer
|
||
struct FeedListFeature {
|
||
@Dependency(\.apiService) var apiService
|
||
@ObservableState
|
||
struct State: Equatable {
|
||
var isFirstLoad: Bool = true
|
||
var feeds: [Feed] = [] // 预留 feed 内容
|
||
var isLoading: Bool = false
|
||
var error: String? = nil
|
||
var isEditFeedPresented: Bool = false // 新增:控制 CreateFeedView 弹窗
|
||
// 新增:动态内容
|
||
var moments: [MomentsInfo] = []
|
||
// 新增:只加载一次标志
|
||
var isLoaded: Bool = false
|
||
// 分页相关
|
||
var currentPage: Int = 1
|
||
var hasMore: Bool = true
|
||
var isLoadingMore: Bool = false
|
||
// 新增:DetailView相关状态
|
||
var showDetail: Bool = false
|
||
var selectedMoment: MomentsInfo?
|
||
// 新增:点赞相关状态
|
||
var likeLoadingDynamicIds: Set<Int> = []
|
||
|
||
init() {
|
||
// 默认初始化
|
||
}
|
||
}
|
||
|
||
enum Action: Equatable {
|
||
case onAppear
|
||
case reload
|
||
case loadMore
|
||
case loadMoreResponse(TaskResult<MomentsLatestResponse>)
|
||
case editFeedButtonTapped // 新增:点击 add 按钮
|
||
case editFeedDismissed // 新增:关闭编辑页
|
||
case testButtonTapped // 新增:点击测试按钮
|
||
// 新增:动态内容相关
|
||
case fetchFeeds
|
||
case fetchFeedsResponse(TaskResult<MomentsLatestResponse>)
|
||
// 新增:DetailView相关Action
|
||
case showDetail(MomentsInfo)
|
||
case detailDismissed
|
||
// 新增:点赞相关Action
|
||
case likeDynamic(Int, Int, Int, Int) // dynamicId, uid, likedUid, worldId
|
||
case likeResponse(TaskResult<LikeDynamicResponse>, dynamicId: Int, loadingId: UUID?)
|
||
// 新增:CreateFeed发布成功通知
|
||
case createFeedPublishSuccess
|
||
// 预留后续 Action
|
||
case checkAuthAndLoad
|
||
}
|
||
|
||
func reduce(into state: inout State, action: Action) -> Effect<Action> {
|
||
switch action {
|
||
case .onAppear:
|
||
guard state.isFirstLoad else { return .none }
|
||
state.isFirstLoad = false
|
||
debugInfoSync("📱 FeedListFeature onAppear")
|
||
// 直接触发认证检查和数据加载
|
||
return .send(.checkAuthAndLoad)
|
||
|
||
case .checkAuthAndLoad:
|
||
// 新增:认证检查和数据加载
|
||
return .run { send in
|
||
// 检查认证信息是否已保存
|
||
let accountModel = await UserInfoManager.getAccountModel()
|
||
if accountModel?.uid != nil {
|
||
debugInfoSync("✅ FeedListFeature: 认证信息已准备好,开始获取动态")
|
||
await send(.fetchFeeds)
|
||
return
|
||
} else {
|
||
debugInfoSync("⏳ FeedListFeature: 认证信息未准备好,等待...")
|
||
// 增加等待时间和重试次数
|
||
for attempt in 1...3 {
|
||
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5秒
|
||
let retryAccountModel = await UserInfoManager.getAccountModel()
|
||
if retryAccountModel?.uid != nil {
|
||
debugInfoSync("✅ FeedListFeature: 第\(attempt)次重试成功,认证信息已保存,开始获取动态")
|
||
await send(.fetchFeeds)
|
||
return
|
||
} else {
|
||
debugInfoSync("⏳ FeedListFeature: 第\(attempt)次重试,认证信息仍未准备好")
|
||
}
|
||
}
|
||
debugInfoSync("❌ FeedListFeature: 多次重试后认证信息仍未准备好")
|
||
}
|
||
}
|
||
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:
|
||
state.isLoading = true
|
||
state.error = nil
|
||
debugInfoSync("🔄 FeedListFeature: 开始获取动态")
|
||
// 发起 API 请求
|
||
return .run { [apiService] send in
|
||
await send(.fetchFeedsResponse(TaskResult {
|
||
let request = LatestDynamicsRequest(dynamicId: "", pageSize: 20, types: [.text, .picture])
|
||
debugInfoSync("📡 FeedListFeature: 发送请求: \(request.endpoint)")
|
||
debugInfoSync(" 参数: dynamicId=\(request.dynamicId), pageSize=\(request.pageSize)")
|
||
return try await apiService.request(request)
|
||
}))
|
||
}
|
||
case let .fetchFeedsResponse(.success(response)):
|
||
state.isLoading = false
|
||
debugInfoSync("✅ FeedListFeature: API 请求成功")
|
||
debugInfoSync(" 响应码: \(response.code)")
|
||
debugInfoSync(" 消息: \(response.message)")
|
||
debugInfoSync(" 数据数量: \(response.data?.dynamicList.count ?? 0)")
|
||
if let list = response.data?.dynamicList {
|
||
state.moments = list
|
||
state.error = nil
|
||
state.currentPage = 1
|
||
state.hasMore = (list.count >= 20)
|
||
debugInfoSync("✅ FeedListFeature: 数据加载成功")
|
||
debugInfoSync(" 动态数量: \(list.count)")
|
||
debugInfoSync(" 是否有更多: \(state.hasMore)")
|
||
} else {
|
||
state.moments = []
|
||
state.error = response.message
|
||
state.hasMore = false
|
||
debugErrorSync("❌ FeedListFeature: 数据为空")
|
||
debugErrorSync(" 错误消息: \(response.message)")
|
||
}
|
||
return .none
|
||
case let .fetchFeedsResponse(.failure(error)):
|
||
state.isLoading = false
|
||
state.moments = []
|
||
state.error = error.localizedDescription
|
||
state.hasMore = false
|
||
debugErrorSync("❌ FeedListFeature: API 请求失败")
|
||
debugErrorSync(" 错误: \(error.localizedDescription)")
|
||
return .none
|
||
case .editFeedButtonTapped:
|
||
state.isEditFeedPresented = true
|
||
return .none
|
||
case .editFeedDismissed:
|
||
state.isEditFeedPresented = false
|
||
return .none
|
||
case .createFeedPublishSuccess:
|
||
// CreateFeed发布成功,触发刷新并关闭编辑页面
|
||
return .merge(
|
||
.send(.reload),
|
||
.send(.editFeedDismissed)
|
||
)
|
||
case .testButtonTapped:
|
||
debugInfoSync("[LOG] FeedListFeature testButtonTapped")
|
||
return .none
|
||
case let .showDetail(moment):
|
||
state.selectedMoment = moment
|
||
state.showDetail = true
|
||
return .none
|
||
case .detailDismissed:
|
||
state.showDetail = false
|
||
state.selectedMoment = nil
|
||
return .none
|
||
case let .likeDynamic(dynamicId, uid, likedUid, worldId):
|
||
// 添加loading状态
|
||
state.likeLoadingDynamicIds.insert(dynamicId)
|
||
|
||
// 找到对应的动态并获取当前点赞状态
|
||
guard let index = state.moments.firstIndex(where: { $0.dynamicId == dynamicId }) else {
|
||
// 找不到对应的动态,显示错误信息
|
||
setAPILoadingErrorSync(UUID(), errorMessage: "找不到对应的动态")
|
||
state.likeLoadingDynamicIds.remove(dynamicId)
|
||
return .none
|
||
}
|
||
|
||
let currentMoment = state.moments[index]
|
||
let status = currentMoment.isLike ? 0 : 1 // 0: 取消点赞, 1: 点赞
|
||
|
||
let request = LikeDynamicRequest(
|
||
dynamicId: dynamicId,
|
||
uid: uid,
|
||
status: status,
|
||
likedUid: likedUid,
|
||
worldId: worldId
|
||
)
|
||
|
||
return .run { [apiService] send in
|
||
let loadingId = await APILoadingManager.shared.startLoading(
|
||
shouldShowLoading: request.shouldShowLoading,
|
||
shouldShowError: request.shouldShowError
|
||
)
|
||
do {
|
||
let response: LikeDynamicResponse = try await apiService.request(request)
|
||
await send(.likeResponse(.success(response), dynamicId: dynamicId, loadingId: loadingId))
|
||
} catch {
|
||
await send(.likeResponse(.failure(error), dynamicId: dynamicId, loadingId: loadingId))
|
||
}
|
||
}
|
||
|
||
case let .likeResponse(.success(response), dynamicId, loadingId):
|
||
state.likeLoadingDynamicIds.remove(dynamicId)
|
||
if let loadingId = loadingId {
|
||
if let data = response.data, let success = data.success, success {
|
||
Task { @MainActor in
|
||
APILoadingManager.shared.finishLoading(loadingId)
|
||
}
|
||
if let index = state.moments.firstIndex(where: { $0.dynamicId == dynamicId }) {
|
||
let currentMoment = state.moments[index]
|
||
let newLikeState = !currentMoment.isLike
|
||
let updatedMoment = MomentsInfo(
|
||
dynamicId: currentMoment.dynamicId,
|
||
uid: currentMoment.uid,
|
||
nick: currentMoment.nick,
|
||
avatar: currentMoment.avatar,
|
||
type: currentMoment.type,
|
||
content: currentMoment.content,
|
||
likeCount: data.likeCount ?? currentMoment.likeCount,
|
||
isLike: newLikeState,
|
||
commentCount: currentMoment.commentCount,
|
||
publishTime: currentMoment.publishTime,
|
||
worldId: currentMoment.worldId,
|
||
status: currentMoment.status,
|
||
playCount: currentMoment.playCount,
|
||
dynamicResList: currentMoment.dynamicResList,
|
||
gender: currentMoment.gender,
|
||
squareTop: currentMoment.squareTop,
|
||
topicTop: currentMoment.topicTop,
|
||
newUser: currentMoment.newUser,
|
||
defUser: currentMoment.defUser,
|
||
scene: currentMoment.scene,
|
||
userVipInfoVO: currentMoment.userVipInfoVO,
|
||
headwearPic: currentMoment.headwearPic,
|
||
headwearEffect: currentMoment.headwearEffect,
|
||
headwearType: currentMoment.headwearType,
|
||
headwearName: currentMoment.headwearName,
|
||
headwearId: currentMoment.headwearId,
|
||
experLevelPic: currentMoment.experLevelPic,
|
||
charmLevelPic: currentMoment.charmLevelPic,
|
||
isCustomWord: currentMoment.isCustomWord,
|
||
labelList: currentMoment.labelList
|
||
)
|
||
state.moments[index] = updatedMoment
|
||
}
|
||
} else {
|
||
let errorMessage = response.message.isEmpty ? "点赞失败,请重试" : response.message
|
||
setAPILoadingErrorSync(loadingId, errorMessage: errorMessage)
|
||
}
|
||
}
|
||
return .none
|
||
|
||
case let .likeResponse(.failure(error), dynamicId, loadingId):
|
||
state.likeLoadingDynamicIds.remove(dynamicId)
|
||
if let loadingId = loadingId {
|
||
setAPILoadingErrorSync(loadingId, errorMessage: error.localizedDescription)
|
||
}
|
||
return .none
|
||
}
|
||
}
|
||
}
|
||
|
||
// Feed 数据模型占位,后续可替换为真实模型
|
||
enum Feed: Equatable, Identifiable {
|
||
case placeholder(id: UUID = UUID())
|
||
var id: UUID {
|
||
switch self {
|
||
case .placeholder(let id): return id
|
||
}
|
||
}
|
||
}
|