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_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 = (
|
||||
|
||||
@ -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 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user