Implement orphaned settings and delete unused files

IMPLEMENTED:
- HDR Mode: Now calls setHDRMode() on camera in onAppear and onChange
- Skin Smoothing: Applies CIGaussianBlur filter when enabled (premium)
- Center Stage: Enables/disables AVCaptureDevice.isCenterStageEnabled
- Zoom Factor Persistence: Saves and restores zoom level across sessions

DELETED:
- GridOverlay.swift: MijickCamera's built-in grid is used
- PostCapturePreviewView.swift: PhotoReviewView is used instead

Also:
- Added CoreImage import for CIFilter usage
- Clamp zoom to saved value on restore
This commit is contained in:
Matt Bruce 2026-01-04 15:24:13 -06:00
parent 77d9cf9584
commit 56890cb519
4 changed files with 88 additions and 263 deletions

View File

@ -427,8 +427,8 @@
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SelfieCam needs photo library access to automatically save your captured photos and videos to your device, making them available in the Photos app and other compatible applications.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
@ -464,8 +464,8 @@
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SelfieCam needs photo library access to automatically save your captured photos and videos to your device, making them available in the Photos app and other compatible applications.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (

View File

@ -1,36 +0,0 @@
import SwiftUI
import Bedrock
// Grid Overlay as separate view
struct GridOverlay: View {
var isVisible: Bool
var body: some View {
if isVisible {
GeometryReader { geo in
Path { path in
let w = geo.size.width
let h = geo.size.height
let thirdW = w / 3
let thirdH = h / 3
// Vertical lines
path.move(to: CGPoint(x: thirdW, y: 0))
path.addLine(to: CGPoint(x: thirdW, y: h))
path.move(to: CGPoint(x: 2 * thirdW, y: 0))
path.addLine(to: CGPoint(x: 2 * thirdW, y: h))
// Horizontal lines
path.move(to: CGPoint(x: 0, y: thirdH))
path.addLine(to: CGPoint(x: w, y: thirdH))
path.move(to: CGPoint(x: 0, y: 2 * thirdH))
path.addLine(to: CGPoint(x: w, y: 2 * thirdH))
}
.stroke(Color.white, lineWidth: Design.Grid.lineWidth)
.opacity(Design.Grid.opacity)
}
.ignoresSafeArea()
.accessibilityHidden(true)
}
}
}

View File

@ -1,222 +0,0 @@
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(activityItems: [image])
} else if let url = capturedVideoURL {
ShareSheet(activityItems: [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)
}
}
#Preview {
PostCapturePreviewView(
capturedImage: UIImage(systemName: "photo"),
capturedVideoURL: nil,
isAutoSaveEnabled: false,
onRetake: {},
onSave: {}
)
}

View File

@ -9,6 +9,7 @@ import AVFoundation
import SwiftUI
import Bedrock
import MijickCamera
import CoreImage
// MARK: - Custom Camera Screen
@ -55,11 +56,13 @@ struct CustomCameraScreen: MCameraScreen {
}
.onEnded { value in
let newZoom = lastMagnification * value
lastMagnification = newZoom
// Clamp to reasonable range
let clampedZoom = min(max(newZoom, 1.0), 5.0)
lastMagnification = clampedZoom
do {
try setZoomFactor(clampedZoom)
// Save zoom factor for persistence
cameraSettings.currentZoomFactor = clampedZoom
} catch {
print("Failed to set zoom factor: \(error)")
}
@ -160,12 +163,30 @@ struct CustomCameraScreen: MCameraScreen {
setFlashMode(cameraSettings.flashMode.toMijickFlashMode)
// Tell MijickCamera whether to disable iOS flash (only matters if sync is on)
updateFlashSyncState()
// Initialize zoom gesture state
// Initialize zoom gesture state and restore saved zoom factor
let savedZoom = cameraSettings.currentZoomFactor
if savedZoom > 1.0 {
do {
try setZoomFactor(savedZoom)
lastMagnification = savedZoom
Design.debugLog("Restored zoom factor: \(savedZoom)")
} catch {
lastMagnification = zoomFactor
Design.debugLog("Failed to restore zoom: \(error)")
}
} else {
lastMagnification = zoomFactor
}
// Track initial camera position
currentCameraPosition = cameraSettings.cameraPosition
// Sync grid visibility with MijickCamera (defaults to true, so we need to set it)
setGridVisibility(cameraSettings.isGridVisible)
// Apply HDR mode from settings
applyHDRMode(cameraSettings.hdrMode)
// Apply Center Stage from settings
applyCenterStage(cameraSettings.isCenterStageEnabled)
// Apply skin smoothing filter from settings
applySkinSmoothing(cameraSettings.isSkinSmoothingEnabled)
}
.onChange(of: cameraSettings.cameraPositionRaw) { _, newRaw in
// Switch camera when position changes in settings
@ -191,6 +212,18 @@ struct CustomCameraScreen: MCameraScreen {
// Sync grid visibility with MijickCamera
setGridVisibility(newValue)
}
.onChange(of: cameraSettings.hdrMode) { _, newValue in
// Apply HDR mode when setting changes
applyHDRMode(newValue)
}
.onChange(of: cameraSettings.isCenterStageEnabled) { _, newValue in
// Apply Center Stage when setting changes
applyCenterStage(newValue)
}
.onChange(of: cameraSettings.isSkinSmoothingEnabled) { _, newValue in
// Apply skin smoothing filter when setting changes
applySkinSmoothing(newValue)
}
.onChange(of: cameraManager.capturedMedia) { _, newMedia in
// Directly observe capture completion - bypasses MijickCamera's callback issues
if let media = newMedia, let image = media.getImage() {
@ -286,4 +319,54 @@ struct CustomCameraScreen: MCameraScreen {
captureOutput()
}
}
// MARK: - HDR Mode
/// Applies HDR mode to the camera
private func applyHDRMode(_ mode: CameraHDRMode) {
do {
try setHDRMode(mode.toMijickHDRMode)
Design.debugLog("HDR mode set to \(mode)")
} catch {
Design.debugLog("Failed to set HDR mode: \(error)")
}
}
// MARK: - Center Stage
/// Applies Center Stage setting to the camera
private func applyCenterStage(_ enabled: Bool) {
// Center Stage is a system-wide setting on AVCaptureDevice
// Only available on devices with Ultra Wide front camera (iPhone 11+, iPad Pro 2021+)
guard AVCaptureDevice.isCenterStageEnabled != enabled else { return }
// Check if Center Stage is supported
if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front),
device.activeFormat.isCenterStageSupported {
AVCaptureDevice.isCenterStageEnabled = enabled
Design.debugLog("Center Stage set to \(enabled)")
} else {
Design.debugLog("Center Stage not supported on this device")
}
}
// MARK: - Skin Smoothing
/// Applies skin smoothing filter to the camera
private func applySkinSmoothing(_ enabled: Bool) {
if enabled {
// Create a subtle skin smoothing effect using CIFilter
// CISmoothLinearGradient is not ideal, using a combination for subtle smoothing
if let smoothingFilter = CIFilter(name: "CIGaussianBlur") {
// Very subtle blur for skin smoothing effect
smoothingFilter.setValue(0.8, forKey: kCIInputRadiusKey)
setCameraFilters([smoothingFilter])
Design.debugLog("Skin smoothing filter applied")
}
} else {
// Remove all filters
setCameraFilters([])
Design.debugLog("Skin smoothing filter removed")
}
}
}