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:
parent
77d9cf9584
commit
56890cb519
@ -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_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_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
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_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_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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: {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -9,6 +9,7 @@ import AVFoundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Bedrock
|
import Bedrock
|
||||||
import MijickCamera
|
import MijickCamera
|
||||||
|
import CoreImage
|
||||||
|
|
||||||
// MARK: - Custom Camera Screen
|
// MARK: - Custom Camera Screen
|
||||||
|
|
||||||
@ -55,11 +56,13 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
}
|
}
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
let newZoom = lastMagnification * value
|
let newZoom = lastMagnification * value
|
||||||
lastMagnification = newZoom
|
|
||||||
// Clamp to reasonable range
|
// Clamp to reasonable range
|
||||||
let clampedZoom = min(max(newZoom, 1.0), 5.0)
|
let clampedZoom = min(max(newZoom, 1.0), 5.0)
|
||||||
|
lastMagnification = clampedZoom
|
||||||
do {
|
do {
|
||||||
try setZoomFactor(clampedZoom)
|
try setZoomFactor(clampedZoom)
|
||||||
|
// Save zoom factor for persistence
|
||||||
|
cameraSettings.currentZoomFactor = clampedZoom
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to set zoom factor: \(error)")
|
print("Failed to set zoom factor: \(error)")
|
||||||
}
|
}
|
||||||
@ -160,12 +163,30 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
setFlashMode(cameraSettings.flashMode.toMijickFlashMode)
|
setFlashMode(cameraSettings.flashMode.toMijickFlashMode)
|
||||||
// Tell MijickCamera whether to disable iOS flash (only matters if sync is on)
|
// Tell MijickCamera whether to disable iOS flash (only matters if sync is on)
|
||||||
updateFlashSyncState()
|
updateFlashSyncState()
|
||||||
// Initialize zoom gesture state
|
// Initialize zoom gesture state and restore saved zoom factor
|
||||||
lastMagnification = zoomFactor
|
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
|
// Track initial camera position
|
||||||
currentCameraPosition = cameraSettings.cameraPosition
|
currentCameraPosition = cameraSettings.cameraPosition
|
||||||
// Sync grid visibility with MijickCamera (defaults to true, so we need to set it)
|
// Sync grid visibility with MijickCamera (defaults to true, so we need to set it)
|
||||||
setGridVisibility(cameraSettings.isGridVisible)
|
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
|
.onChange(of: cameraSettings.cameraPositionRaw) { _, newRaw in
|
||||||
// Switch camera when position changes in settings
|
// Switch camera when position changes in settings
|
||||||
@ -191,6 +212,18 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
// Sync grid visibility with MijickCamera
|
// Sync grid visibility with MijickCamera
|
||||||
setGridVisibility(newValue)
|
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
|
.onChange(of: cameraManager.capturedMedia) { _, newMedia in
|
||||||
// Directly observe capture completion - bypasses MijickCamera's callback issues
|
// Directly observe capture completion - bypasses MijickCamera's callback issues
|
||||||
if let media = newMedia, let image = media.getImage() {
|
if let media = newMedia, let image = media.getImage() {
|
||||||
@ -286,4 +319,54 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
captureOutput()
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user