From 12dd03d5b35a7304c16bfe8e7e8a05932e16d934 Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Fri, 1 Aug 2025 15:11:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0AppSettingFeature?= =?UTF-8?q?=E4=BB=A5=E5=A2=9E=E5=BC=BA=E5=9B=BE=E7=89=87=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AppSettingFeature中新增相机和相册选择的状态和Action,优化图片源选择逻辑。 - 更新AppSettingView以支持相机和相册的弹窗显示,提升用户交互体验。 - 修改ImagePickerWithPreviewView以根据相机或相册选择动态显示内容,避免空页面闪烁。 - 确保相机和相册选择的逻辑清晰,增强代码可读性和维护性。 --- yana/Features/AppSettingFeature.swift | 84 ++++++++++++++----- yana/Views/AppSettingView.swift | 47 +++++------ .../ImagePickerWithPreviewView.swift | 44 ++++++---- 3 files changed, 114 insertions(+), 61 deletions(-) diff --git a/yana/Features/AppSettingFeature.swift b/yana/Features/AppSettingFeature.swift index 7ea4af0..5d07b1c 100644 --- a/yana/Features/AppSettingFeature.swift +++ b/yana/Features/AppSettingFeature.swift @@ -1,5 +1,7 @@ import Foundation import ComposableArchitecture +import SwiftUI +import PhotosUI // 图片源选择枚举 enum AppImageSource: Equatable { @@ -41,14 +43,16 @@ struct AppSettingFeature { self.avatarURL = avatarURL self.userInfo = userInfo } - // 新增:TCA驱动图片选择弹窗 - var showImagePicker: Bool = false - // 新增:图片源选择 ActionSheet - var showImageSourceActionSheet: Bool = false - // 新增:选择的图片源 - var selectedImageSource: AppImageSource? = nil - // 新增:弹窗状态 + // 图片源选择 ActionSheet + var showImageSourceActionSheet: Bool = false + + // 新增:直接管理相机和相册选择 + var showCamera: Bool = false + var showPhotoPicker: Bool = false + var selectedPhotoItems: [PhotosPickerItem] = [] + + // 弹窗状态 var showLogoutConfirmation: Bool = false var showAboutUs: Bool = false } @@ -85,14 +89,18 @@ struct AppSettingFeature { case nicknameInputChanged(String) case nicknameEditAlert(Bool) case testPushTapped - // 新增:TCA驱动图片选择弹窗 - case setShowImagePicker(Bool) - // 新增:图片源选择 ActionSheet + + // 图片源选择 case setShowImageSourceActionSheet(Bool) - // 新增:图片源选择 case selectImageSource(AppImageSource) - // 新增:弹窗相关 + // 新增:直接处理相机和相册 + case setShowCamera(Bool) + case setShowPhotoPicker(Bool) + case cameraImagePicked(UIImage?) + case photoPickerItemsChanged([PhotosPickerItem]) + + // 弹窗相关 case showLogoutConfirmation(Bool) case showAboutUs(Bool) case logoutConfirmed @@ -214,8 +222,6 @@ struct AppSettingFeature { } } case let .avatarUploadResult(.success(url)): - state.isUploadingAvatar = false - // 调用 updateUser API,仅传 avatar state.isUpdatingUser = true state.updateUserError = nil guard let userInfo = state.userInfo else { return .none } @@ -279,17 +285,55 @@ struct AppSettingFeature { return .none case .testPushTapped: return .none - case .setShowImagePicker(let show): - state.showImagePicker = show - return .none + + // 图片源选择处理 case .setShowImageSourceActionSheet(let show): state.showImageSourceActionSheet = show return .none + case .selectImageSource(let source): state.showImageSourceActionSheet = false - state.showImagePicker = true - state.selectedImageSource = source - // 这里可以传递选择的源到 ImagePickerWithPreviewView + switch source { + case .camera: + state.showCamera = true + case .photoLibrary: + state.showPhotoPicker = true + } + return .none + + // 相机和相册处理 + case .setShowCamera(let show): + state.showCamera = show + return .none + + case .setShowPhotoPicker(let show): + state.showPhotoPicker = show + return .none + + case .cameraImagePicked(let image): + state.showCamera = false + if let image = image, + let imageData = image.jpegData(compressionQuality: 0.8) { + return .send(.avatarSelected(imageData)) + } + return .none + + case .photoPickerItemsChanged(let items): + state.selectedPhotoItems = items + if !items.isEmpty { + state.showPhotoPicker = false + // 处理选中的图片 + return .run { send in + for item in items { + if let data = try? await item.loadTransferable(type: Data.self), + let image = UIImage(data: data), + let imageData = image.jpegData(compressionQuality: 0.8) { + await send(.avatarSelected(imageData)) + break // 只处理第一张图片 + } + } + } + } return .none case .showLogoutConfirmation(let show): diff --git a/yana/Views/AppSettingView.swift b/yana/Views/AppSettingView.swift index 88a6cb2..de5be64 100644 --- a/yana/Views/AppSettingView.swift +++ b/yana/Views/AppSettingView.swift @@ -11,11 +11,6 @@ import PhotosUI struct AppSettingView: View { let store: StoreOf - // 直接let声明pickerStore - let pickerStore = Store( - initialState: ImagePickerWithPreviewReducer.State(inner: ImagePickerWithPreviewState(selectionMode: .single)), - reducer: { ImagePickerWithPreviewReducer() } - ) var body: some View { WithPerceptionTracking { @@ -136,38 +131,38 @@ struct AppSettingView: View { ) { 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 + let viewWithCamera = viewWithActionSheet .sheet(isPresented: Binding( - get: { store.showImagePicker }, - set: { store.send(.setShowImagePicker($0)) } + get: { store.showCamera }, + set: { store.send(.setShowCamera($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)) - } - ) + CameraPicker { image in + store.send(.cameraImagePicked(image)) + } } - let viewWithAlert = viewWithImagePicker + 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)) } diff --git a/yana/Views/Components/ImagePickerWithPreview/ImagePickerWithPreviewView.swift b/yana/Views/Components/ImagePickerWithPreview/ImagePickerWithPreviewView.swift index 966ebd2..8315846 100644 --- a/yana/Views/Components/ImagePickerWithPreview/ImagePickerWithPreviewView.swift +++ b/yana/Views/Components/ImagePickerWithPreview/ImagePickerWithPreviewView.swift @@ -19,22 +19,36 @@ public struct ImagePickerWithPreviewView: View { public var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in - ZStack { - Color.clear - } - .background(.red) - .ignoresSafeArea() - .modifier(CameraSheetModifier(viewStore: viewStore, onCancel: onCancel)) - .modifier(PhotosPickerModifier(viewStore: viewStore, loadedImages: $loadedImages, isLoadingImages: $isLoadingImages, loadingId: $loadingId, onCancel: onCancel)) - .modifier(PreviewCoverModifier(viewStore: viewStore, loadedImages: loadedImages, onUpload: onUpload, loadingId: $loadingId, onCancel: onCancel)) - .modifier(ErrorToastModifier(viewStore: viewStore)) - .onChange(of: viewStore.inner.isLoading) { _, isLoading in - if isLoading && loadingId == nil { - loadingId = APILoadingManager.shared.startLoading() - } else if !isLoading, let id = loadingId { - APILoadingManager.shared.finishLoading(id) - loadingId = nil + // 只有当需要显示相机或相册时才显示内容 + if viewStore.inner.showCamera || viewStore.inner.showPhotoPicker { + ZStack { + Color.clear } + .background(.clear) // 改为透明背景,避免红色背景 + .ignoresSafeArea() + .modifier(CameraSheetModifier(viewStore: viewStore, onCancel: onCancel)) + .modifier(PhotosPickerModifier(viewStore: viewStore, loadedImages: $loadedImages, isLoadingImages: $isLoadingImages, loadingId: $loadingId, onCancel: onCancel)) + .modifier(PreviewCoverModifier(viewStore: viewStore, loadedImages: loadedImages, onUpload: onUpload, loadingId: $loadingId, onCancel: onCancel)) + .modifier(ErrorToastModifier(viewStore: viewStore)) + .onChange(of: viewStore.inner.isLoading) { _, isLoading in + if isLoading && loadingId == nil { + loadingId = APILoadingManager.shared.startLoading() + } else if !isLoading, let id = loadingId { + APILoadingManager.shared.finishLoading(id) + loadingId = nil + } + } + } else { + // 不显示任何内容,避免空页面闪烁 + EmptyView() + .onChange(of: viewStore.inner.isLoading) { _, isLoading in + if isLoading && loadingId == nil { + loadingId = APILoadingManager.shared.startLoading() + } else if !isLoading, let id = loadingId { + APILoadingManager.shared.finishLoading(id) + loadingId = nil + } + } } } }