Files
e-party-iOS/yana/Features/AppSettingFeature.swift
edwinQQQ 01779a95c8 feat: 更新AppSettingFeature以增强用户体验和本地化支持
- 在AppSettingFeature中新增登出确认和关于我们弹窗的状态和Action。
- 更新AppSettingView以支持登出确认和关于我们弹窗的逻辑。
- 替换多个视图中的NSLocalizedString为LocalizedString,提升本地化一致性。
- 在Localizable.strings中新增相关本地化文本,确保多语言支持。
2025-07-31 18:29:03 +08:00

305 lines
11 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
//
enum AppImageSource: Equatable {
case camera
case photoLibrary
}
@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() {
//
}
// userInfoavatarURLnicknameinit
init(nickname: String = "", avatarURL: String? = nil, userInfo: UserInfo? = nil) {
self.nickname = nickname
self.avatarURL = avatarURL
self.userInfo = userInfo
}
// TCA
var showImagePicker: Bool = false
// ActionSheet
var showImageSourceActionSheet: Bool = false
//
var selectedImageSource: AppImageSource? = nil
//
var showLogoutConfirmation: Bool = false
var showAboutUs: 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)
// ActionSheet
case setShowImageSourceActionSheet(Bool)
//
case selectImageSource(AppImageSource)
//
case showLogoutConfirmation(Bool)
case showAboutUs(Bool)
case logoutConfirmed
}
@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:
//
state.showLogoutConfirmation = true
return .none
case .logoutConfirmed:
//
return .run { send in
await UserInfoManager.clearAllAuthenticationData()
// FeatureMainFeature
// .noneMainFeatureAppSettingFeature.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:
state.showAboutUs = true
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 }
// avatarURLUI
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 userinfo1
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
case .setShowImageSourceActionSheet(let show):
state.showImageSourceActionSheet = show
return .none
case .selectImageSource(let source):
state.showImageSourceActionSheet = false
state.showImagePicker = true
state.selectedImageSource = source
// ImagePickerWithPreviewView
return .none
case .showLogoutConfirmation(let show):
state.showLogoutConfirmation = show
return .none
case .showAboutUs(let show):
state.showAboutUs = show
return .none
}
}
}