- 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
233 lines
6.4 KiB
Swift
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: {}
|
|
)
|
|
}
|