feat: 更新AppSettingView以集成图片选择与预览功能
- 将图片选择功能整合到AppSettingView中,使用ImagePickerWithPreviewView提升用户体验。 - 移除冗余的照片选择处理逻辑,简化代码结构。 - 更新昵称编辑功能的实现,确保用户输入限制在15个字符内。 - 优化导航栏和用户协议、隐私政策的展示,增强界面交互性。
This commit is contained in:
@@ -11,44 +11,51 @@ import PhotosUI
|
|||||||
|
|
||||||
struct AppSettingView: View {
|
struct AppSettingView: View {
|
||||||
let store: StoreOf<AppSettingFeature>
|
let store: StoreOf<AppSettingFeature>
|
||||||
@State private var showPhotoPicker = false
|
// 直接let声明pickerStore
|
||||||
|
let pickerStore = Store(
|
||||||
|
initialState: ImagePickerWithPreviewReducer.State(inner: ImagePickerWithPreviewState(selectionMode: .single)),
|
||||||
|
reducer: { ImagePickerWithPreviewReducer() }
|
||||||
|
)
|
||||||
|
@State private var showImagePicker = false
|
||||||
@State private var showNicknameAlert = false
|
@State private var showNicknameAlert = false
|
||||||
@State private var nicknameInput = ""
|
@State private var nicknameInput = ""
|
||||||
@State private var selectedPhotoItem: PhotosPickerItem?
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
// WithPerceptionTracking {
|
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
ZStack {
|
||||||
WithPerceptionTracking{
|
mainContent(viewStore: viewStore)
|
||||||
mainContent(viewStore: viewStore)
|
if showImagePicker {
|
||||||
.navigationBarHidden(true)
|
WithPerceptionTracking{
|
||||||
.photosPicker(
|
ImagePickerWithPreviewView(store: pickerStore) { images in
|
||||||
isPresented: $showPhotoPicker,
|
if let image = images.first, let data = image.jpegData(compressionQuality: 0.8) {
|
||||||
selection: $selectedPhotoItem,
|
viewStore.send(.avatarSelected(data))
|
||||||
matching: .images,
|
}
|
||||||
photoLibrary: .shared()
|
showImagePicker = false
|
||||||
)
|
|
||||||
.onChange(of: selectedPhotoItem) { item in
|
|
||||||
handlePhotoSelection(item: item, viewStore: viewStore)
|
|
||||||
selectedPhotoItem = nil
|
|
||||||
}
|
|
||||||
.alert("修改昵称", isPresented: $showNicknameAlert) {
|
|
||||||
nicknameAlertContent(viewStore: viewStore)
|
|
||||||
} message: {
|
|
||||||
Text("昵称最长15个字符")
|
|
||||||
}
|
|
||||||
.sheet(isPresented: userAgreementBinding(viewStore: viewStore)) {
|
|
||||||
WebView(url: URL(string: "https://www.yana.com/user-agreement")!)
|
|
||||||
}
|
|
||||||
.sheet(isPresented: privacyPolicyBinding(viewStore: viewStore)) {
|
|
||||||
WebView(url: URL(string: "https://www.yana.com/privacy-policy")!)
|
|
||||||
}
|
}
|
||||||
|
.zIndex(10)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// }
|
.navigationBarHidden(true)
|
||||||
|
.alert("修改昵称", isPresented: $showNicknameAlert) {
|
||||||
|
nicknameAlertContent(viewStore: viewStore)
|
||||||
|
} message: {
|
||||||
|
Text("昵称最长15个字符")
|
||||||
|
}
|
||||||
|
.sheet(isPresented: userAgreementBinding(viewStore: viewStore)) {
|
||||||
|
WebView(url: URL(string: "https://www.yana.com/user-agreement")!)
|
||||||
|
}
|
||||||
|
.sheet(isPresented: privacyPolicyBinding(viewStore: viewStore)) {
|
||||||
|
WebView(url: URL(string: "https://www.yana.com/privacy-policy")!)
|
||||||
|
}
|
||||||
|
.onChange(of: showImagePicker) { newValue in
|
||||||
|
if newValue {
|
||||||
|
pickerStore.send(.inner(.showActionSheet(true)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 主要内容
|
// MARK: - 主要内容
|
||||||
private func mainContent(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func mainContent(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -56,98 +63,38 @@ struct AppSettingView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
topBar
|
topBar
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 32) {
|
WithPerceptionTracking {
|
||||||
// 头像区域
|
VStack(spacing: 32) {
|
||||||
avatarSection(viewStore: viewStore)
|
// 头像区域
|
||||||
|
avatarSection(viewStore: viewStore)
|
||||||
// 昵称设置项
|
// 昵称设置项
|
||||||
nicknameSection(viewStore: viewStore)
|
nicknameSection(viewStore: viewStore)
|
||||||
|
// 设置项区域
|
||||||
// 设置项区域
|
settingsSection(viewStore: viewStore)
|
||||||
settingsSection(viewStore: viewStore)
|
// 退出登录按钮
|
||||||
|
logoutButton(viewStore: viewStore)
|
||||||
// 退出登录按钮
|
}
|
||||||
logoutButton(viewStore: viewStore)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 照片选择处理
|
|
||||||
private func handlePhotoSelection(item: PhotosPickerItem?, viewStore: ViewStoreOf<AppSettingFeature>) {
|
|
||||||
if let item = item {
|
|
||||||
loadAndProcessImage(item: item) { data in
|
|
||||||
if let data = data {
|
|
||||||
viewStore.send(.avatarSelected(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 昵称Alert内容
|
|
||||||
@ViewBuilder
|
|
||||||
private func nicknameAlertContent(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
|
||||||
TextField("请输入昵称", text: $nicknameInput)
|
|
||||||
.onChange(of: nicknameInput) { newValue in
|
|
||||||
if newValue.count > 15 {
|
|
||||||
nicknameInput = String(newValue.prefix(15))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button("确定") {
|
|
||||||
let trimmed = nicknameInput.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
if !trimmed.isEmpty && trimmed != viewStore.nickname {
|
|
||||||
viewStore.send(.nicknameEditConfirmed(trimmed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button("取消", role: .cancel) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 顶部栏
|
|
||||||
private var topBar: some View {
|
|
||||||
HStack {
|
|
||||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
|
||||||
Button(action: {
|
|
||||||
viewStore.send(.dismissTapped)
|
|
||||||
}) {
|
|
||||||
Image(systemName: "chevron.left")
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.font(.system(size: 20, weight: .medium))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text(NSLocalizedString("appSetting.title", comment: "Settings"))
|
|
||||||
.font(.system(size: 18, weight: .semibold))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// 占位符,保持标题居中
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
.padding(.top, 8)
|
|
||||||
.padding(.bottom, 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 头像区域
|
// MARK: - 头像区域
|
||||||
private func avatarSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func avatarSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
avatarImageView(viewStore: viewStore)
|
avatarImageView(viewStore: viewStore)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
showPhotoPicker = true
|
showImagePicker = true
|
||||||
}
|
}
|
||||||
cameraButton
|
cameraButton
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
showPhotoPicker = true
|
showImagePicker = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 24)
|
.padding(.top, 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 头像图片视图
|
// MARK: - 头像图片视图
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func avatarImageView(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func avatarImageView(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
@@ -159,7 +106,7 @@ struct AppSettingView: View {
|
|||||||
defaultAvatarView
|
defaultAvatarView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 加载状态头像
|
// MARK: - 加载状态头像
|
||||||
private var loadingAvatarView: some View {
|
private var loadingAvatarView: some View {
|
||||||
Circle()
|
Circle()
|
||||||
@@ -171,7 +118,7 @@ struct AppSettingView: View {
|
|||||||
.scaleEffect(1.2)
|
.scaleEffect(1.2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 网络头像
|
// MARK: - 网络头像
|
||||||
private func networkAvatarView(url: URL) -> some View {
|
private func networkAvatarView(url: URL) -> some View {
|
||||||
CachedAsyncImage(url: url.absoluteString) { image in
|
CachedAsyncImage(url: url.absoluteString) { image in
|
||||||
@@ -184,7 +131,7 @@ struct AppSettingView: View {
|
|||||||
.frame(width: 120, height: 120)
|
.frame(width: 120, height: 120)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 默认头像
|
// MARK: - 默认头像
|
||||||
private var defaultAvatarView: some View {
|
private var defaultAvatarView: some View {
|
||||||
Circle()
|
Circle()
|
||||||
@@ -196,7 +143,7 @@ struct AppSettingView: View {
|
|||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 相机按钮
|
// MARK: - 相机按钮
|
||||||
private var cameraButton: some View {
|
private var cameraButton: some View {
|
||||||
Button(action: {}) {
|
Button(action: {}) {
|
||||||
@@ -208,7 +155,7 @@ struct AppSettingView: View {
|
|||||||
}
|
}
|
||||||
.offset(x: 8, y: 8)
|
.offset(x: 8, y: 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 昵称设置项
|
// MARK: - 昵称设置项
|
||||||
private func nicknameSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func nicknameSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -227,12 +174,12 @@ struct AppSettingView: View {
|
|||||||
nicknameInput = viewStore.nickname
|
nicknameInput = viewStore.nickname
|
||||||
showNicknameAlert = true
|
showNicknameAlert = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider().background(Color.gray.opacity(0.3))
|
Divider().background(Color.gray.opacity(0.3))
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 设置项区域
|
// MARK: - 设置项区域
|
||||||
private func settingsSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func settingsSection(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -245,7 +192,7 @@ struct AppSettingView: View {
|
|||||||
.background(Color.clear)
|
.background(Color.clear)
|
||||||
.padding(.horizontal, 0)
|
.padding(.horizontal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 个人信息权限行
|
// MARK: - 个人信息权限行
|
||||||
private func personalInfoPermissionsRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func personalInfoPermissionsRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
settingRow(
|
settingRow(
|
||||||
@@ -253,7 +200,7 @@ struct AppSettingView: View {
|
|||||||
action: { viewStore.send(.personalInfoPermissionsTapped) }
|
action: { viewStore.send(.personalInfoPermissionsTapped) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 帮助行
|
// MARK: - 帮助行
|
||||||
private func helpRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func helpRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
settingRow(
|
settingRow(
|
||||||
@@ -261,7 +208,7 @@ struct AppSettingView: View {
|
|||||||
action: { viewStore.send(.helpTapped) }
|
action: { viewStore.send(.helpTapped) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 清除缓存行
|
// MARK: - 清除缓存行
|
||||||
private func clearCacheRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func clearCacheRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
settingRow(
|
settingRow(
|
||||||
@@ -269,7 +216,7 @@ struct AppSettingView: View {
|
|||||||
action: { viewStore.send(.clearCacheTapped) }
|
action: { viewStore.send(.clearCacheTapped) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 检查更新行
|
// MARK: - 检查更新行
|
||||||
private func checkUpdatesRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func checkUpdatesRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
settingRow(
|
settingRow(
|
||||||
@@ -277,7 +224,7 @@ struct AppSettingView: View {
|
|||||||
action: { viewStore.send(.checkUpdatesTapped) }
|
action: { viewStore.send(.checkUpdatesTapped) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 关于我们行
|
// MARK: - 关于我们行
|
||||||
private func aboutUsRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func aboutUsRow(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
settingRow(
|
settingRow(
|
||||||
@@ -285,7 +232,7 @@ struct AppSettingView: View {
|
|||||||
action: { viewStore.send(.aboutUsTapped) }
|
action: { viewStore.send(.aboutUsTapped) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 设置项行
|
// MARK: - 设置项行
|
||||||
private func settingRow(title: String, action: @escaping () -> Void) -> some View {
|
private func settingRow(title: String, action: @escaping () -> Void) -> some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -301,12 +248,12 @@ struct AppSettingView: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider().background(Color.gray.opacity(0.3))
|
Divider().background(Color.gray.opacity(0.3))
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 退出登录按钮
|
// MARK: - 退出登录按钮
|
||||||
private func logoutButton(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
private func logoutButton(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@@ -323,7 +270,7 @@ struct AppSettingView: View {
|
|||||||
}
|
}
|
||||||
.padding(.bottom, 32)
|
.padding(.bottom, 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 用户协议绑定
|
// MARK: - 用户协议绑定
|
||||||
private func userAgreementBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
|
private func userAgreementBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
|
||||||
viewStore.binding(
|
viewStore.binding(
|
||||||
@@ -331,7 +278,7 @@ struct AppSettingView: View {
|
|||||||
send: AppSettingFeature.Action.userAgreementDismissed
|
send: AppSettingFeature.Action.userAgreementDismissed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 隐私政策绑定
|
// MARK: - 隐私政策绑定
|
||||||
private func privacyPolicyBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
|
private func privacyPolicyBinding(viewStore: ViewStoreOf<AppSettingFeature>) -> Binding<Bool> {
|
||||||
viewStore.binding(
|
viewStore.binding(
|
||||||
@@ -339,7 +286,55 @@ struct AppSettingView: View {
|
|||||||
send: AppSettingFeature.Action.privacyPolicyDismissed
|
send: AppSettingFeature.Action.privacyPolicyDismissed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 昵称Alert内容
|
||||||
|
@ViewBuilder
|
||||||
|
private func nicknameAlertContent(viewStore: ViewStoreOf<AppSettingFeature>) -> some View {
|
||||||
|
TextField("请输入昵称", text: $nicknameInput)
|
||||||
|
.onChange(of: nicknameInput) { newValue in
|
||||||
|
if newValue.count > 15 {
|
||||||
|
nicknameInput = String(newValue.prefix(15))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button("确定") {
|
||||||
|
let trimmed = nicknameInput.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if !trimmed.isEmpty && trimmed != viewStore.nickname {
|
||||||
|
viewStore.send(.nicknameEditConfirmed(trimmed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button("取消", role: .cancel) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 顶部栏
|
||||||
|
private var topBar: some View {
|
||||||
|
HStack {
|
||||||
|
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||||
|
Button(action: {
|
||||||
|
viewStore.send(.dismissTapped)
|
||||||
|
}) {
|
||||||
|
Image(systemName: "chevron.left")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.system(size: 20, weight: .medium))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(NSLocalizedString("appSetting.title", comment: "Settings"))
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 占位符,保持标题居中
|
||||||
|
Color.clear
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 8)
|
||||||
|
.padding(.bottom, 16)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 图片处理
|
// MARK: - 图片处理
|
||||||
private func loadAndProcessImage(item: PhotosPickerItem, completion: @escaping (Data?) -> Void) {
|
private func loadAndProcessImage(item: PhotosPickerItem, completion: @escaping (Data?) -> Void) {
|
||||||
item.loadTransferable(type: Data.self) { result in
|
item.loadTransferable(type: Data.self) { result in
|
||||||
|
Reference in New Issue
Block a user