feat: 更新AppSettingFeature以增强用户设置功能
- 重构AppSettingFeature,采用@Reducer和@ObservableState以优化状态管理。 - 新增用户信息加载逻辑,支持从服务器获取用户信息并更新界面。 - 更新AppSettingView,整合头像、昵称及其他设置项的展示,提升用户体验。 - 增加多语言支持,更新Localizable.strings文件以适应新的设置项。
This commit is contained in:
@@ -9,9 +9,9 @@ alwaysApply: true
|
|||||||
|
|
||||||
## OBJECTIVE
|
## 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.
|
- 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.**
|
- **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.
|
- Strictly adhere to my requirements and meticulously complete the tasks.
|
||||||
@@ -24,10 +24,6 @@ alwaysApply: true
|
|||||||
- Emphasize code readability over performance optimization.
|
- Emphasize code readability over performance optimization.
|
||||||
- Maintain a professional and supportive tone, ensuring clarity of content.
|
- 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
|
## RESPONSE FORMAT
|
||||||
|
|
||||||
- **Utilize the Chain-of-Thought (CoT) method to reason and respond, explaining your thought process step by step.**
|
- **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 Foundation
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
struct AppSettingFeature: Reducer {
|
@Reducer
|
||||||
|
struct AppSettingFeature {
|
||||||
|
@ObservableState
|
||||||
struct State: Equatable {
|
struct State: Equatable {
|
||||||
var nickname: String = "hahahaha"
|
var nickname: String = ""
|
||||||
var avatarURL: String? = nil
|
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 {
|
enum Action: Equatable {
|
||||||
case onAppear
|
case onAppear
|
||||||
case editNicknameTapped
|
case editNicknameTapped
|
||||||
case logoutTapped
|
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> {
|
|
||||||
|
@Dependency(\.apiService) var apiService
|
||||||
|
|
||||||
|
var body: some ReducerOf<Self> {
|
||||||
|
Reduce { state, action in
|
||||||
switch action {
|
switch action {
|
||||||
case .onAppear:
|
case .onAppear:
|
||||||
return .none
|
return .send(.loadUserInfo)
|
||||||
|
|
||||||
case .editNicknameTapped:
|
case .editNicknameTapped:
|
||||||
// 预留编辑昵称逻辑
|
// 预留编辑昵称逻辑
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
case .logoutTapped:
|
case .logoutTapped:
|
||||||
// 预留登出逻辑
|
// 预留登出逻辑
|
||||||
return .none
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -119,3 +119,14 @@
|
|||||||
"setting.about" = "About Us";
|
"setting.about" = "About Us";
|
||||||
"setting.version" = "Version Info";
|
"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.2hoursago" = "2小时前";
|
||||||
"feed.demoContent" = "今天是美好的一天,分享一些生活中的小确幸。希望大家都能珍惜每一个当下的时刻。";
|
"feed.demoContent" = "今天是美好的一天,分享一些生活中的小确幸。希望大家都能珍惜每一个当下的时刻。";
|
||||||
"feed.vip" = "VIP%d";
|
"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,20 +8,114 @@ struct AppSettingView: View {
|
|||||||
WithViewStore(self.store, observe: { $0 }) { viewStore in
|
WithViewStore(self.store, observe: { $0 }) { viewStore in
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(red: 22/255, green: 17/255, blue: 44/255).ignoresSafeArea()
|
Color(red: 22/255, green: 17/255, blue: 44/255).ignoresSafeArea()
|
||||||
|
// VStack(spacing: 0) {
|
||||||
|
// 主要内容
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Spacer().frame(height: 24)
|
// 头像区域
|
||||||
// 顶部标题
|
avatarSection(viewStore: viewStore)
|
||||||
Text("Edit")
|
|
||||||
.font(.system(size: 22, weight: .semibold))
|
// 昵称设置项
|
||||||
|
nicknameSection(viewStore: viewStore)
|
||||||
|
|
||||||
|
// 其他设置项
|
||||||
|
settingsSection(viewStore: viewStore)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 底部大按钮
|
||||||
|
logoutButton(viewStore: viewStore)
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
.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)
|
.foregroundColor(.white)
|
||||||
.padding(.top, 8)
|
}
|
||||||
// 头像
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
viewStore.send(.onAppear)
|
||||||
|
}
|
||||||
|
.webView(
|
||||||
|
isPresented: userAgreementBinding(viewStore: viewStore),
|
||||||
|
url: APIConfiguration.webURL(for: .userAgreement)
|
||||||
|
)
|
||||||
|
.webView(
|
||||||
|
isPresented: privacyPolicyBinding(viewStore: viewStore),
|
||||||
|
url: APIConfiguration.webURL(for: .privacyPolicy)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - 头像区域
|
||||||
|
private func avatarSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
Image("avatar_placeholder")
|
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()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
|
} placeholder: {
|
||||||
|
defaultAvatarView
|
||||||
|
}
|
||||||
.frame(width: 120, height: 120)
|
.frame(width: 120, height: 120)
|
||||||
.clipShape(Circle())
|
.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: {}) {
|
Button(action: {}) {
|
||||||
ZStack {
|
ZStack {
|
||||||
Circle().fill(Color.purple).frame(width: 36, height: 36)
|
Circle().fill(Color.purple).frame(width: 36, height: 36)
|
||||||
@@ -31,10 +125,12 @@ struct AppSettingView: View {
|
|||||||
}
|
}
|
||||||
.offset(x: 8, y: 8)
|
.offset(x: 8, y: 8)
|
||||||
}
|
}
|
||||||
.padding(.top, 24)
|
|
||||||
// 昵称
|
// MARK: - 昵称设置项
|
||||||
|
private func nicknameSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Nickname")
|
Text(NSLocalizedString("appSetting.nickname", comment: "Nickname"))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(viewStore.nickname)
|
Text(viewStore.nickname)
|
||||||
@@ -47,25 +143,92 @@ struct AppSettingView: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
viewStore.send(.editNicknameTapped)
|
viewStore.send(.editNicknameTapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider().background(Color.gray.opacity(0.3))
|
Divider().background(Color.gray.opacity(0.3))
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
// 其他设置项
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 设置项区域
|
||||||
|
private func settingsSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
settingRow("Personal Information and Permissions")
|
personalInfoPermissionsRow(viewStore: viewStore)
|
||||||
settingRow("Help")
|
helpRow(viewStore: viewStore)
|
||||||
settingRow("Clear Cache")
|
clearCacheRow(viewStore: viewStore)
|
||||||
settingRow("Check for Updates")
|
checkUpdatesRow(viewStore: viewStore)
|
||||||
settingRow("Log Out")
|
aboutUsRow(viewStore: viewStore)
|
||||||
settingRow("About Us")
|
|
||||||
}
|
}
|
||||||
.background(Color.clear)
|
.background(Color.clear)
|
||||||
.padding(.horizontal, 0)
|
.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)
|
||||||
Spacer()
|
Spacer()
|
||||||
// 底部大按钮
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.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: {
|
Button(action: {
|
||||||
viewStore.send(.logoutTapped)
|
viewStore.send(.logoutTapped)
|
||||||
}) {
|
}) {
|
||||||
Text("Log out of account")
|
Text(NSLocalizedString("appSetting.logoutAccount", comment: "Log out of account"))
|
||||||
.font(.system(size: 18, weight: .semibold))
|
.font(.system(size: 18, weight: .semibold))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
@@ -76,23 +239,20 @@ struct AppSettingView: View {
|
|||||||
}
|
}
|
||||||
.padding(.bottom, 32)
|
.padding(.bottom, 32)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// MARK: - 用户协议绑定
|
||||||
}
|
private func userAgreementBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
|
||||||
// 设置项行
|
viewStore.binding(
|
||||||
func settingRow(_ title: String) -> some View {
|
get: \.showUserAgreement,
|
||||||
return VStack(spacing: 0) {
|
send: AppSettingFeature.Action.userAgreementDismissed
|
||||||
HStack {
|
)
|
||||||
Text(title)
|
}
|
||||||
.foregroundColor(.white)
|
|
||||||
Spacer()
|
// MARK: - 隐私政策绑定
|
||||||
Image(systemName: "chevron.right")
|
private func privacyPolicyBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
|
||||||
.foregroundColor(.gray)
|
viewStore.binding(
|
||||||
}
|
get: \.showPrivacyPolicy,
|
||||||
.padding(.horizontal, 32)
|
send: AppSettingFeature.Action.privacyPolicyDismissed
|
||||||
.padding(.vertical, 18)
|
)
|
||||||
Divider().background(Color.gray.opacity(0.3))
|
|
||||||
.padding(.horizontal, 32)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user