Files
e-party-iOS/yana/Views/AppSettingView.swift
edwinQQQ dc8ba46f86 feat: 更新登出确认逻辑和弹窗实现
- 修改MainFeature以将登出操作的Action名称从.logoutTapped更新为.logoutConfirmed,增强逻辑清晰度。
- 在AppSettingView中新增登出确认弹窗的实现,替换原有的登出确认逻辑,提升用户体验和交互性。
- 确保弹窗内容本地化,增强多语言支持。
2025-07-31 18:39:53 +08:00

437 lines
17 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>
// letpickerStore
let pickerStore = Store(
initialState: ImagePickerWithPreviewReducer.State(inner: ImagePickerWithPreviewState(selectionMode: .single)),
reducer: { ImagePickerWithPreviewReducer() }
)
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 {
//
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.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))
//
pickerStore.send(.inner(.selectSource(.camera)))
}
Button(LocalizedString("app_settings.select_from_album", comment: "从相册选择")) {
store.send(.selectImageSource(AppImageSource.photoLibrary))
//
pickerStore.send(.inner(.selectSource(.photoLibrary)))
}
Button(LocalizedString("common.cancel", comment: "取消"), role: .cancel) { }
}
let viewWithImagePicker = viewWithActionSheet
.sheet(isPresented: Binding(
get: { store.showImagePicker },
set: { store.send(.setShowImagePicker($0)) }
)) {
ImagePickerWithPreviewView(
store: pickerStore,
onUpload: { images in
if let firstImage = images.first,
let imageData = firstImage.jpegData(compressionQuality: 0.8) {
store.send(.avatarSelected(imageData))
}
store.send(.setShowImagePicker(false))
},
onCancel: {
store.send(.setShowImagePicker(false))
}
)
}
let viewWithAlert = viewWithImagePicker
.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: 100, height: 100)
.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: 100, height: 100)
}
}
}
}
}
// MARK: -
@ViewBuilder
private func personalInfoSection() -> some View {
WithPerceptionTracking {
VStack(spacing: 0) {
//
SettingRow(
icon: "person",
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(
icon: "hand.raised",
title: LocalizedString("appSetting.personalInfoPermissions", comment: "个人信息与权限"),
subtitle: "",
action: { store.send(.personalInfoPermissionsTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 50)
SettingRow(
icon: "questionmark.circle",
title: LocalizedString("appSetting.help", comment: "帮助"),
subtitle: "",
action: { store.send(.helpTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 50)
SettingRow(
icon: "trash",
title: LocalizedString("appSetting.clearCache", comment: "清除缓存"),
subtitle: "",
action: { store.send(.clearCacheTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 50)
SettingRow(
icon: "arrow.clockwise",
title: LocalizedString("appSetting.checkUpdates", comment: "检查更新"),
subtitle: "",
action: { store.send(.checkUpdatesTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 50)
SettingRow(
icon: "person.crop.circle.badge.minus",
title: LocalizedString("appSetting.deactivateAccount", comment: "注销账号"),
subtitle: "",
action: { store.send(.deactivateAccountTapped) }
)
Divider()
.background(Color.white.opacity(0.2))
.padding(.leading, 50)
SettingRow(
icon: "info.circle",
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 icon: String
let title: String
let subtitle: String
let action: (() -> Void)?
var body: some View {
Button(action: {
action?()
}) {
HStack(spacing: 16) {
Image(systemName: icon)
.font(.system(size: 18))
.foregroundColor(.white)
.frame(width: 24)
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)
}
}