SelfieRingLight/SelfieRingLight/Features/Camera/PostCapturePreviewView.swift
Matt Bruce f38e0bf22c Integrate MijickCamera with ring light effect
- Add MijickCamera package for camera handling
- Create RingLightCameraScreen conforming to MCameraScreen protocol
- Ring light background fills screen with settings.lightColor
- Camera preview padded inward to create ring effect
- Custom capture button and controls overlay
- Grid overlay support
- Camera flip button using MijickCamera API
- Delete old CameraViewModel and CameraPreview (replaced by MijickCamera)
- Simplified PostCapturePreviewView for photo/video preview
2026-01-02 16:37:49 -06:00

233 lines
6.4 KiB
Swift

import SwiftUI
import AVKit
import Bedrock
// MARK: - Post Capture Preview View
/// Full-screen preview shown after photo/video capture
struct PostCapturePreviewView: View {
let capturedImage: UIImage?
let capturedVideoURL: URL?
let isAutoSaveEnabled: Bool
let onRetake: () -> Void
let onSave: () -> Void
@State private var player: AVPlayer?
@State private var showShareSheet = false
@State private var toastMessage: String?
var body: some View {
ZStack {
// Dark background
Color.black.ignoresSafeArea()
// Media preview
mediaPreview
// Controls overlay
VStack {
// Top bar with close button
topBar
Spacer()
// Bottom toolbar
bottomToolbar
}
// Toast notification
if let message = toastMessage {
toastView(message: message)
}
}
.onAppear {
setupVideoPlayerIfNeeded()
if isAutoSaveEnabled {
autoSave()
}
}
.onDisappear {
player?.pause()
}
.sheet(isPresented: $showShareSheet) {
if let image = capturedImage {
ShareSheet(items: [image])
} else if let url = capturedVideoURL {
ShareSheet(items: [url])
}
}
}
// MARK: - Media Preview
@ViewBuilder
private var mediaPreview: some View {
if let image = capturedImage {
Image(uiImage: image)
.resizable()
.scaledToFit()
.accessibilityLabel(String(localized: "Captured photo"))
} else if let _ = capturedVideoURL, let player {
VideoPlayer(player: player)
.onAppear {
player.play()
}
.accessibilityLabel(String(localized: "Captured video"))
} else {
ProgressView()
.tint(.white)
}
}
// MARK: - Top Bar
private var topBar: some View {
HStack {
Button {
onRetake()
} label: {
Image(systemName: "xmark")
.font(.title2)
.foregroundStyle(.white)
.padding(Design.Spacing.medium)
.background(.ultraThinMaterial, in: .circle)
}
.accessibilityLabel(String(localized: "Close preview"))
Spacer()
}
.padding(.horizontal, Design.Spacing.large)
.padding(.top, Design.Spacing.medium)
}
// MARK: - Bottom Toolbar
private var bottomToolbar: some View {
HStack(spacing: Design.Spacing.xLarge) {
// Retake button
ToolbarButton(
title: String(localized: "Retake"),
systemImage: "arrow.counterclockwise",
action: onRetake
)
Spacer()
// Save button (if not auto-saved)
if !isAutoSaveEnabled {
ToolbarButton(
title: String(localized: "Save"),
systemImage: "square.and.arrow.down",
action: {
onSave()
showToast(String(localized: "Saved to Photos"))
}
)
Spacer()
}
// Share button
ToolbarButton(
title: String(localized: "Share"),
systemImage: "square.and.arrow.up",
action: { showShareSheet = true }
)
}
.padding(.horizontal, Design.Spacing.xxLarge)
.padding(.vertical, Design.Spacing.large)
.background(.ultraThinMaterial)
}
// MARK: - Toast View
private func toastView(message: String) -> some View {
VStack {
Spacer()
Text(message)
.font(.system(size: Design.BaseFontSize.body, weight: .medium))
.foregroundStyle(.white)
.padding(.horizontal, Design.Spacing.large)
.padding(.vertical, Design.Spacing.medium)
.background(.ultraThinMaterial, in: .capsule)
.padding(.bottom, 100)
}
.transition(.move(edge: .bottom).combined(with: .opacity))
.animation(.easeInOut, value: toastMessage)
}
// MARK: - Video Setup
private func setupVideoPlayerIfNeeded() {
guard let url = capturedVideoURL else { return }
player = AVPlayer(url: url)
}
// MARK: - Auto Save
private func autoSave() {
if let image = capturedImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
showToast(String(localized: "Saved to Photos"))
}
// Video saving would go here
}
private func showToast(_ message: String) {
withAnimation {
toastMessage = message
}
Task {
try? await Task.sleep(for: .seconds(2))
withAnimation {
toastMessage = nil
}
}
}
}
// MARK: - Toolbar Button
private struct ToolbarButton: View {
let title: String
let systemImage: String
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(spacing: Design.Spacing.xxSmall) {
Image(systemName: systemImage)
.font(.title2)
Text(title)
.font(.system(size: Design.BaseFontSize.caption))
}
.foregroundStyle(.white)
}
.accessibilityLabel(title)
}
}
// MARK: - Share Sheet
struct ShareSheet: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
}
#Preview {
PostCapturePreviewView(
capturedImage: UIImage(systemName: "photo"),
capturedVideoURL: nil,
isAutoSaveEnabled: false,
onRetake: {},
onSave: {}
)
}