
- 更新Yana项目文档,调整适用版本至iOS 17,确保与最新开发环境兼容。 - 在多个视图中重构代码,优化状态管理和视图逻辑,提升用户体验。 - 添加默认初始化器以简化状态管理,确保各个Feature的状态一致性。 - 更新视图组件,移除不必要的硬编码,增强代码可读性和维护性。 - 修复多个视图中的逻辑错误,确保功能正常运行。
260 lines
9.9 KiB
Swift
260 lines
9.9 KiB
Swift
import Foundation
|
||
import ComposableArchitecture
|
||
|
||
@Reducer
|
||
struct AppSettingFeature {
|
||
@ObservableState
|
||
struct State: Equatable {
|
||
var nickname: String = ""
|
||
var avatarURL: String? = nil
|
||
var userInfo: UserInfo? = nil
|
||
var isLoadingUserInfo: Bool = false
|
||
var userInfoError: String? = nil
|
||
|
||
// WebView 导航状态
|
||
var showUserAgreement: Bool = false
|
||
var showPrivacyPolicy: Bool = false
|
||
var showDeactivateAccount: Bool = false
|
||
|
||
// 头像/昵称修改相关
|
||
var isUploadingAvatar: Bool = false
|
||
var avatarUploadError: String? = nil
|
||
var isEditingNickname: Bool = false
|
||
var nicknameInput: String = ""
|
||
var isUpdatingUser: Bool = false
|
||
var updateUserError: String? = nil
|
||
|
||
// 默认初始化器
|
||
init() {
|
||
// 默认初始化
|
||
}
|
||
|
||
// 带userInfo、avatarURL、nickname的init
|
||
init(nickname: String = "", avatarURL: String? = nil, userInfo: UserInfo? = nil) {
|
||
self.nickname = nickname
|
||
self.avatarURL = avatarURL
|
||
self.userInfo = userInfo
|
||
}
|
||
// 新增:TCA驱动图片选择弹窗
|
||
var showImagePicker: Bool = false
|
||
}
|
||
|
||
enum Action: Equatable {
|
||
case onAppear
|
||
case editNicknameTapped
|
||
case logoutTapped
|
||
case dismissTapped
|
||
|
||
// 用户信息相关
|
||
case loadUserInfo
|
||
case userInfoResponse(Result<UserInfo, APIError>)
|
||
|
||
// WebView 导航
|
||
case personalInfoPermissionsTapped
|
||
case helpTapped
|
||
case clearCacheTapped
|
||
case checkUpdatesTapped
|
||
case aboutUsTapped
|
||
case deactivateAccountTapped
|
||
|
||
// WebView 关闭
|
||
case userAgreementDismissed
|
||
case privacyPolicyDismissed
|
||
case deactivateAccountDismissed
|
||
|
||
// 头像/昵称修改
|
||
case avatarTapped
|
||
case avatarSelected(Data)
|
||
case avatarUploadResult(Result<String, APIError>)
|
||
case nicknameEditConfirmed(String)
|
||
case updateUser(Result<UpdateUserResponse, APIError>)
|
||
case nicknameInputChanged(String)
|
||
case nicknameEditAlert(Bool)
|
||
case testPushTapped
|
||
// 新增:TCA驱动图片选择弹窗
|
||
case setShowImagePicker(Bool)
|
||
}
|
||
|
||
@Dependency(\.apiService) var apiService
|
||
|
||
func reduce(into state: inout State, action: Action) -> Effect<Action> {
|
||
switch action {
|
||
case .onAppear:
|
||
return .send(.loadUserInfo)
|
||
|
||
case .editNicknameTapped:
|
||
// 预留编辑昵称逻辑
|
||
return .none
|
||
|
||
case .logoutTapped:
|
||
// 清理所有认证信息,并向上层发送登出事件
|
||
return .run { send in
|
||
await UserInfoManager.clearAllAuthenticationData()
|
||
// 向上层Feature传递登出事件(需在MainFeature处理)
|
||
// 这里直接返回.none,由MainFeature监听AppSettingFeature.Action.logoutTapped后处理
|
||
}
|
||
|
||
case .dismissTapped:
|
||
// 返回上一页,由 MainFeature 处理 navigationPath 的 pop 操作
|
||
return .none
|
||
|
||
case .loadUserInfo:
|
||
state.isLoadingUserInfo = true
|
||
state.userInfoError = nil
|
||
return .run { send in
|
||
// do {
|
||
if let userInfo = await UserInfoManager.getUserInfo() {
|
||
await send(.userInfoResponse(.success(userInfo)))
|
||
} else {
|
||
await send(.userInfoResponse(.failure(APIError.custom("用户信息不存在"))))
|
||
}
|
||
// } catch {
|
||
// let apiError: APIError
|
||
// if let error = error as? APIError {
|
||
// apiError = error
|
||
// } else {
|
||
// apiError = APIError.custom(error.localizedDescription)
|
||
// }
|
||
// await send(.userInfoResponse(.failure(apiError)))
|
||
// }
|
||
}
|
||
|
||
case let .userInfoResponse(.success(userInfo)):
|
||
state.userInfo = userInfo
|
||
state.nickname = userInfo.nick ?? ""
|
||
state.avatarURL = userInfo.avatar // 确保同步
|
||
state.isLoadingUserInfo = false
|
||
return .none
|
||
|
||
case let .userInfoResponse(.failure(error)):
|
||
state.userInfoError = error.localizedDescription
|
||
state.isLoadingUserInfo = false
|
||
return .none
|
||
|
||
case .personalInfoPermissionsTapped:
|
||
state.showPrivacyPolicy = true
|
||
return .none
|
||
|
||
case .helpTapped:
|
||
state.showUserAgreement = true
|
||
return .none
|
||
|
||
case .clearCacheTapped:
|
||
// 预留清除缓存逻辑
|
||
return .none
|
||
|
||
case .checkUpdatesTapped:
|
||
// 预留检查更新逻辑
|
||
return .none
|
||
|
||
case .aboutUsTapped:
|
||
// 预留关于我们逻辑
|
||
return .none
|
||
|
||
case .deactivateAccountTapped:
|
||
state.showDeactivateAccount = true
|
||
return .none
|
||
|
||
case .userAgreementDismissed:
|
||
state.showUserAgreement = false
|
||
return .none
|
||
|
||
case .privacyPolicyDismissed:
|
||
state.showPrivacyPolicy = false
|
||
return .none
|
||
|
||
case .deactivateAccountDismissed:
|
||
state.showDeactivateAccount = false
|
||
return .none
|
||
|
||
case .avatarTapped:
|
||
// 触发头像选择器
|
||
return .none
|
||
case let .avatarSelected(imageData):
|
||
state.isUploadingAvatar = true
|
||
state.avatarUploadError = nil
|
||
return .run { [avatarData = imageData] send in
|
||
guard let uiImage = UIImage(data: avatarData) else {
|
||
await send(.avatarUploadResult(.failure(APIError.custom("图片格式错误"))))
|
||
return
|
||
}
|
||
// 上传图片到腾讯云
|
||
if let url = await COSManager.shared.uploadUIImage(uiImage, apiService: apiService) {
|
||
await send(.avatarUploadResult(.success(url)))
|
||
} else {
|
||
await send(.avatarUploadResult(.failure(APIError.custom("头像上传失败"))))
|
||
}
|
||
}
|
||
case let .avatarUploadResult(.success(url)):
|
||
state.isUploadingAvatar = false
|
||
// 调用 updateUser API,仅传 avatar
|
||
state.isUpdatingUser = true
|
||
state.updateUserError = nil
|
||
guard let userInfo = state.userInfo else { return .none }
|
||
// 头像上传后,先临时更新本地avatarURL,提升UI响应
|
||
state.avatarURL = url
|
||
return .run { send in
|
||
let ticket = await UserInfoManager.getCurrentUserTicket() ?? ""
|
||
let req = UpdateUserRequest(avatar: url, nick: nil, uid: userInfo.uid ?? 0, ticket: ticket)
|
||
do {
|
||
let resp: UpdateUserResponse = try await apiService.request(req)
|
||
await send(.updateUser(.success(resp)))
|
||
} catch {
|
||
let apiError = error as? APIError ?? APIError.custom(error.localizedDescription)
|
||
await send(.updateUser(.failure(apiError)))
|
||
}
|
||
}
|
||
case let .avatarUploadResult(.failure(error)):
|
||
state.isUploadingAvatar = false
|
||
state.avatarUploadError = error.localizedDescription
|
||
return .none
|
||
case .nicknameEditAlert(let show):
|
||
state.isEditingNickname = show
|
||
state.nicknameInput = state.nickname
|
||
return .none
|
||
case .nicknameInputChanged(let text):
|
||
state.nicknameInput = String(text.prefix(15))
|
||
return .none
|
||
case .nicknameEditConfirmed(let newNick):
|
||
guard let userInfo = state.userInfo else { return .none }
|
||
state.isUpdatingUser = true
|
||
state.updateUserError = nil
|
||
return .run { send in
|
||
let ticket = await UserInfoManager.getCurrentUserTicket() ?? ""
|
||
let req = UpdateUserRequest(avatar: nil, nick: newNick, uid: userInfo.uid ?? 0, ticket: ticket)
|
||
do {
|
||
let resp: UpdateUserResponse = try await apiService.request(req)
|
||
await send(.updateUser(.success(resp)))
|
||
} catch {
|
||
let apiError = error as? APIError ?? APIError.custom(error.localizedDescription)
|
||
await send(.updateUser(.failure(apiError)))
|
||
}
|
||
}
|
||
case .updateUser(.success(_)):
|
||
state.isUpdatingUser = false
|
||
// 不直接用 resp.data,触发拉取完整 userinfo,延迟1秒
|
||
if let uid = state.userInfo?.uid {
|
||
return .run { send in
|
||
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||
if let newUser = await UserInfoManager.fetchUserInfoFromServer(uid: String(uid), apiService: apiService) {
|
||
await send(.userInfoResponse(.success(newUser)))
|
||
} else {
|
||
await send(.userInfoResponse(.failure(APIError.custom("获取最新用户信息失败"))))
|
||
}
|
||
}
|
||
}
|
||
state.isEditingNickname = false
|
||
return .none
|
||
case let .updateUser(.failure(error)):
|
||
state.isUpdatingUser = false
|
||
state.updateUserError = error.localizedDescription
|
||
return .none
|
||
case .testPushTapped:
|
||
return .none
|
||
case .setShowImagePicker(let show):
|
||
state.showImagePicker = show
|
||
return .none
|
||
}
|
||
}
|
||
}
|