Files
e-party-iOS/yana/Features/MeFeature.swift
edwinQQQ 4ff92c8c4d feat: 修复MainView Tab切换问题并优化MeView逻辑
- 新增MainView Tab切换问题分析文档,详细描述问题原因及解决方案。
- 优化BottomTabView的绑定逻辑,简化状态管理,确保Tab切换时状态正确更新。
- 在MeView中实现用户信息加载逻辑调整,确保动态列表仅在首次进入时加载,并添加错误处理视图。
- 创建EmptyStateView组件,提供统一的空状态展示和重试功能。
- 增强调试信息输出,便于后续问题排查和用户体验提升。
2025-08-05 15:51:07 +08:00

266 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import ComposableArchitecture
@Reducer
struct MeFeature {
@Dependency(\.apiService) var apiService
@ObservableState
struct State: Equatable {
var isFirstLoad: Bool = true
var isUserInfoFirstLoad: Bool = true
var userInfo: UserInfo?
var isLoadingUserInfo: Bool = false
var userInfoError: String?
var moments: [MomentsInfo] = []
var isLoadingMoments: Bool = false
var momentsError: String?
var hasMore: Bool = true
var isLoadingMore: Bool = false
var isRefreshing: Bool = false
var page: Int = 1
var pageSize: Int = 20
var uid: Int = 0
// IDnil
var displayUID: Int?
// DetailView
var showDetail: Bool = false
var selectedMoment: MomentsInfo?
//
var showErrorView: Bool = false
var momentsFirstLoadFailed: Bool = false
init(displayUID: Int? = nil) {
self.displayUID = displayUID
// displayUIDniluid
if let displayUID = displayUID {
self.uid = displayUID
}
}
// ID
var effectiveUID: Int {
return displayUID ?? uid
}
//
var isDisplayingOtherUser: Bool {
return displayUID != nil && displayUID != uid
}
}
enum Action: Equatable {
case onAppear
case refresh
case loadMore
case loadUserInfo
case retryMoments
case userInfoResponse(Result<UserInfo, APIError>)
case momentsResponse(Result<MyMomentsResponse, APIError>)
//
case settingButtonTapped
// DetailViewAction
case showDetail(MomentsInfo)
case detailDismissed
}
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .onAppear:
debugInfoSync("\n📱 MeFeature onAppear")
debugInfoSync(" isFirstLoad: \(state.isFirstLoad)")
debugInfoSync(" isUserInfoFirstLoad: \(state.isUserInfoFirstLoad)")
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
//
let userInfoEffect = fetchUserInfo(uid: state.effectiveUID)
//
if state.isFirstLoad {
state.isFirstLoad = false
return .merge(
userInfoEffect,
fetchMoments(uid: state.effectiveUID, page: 1, pageSize: state.pageSize)
)
} else {
return userInfoEffect
}
case .refresh:
guard state.effectiveUID > 0 else { return .none }
debugInfoSync("\n🔄 MeFeature refresh")
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
state.isRefreshing = true
state.page = 1
state.hasMore = true
state.userInfoError = nil //
state.momentsError = nil //
state.showErrorView = false //
return .merge(
fetchUserInfo(uid: state.effectiveUID),
fetchMoments(uid: state.effectiveUID, page: 1, pageSize: state.pageSize)
)
case .loadUserInfo:
guard state.effectiveUID > 0 else { return .none }
debugInfoSync("\n👤 MeFeature loadUserInfo")
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
return fetchUserInfo(uid: state.effectiveUID)
case .retryMoments:
guard state.effectiveUID > 0 else { return .none }
debugInfoSync("\n🔄 MeFeature retryMoments")
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
state.showErrorView = false //
state.momentsFirstLoadFailed = false
state.isLoadingMoments = true
state.page = 1
state.hasMore = true
state.momentsError = nil
return fetchMoments(uid: state.effectiveUID, page: 1, pageSize: state.pageSize)
case .loadMore:
guard state.effectiveUID > 0, state.hasMore, !state.isLoadingMore else { return .none }
state.isLoadingMore = true
return fetchMoments(uid: state.effectiveUID, page: state.page + 1, pageSize: state.pageSize)
case let .userInfoResponse(result):
state.isLoadingUserInfo = false
state.isRefreshing = false
switch result {
case let .success(userInfo):
state.userInfo = userInfo
state.userInfoError = nil
case let .failure(error):
state.userInfoError = error.localizedDescription
}
return .none
case let .momentsResponse(result):
state.isLoadingMoments = false
state.isLoadingMore = false
state.isRefreshing = false
switch result {
case let .success(resp):
let myMoments = resp.data ?? []
// MyMomentInfo MomentsInfo
let newMoments = myMoments.map { myMoment in
var momentsInfo = myMoment.toMomentsInfo()
//
if let userInfo = state.userInfo {
// 使
momentsInfo = MomentsInfo(
dynamicId: momentsInfo.dynamicId,
uid: momentsInfo.uid,
nick: userInfo.nick ?? userInfo.nickname ?? "未知用户",
avatar: userInfo.avatar ?? "",
type: momentsInfo.type,
content: momentsInfo.content,
likeCount: momentsInfo.likeCount,
isLike: momentsInfo.isLike,
commentCount: momentsInfo.commentCount,
publishTime: momentsInfo.publishTime,
worldId: momentsInfo.worldId,
status: momentsInfo.status,
playCount: momentsInfo.playCount,
dynamicResList: momentsInfo.dynamicResList,
gender: userInfo.gender,
squareTop: momentsInfo.squareTop,
topicTop: momentsInfo.topicTop,
newUser: userInfo.newUser,
defUser: userInfo.defUser,
scene: momentsInfo.scene,
userVipInfoVO: nil, // UserVipInfoVO UserVipInfo nil
headwearPic: userInfo.userHeadwear?.pic,
headwearEffect: userInfo.userHeadwear?.effect,
headwearType: userInfo.userHeadwear?.type,
headwearName: userInfo.userHeadwear?.headwearName,
headwearId: userInfo.userHeadwear?.headwearId,
experLevelPic: userInfo.userLevelVo?.experUrl,
charmLevelPic: userInfo.userLevelVo?.charmUrl,
isCustomWord: momentsInfo.isCustomWord,
labelList: momentsInfo.labelList
)
}
return momentsInfo
}
if state.page == 1 {
state.moments = newMoments
} else {
state.moments += newMoments
}
state.hasMore = newMoments.count == state.pageSize
if state.hasMore { state.page += 1 }
state.momentsError = nil
state.showErrorView = false //
state.momentsFirstLoadFailed = false
debugInfoSync("✅ 我的动态加载成功")
debugInfoSync(" 加载数量: \(newMoments.count)")
debugInfoSync(" 总数量: \(state.moments.count)")
debugInfoSync(" 是否有更多: \(state.hasMore)")
case let .failure(error):
state.momentsError = error.localizedDescription
//
if state.page == 1 {
state.showErrorView = true
state.momentsFirstLoadFailed = true
}
debugErrorSync("❌ 我的动态加载失败: \(error.localizedDescription)")
}
return .none
case .settingButtonTapped:
// MainFeature
return .none
case .showDetail(let moment):
state.selectedMoment = moment
state.showDetail = true
return .none
case .detailDismissed:
state.showDetail = false
state.selectedMoment = nil
return .none
}
}
private func fetchUserInfo(uid: Int) -> Effect<Action> {
.run { send in
debugInfoSync("👤 开始获取用户信息")
debugInfoSync(" UID: \(uid)")
if let userInfo = await UserInfoManager.fetchUserInfoFromServer(uid: String(uid), apiService: apiService) {
debugInfoSync("✅ 用户信息获取成功")
debugInfoSync(" 昵称: \(userInfo.nick ?? userInfo.nickname ?? "未知")")
debugInfoSync(" 头像: \(userInfo.avatar ?? "")")
await send(.userInfoResponse(.success(userInfo)))
} else {
debugErrorSync("❌ 用户信息获取失败")
await send(.userInfoResponse(.failure(.noData)))
}
}
}
private func fetchMoments(uid: Int, page: Int, pageSize: Int) -> Effect<Action> {
.run { send in
debugInfoSync("🔄 开始获取我的动态")
debugInfoSync(" UID: \(uid)")
debugInfoSync(" 页码: \(page)")
debugInfoSync(" 页大小: \(pageSize)")
do {
let req = GetMyDynamicRequest(fromUid: uid, uid: uid, page: page, pageSize: pageSize)
debugInfoSync("📡 发送请求: \(req.endpoint)")
debugInfoSync(" 参数: fromUid=\(uid), uid=\(uid), page=\(page), pageSize=\(pageSize)")
let resp = try await apiService.request(req)
debugInfoSync("✅ API 请求成功")
debugInfoSync(" 响应码: \(resp.code)")
debugInfoSync(" 消息: \(resp.message)")
debugInfoSync(" 数据数量: \(resp.data?.count ?? 0)")
await send(.momentsResponse(.success(resp)))
} catch {
debugErrorSync("❌ API 请求失败: \(error.localizedDescription)")
if let apiError = error as? APIError {
debugErrorSync(" API错误类型: \(apiError)")
}
await send(.momentsResponse(.failure(error as? APIError ?? .unknown(error.localizedDescription))))
}
}
}
}