feat: 更新AppSettingFeature以增强图片选择功能和用户体验
- 在AppSettingFeature中新增相机和相册选择的状态和Action,优化图片源选择逻辑。 - 更新AppSettingView以支持相机和相册的弹窗显示,提升用户交互体验。 - 修改ImagePickerWithPreviewView以根据相机或相册选择动态显示内容,避免空页面闪烁。 - 确保相机和相册选择的逻辑清晰,增强代码可读性和维护性。
This commit is contained in:
@@ -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):
|
||||
|
@@ -11,11 +11,6 @@ import PhotosUI
|
||||
|
||||
struct AppSettingView: View {
|
||||
let store: StoreOf<AppSettingFeature>
|
||||
// 直接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)) }
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user