feat: 更新AppSettingFeature以增强用户设置功能

- 重构AppSettingFeature,采用@Reducer和@ObservableState以优化状态管理。
- 新增用户信息加载逻辑,支持从服务器获取用户信息并更新界面。
- 更新AppSettingView,整合头像、昵称及其他设置项的展示,提升用户体验。
- 增加多语言支持,更新Localizable.strings文件以适应新的设置项。
This commit is contained in:
edwinQQQ
2025-07-24 11:24:04 +08:00
parent f30026821a
commit 71c40e465d
5 changed files with 353 additions and 88 deletions

View File

@@ -9,9 +9,9 @@ alwaysApply: true
## OBJECTIVE
As an expert AI programming assistant, your task is to provide me with clear and readable SwiftUI code. You should:
As an expert AI programming assistant, your task is to provide me with clear, readable, and effective code. You should:
- Utilize the latest versions of SwiftUI and Swift, being familiar with the newest features and best practices.
- Utilize the latest versions of SwiftUI, Swift(6) and TCA(1.20.2), being familiar with the newest features and best practices.
- Provide careful and accurate answers that are well-founded and thoughtfully considered.
- **Explicitly use the Chain-of-Thought (CoT) method in your reasoning and answers, explaining your thought process step by step.**
- Strictly adhere to my requirements and meticulously complete the tasks.
@@ -23,11 +23,7 @@ alwaysApply: true
- Keep answers concise and direct, minimizing unnecessary wording.
- Emphasize code readability over performance optimization.
- Maintain a professional and supportive tone, ensuring clarity of content.
## AUDIENCE
The target audience is me, a native Chinese developer eager to learn Swift 6 and Xcode 15.9, seeking guidance and advice on utilizing the latest technologies.
## RESPONSE FORMAT
- **Utilize the Chain-of-Thought (CoT) method to reason and respond, explaining your thought process step by step.**

View File

@@ -1,27 +1,114 @@
import Foundation
import ComposableArchitecture
struct AppSettingFeature: Reducer {
@Reducer
struct AppSettingFeature {
@ObservableState
struct State: Equatable {
var nickname: String = "hahahaha"
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
}
enum Action: Equatable {
case onAppear
case editNicknameTapped
case logoutTapped
// action
//
case loadUserInfo
case userInfoLoaded(UserInfo?)
case userInfoLoadFailed(String)
// WebView
case personalInfoPermissionsTapped
case helpTapped
case clearCacheTapped
case checkUpdatesTapped
case aboutUsTapped
// WebView
case userAgreementDismissed
case privacyPolicyDismissed
}
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .onAppear:
return .none
case .editNicknameTapped:
//
return .none
case .logoutTapped:
//
return .none
@Dependency(\.apiService) var apiService
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
return .send(.loadUserInfo)
case .editNicknameTapped:
//
return .none
case .logoutTapped:
//
return .none
case .loadUserInfo:
state.isLoadingUserInfo = true
state.userInfoError = nil
return .run { send in
let currentUid = await UserInfoManager.getCurrentUserId()
if let userInfo = await UserInfoManager.fetchUserInfoFromServer(
uid: currentUid,
apiService: apiService
) {
await send(.userInfoLoaded(userInfo))
} else {
await send(.userInfoLoadFailed("获取用户信息失败"))
}
}
case let .userInfoLoaded(userInfo):
state.isLoadingUserInfo = false
state.userInfo = userInfo
state.nickname = userInfo?.nick ?? "hahahaha"
state.avatarURL = userInfo?.avatar
return .none
case let .userInfoLoadFailed(error):
state.isLoadingUserInfo = false
state.userInfoError = error
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 .userAgreementDismissed:
state.showUserAgreement = false
return .none
case .privacyPolicyDismissed:
state.showPrivacyPolicy = false
return .none
}
}
}
}
}

View File

@@ -118,4 +118,15 @@
"setting.language" = "Language Settings";
"setting.about" = "About Us";
"setting.version" = "Version Info";
"setting.logout" = "Logout";
"setting.logout" = "Logout";
// MARK: - App Setting
"appSetting.title" = "Edit";
"appSetting.nickname" = "Nickname";
"appSetting.personalInfoPermissions" = "Personal Information and Permissions";
"appSetting.help" = "Help";
"appSetting.clearCache" = "Clear Cache";
"appSetting.checkUpdates" = "Check for Updates";
"appSetting.logout" = "Log Out";
"appSetting.aboutUs" = "About Us";
"appSetting.logoutAccount" = "Log out of account";

View File

@@ -115,3 +115,14 @@
"feed.2hoursago" = "2小时前";
"feed.demoContent" = "今天是美好的一天,分享一些生活中的小确幸。希望大家都能珍惜每一个当下的时刻。";
"feed.vip" = "VIP%d";
// MARK: - App Setting
"appSetting.title" = "编辑";
"appSetting.nickname" = "昵称";
"appSetting.personalInfoPermissions" = "个人信息与权限";
"appSetting.help" = "帮助";
"appSetting.clearCache" = "清除缓存";
"appSetting.checkUpdates" = "检查更新";
"appSetting.logout" = "退出登录";
"appSetting.aboutUs" = "关于我们";
"appSetting.logoutAccount" = "退出账户";

View File

@@ -8,80 +8,203 @@ struct AppSettingView: View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
ZStack {
Color(red: 22/255, green: 17/255, blue: 44/255).ignoresSafeArea()
VStack(spacing: 0) {
Spacer().frame(height: 24)
//
Text("Edit")
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.white)
.padding(.top, 8)
//
ZStack(alignment: .bottomTrailing) {
Image("avatar_placeholder")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 120, height: 120)
.clipShape(Circle())
Button(action: {}) {
ZStack {
Circle().fill(Color.purple).frame(width: 36, height: 36)
Image(systemName: "camera.fill")
.foregroundColor(.white)
}
}
.offset(x: 8, y: 8)
}
.padding(.top, 24)
//
HStack {
Text("Nickname")
.foregroundColor(.white)
Spacer()
Text(viewStore.nickname)
.foregroundColor(.gray)
Image(systemName: "chevron.right")
.foregroundColor(.gray)
}
.padding(.horizontal, 32)
.padding(.vertical, 18)
.onTapGesture {
viewStore.send(.editNicknameTapped)
}
Divider().background(Color.gray.opacity(0.3))
.padding(.horizontal, 32)
//
// VStack(spacing: 0) {
//
VStack(spacing: 0) {
settingRow("Personal Information and Permissions")
settingRow("Help")
settingRow("Clear Cache")
settingRow("Check for Updates")
settingRow("Log Out")
settingRow("About Us")
//
avatarSection(viewStore: viewStore)
//
nicknameSection(viewStore: viewStore)
//
settingsSection(viewStore: viewStore)
Spacer()
//
logoutButton(viewStore: viewStore)
}
.background(Color.clear)
.padding(.horizontal, 0)
Spacer()
//
Button(action: {
viewStore.send(.logoutTapped)
}) {
Text("Log out of account")
.font(.system(size: 18, weight: .semibold))
// }
}
.navigationTitle(NSLocalizedString("appSetting.title", comment: "Edit"))
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {}) {
Image(systemName: "chevron.left")
.font(.system(size: 24, weight: .medium))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 18)
.background(Color.white.opacity(0.08))
.cornerRadius(28)
.padding(.horizontal, 32)
}
.padding(.bottom, 32)
}
}
.onAppear {
viewStore.send(.onAppear)
}
.webView(
isPresented: userAgreementBinding(viewStore: viewStore),
url: APIConfiguration.webURL(for: .userAgreement)
)
.webView(
isPresented: privacyPolicyBinding(viewStore: viewStore),
url: APIConfiguration.webURL(for: .privacyPolicy)
)
}
}
//
func settingRow(_ title: String) -> some View {
return VStack(spacing: 0) {
// MARK: -
private func avatarSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
ZStack(alignment: .bottomTrailing) {
avatarImageView(viewStore: viewStore)
cameraButton
}
.padding(.top, 24)
}
// MARK: -
@ViewBuilder
private func avatarImageView(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
if viewStore.isLoadingUserInfo {
loadingAvatarView
} else if let avatarURLString = viewStore.avatarURL, !avatarURLString.isEmpty, let avatarURL = URL(string: avatarURLString) {
networkAvatarView(url: avatarURL)
} else {
defaultAvatarView
}
}
// MARK: -
private var loadingAvatarView: some View {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 120, height: 120)
.overlay(
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(1.2)
)
}
// MARK: -
private func networkAvatarView(url: URL) -> some View {
CachedAsyncImage(url: url.absoluteString) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
defaultAvatarView
}
.frame(width: 120, height: 120)
.clipShape(Circle())
}
// MARK: -
private var defaultAvatarView: some View {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 120, height: 120)
.overlay(
Image(systemName: "person.fill")
.font(.system(size: 40))
.foregroundColor(.white)
)
}
// MARK: -
private var cameraButton: some View {
Button(action: {}) {
ZStack {
Circle().fill(Color.purple).frame(width: 36, height: 36)
Image(systemName: "camera.fill")
.foregroundColor(.white)
}
}
.offset(x: 8, y: 8)
}
// MARK: -
private func nicknameSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
VStack(spacing: 0) {
HStack {
Text(NSLocalizedString("appSetting.nickname", comment: "Nickname"))
.foregroundColor(.white)
Spacer()
Text(viewStore.nickname)
.foregroundColor(.gray)
Image(systemName: "chevron.right")
.foregroundColor(.gray)
}
.padding(.horizontal, 32)
.padding(.vertical, 18)
.onTapGesture {
viewStore.send(.editNicknameTapped)
}
Divider().background(Color.gray.opacity(0.3))
.padding(.horizontal, 32)
}
}
// MARK: -
private func settingsSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
VStack(spacing: 0) {
personalInfoPermissionsRow(viewStore: viewStore)
helpRow(viewStore: viewStore)
clearCacheRow(viewStore: viewStore)
checkUpdatesRow(viewStore: viewStore)
aboutUsRow(viewStore: viewStore)
}
.background(Color.clear)
.padding(.horizontal, 0)
}
// MARK: -
private func personalInfoPermissionsRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
settingRow(
title: NSLocalizedString("appSetting.personalInfoPermissions", comment: "Personal Information and Permissions"),
action: { viewStore.send(.personalInfoPermissionsTapped) }
)
}
// MARK: -
private func helpRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
settingRow(
title: NSLocalizedString("appSetting.help", comment: "Help"),
action: { viewStore.send(.helpTapped) }
)
}
// MARK: -
private func clearCacheRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
settingRow(
title: NSLocalizedString("appSetting.clearCache", comment: "Clear Cache"),
action: { viewStore.send(.clearCacheTapped) }
)
}
// MARK: -
private func checkUpdatesRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
settingRow(
title: NSLocalizedString("appSetting.checkUpdates", comment: "Check for Updates"),
action: { viewStore.send(.checkUpdatesTapped) }
)
}
// MARK: -
private func aboutUsRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
settingRow(
title: NSLocalizedString("appSetting.aboutUs", comment: "About Us"),
action: { viewStore.send(.aboutUsTapped) }
)
}
// MARK: -
private func settingRow(title: String, action: @escaping () -> Void) -> some View {
VStack(spacing: 0) {
HStack {
Text(title)
.foregroundColor(.white)
@@ -91,8 +214,45 @@ struct AppSettingView: View {
}
.padding(.horizontal, 32)
.padding(.vertical, 18)
.onTapGesture {
action()
}
Divider().background(Color.gray.opacity(0.3))
.padding(.horizontal, 32)
}
}
}
// MARK: - 退
private func logoutButton(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
Button(action: {
viewStore.send(.logoutTapped)
}) {
Text(NSLocalizedString("appSetting.logoutAccount", comment: "Log out of account"))
.font(.system(size: 18, weight: .semibold))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 18)
.background(Color.white.opacity(0.08))
.cornerRadius(28)
.padding(.horizontal, 32)
}
.padding(.bottom, 32)
}
// MARK: -
private func userAgreementBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
viewStore.binding(
get: \.showUserAgreement,
send: AppSettingFeature.Action.userAgreementDismissed
)
}
// MARK: -
private func privacyPolicyBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
viewStore.binding(
get: \.showPrivacyPolicy,
send: AppSettingFeature.Action.privacyPolicyDismissed
)
}
}