Files
e-party-iOS/yana/Views/AppSettingView.swift
edwinQQQ de4428e8a1 feat: 新增设置页面及相关功能实现
- 创建SettingPage视图,包含用户信息管理、头像设置、昵称编辑等功能。
- 实现SettingViewModel,处理设置页面的业务逻辑,包括头像上传、昵称更新等。
- 添加相机和相册选择功能,支持头像更换。
- 更新MainPage和MainViewModel,添加导航逻辑以支持设置页面的访问。
- 完善本地化支持,确保多语言兼容性。
- 新增相关测试建议,确保功能完整性和用户体验。
2025-08-06 18:51:37 +08:00

415 lines
16 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.

//
// AppSettingView.swift
// yana
//
// Created by Edwin on 2024/11/20.
//
import SwiftUI
import ComposableArchitecture
import PhotosUI
struct AppSettingView: View {
let store: StoreOf<AppSettingFeature>
var body: some View {
WithPerceptionTracking {
mainView()
}
.onAppear {
store.send(.onAppear)
}
//
.alert(LocalizedString("appSetting.logoutConfirmation.title", comment: "确认退出"), isPresented: Binding(
get: { store.showLogoutConfirmation },
set: { store.send(.showLogoutConfirmation($0)) }
)) {
Button(LocalizedString("common.cancel", comment: "取消"), role: .cancel) {
store.send(.showLogoutConfirmation(false))
}
Button(LocalizedString("appSetting.logoutConfirmation.confirm", comment: "确认退出"), role: .destructive) {
store.send(.logoutConfirmed)
store.send(.showLogoutConfirmation(false))
}
} message: {
Text(LocalizedString("appSetting.logoutConfirmation.message", comment: "确定要退出当前账户吗?"))
}
//
.alert(LocalizedString("appSetting.aboutUs.title", comment: "关于我们"), isPresented: Binding(
get: { store.showAboutUs },
set: { store.send(.showAboutUs($0)) }
)) {
Button(LocalizedString("common.ok", comment: "确定")) {
store.send(.showAboutUs(false))
}
} message: {
VStack(alignment: .leading, spacing: 8) {
Text(LocalizedString("feedList.title", comment: "享受您的生活时光"))
.font(.headline)
Text(LocalizedString("feedList.slogan", comment: "疾病如同残酷的统治者,\n而时间是我们最宝贵的财富。\n我们活着的每一刻,都是对不可避免命运的胜利。"))
.font(.body)
}
}
}
@ViewBuilder
private func mainView() -> some View {
WithPerceptionTracking {
let baseView = GeometryReader { geometry in
ZStack {
//
Color(hex: 0x0C0527)
.ignoresSafeArea(.all)
VStack(spacing: 0) {
//
HStack {
Button(action: {
store.send(.dismissTapped)
}) {
Image(systemName: "chevron.left")
.font(.system(size: 24, weight: .medium))
.foregroundColor(.white)
.frame(width: 44, height: 44)
}
Spacer()
Text(LocalizedString("appSetting.title", comment: "编辑"))
.font(.system(size: 18, weight: .medium))
.foregroundColor(.white)
Spacer()
//
Color.clear
.frame(width: 44, height: 44)
}
.padding(.horizontal, 16)
.padding(.top, 8)
//
ScrollView {
VStack(spacing: 0) {
//
avatarSection()
.padding(.top, 20)
//
personalInfoSection()
.padding(.top, 30)
//
otherSettingsSection()
.padding(.top, 20)
Spacer(minLength: 40)
// 退
logoutSection()
.padding(.bottom, 40)
}
.padding(.horizontal, 20)
}
}
}
}
.navigationBarHidden(true)
let viewWithActionSheet = baseView
.confirmationDialog(
"请选择图片来源",
isPresented: Binding(
get: { store.showImageSourceActionSheet },
set: { store.send(.setShowImageSourceActionSheet($0)) }
),
titleVisibility: .visible
) {
Button(LocalizedString("app_settings.take_photo", comment: "拍照")) {
store.send(.selectImageSource(AppImageSource.camera))
}
Button(LocalizedString("app_settings.select_from_album", comment: "从相册选择")) {
store.send(.selectImageSource(AppImageSource.photoLibrary))
}
Button(LocalizedString("common.cancel", comment: "取消"), role: .cancel) { }
}
let viewWithCamera = viewWithActionSheet
.sheet(isPresented: Binding(
get: { store.showCamera },
set: { store.send(.setShowCamera($0)) }
)) {
CameraPicker { image in
store.send(.cameraImagePicked(image))
}
}
let viewWithPhotoPicker = viewWithCamera
.photosPicker(
isPresented: Binding(
get: { store.showPhotoPicker },
set: { store.send(.setShowPhotoPicker($0)) }
),
selection: Binding(
get: { store.selectedPhotoItems },
set: { store.send(.photoPickerItemsChanged($0)) }
),
maxSelectionCount: 1,
matching: .images
)
let viewWithAlert = viewWithPhotoPicker
.alert(LocalizedString("appSetting.nickname", comment: "编辑昵称"), isPresented: Binding(
get: { store.isEditingNickname },
set: { store.send(.nicknameEditAlert($0)) }
)) {
TextField(LocalizedString("appSetting.nickname", comment: "请输入昵称"), text: Binding(
get: { store.nicknameInput },
set: { store.send(.nicknameInputChanged($0)) }
))
Button(LocalizedString("common.cancel", comment: "取消")) {
store.send(.nicknameEditAlert(false))
}
Button(LocalizedString("common.confirm", comment: "确认")) {
let trimmed = store.nicknameInput.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty {
store.send(.nicknameEditConfirmed(trimmed))
}
store.send(.nicknameEditAlert(false))
}
} message: {
Text(LocalizedString("appSetting.nickname", comment: "请输入新的昵称"))
}
let viewWithPrivacyPolicy = viewWithAlert
.webView(
isPresented: Binding(
get: { store.showPrivacyPolicy },
set: { isPresented in
if !isPresented {
store.send(.privacyPolicyDismissed)
}
}
),
url: APIConfiguration.webURL(for: .privacyPolicy)
)
let viewWithUserAgreement = viewWithPrivacyPolicy
.webView(
isPresented: Binding(
get: { store.showUserAgreement },
set: { isPresented in
if !isPresented {
store.send(.userAgreementDismissed)
}
}
),
url: APIConfiguration.webURL(for: .userAgreement)
)
let viewWithDeactivateAccount = viewWithUserAgreement
.webView(
isPresented: Binding(
get: { store.showDeactivateAccount },
set: { isPresented in
if !isPresented {
store.send(.deactivateAccountDismissed)
}
}
),
url: APIConfiguration.webURL(for: .deactivateAccount)
)
viewWithDeactivateAccount
}
}
// MARK: -
@ViewBuilder
private func avatarSection() -> some View {
WithPerceptionTracking {
VStack(spacing: 16) {
//
Button(action: {
store.send(.setShowImageSourceActionSheet(true))
}) {
ZStack {
AsyncImage(url: URL(string: store.userInfo?.avatar ?? "")) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
Image(systemName: "person.circle.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundColor(.gray)
}
.frame(width: 120, height: 120)
.clipShape(Circle())
//
VStack {
Spacer()
HStack {
Spacer()
Circle()
.fill(Color.purple)
.frame(width: 32, height: 32)
.overlay(
Image(systemName: "camera")
.font(.system(size: 16, weight: .medium))
.foregroundColor(.white)
)
}
}
.frame(width: 120, height: 120)
}
}
}
}
}
// MARK: -
@ViewBuilder
private func personalInfoSection() -> some View {
WithPerceptionTracking {
VStack(spacing: 0) {
//
SettingRow(
title: LocalizedString("appSetting.nickname", comment: "昵称"),
subtitle: store.userInfo?.nick ?? LocalizedString("app_settings.not_set", comment: "未设置"),
action: {
store.send(.nicknameEditAlert(true))
}
)
}
}
}
// MARK: -
@ViewBuilder
private func otherSettingsSection() -> some View {
WithPerceptionTracking {
VStack(spacing: 0) {
SettingRow(
title: LocalizedString("appSetting.personalInfoPermissions", comment: "个人信息与权限"),
subtitle: "",
action: { store.send(.personalInfoPermissionsTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 16)
SettingRow(
title: LocalizedString("appSetting.help", comment: "帮助"),
subtitle: "",
action: { store.send(.helpTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 16)
SettingRow(
title: LocalizedString("appSetting.clearCache", comment: "清除缓存"),
subtitle: "",
action: { store.send(.clearCacheTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 16)
SettingRow(
title: LocalizedString("appSetting.checkUpdates", comment: "检查更新"),
subtitle: "",
action: { store.send(.checkUpdatesTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 16)
SettingRow(
title: LocalizedString("appSetting.deactivateAccount", comment: "注销账号"),
subtitle: "",
action: { store.send(.deactivateAccountTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 16)
SettingRow(
title: LocalizedString("appSetting.aboutUs", comment: "关于我们"),
subtitle: "",
action: { store.send(.aboutUsTapped) }
)
}
}
}
// MARK: - 退
@ViewBuilder
private func logoutSection() -> some View {
WithPerceptionTracking {
VStack(spacing: 12) {
// 退
Button(action: {
store.send(.logoutTapped)
}) {
Text(LocalizedString("appSetting.logoutAccount", comment: "退出账户"))
.font(.system(size: 16, weight: .medium))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(Color.red.opacity(0.8))
.cornerRadius(12)
}
}
}
}
}
// MARK: -
//struct SettingRow: View {
// let title: String
// let subtitle: String
// let action: (() -> Void)?
//
// var body: some View {
// Button(action: {
// action?()
// }) {
// HStack(spacing: 16) {
// HStack {
// Text(title)
// .font(.system(size: 16))
// .foregroundColor(.white)
// .multilineTextAlignment(.leading)
//
// Spacer()
//
// if !subtitle.isEmpty {
// Text(subtitle)
// .font(.system(size: 14))
// .foregroundColor(.white.opacity(0.7))
// }
// }
//
// Spacer()
//
// if action != nil {
// Image(systemName: "chevron.right")
// .font(.system(size: 14))
// .foregroundColor(.white.opacity(0.5))
// }
// }
// .padding(.horizontal, 16)
// .padding(.vertical, 12)
// }
// .disabled(action == nil)
// }
//}