BusinessCard/BusinessCard/Views/Components/PhotoPickerWithCropper.swift

80 lines
2.5 KiB
Swift

import SwiftUI
import PhotosUI
import Bedrock
/// A combined photo picker and cropper flow.
/// Shows the system PhotosPicker, and when an image is selected,
/// immediately overlays the cropper. Both dismiss together when done.
struct PhotoPickerWithCropper: View {
@Environment(\.dismiss) private var dismiss
let onSave: (Data) -> Void
let onCancel: () -> Void
@State private var selectedItem: PhotosPickerItem?
@State private var imageData: Data?
@State private var showingCropper = false
var body: some View {
PhotosPicker(
selection: $selectedItem,
matching: .images,
photoLibrary: .shared()
) {
// This is never shown - we use it in inline mode
EmptyView()
}
.photosPickerStyle(.inline)
.photosPickerDisabledCapabilities([.selectionActions])
.ignoresSafeArea()
.onChange(of: selectedItem) { _, newValue in
guard let newValue else { return }
Task { @MainActor in
if let data = try? await newValue.loadTransferable(type: Data.self) {
imageData = data
showingCropper = true
}
}
}
.overlay {
// Cropper overlay
if showingCropper, let imageData {
PhotoCropperSheet(
imageData: imageData,
shouldDismissOnComplete: false
) { croppedData in
if let croppedData {
onSave(croppedData)
} else {
// User cancelled cropper, go back to picker
showingCropper = false
self.imageData = nil
self.selectedItem = nil
}
}
.transition(.move(edge: .trailing))
}
}
.animation(.easeInOut(duration: Design.Animation.quick), value: showingCropper)
.toolbar {
// Only show cancel when cropper is NOT showing (cropper has its own toolbar)
if !showingCropper {
ToolbarItem(placement: .cancellationAction) {
Button(String.localized("Cancel")) {
onCancel()
}
}
}
}
}
}
#Preview {
NavigationStack {
PhotoPickerWithCropper(
onSave: { _ in print("Saved") },
onCancel: { print("Cancelled") }
)
}
}