feat: 更新AppSettingFeature以增强用户设置功能
- 重构AppSettingFeature,采用@Reducer和@ObservableState以优化状态管理。 - 新增用户信息加载逻辑,支持从服务器获取用户信息并更新界面。 - 更新AppSettingView,整合头像、昵称及其他设置项的展示,提升用户体验。 - 增加多语言支持,更新Localizable.strings文件以适应新的设置项。
This commit is contained in:
@@ -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.**
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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";
|
@@ -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" = "退出账户";
|
||||
|
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user