Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
cf95c4e816
commit
fefb6cf363
109
SelfieCam.xcodeproj/xcshareddata/xcschemes/SelfieCam.xcscheme
Normal file
109
SelfieCam.xcodeproj/xcshareddata/xcschemes/SelfieCam.xcscheme
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "EA836ABE2F0ACE8A00077F87"
|
||||||
|
BuildableName = "SelfieCam.app"
|
||||||
|
BlueprintName = "SelfieCam"
|
||||||
|
ReferencedContainer = "container:SelfieCam.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "EA836ACB2F0ACE8B00077F87"
|
||||||
|
BuildableName = "SelfieCamTests.xctest"
|
||||||
|
BlueprintName = "SelfieCamTests"
|
||||||
|
ReferencedContainer = "container:SelfieCam.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "EA836AD52F0ACE8B00077F87"
|
||||||
|
BuildableName = "SelfieCamUITests.xctest"
|
||||||
|
BlueprintName = "SelfieCamUITests"
|
||||||
|
ReferencedContainer = "container:SelfieCam.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "EA836ABE2F0ACE8A00077F87"
|
||||||
|
BuildableName = "SelfieCam.app"
|
||||||
|
BlueprintName = "SelfieCam"
|
||||||
|
ReferencedContainer = "container:SelfieCam.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<EnvironmentVariables>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "ENABLE_DEBUG_PREMIUM"
|
||||||
|
value = "1"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
</EnvironmentVariables>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "EA836ABE2F0ACE8A00077F87"
|
||||||
|
BuildableName = "SelfieCam.app"
|
||||||
|
BlueprintName = "SelfieCam"
|
||||||
|
ReferencedContainer = "container:SelfieCam.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@ -10,5 +10,23 @@
|
|||||||
<integer>4</integer>
|
<integer>4</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>EA836ABE2F0ACE8A00077F87</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>EA836ACB2F0ACE8B00077F87</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>EA836AD52F0ACE8B00077F87</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -54,7 +54,7 @@ struct ContentView: View {
|
|||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings button overlay
|
// Settings button overlay - positioned with safe area consideration
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -72,10 +72,11 @@ struct ContentView: View {
|
|||||||
.accessibilityLabel("Settings")
|
.accessibilityLabel("Settings")
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.large)
|
.padding(.horizontal, Design.Spacing.large)
|
||||||
.padding(.top, Design.Spacing.medium)
|
.padding(.top, Design.Spacing.small) // Reduced from medium to account for safe area
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.safeAreaInset(edge: .top) { Color.clear.frame(height: 0) } // Ensures proper safe area handling
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.animation(.easeInOut(duration: Design.Animation.quick), value: showPhotoReview)
|
.animation(.easeInOut(duration: Design.Animation.quick), value: showPhotoReview)
|
||||||
|
|||||||
@ -26,13 +26,7 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
// Center Stage state
|
// Center Stage state
|
||||||
@State private var isCenterStageEnabled: Bool = AVCaptureDevice.isCenterStageEnabled
|
@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
|
// Screen flash state for front camera
|
||||||
@State private var isShowingScreenFlash: Bool = false
|
@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
|
// Camera preview with pinch gesture - Metal layer doesn't respect SwiftUI clipping
|
||||||
createCameraOutputView()
|
createCameraOutputView()
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
.scaleEffect(x: cameraSettings.isMirrorFlipped ? -1 : 1, y: 1) // Apply horizontal mirror flip
|
||||||
.gesture(
|
.gesture(
|
||||||
MagnificationGesture()
|
MagnificationGesture()
|
||||||
.updating($magnification) { currentState, gestureState, transaction in
|
.updating($magnification) { currentState, gestureState, transaction in
|
||||||
@ -80,95 +75,17 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
let isLandscape = geometry.size.width > geometry.size.height
|
let isLandscape = geometry.size.width > geometry.size.height
|
||||||
|
|
||||||
if isLandscape {
|
if isLandscape {
|
||||||
// Landscape layout: full-width centered controls, capture button on left with zoom above
|
// Landscape layout: capture button on left with zoom above
|
||||||
ZStack {
|
VStack {
|
||||||
// Centered controls across entire screen
|
Spacer()
|
||||||
VStack(spacing: 0) {
|
CaptureButton(action: { performCapture() })
|
||||||
// Top controls area - expandable panel (centered)
|
Spacer()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
.padding(.leading, Design.Spacing.large)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||||
} else {
|
} else {
|
||||||
// Portrait layout: controls on top, capture button at bottom
|
// Portrait layout: capture button at bottom
|
||||||
VStack(spacing: 0) {
|
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()
|
Spacer()
|
||||||
|
|
||||||
// Bottom controls
|
// 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
|
// Screen flash overlay for front camera
|
||||||
if isShowingScreenFlash {
|
if isShowingScreenFlash {
|
||||||
@ -232,26 +115,6 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.easeInOut(duration: 0.05), value: isShowingScreenFlash)
|
.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 {
|
.onAppear {
|
||||||
// Set flash mode from saved settings
|
// Set flash mode from saved settings
|
||||||
setFlashMode(cameraSettings.flashMode.toMijickFlashMode)
|
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
|
// 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() {
|
private func updateFlashSyncState() {
|
||||||
// Tell MijickCamera whether we're handling flash ourselves (sync enabled)
|
// Tell MijickCamera whether we're handling flash ourselves (sync enabled)
|
||||||
@ -443,7 +162,7 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
private var shouldUseCustomScreenFlash: Bool {
|
private var shouldUseCustomScreenFlash: Bool {
|
||||||
cameraPosition == .front && cameraSettings.flashMode != .off && cameraSettings.isFlashSyncedWithRingLight
|
cameraPosition == .front && cameraSettings.flashMode != .off && cameraSettings.isFlashSyncedWithRingLight
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs capture with screen flash if needed
|
/// Performs capture with screen flash if needed
|
||||||
private func performCapture() {
|
private func performCapture() {
|
||||||
print("performCapture called - shouldUseCustomScreenFlash: \(shouldUseCustomScreenFlash)")
|
print("performCapture called - shouldUseCustomScreenFlash: \(shouldUseCustomScreenFlash)")
|
||||||
@ -451,16 +170,16 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
// Save original brightness and boost to max
|
// Save original brightness and boost to max
|
||||||
originalBrightness = UIScreen.main.brightness
|
originalBrightness = UIScreen.main.brightness
|
||||||
UIScreen.main.brightness = 1.0
|
UIScreen.main.brightness = 1.0
|
||||||
|
|
||||||
// Show flash overlay
|
// Show flash overlay
|
||||||
isShowingScreenFlash = true
|
isShowingScreenFlash = true
|
||||||
|
|
||||||
// Wait for camera to adjust to bright screen, then capture
|
// Wait for camera to adjust to bright screen, then capture
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
try? await Task.sleep(for: .milliseconds(150))
|
try? await Task.sleep(for: .milliseconds(150))
|
||||||
print("Calling captureOutput() with custom flash")
|
print("Calling captureOutput() with custom flash")
|
||||||
captureOutput()
|
captureOutput()
|
||||||
|
|
||||||
// Keep flash visible briefly after capture
|
// Keep flash visible briefly after capture
|
||||||
try? await Task.sleep(for: .milliseconds(100))
|
try? await Task.sleep(for: .milliseconds(100))
|
||||||
isShowingScreenFlash = false
|
isShowingScreenFlash = false
|
||||||
@ -472,24 +191,4 @@ struct CustomCameraScreen: MCameraScreen {
|
|||||||
captureOutput()
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,8 @@ struct SizeSliderOverlay: View {
|
|||||||
@Binding var selectedSize: CGFloat
|
@Binding var selectedSize: CGFloat
|
||||||
@Binding var isPresented: Bool
|
@Binding var isPresented: Bool
|
||||||
|
|
||||||
private let minSize: CGFloat = 50
|
private let minSize: CGFloat = SettingsViewModel.minRingSize
|
||||||
private let maxSize: CGFloat = 100
|
private let maxSize: CGFloat = SettingsViewModel.maxRingSize
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|||||||
@ -17,48 +17,34 @@ struct SettingsView: View {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
|
|
||||||
// MARK: - Ring Light Section
|
// MARK: - Ring Light Section
|
||||||
|
|
||||||
SettingsSectionHeader(title: "Ring Light", systemImage: "light.max")
|
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
|
// Ring Size Slider
|
||||||
ringSizeSlider
|
ringSizeSlider
|
||||||
|
|
||||||
// Color Preset
|
// Color Preset
|
||||||
colorPresetSection
|
colorPresetSection
|
||||||
|
|
||||||
// MARK: - Camera Section
|
// Ring Light Brightness
|
||||||
|
ringLightBrightnessSlider
|
||||||
SettingsSectionHeader(title: "Camera", systemImage: "camera")
|
|
||||||
|
// MARK: - Camera Controls Section
|
||||||
SettingsToggle(
|
|
||||||
title: String(localized: "Front Flash"),
|
SettingsSectionHeader(title: "Camera Controls", systemImage: "camera")
|
||||||
subtitle: String(localized: "Hides preview during capture for a flash effect"),
|
|
||||||
isOn: $viewModel.isFrontFlashEnabled
|
// Camera Position
|
||||||
)
|
cameraPositionPicker
|
||||||
.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"))
|
|
||||||
|
|
||||||
// Flash Mode
|
// Flash Mode
|
||||||
flashModePicker
|
flashModePicker
|
||||||
@ -66,61 +52,80 @@ struct SettingsView: View {
|
|||||||
// Flash Sync
|
// Flash Sync
|
||||||
SettingsToggle(
|
SettingsToggle(
|
||||||
title: String(localized: "Flash Sync"),
|
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
|
isOn: $viewModel.isFlashSyncedWithRingLight
|
||||||
)
|
)
|
||||||
.accessibilityHint(String(localized: "Syncs flash color with ring light color"))
|
.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
|
// HDR Mode
|
||||||
hdrModePicker
|
hdrModePicker
|
||||||
|
|
||||||
// Photo Quality
|
// Photo Quality
|
||||||
photoQualityPicker
|
photoQualityPicker
|
||||||
|
|
||||||
// Camera Position
|
// MARK: - Display Section
|
||||||
cameraPositionPicker
|
|
||||||
|
SettingsSectionHeader(title: "Display", systemImage: "eye")
|
||||||
|
|
||||||
// Ring Light Enabled
|
|
||||||
SettingsToggle(
|
SettingsToggle(
|
||||||
title: String(localized: "Ring Light Enabled"),
|
title: String(localized: "True Mirror"),
|
||||||
subtitle: String(localized: "Show ring light around camera"),
|
subtitle: String(localized: "Shows horizontally flipped preview like a real mirror"),
|
||||||
isOn: $viewModel.isRingLightEnabled
|
isOn: $viewModel.isMirrorFlipped
|
||||||
)
|
)
|
||||||
.accessibilityHint(String(localized: "Enables or disables the ring light overlay"))
|
.accessibilityHint(String(localized: "Flips the camera preview horizontally"))
|
||||||
|
|
||||||
// Ring Light Brightness
|
SettingsToggle(
|
||||||
ringLightBrightnessSlider
|
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
|
// Timer Selection
|
||||||
timerPicker
|
timerPicker
|
||||||
|
|
||||||
// MARK: - Capture Section
|
|
||||||
|
|
||||||
SettingsSectionHeader(title: "Capture", systemImage: "photo.on.rectangle")
|
|
||||||
|
|
||||||
SettingsToggle(
|
SettingsToggle(
|
||||||
title: String(localized: "Auto-Save"),
|
title: String(localized: "Auto-Save"),
|
||||||
subtitle: String(localized: "Automatically save captures to Photo Library"),
|
subtitle: String(localized: "Automatically save captures to Photo Library"),
|
||||||
isOn: $viewModel.isAutoSaveEnabled
|
isOn: $viewModel.isAutoSaveEnabled
|
||||||
)
|
)
|
||||||
.accessibilityHint(String(localized: "When enabled, photos and videos are saved immediately after capture"))
|
.accessibilityHint(String(localized: "When enabled, photos and videos are saved immediately after capture"))
|
||||||
|
|
||||||
// MARK: - Pro Section
|
// MARK: - Pro Section
|
||||||
|
|
||||||
SettingsSectionHeader(title: "Pro", systemImage: "crown")
|
SettingsSectionHeader(title: "Pro", systemImage: "crown")
|
||||||
|
|
||||||
proSection
|
proSection
|
||||||
|
|
||||||
// MARK: - Sync Section
|
// MARK: - Sync Section
|
||||||
|
|
||||||
SettingsSectionHeader(title: "iCloud Sync", systemImage: "icloud")
|
SettingsSectionHeader(title: "iCloud Sync", systemImage: "icloud")
|
||||||
|
|
||||||
iCloudSyncSection
|
iCloudSyncSection
|
||||||
|
|
||||||
// MARK: - About Section
|
// MARK: - About Section
|
||||||
|
|
||||||
SettingsSectionHeader(title: "About", systemImage: "info.circle")
|
SettingsSectionHeader(title: "About", systemImage: "info.circle")
|
||||||
|
|
||||||
acknowledgmentsSection
|
acknowledgmentsSection
|
||||||
|
|
||||||
Spacer(minLength: Design.Spacing.xxxLarge)
|
Spacer(minLength: Design.Spacing.xxxLarge)
|
||||||
@ -239,48 +244,88 @@ struct SettingsView: View {
|
|||||||
// MARK: - Flash Mode Picker
|
// MARK: - Flash Mode Picker
|
||||||
|
|
||||||
private var flashModePicker: some View {
|
private var flashModePicker: some View {
|
||||||
SegmentedPicker(
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
title: String(localized: "Flash Mode"),
|
Text(String(localized: "Flash Mode"))
|
||||||
options: CameraFlashMode.allCases.map { ($0.displayName, $0) },
|
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||||
selection: $viewModel.flashMode
|
.foregroundStyle(.white)
|
||||||
)
|
|
||||||
.accessibilityLabel(String(localized: "Select flash mode"))
|
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
|
// MARK: - HDR Mode Picker
|
||||||
|
|
||||||
private var hdrModePicker: some View {
|
private var hdrModePicker: some View {
|
||||||
SegmentedPicker(
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
title: String(localized: "HDR Mode"),
|
Text(String(localized: "HDR Mode"))
|
||||||
options: CameraHDRMode.allCases.map { ($0.displayName, $0) },
|
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||||
selection: $viewModel.hdrMode
|
.foregroundStyle(.white)
|
||||||
)
|
|
||||||
.accessibilityLabel(String(localized: "Select HDR mode"))
|
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
|
// MARK: - Photo Quality Picker
|
||||||
|
|
||||||
private var photoQualityPicker: some View {
|
private var photoQualityPicker: some View {
|
||||||
SegmentedPicker(
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
title: String(localized: "Photo Quality"),
|
Text(String(localized: "Photo Quality"))
|
||||||
options: PhotoQuality.allCases.map { ($0.rawValue.capitalized, $0) },
|
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||||
selection: $viewModel.photoQuality
|
.foregroundStyle(.white)
|
||||||
)
|
|
||||||
.accessibilityLabel(String(localized: "Select photo quality"))
|
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
|
// MARK: - Camera Position Picker
|
||||||
|
|
||||||
private var cameraPositionPicker: some View {
|
private var cameraPositionPicker: some View {
|
||||||
SegmentedPicker(
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
title: String(localized: "Camera"),
|
Text(String(localized: "Camera"))
|
||||||
options: [
|
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||||
(String(localized: "Front"), CameraPosition.front),
|
.foregroundStyle(.white)
|
||||||
(String(localized: "Back"), CameraPosition.back)
|
|
||||||
],
|
Text(String(localized: "Choose between front and back camera lenses"))
|
||||||
selection: $viewModel.cameraPosition
|
.font(.system(size: Design.BaseFontSize.caption))
|
||||||
)
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
.accessibilityLabel(String(localized: "Select camera position"))
|
|
||||||
|
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
|
// MARK: - Ring Light Brightness Slider
|
||||||
@ -328,12 +373,22 @@ struct SettingsView: View {
|
|||||||
// MARK: - Timer Picker
|
// MARK: - Timer Picker
|
||||||
|
|
||||||
private var timerPicker: some View {
|
private var timerPicker: some View {
|
||||||
SegmentedPicker(
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
title: String(localized: "Self-Timer"),
|
Text(String(localized: "Self-Timer"))
|
||||||
options: TimerOption.allCases.map { ($0.displayName, $0) },
|
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||||
selection: $viewModel.selectedTimer
|
.foregroundStyle(.white)
|
||||||
)
|
|
||||||
.accessibilityLabel(String(localized: "Select self-timer duration"))
|
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
|
// MARK: - Pro Section
|
||||||
|
|||||||
@ -65,7 +65,7 @@
|
|||||||
"comment" : "A hint for the \"Skin Smoothing\" toggle in the settings view.",
|
"comment" : "A hint for the \"Skin Smoothing\" toggle in the settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"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.",
|
"comment" : "Accessibility hint for the \"Skin Smoothing\" toggle in the Settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
@ -117,11 +117,19 @@
|
|||||||
},
|
},
|
||||||
"Center Stage active" : {
|
"Center Stage active" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Choose between front and back camera lenses" : {
|
||||||
|
"comment" : "A description of the camera position picker.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Close preview" : {
|
"Close preview" : {
|
||||||
"comment" : "A button label that closes the preview screen.",
|
"comment" : "A button label that closes the preview screen.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Controls automatic flash behavior for photos" : {
|
||||||
|
"comment" : "A description below the flash mode picker, explaining its purpose.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Cool Lavender" : {
|
"Cool Lavender" : {
|
||||||
"comment" : "Name of a ring light color preset.",
|
"comment" : "Name of a ring light color preset.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -142,6 +150,10 @@
|
|||||||
"comment" : "Accessibility announcement when restoring purchases in debug mode.",
|
"comment" : "Accessibility announcement when restoring purchases in debug mode.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Directional Gradient Lighting" : {
|
||||||
"comment" : "Benefit provided with the Pro subscription, such as \"Directional Gradient Lighting\".",
|
"comment" : "Benefit provided with the Pro subscription, such as \"Directional Gradient Lighting\".",
|
||||||
"isCommentAutoGenerated" : true
|
"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.",
|
"comment" : "An accessibility hint for the capture button, instructing the user to double-tap it to capture a photo.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Enables or disables the ring light overlay" : {
|
||||||
"comment" : "A toggle that enables or disables the ring light overlay.",
|
"comment" : "A toggle that enables or disables the ring light overlay.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"File size and image quality for saved photos" : {
|
||||||
|
"comment" : "A description of the photo quality setting.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Flash Mode" : {
|
"Flash Mode" : {
|
||||||
"comment" : "Title of a segmented picker that allows the user to select the flash mode of the camera.",
|
"comment" : "Title of a segmented picker that allows the user to select the flash mode of the camera.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -166,6 +186,10 @@
|
|||||||
"comment" : "Title of a toggle that synchronizes the flash color with the ring light color.",
|
"comment" : "Title of a toggle that synchronizes the flash color with the ring light color.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Flips the camera preview horizontally" : {
|
||||||
|
"comment" : "An accessibility hint for the \"True Mirror\" setting.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Front" : {
|
"Front" : {
|
||||||
"comment" : "Option in the camera position picker for using the front camera.",
|
"comment" : "Option in the camera position picker for using the front camera.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -186,8 +210,12 @@
|
|||||||
"comment" : "Title for a picker that allows the user to select the HDR mode of the camera.",
|
"comment" : "Title for a picker that allows the user to select the HDR mode of the camera.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Hides preview during capture for a flash effect" : {
|
"Hide preview during capture for flash effect" : {
|
||||||
"comment" : "Subtitle for the \"Front Flash\" toggle in the Settings view.",
|
"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
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Ice Blue" : {
|
"Ice Blue" : {
|
||||||
@ -275,10 +303,6 @@
|
|||||||
"comment" : "The title of the color picker overlay.",
|
"comment" : "The title of the color picker overlay.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Ring Light Enabled" : {
|
|
||||||
"comment" : "Title of a toggle that enables or disables the ring light overlay.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Ring Light Size" : {
|
"Ring Light Size" : {
|
||||||
"comment" : "The title of the slider that allows the user to select the size of their ring light.",
|
"comment" : "The title of the slider that allows the user to select the size of their ring light.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -335,20 +359,20 @@
|
|||||||
"comment" : "Title for a button that shares the captured media.",
|
"comment" : "Title for a button that shares the captured media.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Show ring light around camera" : {
|
"Show colored light ring around camera preview" : {
|
||||||
"comment" : "Title of a toggle that enables or disables the ring light overlay.",
|
"comment" : "Subtitle for the \"Enable Ring Light\" toggle in the Settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Shows a grid overlay to help compose your shot" : {
|
"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.",
|
"comment" : "A toggle that enables or disables the rule of thirds grid overlay in the camera view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Shows non-flipped preview like a real mirror" : {
|
"Shows horizontally flipped preview like a real mirror" : {
|
||||||
"comment" : "Subtitle for the \"True Mirror\" toggle in the Settings view.",
|
"comment" : "Description of a setting that flips the camera preview horizontally.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Shows rule of thirds grid" : {
|
"Shows rule of thirds grid for composition" : {
|
||||||
"comment" : "Accessibility hint for the grid overlay toggle.",
|
"comment" : "A toggle that enables or disables the display of a rule of thirds grid on the camera preview.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Sign in to iCloud to enable sync" : {
|
"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.",
|
"comment" : "A button label that prompts users to upgrade to the premium version of the app.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Use ring light color for flash" : {
|
"Use ring light color for screen flash" : {
|
||||||
"comment" : "Text for the \"Flash Sync\" toggle in the Settings view.",
|
"comment" : "Accessibility hint for the \"Flash Sync\" toggle in the Settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Use the buttons at the bottom to save or share your photo" : {
|
"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.",
|
"comment" : "An accessibility hint for the photo review view, instructing the user on how to interact with the view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Uses the ring light as a flash when taking photos" : {
|
"Uses screen flash when taking front camera photos" : {
|
||||||
"comment" : "An accessibility hint for the \"Front Flash\" toggle in the Settings view.",
|
"comment" : "A toggle that enables or disables the use of the front camera's flash during photo captures.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Video" : {
|
"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.",
|
"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
|
"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" : {
|
"Zoom %@ times" : {
|
||||||
"comment" : "A label describing the zoom level of the camera view. The argument is the string “%.1f”.",
|
"comment" : "A label describing the zoom level of the camera view. The argument is the string “%.1f”.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user