feat: 更新AppSettingFeature以增强图片选择功能和用户体验

- 在AppSettingFeature中新增相机和相册选择的状态和Action,优化图片源选择逻辑。
- 更新AppSettingView以支持相机和相册的弹窗显示,提升用户交互体验。
- 修改ImagePickerWithPreviewView以根据相机或相册选择动态显示内容,避免空页面闪烁。
- 确保相机和相册选择的逻辑清晰,增强代码可读性和维护性。
This commit is contained in:
edwinQQQ
2025-08-01 15:11:19 +08:00
parent b35b6e1ce1
commit 12dd03d5b3
3 changed files with 114 additions and 61 deletions

View File

@@ -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):

View File

@@ -11,11 +11,6 @@ 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 {
@@ -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)) }

View File

@@ -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
}
}
}
}
}