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