From 56890cb519f4d7df1636353a1efa5fbcf40ac027 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 4 Jan 2026 15:24:13 -0600 Subject: [PATCH] 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 --- SelfieCam.xcodeproj/project.pbxproj | 4 +- SelfieCam/Features/Camera/GridOverlay.swift | 36 --- .../Camera/PostCapturePreviewView.swift | 222 ------------------ .../Camera/Views/CustomCameraScreen.swift | 89 ++++++- 4 files changed, 88 insertions(+), 263 deletions(-) delete mode 100644 SelfieCam/Features/Camera/GridOverlay.swift delete mode 100644 SelfieCam/Features/Camera/PostCapturePreviewView.swift diff --git a/SelfieCam.xcodeproj/project.pbxproj b/SelfieCam.xcodeproj/project.pbxproj index 73425ed..a506c20 100644 --- a/SelfieCam.xcodeproj/project.pbxproj +++ b/SelfieCam.xcodeproj/project.pbxproj @@ -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 = ( diff --git a/SelfieCam/Features/Camera/GridOverlay.swift b/SelfieCam/Features/Camera/GridOverlay.swift deleted file mode 100644 index 09604bd..0000000 --- a/SelfieCam/Features/Camera/GridOverlay.swift +++ /dev/null @@ -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) - } - } -} diff --git a/SelfieCam/Features/Camera/PostCapturePreviewView.swift b/SelfieCam/Features/Camera/PostCapturePreviewView.swift deleted file mode 100644 index a8195db..0000000 --- a/SelfieCam/Features/Camera/PostCapturePreviewView.swift +++ /dev/null @@ -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: {} - ) -} diff --git a/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift b/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift index facd116..a202bda 100644 --- a/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift +++ b/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift @@ -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 - lastMagnification = zoomFactor + // 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") + } + } }