Files
e-party-iOS/yana/Views/AppSettingView.swift
edwinQQQ 17ad000e4b feat: 新增图片源选择功能以增强头像设置体验
- 添加AppImageSource枚举以定义图片源类型(相机和相册)。
- 在AppSettingFeature中新增状态和Action以管理图片源选择。
- 更新AppSettingView以支持图片源选择的ActionSheet和头像选择逻辑。
- 优化ImagePickerWithPreviewView以处理相机和相册选择的取消操作。
2025-07-31 17:29:38 +08:00

359 lines
14 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)
}
}
@ViewBuilder
private func mainView() -> some View {
WithPerceptionTracking {
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)
// ActionSheet
.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) { }
}
//
.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))
}
)
}
//
.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: "请输入新的昵称"))
}
}
}
// 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)
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.system(size: 16))
.foregroundColor(.white)
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)
}
}