diff --git a/SelfieCam.xcodeproj/xcshareddata/xcschemes/SelfieCam.xcscheme b/SelfieCam.xcodeproj/xcshareddata/xcschemes/SelfieCam.xcscheme
new file mode 100644
index 0000000..2287335
--- /dev/null
+++ b/SelfieCam.xcodeproj/xcshareddata/xcschemes/SelfieCam.xcscheme
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SelfieCam.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist b/SelfieCam.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist
index dccc721..ad51775 100644
--- a/SelfieCam.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/SelfieCam.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -10,5 +10,23 @@
4
+ SuppressBuildableAutocreation
+
+ EA836ABE2F0ACE8A00077F87
+
+ primary
+
+
+ EA836ACB2F0ACE8B00077F87
+
+ primary
+
+
+ EA836AD52F0ACE8B00077F87
+
+ primary
+
+
+
diff --git a/SelfieCam/Features/Camera/ContentView.swift b/SelfieCam/Features/Camera/ContentView.swift
index 9c50be2..e607c9f 100644
--- a/SelfieCam/Features/Camera/ContentView.swift
+++ b/SelfieCam/Features/Camera/ContentView.swift
@@ -54,7 +54,7 @@ struct ContentView: View {
.transition(.opacity)
}
- // Settings button overlay
+ // Settings button overlay - positioned with safe area consideration
VStack {
HStack {
Spacer()
@@ -72,10 +72,11 @@ struct ContentView: View {
.accessibilityLabel("Settings")
}
.padding(.horizontal, Design.Spacing.large)
- .padding(.top, Design.Spacing.medium)
+ .padding(.top, Design.Spacing.small) // Reduced from medium to account for safe area
Spacer()
}
+ .safeAreaInset(edge: .top) { Color.clear.frame(height: 0) } // Ensures proper safe area handling
}
.ignoresSafeArea()
.animation(.easeInOut(duration: Design.Animation.quick), value: showPhotoReview)
diff --git a/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift b/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift
index bd0498d..8846ab7 100644
--- a/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift
+++ b/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift
@@ -26,13 +26,7 @@ struct CustomCameraScreen: MCameraScreen {
// Center Stage state
@State private var isCenterStageEnabled: Bool = AVCaptureDevice.isCenterStageEnabled
- // Controls panel expansion state
- @State private var isControlsExpanded: Bool = false
- // Ring light settings overlay state
- @State private var showRingLightColorPicker: Bool = false
- @State private var showRingLightSizeSlider: Bool = false
- @State private var showRingLightOpacitySlider: Bool = false
// Screen flash state for front camera
@State private var isShowingScreenFlash: Bool = false
@@ -47,6 +41,7 @@ struct CustomCameraScreen: MCameraScreen {
// Camera preview with pinch gesture - Metal layer doesn't respect SwiftUI clipping
createCameraOutputView()
.ignoresSafeArea()
+ .scaleEffect(x: cameraSettings.isMirrorFlipped ? -1 : 1, y: 1) // Apply horizontal mirror flip
.gesture(
MagnificationGesture()
.updating($magnification) { currentState, gestureState, transaction in
@@ -80,95 +75,17 @@ struct CustomCameraScreen: MCameraScreen {
let isLandscape = geometry.size.width > geometry.size.height
if isLandscape {
- // Landscape layout: full-width centered controls, capture button on left with zoom above
- ZStack {
- // Centered controls across entire screen
- VStack(spacing: 0) {
- // Top controls area - expandable panel (centered)
- ExpandableControlsPanel(
- isExpanded: $isControlsExpanded,
- hasActiveSettings: hasActiveSettings,
- activeSettingsIcons: activeSettingsIcons,
- flashMode: cameraSettings.flashMode,
- flashIcon: flashIcon,
- onFlashTap: toggleFlash,
- isFlashSyncedWithRingLight: cameraSettings.isFlashSyncedWithRingLight,
- onFlashSyncTap: toggleFlashSync,
- hdrMode: cameraSettings.hdrMode,
- hdrIcon: hdrIcon,
- onHDRTap: toggleHDR,
- isGridVisible: cameraSettings.isGridVisible,
- gridIcon: gridIcon,
- onGridTap: toggleGrid,
- photoQuality: cameraSettings.photoQuality,
- onQualityTap: cycleQuality,
- isCenterStageAvailable: isCenterStageAvailable,
- isCenterStageEnabled: isCenterStageEnabled,
- onCenterStageTap: toggleCenterStage,
- isFrontCamera: cameraPosition == .front,
- onFlipCameraTap: flipCamera,
- isRingLightEnabled: cameraSettings.isRingLightEnabled,
- onRingLightTap: toggleRingLight,
- ringLightColor: cameraSettings.lightColor,
- onRingLightColorTap: toggleRingLightColorPicker,
- ringLightSize: cameraSettings.ringSize,
- onRingLightSizeTap: toggleRingLightSizeSlider,
- ringLightOpacity: cameraSettings.ringLightOpacity,
- onRingLightOpacityTap: toggleRingLightOpacitySlider
- )
- .padding(.horizontal, Design.Spacing.large)
- .padding(.top, Design.Spacing.medium)
-
- Spacer()
- }
-
- // Left side overlay - Capture Button only
- VStack {
- Spacer()
- CaptureButton(action: { performCapture() })
- Spacer()
- }
- .padding(.leading, Design.Spacing.large)
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
+ // Landscape layout: capture button on left with zoom above
+ VStack {
+ Spacer()
+ CaptureButton(action: { performCapture() })
+ Spacer()
}
+ .padding(.leading, Design.Spacing.large)
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
} else {
- // Portrait layout: controls on top, capture button at bottom
+ // Portrait layout: capture button at bottom
VStack(spacing: 0) {
- // Top controls area - expandable panel
- ExpandableControlsPanel(
- isExpanded: $isControlsExpanded,
- hasActiveSettings: hasActiveSettings,
- activeSettingsIcons: activeSettingsIcons,
- flashMode: cameraSettings.flashMode,
- flashIcon: flashIcon,
- onFlashTap: toggleFlash,
- isFlashSyncedWithRingLight: cameraSettings.isFlashSyncedWithRingLight,
- onFlashSyncTap: toggleFlashSync,
- hdrMode: cameraSettings.hdrMode,
- hdrIcon: hdrIcon,
- onHDRTap: toggleHDR,
- isGridVisible: cameraSettings.isGridVisible,
- gridIcon: gridIcon,
- onGridTap: toggleGrid,
- photoQuality: cameraSettings.photoQuality,
- onQualityTap: cycleQuality,
- isCenterStageAvailable: isCenterStageAvailable,
- isCenterStageEnabled: isCenterStageEnabled,
- onCenterStageTap: toggleCenterStage,
- isFrontCamera: cameraPosition == .front,
- onFlipCameraTap: flipCamera,
- isRingLightEnabled: cameraSettings.isRingLightEnabled,
- onRingLightTap: toggleRingLight,
- ringLightColor: cameraSettings.lightColor,
- onRingLightColorTap: toggleRingLightColorPicker,
- ringLightSize: cameraSettings.ringSize,
- onRingLightSizeTap: toggleRingLightSizeSlider,
- ringLightOpacity: cameraSettings.ringLightOpacity,
- onRingLightOpacityTap: toggleRingLightOpacitySlider
- )
- .padding(.horizontal, Design.Spacing.large)
- .padding(.top, Design.Spacing.medium)
-
Spacer()
// Bottom controls
@@ -188,41 +105,7 @@ struct CustomCameraScreen: MCameraScreen {
}
}
- // Ring light color picker overlay
- if showRingLightColorPicker {
- ColorPickerOverlay(
- selectedColor: Binding(
- get: { cameraSettings.selectedLightColor.color },
- set: { cameraSettings.selectedLightColor = RingLightColor.custom(with: $0) }
- ),
- isPresented: $showRingLightColorPicker
- )
- .transition(.opacity)
- }
- // Ring light size slider overlay
- if showRingLightSizeSlider {
- SizeSliderOverlay(
- selectedSize: Binding(
- get: { cameraSettings.ringSize },
- set: { cameraSettings.ringSize = $0 }
- ),
- isPresented: $showRingLightSizeSlider
- )
- .transition(.opacity)
- }
-
- // Ring light opacity slider overlay
- if showRingLightOpacitySlider {
- OpacitySliderOverlay(
- selectedOpacity: Binding(
- get: { cameraSettings.ringLightOpacity },
- set: { cameraSettings.ringLightOpacity = $0 }
- ),
- isPresented: $showRingLightOpacitySlider
- )
- .transition(.opacity)
- }
// Screen flash overlay for front camera
if isShowingScreenFlash {
@@ -232,26 +115,6 @@ struct CustomCameraScreen: MCameraScreen {
}
}
.animation(.easeInOut(duration: 0.05), value: isShowingScreenFlash)
- .gesture(
- // Only add tap gesture when there are overlays to dismiss
- (isControlsExpanded || showRingLightColorPicker || showRingLightSizeSlider || showRingLightOpacitySlider) ?
- TapGesture().onEnded {
- // Collapse panel when tapping outside
- if isControlsExpanded {
- isControlsExpanded = false
- }
- // Hide overlays when tapping outside
- if showRingLightColorPicker {
- showRingLightColorPicker = false
- }
- if showRingLightSizeSlider {
- showRingLightSizeSlider = false
- }
- if showRingLightOpacitySlider {
- showRingLightOpacitySlider = false
- }
- } : nil
- )
.onAppear {
// Set flash mode from saved settings
setFlashMode(cameraSettings.flashMode.toMijickFlashMode)
@@ -275,153 +138,9 @@ struct CustomCameraScreen: MCameraScreen {
}
}
- // MARK: - Active Settings Detection
- /// Returns true if any setting is in a non-default state
- private var hasActiveSettings: Bool {
- cameraSettings.flashMode != .off || cameraSettings.hdrMode != .off || cameraSettings.isGridVisible || isCenterStageEnabled || cameraSettings.isRingLightEnabled
- }
-
- /// Returns icons for currently active settings (for collapsed pill display)
- private var activeSettingsIcons: [String] {
- var icons: [String] = []
- if cameraSettings.flashMode != .off {
- icons.append(flashIcon)
- }
- if cameraSettings.hdrMode != .off {
- icons.append(hdrIcon)
- }
- if cameraSettings.isGridVisible {
- icons.append(gridIcon)
- }
- if cameraSettings.isRingLightEnabled {
- icons.append("circle.fill")
- }
- if isCenterStageEnabled {
- icons.append("person.crop.rectangle.fill")
- }
- return icons
- }
-
- // MARK: - Control Icons
- private var flashIcon: String {
- cameraSettings.flashMode.icon
- }
-
- private var hdrIcon: String {
- switch cameraSettings.hdrMode {
- case .off: return "circle.lefthalf.filled"
- case .auto: return "circle.lefthalf.filled"
- case .on: return "circle.fill"
- }
- }
-
- private var gridIcon: String {
- cameraSettings.isGridVisible ? "grid" : "grid"
- }
-
- private var isCenterStageAvailable: Bool {
- guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else {
- return false
- }
- return device.activeFormat.isCenterStageSupported
- }
// MARK: - Actions
- private func toggleFlash() {
- let nextMode: CameraFlashMode
- switch cameraSettings.flashMode {
- case .off: nextMode = .auto
- case .auto: nextMode = .on
- case .on: nextMode = .off
- }
- cameraSettings.flashMode = nextMode
- // Update MijickCamera's flash mode so it knows to use iOS Retina Flash
- setFlashMode(nextMode.toMijickFlashMode)
- }
-
- private func toggleFlashSync() {
- cameraSettings.isFlashSyncedWithRingLight.toggle()
- }
-
- private func toggleHDR() {
- Task {
- do {
- let nextMode: CameraHDRMode
- switch cameraSettings.hdrMode {
- case .off: nextMode = .auto
- case .auto: nextMode = .on
- case .on: nextMode = .off
- }
- try setHDRMode(nextMode.toMijickHDRMode)
- cameraSettings.hdrMode = nextMode
- } catch {
- print("Failed to set HDR mode: \(error)")
- }
- }
- }
-
- private func toggleGrid() {
- cameraSettings.isGridVisible.toggle()
- setGridVisibility(cameraSettings.isGridVisible)
- }
-
- private func flipCamera() {
- Task {
- do {
- let newPosition: CameraPosition = (cameraPosition == .front) ? .back : .front
- try await setCameraPosition(newPosition)
- cameraSettings.cameraPosition = newPosition
- } catch {
- print("Failed to flip camera: \(error)")
- }
- }
- }
-
- private func toggleCenterStage() {
- // Get the current camera device using AVFoundation
- let deviceTypes: [AVCaptureDevice.DeviceType] = [
- .builtInWideAngleCamera,
- .builtInUltraWideCamera,
- .builtInTelephotoCamera
- ]
-
- guard let device = AVCaptureDevice.default(deviceTypes[0], for: .video, position: cameraPosition == .front ? .front : .back) else {
- print("No camera device available for Center Stage toggle")
- return
- }
-
- do {
- // Configure Center Stage globally (these are static properties)
- try device.lockForConfiguration()
-
- // Set control mode to app-controlled
- if device.activeFormat.isCenterStageSupported {
- AVCaptureDevice.centerStageControlMode = .app
- AVCaptureDevice.isCenterStageEnabled = !isCenterStageEnabled
- }
-
- device.unlockForConfiguration()
-
- // Update our state
- isCenterStageEnabled.toggle()
-
- print("Center Stage toggled to: \(isCenterStageEnabled)")
- } catch {
- print("Failed to toggle Center Stage: \(error)")
- }
- }
-
- private func cycleQuality() {
- let allCases = PhotoQuality.allCases
- let currentIndex = allCases.firstIndex(of: cameraSettings.photoQuality) ?? 0
- let nextIndex = (currentIndex + 1) % allCases.count
- cameraSettings.photoQuality = allCases[nextIndex]
- }
-
- private func toggleRingLight() {
- cameraSettings.isRingLightEnabled.toggle()
- }
private func updateFlashSyncState() {
// Tell MijickCamera whether we're handling flash ourselves (sync enabled)
@@ -443,7 +162,7 @@ struct CustomCameraScreen: MCameraScreen {
private var shouldUseCustomScreenFlash: Bool {
cameraPosition == .front && cameraSettings.flashMode != .off && cameraSettings.isFlashSyncedWithRingLight
}
-
+
/// Performs capture with screen flash if needed
private func performCapture() {
print("performCapture called - shouldUseCustomScreenFlash: \(shouldUseCustomScreenFlash)")
@@ -451,16 +170,16 @@ struct CustomCameraScreen: MCameraScreen {
// Save original brightness and boost to max
originalBrightness = UIScreen.main.brightness
UIScreen.main.brightness = 1.0
-
+
// Show flash overlay
isShowingScreenFlash = true
-
+
// Wait for camera to adjust to bright screen, then capture
Task { @MainActor in
try? await Task.sleep(for: .milliseconds(150))
print("Calling captureOutput() with custom flash")
captureOutput()
-
+
// Keep flash visible briefly after capture
try? await Task.sleep(for: .milliseconds(100))
isShowingScreenFlash = false
@@ -472,24 +191,4 @@ struct CustomCameraScreen: MCameraScreen {
captureOutput()
}
}
-
- private func toggleRingLightColorPicker() {
- showRingLightColorPicker = true
- showRingLightSizeSlider = false // Hide other overlay
- isControlsExpanded = false // Collapse controls panel
- }
-
- private func toggleRingLightSizeSlider() {
- showRingLightSizeSlider = true
- showRingLightColorPicker = false // Hide other overlay
- showRingLightOpacitySlider = false // Hide other overlay
- isControlsExpanded = false // Collapse controls panel
- }
-
- private func toggleRingLightOpacitySlider() {
- showRingLightOpacitySlider = true
- showRingLightColorPicker = false // Hide other overlay
- showRingLightSizeSlider = false // Hide other overlay
- isControlsExpanded = false // Collapse controls panel
- }
}
diff --git a/SelfieCam/Features/Camera/Views/SizeSliderOverlay.swift b/SelfieCam/Features/Camera/Views/SizeSliderOverlay.swift
index 03e3b3e..98220b6 100644
--- a/SelfieCam/Features/Camera/Views/SizeSliderOverlay.swift
+++ b/SelfieCam/Features/Camera/Views/SizeSliderOverlay.swift
@@ -14,8 +14,8 @@ struct SizeSliderOverlay: View {
@Binding var selectedSize: CGFloat
@Binding var isPresented: Bool
- private let minSize: CGFloat = 50
- private let maxSize: CGFloat = 100
+ private let minSize: CGFloat = SettingsViewModel.minRingSize
+ private let maxSize: CGFloat = SettingsViewModel.maxRingSize
var body: some View {
ZStack {
diff --git a/SelfieCam/Features/Settings/SettingsView.swift b/SelfieCam/Features/Settings/SettingsView.swift
index c287eba..b6044fd 100644
--- a/SelfieCam/Features/Settings/SettingsView.swift
+++ b/SelfieCam/Features/Settings/SettingsView.swift
@@ -17,48 +17,34 @@ struct SettingsView: View {
NavigationStack {
ScrollView {
VStack(spacing: Design.Spacing.medium) {
-
+
// MARK: - Ring Light Section
-
+
SettingsSectionHeader(title: "Ring Light", systemImage: "light.max")
-
+
+ // Ring Light Enabled
+ SettingsToggle(
+ title: String(localized: "Enable Ring Light"),
+ subtitle: String(localized: "Show colored light ring around camera preview"),
+ isOn: $viewModel.isRingLightEnabled
+ )
+ .accessibilityHint(String(localized: "Enables or disables the ring light overlay"))
+
// Ring Size Slider
ringSizeSlider
-
+
// Color Preset
colorPresetSection
-
- // MARK: - Camera Section
-
- SettingsSectionHeader(title: "Camera", systemImage: "camera")
-
- SettingsToggle(
- title: String(localized: "Front Flash"),
- subtitle: String(localized: "Hides preview during capture for a flash effect"),
- isOn: $viewModel.isFrontFlashEnabled
- )
- .accessibilityHint(String(localized: "Uses the ring light as a flash when taking photos"))
-
- SettingsToggle(
- title: String(localized: "True Mirror"),
- subtitle: String(localized: "Shows non-flipped preview like a real mirror"),
- isOn: $viewModel.isMirrorFlipped
- )
- .accessibilityHint(String(localized: "When enabled, the preview is not mirrored"))
-
- SettingsToggle(
- title: String(localized: "Skin Smoothing"),
- subtitle: String(localized: "Applies subtle real-time smoothing"),
- isOn: $viewModel.isSkinSmoothingEnabled
- )
- .accessibilityHint(String(localized: "Applies light skin smoothing to the camera preview"))
-
- SettingsToggle(
- title: String(localized: "Grid Overlay"),
- subtitle: String(localized: "Shows rule of thirds grid"),
- isOn: $viewModel.isGridVisible
- )
- .accessibilityHint(String(localized: "Shows a grid overlay to help compose your shot"))
+
+ // Ring Light Brightness
+ ringLightBrightnessSlider
+
+ // MARK: - Camera Controls Section
+
+ SettingsSectionHeader(title: "Camera Controls", systemImage: "camera")
+
+ // Camera Position
+ cameraPositionPicker
// Flash Mode
flashModePicker
@@ -66,61 +52,80 @@ struct SettingsView: View {
// Flash Sync
SettingsToggle(
title: String(localized: "Flash Sync"),
- subtitle: String(localized: "Use ring light color for flash"),
+ subtitle: String(localized: "Use ring light color for screen flash"),
isOn: $viewModel.isFlashSyncedWithRingLight
)
.accessibilityHint(String(localized: "Syncs flash color with ring light color"))
+ // Front Flash
+ SettingsToggle(
+ title: String(localized: "Front Flash"),
+ subtitle: String(localized: "Hide preview during capture for flash effect"),
+ isOn: $viewModel.isFrontFlashEnabled
+ )
+ .accessibilityHint(String(localized: "Uses screen flash when taking front camera photos"))
+
// HDR Mode
hdrModePicker
// Photo Quality
photoQualityPicker
- // Camera Position
- cameraPositionPicker
+ // MARK: - Display Section
+
+ SettingsSectionHeader(title: "Display", systemImage: "eye")
- // Ring Light Enabled
SettingsToggle(
- title: String(localized: "Ring Light Enabled"),
- subtitle: String(localized: "Show ring light around camera"),
- isOn: $viewModel.isRingLightEnabled
+ title: String(localized: "True Mirror"),
+ subtitle: String(localized: "Shows horizontally flipped preview like a real mirror"),
+ isOn: $viewModel.isMirrorFlipped
)
- .accessibilityHint(String(localized: "Enables or disables the ring light overlay"))
+ .accessibilityHint(String(localized: "Flips the camera preview horizontally"))
- // Ring Light Brightness
- ringLightBrightnessSlider
+ SettingsToggle(
+ title: String(localized: "Grid Overlay"),
+ subtitle: String(localized: "Shows rule of thirds grid for composition"),
+ isOn: $viewModel.isGridVisible
+ )
+ .accessibilityHint(String(localized: "Shows a grid overlay to help compose your shot"))
+
+ SettingsToggle(
+ title: String(localized: "Skin Smoothing"),
+ subtitle: String(localized: "Applies subtle real-time skin smoothing"),
+ isOn: $viewModel.isSkinSmoothingEnabled
+ )
+ .accessibilityHint(String(localized: "Applies light skin smoothing to the camera preview"))
+
+ // MARK: - Capture Section
+
+ SettingsSectionHeader(title: "Capture", systemImage: "photo.on.rectangle")
// Timer Selection
timerPicker
-
- // MARK: - Capture Section
-
- SettingsSectionHeader(title: "Capture", systemImage: "photo.on.rectangle")
-
+
SettingsToggle(
title: String(localized: "Auto-Save"),
subtitle: String(localized: "Automatically save captures to Photo Library"),
isOn: $viewModel.isAutoSaveEnabled
)
.accessibilityHint(String(localized: "When enabled, photos and videos are saved immediately after capture"))
-
+
// MARK: - Pro Section
-
+
SettingsSectionHeader(title: "Pro", systemImage: "crown")
-
+
proSection
-
+
// MARK: - Sync Section
-
+
SettingsSectionHeader(title: "iCloud Sync", systemImage: "icloud")
-
+
iCloudSyncSection
-
+
// MARK: - About Section
-
+
SettingsSectionHeader(title: "About", systemImage: "info.circle")
-
+
acknowledgmentsSection
Spacer(minLength: Design.Spacing.xxxLarge)
@@ -239,48 +244,88 @@ struct SettingsView: View {
// MARK: - Flash Mode Picker
private var flashModePicker: some View {
- SegmentedPicker(
- title: String(localized: "Flash Mode"),
- options: CameraFlashMode.allCases.map { ($0.displayName, $0) },
- selection: $viewModel.flashMode
- )
- .accessibilityLabel(String(localized: "Select flash mode"))
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text(String(localized: "Flash Mode"))
+ .font(.system(size: Design.BaseFontSize.medium, weight: .medium))
+ .foregroundStyle(.white)
+
+ Text(String(localized: "Controls automatic flash behavior for photos"))
+ .font(.system(size: Design.BaseFontSize.caption))
+ .foregroundStyle(.white.opacity(Design.Opacity.medium))
+
+ SegmentedPicker(
+ title: "",
+ options: CameraFlashMode.allCases.map { ($0.displayName, $0) },
+ selection: $viewModel.flashMode
+ )
+ .accessibilityLabel(String(localized: "Select flash mode"))
+ }
}
// MARK: - HDR Mode Picker
private var hdrModePicker: some View {
- SegmentedPicker(
- title: String(localized: "HDR Mode"),
- options: CameraHDRMode.allCases.map { ($0.displayName, $0) },
- selection: $viewModel.hdrMode
- )
- .accessibilityLabel(String(localized: "Select HDR mode"))
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text(String(localized: "HDR Mode"))
+ .font(.system(size: Design.BaseFontSize.medium, weight: .medium))
+ .foregroundStyle(.white)
+
+ Text(String(localized: "High Dynamic Range for better lighting in photos"))
+ .font(.system(size: Design.BaseFontSize.caption))
+ .foregroundStyle(.white.opacity(Design.Opacity.medium))
+
+ SegmentedPicker(
+ title: "",
+ options: CameraHDRMode.allCases.map { ($0.displayName, $0) },
+ selection: $viewModel.hdrMode
+ )
+ .accessibilityLabel(String(localized: "Select HDR mode"))
+ }
}
// MARK: - Photo Quality Picker
private var photoQualityPicker: some View {
- SegmentedPicker(
- title: String(localized: "Photo Quality"),
- options: PhotoQuality.allCases.map { ($0.rawValue.capitalized, $0) },
- selection: $viewModel.photoQuality
- )
- .accessibilityLabel(String(localized: "Select photo quality"))
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text(String(localized: "Photo Quality"))
+ .font(.system(size: Design.BaseFontSize.medium, weight: .medium))
+ .foregroundStyle(.white)
+
+ Text(String(localized: "File size and image quality for saved photos"))
+ .font(.system(size: Design.BaseFontSize.caption))
+ .foregroundStyle(.white.opacity(Design.Opacity.medium))
+
+ SegmentedPicker(
+ title: "",
+ options: PhotoQuality.allCases.map { ($0.rawValue.capitalized, $0) },
+ selection: $viewModel.photoQuality
+ )
+ .accessibilityLabel(String(localized: "Select photo quality"))
+ }
}
// MARK: - Camera Position Picker
private var cameraPositionPicker: some View {
- SegmentedPicker(
- title: String(localized: "Camera"),
- options: [
- (String(localized: "Front"), CameraPosition.front),
- (String(localized: "Back"), CameraPosition.back)
- ],
- selection: $viewModel.cameraPosition
- )
- .accessibilityLabel(String(localized: "Select camera position"))
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text(String(localized: "Camera"))
+ .font(.system(size: Design.BaseFontSize.medium, weight: .medium))
+ .foregroundStyle(.white)
+
+ Text(String(localized: "Choose between front and back camera lenses"))
+ .font(.system(size: Design.BaseFontSize.caption))
+ .foregroundStyle(.white.opacity(Design.Opacity.medium))
+
+ SegmentedPicker(
+ title: "",
+ options: [
+ (String(localized: "Front"), CameraPosition.front),
+ (String(localized: "Back"), CameraPosition.back)
+ ],
+ selection: $viewModel.cameraPosition
+ )
+ .accessibilityLabel(String(localized: "Select camera position"))
+ }
}
// MARK: - Ring Light Brightness Slider
@@ -328,12 +373,22 @@ struct SettingsView: View {
// MARK: - Timer Picker
private var timerPicker: some View {
- SegmentedPicker(
- title: String(localized: "Self-Timer"),
- options: TimerOption.allCases.map { ($0.displayName, $0) },
- selection: $viewModel.selectedTimer
- )
- .accessibilityLabel(String(localized: "Select self-timer duration"))
+ VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
+ Text(String(localized: "Self-Timer"))
+ .font(.system(size: Design.BaseFontSize.medium, weight: .medium))
+ .foregroundStyle(.white)
+
+ Text(String(localized: "Delay before photo capture for self-portraits"))
+ .font(.system(size: Design.BaseFontSize.caption))
+ .foregroundStyle(.white.opacity(Design.Opacity.medium))
+
+ SegmentedPicker(
+ title: "",
+ options: TimerOption.allCases.map { ($0.displayName, $0) },
+ selection: $viewModel.selectedTimer
+ )
+ .accessibilityLabel(String(localized: "Select self-timer duration"))
+ }
}
// MARK: - Pro Section
diff --git a/SelfieCam/Resources/Localizable.xcstrings b/SelfieCam/Resources/Localizable.xcstrings
index e0dd7f5..4d91157 100644
--- a/SelfieCam/Resources/Localizable.xcstrings
+++ b/SelfieCam/Resources/Localizable.xcstrings
@@ -65,7 +65,7 @@
"comment" : "A hint for the \"Skin Smoothing\" toggle in the settings view.",
"isCommentAutoGenerated" : true
},
- "Applies subtle real-time smoothing" : {
+ "Applies subtle real-time skin smoothing" : {
"comment" : "Accessibility hint for the \"Skin Smoothing\" toggle in the Settings view.",
"isCommentAutoGenerated" : true
},
@@ -117,11 +117,19 @@
},
"Center Stage active" : {
+ },
+ "Choose between front and back camera lenses" : {
+ "comment" : "A description of the camera position picker.",
+ "isCommentAutoGenerated" : true
},
"Close preview" : {
"comment" : "A button label that closes the preview screen.",
"isCommentAutoGenerated" : true
},
+ "Controls automatic flash behavior for photos" : {
+ "comment" : "A description below the flash mode picker, explaining its purpose.",
+ "isCommentAutoGenerated" : true
+ },
"Cool Lavender" : {
"comment" : "Name of a ring light color preset.",
"isCommentAutoGenerated" : true
@@ -142,6 +150,10 @@
"comment" : "Accessibility announcement when restoring purchases in debug mode.",
"isCommentAutoGenerated" : true
},
+ "Delay before photo capture for self-portraits" : {
+ "comment" : "A description of the purpose of the \"Self-Timer\" setting in the settings screen.",
+ "isCommentAutoGenerated" : true
+ },
"Directional Gradient Lighting" : {
"comment" : "Benefit provided with the Pro subscription, such as \"Directional Gradient Lighting\".",
"isCommentAutoGenerated" : true
@@ -154,10 +166,18 @@
"comment" : "An accessibility hint for the capture button, instructing the user to double-tap it to capture a photo.",
"isCommentAutoGenerated" : true
},
+ "Enable Ring Light" : {
+ "comment" : "Title of a toggle in the Settings view that allows the user to enable or disable the ring light overlay.",
+ "isCommentAutoGenerated" : true
+ },
"Enables or disables the ring light overlay" : {
"comment" : "A toggle that enables or disables the ring light overlay.",
"isCommentAutoGenerated" : true
},
+ "File size and image quality for saved photos" : {
+ "comment" : "A description of the photo quality setting.",
+ "isCommentAutoGenerated" : true
+ },
"Flash Mode" : {
"comment" : "Title of a segmented picker that allows the user to select the flash mode of the camera.",
"isCommentAutoGenerated" : true
@@ -166,6 +186,10 @@
"comment" : "Title of a toggle that synchronizes the flash color with the ring light color.",
"isCommentAutoGenerated" : true
},
+ "Flips the camera preview horizontally" : {
+ "comment" : "An accessibility hint for the \"True Mirror\" setting.",
+ "isCommentAutoGenerated" : true
+ },
"Front" : {
"comment" : "Option in the camera position picker for using the front camera.",
"isCommentAutoGenerated" : true
@@ -186,8 +210,12 @@
"comment" : "Title for a picker that allows the user to select the HDR mode of the camera.",
"isCommentAutoGenerated" : true
},
- "Hides preview during capture for a flash effect" : {
- "comment" : "Subtitle for the \"Front Flash\" toggle in the Settings view.",
+ "Hide preview during capture for flash effect" : {
+ "comment" : "Text displayed in a toggle within the \"Camera Controls\" section, allowing the user to enable or disable the feature of hiding the camera preview during a photo capture to simulate a flash effect.",
+ "isCommentAutoGenerated" : true
+ },
+ "High Dynamic Range for better lighting in photos" : {
+ "comment" : "A description of the High Dynamic Range (HDR) mode in the settings view.",
"isCommentAutoGenerated" : true
},
"Ice Blue" : {
@@ -275,10 +303,6 @@
"comment" : "The title of the color picker overlay.",
"isCommentAutoGenerated" : true
},
- "Ring Light Enabled" : {
- "comment" : "Title of a toggle that enables or disables the ring light overlay.",
- "isCommentAutoGenerated" : true
- },
"Ring Light Size" : {
"comment" : "The title of the slider that allows the user to select the size of their ring light.",
"isCommentAutoGenerated" : true
@@ -335,20 +359,20 @@
"comment" : "Title for a button that shares the captured media.",
"isCommentAutoGenerated" : true
},
- "Show ring light around camera" : {
- "comment" : "Title of a toggle that enables or disables the ring light overlay.",
+ "Show colored light ring around camera preview" : {
+ "comment" : "Subtitle for the \"Enable Ring Light\" toggle in the Settings view.",
"isCommentAutoGenerated" : true
},
"Shows a grid overlay to help compose your shot" : {
"comment" : "A toggle that enables or disables the rule of thirds grid overlay in the camera view.",
"isCommentAutoGenerated" : true
},
- "Shows non-flipped preview like a real mirror" : {
- "comment" : "Subtitle for the \"True Mirror\" toggle in the Settings view.",
+ "Shows horizontally flipped preview like a real mirror" : {
+ "comment" : "Description of a setting that flips the camera preview horizontally.",
"isCommentAutoGenerated" : true
},
- "Shows rule of thirds grid" : {
- "comment" : "Accessibility hint for the grid overlay toggle.",
+ "Shows rule of thirds grid for composition" : {
+ "comment" : "A toggle that enables or disables the display of a rule of thirds grid on the camera preview.",
"isCommentAutoGenerated" : true
},
"Sign in to iCloud to enable sync" : {
@@ -428,16 +452,16 @@
"comment" : "A button label that prompts users to upgrade to the premium version of the app.",
"isCommentAutoGenerated" : true
},
- "Use ring light color for flash" : {
- "comment" : "Text for the \"Flash Sync\" toggle in the Settings view.",
+ "Use ring light color for screen flash" : {
+ "comment" : "Accessibility hint for the \"Flash Sync\" toggle in the Settings view.",
"isCommentAutoGenerated" : true
},
"Use the buttons at the bottom to save or share your photo" : {
"comment" : "An accessibility hint for the photo review view, instructing the user on how to interact with the view.",
"isCommentAutoGenerated" : true
},
- "Uses the ring light as a flash when taking photos" : {
- "comment" : "An accessibility hint for the \"Front Flash\" toggle in the Settings view.",
+ "Uses screen flash when taking front camera photos" : {
+ "comment" : "A toggle that enables or disables the use of the front camera's flash during photo captures.",
"isCommentAutoGenerated" : true
},
"Video" : {
@@ -460,10 +484,6 @@
"comment" : "A hint provided by the \"Auto-Save\" toggle in the Settings view, explaining that photos and videos are saved immediately after capture when enabled.",
"isCommentAutoGenerated" : true
},
- "When enabled, the preview is not mirrored" : {
- "comment" : "Accessibility hint for the \"True Mirror\" setting in the Settings view.",
- "isCommentAutoGenerated" : true
- },
"Zoom %@ times" : {
"comment" : "A label describing the zoom level of the camera view. The argument is the string â%.1fâ.",
"isCommentAutoGenerated" : true