Fix UI issues: full-screen preview, ring size limits, cleaner layout
Changes: 1. Camera preview now fills available space (not forced square) - Maintains proper aspect ratio for captured photos - Controls overlay on top of preview 2. Ring size now limited based on screen dimensions - Maximum is 1/4 of smaller screen dimension - Prevents content from shifting off-screen 3. Removed light intensity slider - Was causing color changes (opacity approach) - Ring light now always at full brightness 4. Removed crown icon from main screen - Pro upgrade moved to Settings > Pro section - Cleaner camera interface 5. Smaller top icons - Grid and settings buttons use .body font - Less visual clutter
This commit is contained in:
parent
ef15a8c21a
commit
bf5853d999
@ -15,27 +15,24 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let maxRingSize = calculateMaxRingSize(for: geometry)
|
||||
let effectiveRingSize = min(settings.ringSize, maxRingSize)
|
||||
|
||||
ZStack {
|
||||
// MARK: - Ring Light Background
|
||||
ringLightBackground
|
||||
|
||||
// MARK: - Camera Preview (centered with border inset)
|
||||
cameraPreviewArea(in: geometry)
|
||||
// MARK: - Camera Preview (full screen with ring border)
|
||||
cameraPreviewArea(ringSize: effectiveRingSize)
|
||||
|
||||
// MARK: - Grid Overlay
|
||||
if settings.isGridVisible && !viewModel.isPreviewHidden {
|
||||
let previewSize = min(
|
||||
geometry.size.width - (settings.ringSize * 2),
|
||||
geometry.size.height - (settings.ringSize * 2)
|
||||
)
|
||||
GridOverlay(isVisible: true)
|
||||
.frame(width: previewSize, height: previewSize)
|
||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
||||
.padding(effectiveRingSize)
|
||||
}
|
||||
|
||||
// MARK: - Controls Overlay
|
||||
controlsOverlay
|
||||
// MARK: - Controls Overlay (on top of preview)
|
||||
controlsOverlay(ringSize: effectiveRingSize)
|
||||
|
||||
// MARK: - Permission Denied View
|
||||
if !viewModel.isCameraAuthorized && viewModel.captureSession != nil {
|
||||
@ -47,6 +44,13 @@ struct ContentView: View {
|
||||
toastView(message: message)
|
||||
}
|
||||
}
|
||||
.onChange(of: geometry.size) { _, newSize in
|
||||
// Update max ring size when screen size changes
|
||||
let newMax = min(newSize.width, newSize.height) / 4
|
||||
if settings.ringSize > newMax {
|
||||
settings.ringSize = newMax
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.task {
|
||||
@ -59,7 +63,7 @@ struct ContentView: View {
|
||||
ProPaywallView()
|
||||
}
|
||||
.sheet(isPresented: $showSettings) {
|
||||
SettingsView(viewModel: viewModel.settings)
|
||||
SettingsView(viewModel: viewModel.settings, showPaywall: $showPaywall)
|
||||
}
|
||||
.fullScreenCover(isPresented: $viewModel.showPostCapturePreview) {
|
||||
if let media = viewModel.capturedMedia {
|
||||
@ -86,29 +90,28 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Max Ring Size Calculation
|
||||
|
||||
/// Calculates maximum ring size based on screen dimensions
|
||||
/// Ring should not exceed 1/4 of the smaller dimension
|
||||
private func calculateMaxRingSize(for geometry: GeometryProxy) -> CGFloat {
|
||||
min(geometry.size.width, geometry.size.height) / 4
|
||||
}
|
||||
|
||||
// MARK: - Ring Light Background
|
||||
|
||||
@ViewBuilder
|
||||
private var ringLightBackground: some View {
|
||||
let baseColor = premiumManager.isPremiumUnlocked ? settings.lightColor : Color.RingLight.pureWhite
|
||||
|
||||
// Apply light intensity as opacity
|
||||
baseColor
|
||||
.opacity(settings.lightIntensity)
|
||||
.ignoresSafeArea()
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.lightIntensity)
|
||||
}
|
||||
|
||||
// MARK: - Camera Preview Area
|
||||
|
||||
@ViewBuilder
|
||||
private func cameraPreviewArea(in geometry: GeometryProxy) -> some View {
|
||||
// Calculate the size of the preview area (full screen minus ring on all sides)
|
||||
let previewSize = min(
|
||||
geometry.size.width - (settings.ringSize * 2),
|
||||
geometry.size.height - (settings.ringSize * 2)
|
||||
)
|
||||
|
||||
private func cameraPreviewArea(ringSize: CGFloat) -> some View {
|
||||
if viewModel.isCameraAuthorized {
|
||||
// Show preview unless front flash is active
|
||||
if !viewModel.isPreviewHidden {
|
||||
@ -117,16 +120,17 @@ struct ContentView: View {
|
||||
isMirrorFlipped: settings.isMirrorFlipped,
|
||||
zoomFactor: settings.currentZoomFactor
|
||||
)
|
||||
.frame(width: previewSize, height: previewSize)
|
||||
.padding(ringSize)
|
||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: ringSize)
|
||||
}
|
||||
} else {
|
||||
// Show placeholder while requesting permission
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
||||
Rectangle()
|
||||
.fill(.black)
|
||||
.frame(width: previewSize, height: previewSize)
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
||||
.padding(ringSize)
|
||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: ringSize)
|
||||
.overlay {
|
||||
if viewModel.captureSession == nil {
|
||||
ProgressView()
|
||||
@ -139,38 +143,26 @@ struct ContentView: View {
|
||||
|
||||
// MARK: - Controls Overlay
|
||||
|
||||
private var controlsOverlay: some View {
|
||||
private func controlsOverlay(ringSize: CGFloat) -> some View {
|
||||
VStack {
|
||||
// Top bar
|
||||
topControlBar
|
||||
.padding(.top, ringSize + Design.Spacing.small)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Bottom capture controls
|
||||
bottomControlBar
|
||||
.padding(.bottom, ringSize + Design.Spacing.medium)
|
||||
}
|
||||
.padding(settings.ringSize + Design.Spacing.medium)
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
||||
.padding(.horizontal, ringSize + Design.Spacing.small)
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: ringSize)
|
||||
}
|
||||
|
||||
// MARK: - Top Control Bar
|
||||
|
||||
private var topControlBar: some View {
|
||||
HStack {
|
||||
// Pro/Crown button
|
||||
Button {
|
||||
showPaywall = true
|
||||
} label: {
|
||||
Image(systemName: premiumManager.isPremiumUnlocked ? "crown.fill" : "crown")
|
||||
.font(.title2)
|
||||
.foregroundStyle(premiumManager.isPremiumUnlocked ? .yellow : .white)
|
||||
.padding(Design.Spacing.small)
|
||||
.background(.ultraThinMaterial, in: .circle)
|
||||
}
|
||||
.accessibilityLabel(premiumManager.isPremiumUnlocked ?
|
||||
String(localized: "Pro unlocked") :
|
||||
String(localized: "Upgrade to Pro"))
|
||||
|
||||
Spacer()
|
||||
|
||||
// Grid toggle
|
||||
@ -178,7 +170,7 @@ struct ContentView: View {
|
||||
viewModel.settings.isGridVisible.toggle()
|
||||
} label: {
|
||||
Image(systemName: "square.grid.3x3")
|
||||
.font(.title2)
|
||||
.font(.body)
|
||||
.foregroundStyle(viewModel.settings.isGridVisible ? .yellow : .white)
|
||||
.padding(Design.Spacing.small)
|
||||
.background(.ultraThinMaterial, in: .circle)
|
||||
@ -191,7 +183,7 @@ struct ContentView: View {
|
||||
showSettings = true
|
||||
} label: {
|
||||
Image(systemName: "gearshape.fill")
|
||||
.font(.title2)
|
||||
.font(.body)
|
||||
.foregroundStyle(.white)
|
||||
.padding(Design.Spacing.small)
|
||||
.background(.ultraThinMaterial, in: .circle)
|
||||
@ -203,22 +195,26 @@ struct ContentView: View {
|
||||
// MARK: - Bottom Control Bar
|
||||
|
||||
private var bottomControlBar: some View {
|
||||
HStack(spacing: Design.Spacing.xxxxLarge) {
|
||||
HStack {
|
||||
// Switch camera button
|
||||
Button {
|
||||
viewModel.switchCamera()
|
||||
} label: {
|
||||
Image(systemName: "camera.rotate.fill")
|
||||
.font(.title)
|
||||
.font(.title2)
|
||||
.foregroundStyle(.white)
|
||||
.padding(Design.Spacing.medium)
|
||||
.background(.ultraThinMaterial, in: .circle)
|
||||
}
|
||||
.accessibilityLabel(String(localized: "Switch camera"))
|
||||
|
||||
Spacer()
|
||||
|
||||
// Capture button
|
||||
captureButton
|
||||
|
||||
Spacer()
|
||||
|
||||
// Capture mode selector
|
||||
captureModeMenu
|
||||
}
|
||||
@ -273,7 +269,7 @@ struct ContentView: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: viewModel.settings.selectedCaptureMode.systemImage)
|
||||
.font(.title)
|
||||
.font(.title2)
|
||||
.foregroundStyle(.white)
|
||||
.padding(Design.Spacing.medium)
|
||||
.background(.ultraThinMaterial, in: .circle)
|
||||
@ -351,7 +347,7 @@ struct ContentView: View {
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(.ultraThinMaterial, in: Capsule())
|
||||
.padding(.bottom, Design.Spacing.xxxLarge + settings.ringSize)
|
||||
.padding(.bottom, Design.Spacing.xxxLarge)
|
||||
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||
.animation(.spring(duration: Design.Animation.quick), value: message)
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import Bedrock
|
||||
|
||||
struct SettingsView: View {
|
||||
@Bindable var viewModel: SettingsViewModel
|
||||
@Binding var showPaywall: Bool
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
@ -20,9 +21,6 @@ struct SettingsView: View {
|
||||
// Color Preset
|
||||
colorPresetSection
|
||||
|
||||
// Brightness Slider
|
||||
brightnessSlider
|
||||
|
||||
// MARK: - Camera Section
|
||||
|
||||
SettingsSectionHeader(title: "Camera", systemImage: "camera")
|
||||
@ -69,6 +67,12 @@ struct SettingsView: View {
|
||||
)
|
||||
.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")
|
||||
@ -162,44 +166,6 @@ struct SettingsView: View {
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
}
|
||||
|
||||
// MARK: - Light Intensity Slider
|
||||
|
||||
private var brightnessSlider: some View {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||
HStack {
|
||||
Text(String(localized: "Light Intensity"))
|
||||
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(Int(viewModel.lightIntensity * 100))%")
|
||||
.font(.system(size: Design.BaseFontSize.body, weight: .medium, design: .rounded))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
Image(systemName: "light.min")
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
|
||||
Slider(value: $viewModel.lightIntensity, in: 0.5...1.0, step: 0.05)
|
||||
.tint(Color.Accent.primary)
|
||||
|
||||
Image(systemName: "light.max")
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
Text(String(localized: "Adjusts the opacity/intensity of the ring light"))
|
||||
.font(.system(size: Design.BaseFontSize.caption))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.accessibilityLabel(String(localized: "Light intensity"))
|
||||
.accessibilityValue("\(Int(viewModel.lightIntensity * 100)) percent")
|
||||
}
|
||||
|
||||
// MARK: - Timer Picker
|
||||
|
||||
private var timerPicker: some View {
|
||||
@ -211,6 +177,49 @@ struct SettingsView: View {
|
||||
.accessibilityLabel(String(localized: "Select self-timer duration"))
|
||||
}
|
||||
|
||||
// MARK: - Pro Section
|
||||
|
||||
private var proSection: some View {
|
||||
Button {
|
||||
dismiss()
|
||||
// Small delay to allow sheet to dismiss before showing paywall
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
showPaywall = true
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
Image(systemName: "crown.fill")
|
||||
.font(.title2)
|
||||
.foregroundStyle(Color.Status.warning)
|
||||
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(String(localized: "Upgrade to Pro"))
|
||||
.font(.system(size: Design.BaseFontSize.medium, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(String(localized: "Unlock premium colors, video, and more"))
|
||||
.font(.system(size: Design.BaseFontSize.caption))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.body)
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
.padding(Design.Spacing.medium)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(Color.Accent.primary.opacity(Design.Opacity.subtle))
|
||||
.strokeBorder(Color.Accent.primary.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(String(localized: "Upgrade to Pro"))
|
||||
.accessibilityHint(String(localized: "Opens upgrade options"))
|
||||
}
|
||||
|
||||
// MARK: - iCloud Sync Section
|
||||
|
||||
private var iCloudSyncSection: some View {
|
||||
@ -335,6 +344,6 @@ private struct ColorPresetButton: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SettingsView(viewModel: SettingsViewModel())
|
||||
SettingsView(viewModel: SettingsViewModel(), showPaywall: .constant(false))
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
@ -101,12 +101,6 @@ final class SettingsViewModel: RingLightConfigurable {
|
||||
set { updateSettings { $0.lightColorId = newValue } }
|
||||
}
|
||||
|
||||
/// Ring light intensity/opacity (0.5 to 1.0)
|
||||
var lightIntensity: Double {
|
||||
get { cloudSync.data.lightIntensity }
|
||||
set { updateSettings { $0.lightIntensity = newValue } }
|
||||
}
|
||||
|
||||
/// Whether front flash is enabled (hides preview during capture)
|
||||
var isFrontFlashEnabled: Bool {
|
||||
get { cloudSync.data.isFrontFlashEnabled }
|
||||
@ -218,7 +212,7 @@ final class SettingsViewModel: RingLightConfigurable {
|
||||
// MARK: - Validation
|
||||
|
||||
var isValidConfiguration: Bool {
|
||||
ringSize >= Self.minRingSize && lightIntensity >= 0.5
|
||||
ringSize >= Self.minRingSize
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
"comment" : "Description of a timer option when the user selects \"10 seconds\".",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Adjusts the opacity/intensity of the ring light" : {
|
||||
"comment" : "A description of the light intensity slider in the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Adjusts the size of the light ring around the camera preview" : {
|
||||
"comment" : "A description of the ring size slider in the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -49,6 +53,14 @@
|
||||
"comment" : "Accessibility hint for the \"Skin Smoothing\" toggle in the Settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Auto-Save" : {
|
||||
"comment" : "Title of a toggle that enables automatic saving of captured photos and videos to the user's Photo Library.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Automatically save captures to Photo Library" : {
|
||||
"comment" : "A toggle option in the Settings view that allows the user to enable or disable automatic saving of captured photos and videos to the user's Photo Library.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Best Value • Save 33%" : {
|
||||
"comment" : "A promotional text displayed below an annual subscription package, highlighting its value.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -73,6 +85,22 @@
|
||||
"comment" : "A label describing the currently selected capture mode. The placeholder is replaced with the actual mode name.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Captured boomerang" : {
|
||||
"comment" : "A label describing a captured boomerang.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Captured photo" : {
|
||||
"comment" : "A label describing a captured photo.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Captured video" : {
|
||||
"comment" : "A label describing a captured video.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Close preview" : {
|
||||
"comment" : "A button label that closes the preview screen.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Cool Lavender" : {
|
||||
"comment" : "Name of a ring light color preset.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -93,6 +121,14 @@
|
||||
"comment" : "The text for a button that dismisses a view. In this case, it dismisses the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Edit" : {
|
||||
"comment" : "Label for the button that allows the user to edit their captured photo or video.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Front Flash" : {
|
||||
"comment" : "Title of a toggle in the Settings view that controls whether the front flash is enabled.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Go Pro" : {
|
||||
"comment" : "The title of the \"Go Pro\" button in the Pro paywall.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -101,8 +137,8 @@
|
||||
"comment" : "Text displayed in a settings toggle for showing a grid overlay to help compose your shot.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Higher brightness = brighter ring light effect" : {
|
||||
"comment" : "A description of how to adjust the brightness of the screen.",
|
||||
"Hides preview during capture for a flash effect" : {
|
||||
"comment" : "Subtitle for the \"Front Flash\" toggle in the Settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Ice Blue" : {
|
||||
@ -116,6 +152,14 @@
|
||||
"comment" : "A label displayed above a section of the settings view related to light colors.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Light intensity" : {
|
||||
"comment" : "An accessibility label for the light intensity slider in the settings view. The value is dynamically set based on the slider's current value.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Light Intensity" : {
|
||||
"comment" : "A label describing the slider that adjusts the intensity of the ring light.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"No Watermarks • Ad-Free" : {
|
||||
"comment" : "Description of a benefit that comes with the Pro subscription.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -167,6 +211,14 @@
|
||||
"comment" : "A button that restores purchases.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Retake" : {
|
||||
"comment" : "Title for a button that allows the user to retake a captured photo or video.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Ring Glow" : {
|
||||
"comment" : "Title of a slider that controls the intensity of the ring glow effect in the captured media.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Ring size" : {
|
||||
"comment" : "An accessibility label for the ring size slider in the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -175,12 +227,8 @@
|
||||
"comment" : "The label for the ring size slider in the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Screen brightness" : {
|
||||
"comment" : "An accessibility label for the screen brightness setting in the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Screen Brightness" : {
|
||||
"comment" : "A label displayed above the brightness slider in the settings view.",
|
||||
"Saved to Photos" : {
|
||||
"comment" : "Text shown as a toast message when a photo is successfully saved to Photos.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Select self-timer duration" : {
|
||||
@ -195,6 +243,10 @@
|
||||
"comment" : "The title of the settings screen.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Share" : {
|
||||
"comment" : "Title for a button that shares the captured media.",
|
||||
"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
|
||||
@ -214,6 +266,9 @@
|
||||
"Skin Smoothing" : {
|
||||
"comment" : "A toggle that enables or disables real-time skin smoothing.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Smoothing" : {
|
||||
|
||||
},
|
||||
"Soft Pink" : {
|
||||
"comment" : "Name of a ring light color preset.",
|
||||
@ -278,10 +333,18 @@
|
||||
"comment" : "Description of a benefit that comes with the Pro subscription, specifically related to the boomerang tool.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Unlock filters, AI remove, and more with Pro" : {
|
||||
"comment" : "A teaser text that appears below the capture edit view, promoting a premium feature.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Upgrade to Pro" : {
|
||||
"comment" : "A button label that prompts users to upgrade to the premium version of the app.",
|
||||
"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.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Video" : {
|
||||
"comment" : "Display name for the \"Video\" capture mode.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -298,6 +361,10 @@
|
||||
"comment" : "A color option for the ring light, named after a warm, creamy shade of white.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"When enabled, photos and videos are saved immediately after capture" : {
|
||||
"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
|
||||
|
||||
@ -11,9 +11,6 @@ protocol RingLightConfigurable {
|
||||
/// The color of the ring light
|
||||
var lightColor: Color { get }
|
||||
|
||||
/// Ring light intensity/opacity (0.5 to 1.0)
|
||||
var lightIntensity: Double { get set }
|
||||
|
||||
/// Whether front flash is enabled (hides preview during capture)
|
||||
var isFrontFlashEnabled: Bool { get set }
|
||||
|
||||
|
||||
@ -35,9 +35,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
||||
/// ID of the selected light color preset
|
||||
var lightColorId: String = "pureWhite"
|
||||
|
||||
/// Ring light intensity/opacity (0.5 to 1.0)
|
||||
var lightIntensity: Double = 1.0
|
||||
|
||||
/// Whether front flash is enabled (hides preview during capture)
|
||||
var isFrontFlashEnabled: Bool = true
|
||||
|
||||
@ -77,7 +74,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
||||
init(
|
||||
ringSize: CGFloat,
|
||||
lightColorId: String,
|
||||
lightIntensity: Double,
|
||||
isFrontFlashEnabled: Bool,
|
||||
isMirrorFlipped: Bool,
|
||||
isSkinSmoothingEnabled: Bool,
|
||||
@ -89,7 +85,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
||||
) {
|
||||
self.ringSizeValue = Double(ringSize)
|
||||
self.lightColorId = lightColorId
|
||||
self.lightIntensity = lightIntensity
|
||||
self.isFrontFlashEnabled = isFrontFlashEnabled
|
||||
self.isMirrorFlipped = isMirrorFlipped
|
||||
self.isSkinSmoothingEnabled = isSkinSmoothingEnabled
|
||||
@ -108,7 +103,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
||||
case lastModified
|
||||
case ringSizeValue
|
||||
case lightColorId
|
||||
case lightIntensity
|
||||
case isFrontFlashEnabled
|
||||
case isMirrorFlipped
|
||||
case isSkinSmoothingEnabled
|
||||
@ -126,7 +120,6 @@ extension SyncedSettings: Equatable {
|
||||
static func == (lhs: SyncedSettings, rhs: SyncedSettings) -> Bool {
|
||||
lhs.ringSizeValue == rhs.ringSizeValue &&
|
||||
lhs.lightColorId == rhs.lightColorId &&
|
||||
lhs.lightIntensity == rhs.lightIntensity &&
|
||||
lhs.isFrontFlashEnabled == rhs.isFrontFlashEnabled &&
|
||||
lhs.isMirrorFlipped == rhs.isMirrorFlipped &&
|
||||
lhs.isSkinSmoothingEnabled == rhs.isSkinSmoothingEnabled &&
|
||||
|
||||
Loading…
Reference in New Issue
Block a user