Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-09 09:55:18 -06:00
parent c2056e719d
commit 9c4d9304e6
3 changed files with 28 additions and 24 deletions

View File

@ -46,10 +46,10 @@ struct CardEditorView: View {
// Photo picker state // Photo picker state
@State private var pendingImageType: ImageType? // For showing PhotoSourcePicker @State private var pendingImageType: ImageType? // For showing PhotoSourcePicker
@State private var activeImageType: ImageType? // Tracks which type we're editing through the full flow @State private var nextImageType: ImageType? // Stored when user selects action, used after dismiss
@State private var photoPickerImageType: ImageType? // For showing PhotoPickerWithCropper (item-based)
@State private var cameraImageType: ImageType? // For showing CameraWithCropper (item-based)
@State private var pendingAction: PendingPhotoAction? // Action to take after source picker dismisses @State private var pendingAction: PendingPhotoAction? // Action to take after source picker dismisses
@State private var showingPhotoPicker = false
@State private var showingCamera = false
private enum PendingPhotoAction { private enum PendingPhotoAction {
case library case library
@ -225,17 +225,18 @@ struct CardEditorView: View {
} }
.sheet(item: $pendingImageType, onDismiss: { .sheet(item: $pendingImageType, onDismiss: {
// After source picker dismisses, show the appropriate picker // After source picker dismisses, show the appropriate picker
guard let action = pendingAction else { return } guard let action = pendingAction, let imageType = nextImageType else { return }
pendingAction = nil pendingAction = nil
nextImageType = nil
// Small delay to ensure sheet is fully dismissed // Small delay to ensure sheet is fully dismissed
Task { @MainActor in Task { @MainActor in
try? await Task.sleep(for: .milliseconds(100)) try? await Task.sleep(for: .milliseconds(100))
switch action { switch action {
case .library: case .library:
showingPhotoPicker = true photoPickerImageType = imageType // Triggers fullScreenCover(item:)
case .camera: case .camera:
showingCamera = true cameraImageType = imageType // Triggers fullScreenCover(item:)
} }
} }
}) { imageType in }) { imageType in
@ -243,11 +244,11 @@ struct CardEditorView: View {
title: imageType.title, title: imageType.title,
hasExistingPhoto: hasExistingPhoto(for: imageType), hasExistingPhoto: hasExistingPhoto(for: imageType),
onSelectFromLibrary: { onSelectFromLibrary: {
activeImageType = imageType nextImageType = imageType // Store for use after dismiss
pendingAction = .library pendingAction = .library
}, },
onTakePhoto: { onTakePhoto: {
activeImageType = imageType nextImageType = imageType // Store for use after dismiss
pendingAction = .camera pendingAction = .camera
}, },
onRemovePhoto: { onRemovePhoto: {
@ -255,33 +256,29 @@ struct CardEditorView: View {
} }
) )
} }
.fullScreenCover(isPresented: $showingPhotoPicker) { .fullScreenCover(item: $photoPickerImageType) { imageType in
NavigationStack { NavigationStack {
PhotoPickerWithCropper( PhotoPickerWithCropper(
aspectRatio: activeImageType?.cropAspectRatio ?? .square, aspectRatio: imageType.cropAspectRatio,
onSave: { croppedData in onSave: { croppedData in
savePhoto(croppedData, for: activeImageType) savePhoto(croppedData, for: imageType)
showingPhotoPicker = false photoPickerImageType = nil
activeImageType = nil
}, },
onCancel: { onCancel: {
showingPhotoPicker = false photoPickerImageType = nil
activeImageType = nil
} }
) )
} }
} }
.fullScreenCover(isPresented: $showingCamera) { .fullScreenCover(item: $cameraImageType) { imageType in
CameraWithCropper( CameraWithCropper(
aspectRatio: activeImageType?.cropAspectRatio ?? .square, aspectRatio: imageType.cropAspectRatio,
onSave: { croppedData in onSave: { croppedData in
savePhoto(croppedData, for: activeImageType) savePhoto(croppedData, for: imageType)
showingCamera = false cameraImageType = nil
activeImageType = nil
}, },
onCancel: { onCancel: {
showingCamera = false cameraImageType = nil
activeImageType = nil
} }
) )
} }
@ -734,8 +731,7 @@ private extension CardEditorView {
} }
} }
func savePhoto(_ data: Data, for imageType: ImageType?) { func savePhoto(_ data: Data, for imageType: ImageType) {
guard let imageType else { return }
switch imageType { switch imageType {
case .profile: photoData = data case .profile: photoData = data
case .cover: coverPhotoData = data case .cover: coverPhotoData = data
@ -805,6 +801,7 @@ private extension CardEditorView {
bio: bio, bio: bio,
accreditations: accreditations, accreditations: accreditations,
photoData: photoData, photoData: photoData,
coverPhotoData: coverPhotoData,
logoData: logoData logoData: logoData
) )
@ -837,6 +834,7 @@ private extension CardEditorView {
bio: bio, bio: bio,
accreditations: accreditations, accreditations: accreditations,
photoData: photoData, photoData: photoData,
coverPhotoData: coverPhotoData,
logoData: logoData logoData: logoData
) )

View File

@ -54,6 +54,7 @@ struct CameraWithCropper: View {
} }
} }
.transition(.move(edge: .trailing)) .transition(.move(edge: .trailing))
.id(aspectRatio.ratio) // Force recreate cropper when aspect ratio changes
} }
} }
.animation(.easeInOut(duration: Design.Animation.quick), value: showingCropper) .animation(.easeInOut(duration: Design.Animation.quick), value: showingCropper)

View File

@ -25,6 +25,7 @@ struct PhotoPickerWithCropper: View {
@State private var selectedItem: PhotosPickerItem? @State private var selectedItem: PhotosPickerItem?
@State private var imageData: Data? @State private var imageData: Data?
@State private var showingCropper = false @State private var showingCropper = false
@State private var pickerRefreshID = UUID() // Force picker refresh after cancel
var body: some View { var body: some View {
PhotosPicker( PhotosPicker(
@ -38,6 +39,7 @@ struct PhotoPickerWithCropper: View {
.photosPickerStyle(.inline) .photosPickerStyle(.inline)
.photosPickerDisabledCapabilities([.selectionActions]) .photosPickerDisabledCapabilities([.selectionActions])
.ignoresSafeArea() .ignoresSafeArea()
.id(pickerRefreshID) // Force recreate picker when ID changes
.onChange(of: selectedItem) { _, newValue in .onChange(of: selectedItem) { _, newValue in
guard let newValue else { return } guard let newValue else { return }
Task { @MainActor in Task { @MainActor in
@ -62,9 +64,12 @@ struct PhotoPickerWithCropper: View {
showingCropper = false showingCropper = false
self.imageData = nil self.imageData = nil
self.selectedItem = nil self.selectedItem = nil
// Generate new ID to force picker refresh, allowing same image to be reselected
pickerRefreshID = UUID()
} }
} }
.transition(.move(edge: .trailing)) .transition(.move(edge: .trailing))
.id(aspectRatio.ratio) // Force recreate cropper when aspect ratio changes
} }
} }
.animation(.easeInOut(duration: Design.Animation.quick), value: showingCropper) .animation(.easeInOut(duration: Design.Animation.quick), value: showingCropper)