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 {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
|
let maxRingSize = calculateMaxRingSize(for: geometry)
|
||||||
|
let effectiveRingSize = min(settings.ringSize, maxRingSize)
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
// MARK: - Ring Light Background
|
// MARK: - Ring Light Background
|
||||||
ringLightBackground
|
ringLightBackground
|
||||||
|
|
||||||
// MARK: - Camera Preview (centered with border inset)
|
// MARK: - Camera Preview (full screen with ring border)
|
||||||
cameraPreviewArea(in: geometry)
|
cameraPreviewArea(ringSize: effectiveRingSize)
|
||||||
|
|
||||||
// MARK: - Grid Overlay
|
// MARK: - Grid Overlay
|
||||||
if settings.isGridVisible && !viewModel.isPreviewHidden {
|
if settings.isGridVisible && !viewModel.isPreviewHidden {
|
||||||
let previewSize = min(
|
|
||||||
geometry.size.width - (settings.ringSize * 2),
|
|
||||||
geometry.size.height - (settings.ringSize * 2)
|
|
||||||
)
|
|
||||||
GridOverlay(isVisible: true)
|
GridOverlay(isVisible: true)
|
||||||
.frame(width: previewSize, height: previewSize)
|
.padding(effectiveRingSize)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
|
||||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Controls Overlay
|
// MARK: - Controls Overlay (on top of preview)
|
||||||
controlsOverlay
|
controlsOverlay(ringSize: effectiveRingSize)
|
||||||
|
|
||||||
// MARK: - Permission Denied View
|
// MARK: - Permission Denied View
|
||||||
if !viewModel.isCameraAuthorized && viewModel.captureSession != nil {
|
if !viewModel.isCameraAuthorized && viewModel.captureSession != nil {
|
||||||
@ -47,6 +44,13 @@ struct ContentView: View {
|
|||||||
toastView(message: message)
|
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()
|
.ignoresSafeArea()
|
||||||
.task {
|
.task {
|
||||||
@ -59,7 +63,7 @@ struct ContentView: View {
|
|||||||
ProPaywallView()
|
ProPaywallView()
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showSettings) {
|
.sheet(isPresented: $showSettings) {
|
||||||
SettingsView(viewModel: viewModel.settings)
|
SettingsView(viewModel: viewModel.settings, showPaywall: $showPaywall)
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $viewModel.showPostCapturePreview) {
|
.fullScreenCover(isPresented: $viewModel.showPostCapturePreview) {
|
||||||
if let media = viewModel.capturedMedia {
|
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
|
// MARK: - Ring Light Background
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var ringLightBackground: some View {
|
private var ringLightBackground: some View {
|
||||||
let baseColor = premiumManager.isPremiumUnlocked ? settings.lightColor : Color.RingLight.pureWhite
|
let baseColor = premiumManager.isPremiumUnlocked ? settings.lightColor : Color.RingLight.pureWhite
|
||||||
|
|
||||||
// Apply light intensity as opacity
|
|
||||||
baseColor
|
baseColor
|
||||||
.opacity(settings.lightIntensity)
|
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.lightIntensity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Camera Preview Area
|
// MARK: - Camera Preview Area
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func cameraPreviewArea(in geometry: GeometryProxy) -> some View {
|
private func cameraPreviewArea(ringSize: CGFloat) -> 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)
|
|
||||||
)
|
|
||||||
|
|
||||||
if viewModel.isCameraAuthorized {
|
if viewModel.isCameraAuthorized {
|
||||||
// Show preview unless front flash is active
|
// Show preview unless front flash is active
|
||||||
if !viewModel.isPreviewHidden {
|
if !viewModel.isPreviewHidden {
|
||||||
@ -117,16 +120,17 @@ struct ContentView: View {
|
|||||||
isMirrorFlipped: settings.isMirrorFlipped,
|
isMirrorFlipped: settings.isMirrorFlipped,
|
||||||
zoomFactor: settings.currentZoomFactor
|
zoomFactor: settings.currentZoomFactor
|
||||||
)
|
)
|
||||||
.frame(width: previewSize, height: previewSize)
|
.padding(ringSize)
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
.animation(.easeInOut(duration: Design.Animation.quick), value: ringSize)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show placeholder while requesting permission
|
// Show placeholder while requesting permission
|
||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
Rectangle()
|
||||||
.fill(.black)
|
.fill(.black)
|
||||||
.frame(width: previewSize, height: previewSize)
|
.padding(ringSize)
|
||||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||||
|
.animation(.easeInOut(duration: Design.Animation.quick), value: ringSize)
|
||||||
.overlay {
|
.overlay {
|
||||||
if viewModel.captureSession == nil {
|
if viewModel.captureSession == nil {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
@ -139,38 +143,26 @@ struct ContentView: View {
|
|||||||
|
|
||||||
// MARK: - Controls Overlay
|
// MARK: - Controls Overlay
|
||||||
|
|
||||||
private var controlsOverlay: some View {
|
private func controlsOverlay(ringSize: CGFloat) -> some View {
|
||||||
VStack {
|
VStack {
|
||||||
// Top bar
|
// Top bar
|
||||||
topControlBar
|
topControlBar
|
||||||
|
.padding(.top, ringSize + Design.Spacing.small)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// Bottom capture controls
|
// Bottom capture controls
|
||||||
bottomControlBar
|
bottomControlBar
|
||||||
|
.padding(.bottom, ringSize + Design.Spacing.medium)
|
||||||
}
|
}
|
||||||
.padding(settings.ringSize + Design.Spacing.medium)
|
.padding(.horizontal, ringSize + Design.Spacing.small)
|
||||||
.animation(.easeInOut(duration: Design.Animation.quick), value: settings.ringSize)
|
.animation(.easeInOut(duration: Design.Animation.quick), value: ringSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Top Control Bar
|
// MARK: - Top Control Bar
|
||||||
|
|
||||||
private var topControlBar: some View {
|
private var topControlBar: some View {
|
||||||
HStack {
|
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()
|
Spacer()
|
||||||
|
|
||||||
// Grid toggle
|
// Grid toggle
|
||||||
@ -178,7 +170,7 @@ struct ContentView: View {
|
|||||||
viewModel.settings.isGridVisible.toggle()
|
viewModel.settings.isGridVisible.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.grid.3x3")
|
Image(systemName: "square.grid.3x3")
|
||||||
.font(.title2)
|
.font(.body)
|
||||||
.foregroundStyle(viewModel.settings.isGridVisible ? .yellow : .white)
|
.foregroundStyle(viewModel.settings.isGridVisible ? .yellow : .white)
|
||||||
.padding(Design.Spacing.small)
|
.padding(Design.Spacing.small)
|
||||||
.background(.ultraThinMaterial, in: .circle)
|
.background(.ultraThinMaterial, in: .circle)
|
||||||
@ -191,7 +183,7 @@ struct ContentView: View {
|
|||||||
showSettings = true
|
showSettings = true
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "gearshape.fill")
|
Image(systemName: "gearshape.fill")
|
||||||
.font(.title2)
|
.font(.body)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(Design.Spacing.small)
|
.padding(Design.Spacing.small)
|
||||||
.background(.ultraThinMaterial, in: .circle)
|
.background(.ultraThinMaterial, in: .circle)
|
||||||
@ -203,22 +195,26 @@ struct ContentView: View {
|
|||||||
// MARK: - Bottom Control Bar
|
// MARK: - Bottom Control Bar
|
||||||
|
|
||||||
private var bottomControlBar: some View {
|
private var bottomControlBar: some View {
|
||||||
HStack(spacing: Design.Spacing.xxxxLarge) {
|
HStack {
|
||||||
// Switch camera button
|
// Switch camera button
|
||||||
Button {
|
Button {
|
||||||
viewModel.switchCamera()
|
viewModel.switchCamera()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "camera.rotate.fill")
|
Image(systemName: "camera.rotate.fill")
|
||||||
.font(.title)
|
.font(.title2)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(Design.Spacing.medium)
|
.padding(Design.Spacing.medium)
|
||||||
.background(.ultraThinMaterial, in: .circle)
|
.background(.ultraThinMaterial, in: .circle)
|
||||||
}
|
}
|
||||||
.accessibilityLabel(String(localized: "Switch camera"))
|
.accessibilityLabel(String(localized: "Switch camera"))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
// Capture button
|
// Capture button
|
||||||
captureButton
|
captureButton
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
// Capture mode selector
|
// Capture mode selector
|
||||||
captureModeMenu
|
captureModeMenu
|
||||||
}
|
}
|
||||||
@ -273,7 +269,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: viewModel.settings.selectedCaptureMode.systemImage)
|
Image(systemName: viewModel.settings.selectedCaptureMode.systemImage)
|
||||||
.font(.title)
|
.font(.title2)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(Design.Spacing.medium)
|
.padding(Design.Spacing.medium)
|
||||||
.background(.ultraThinMaterial, in: .circle)
|
.background(.ultraThinMaterial, in: .circle)
|
||||||
@ -351,7 +347,7 @@ struct ContentView: View {
|
|||||||
.padding(.horizontal, Design.Spacing.large)
|
.padding(.horizontal, Design.Spacing.large)
|
||||||
.padding(.vertical, Design.Spacing.medium)
|
.padding(.vertical, Design.Spacing.medium)
|
||||||
.background(.ultraThinMaterial, in: Capsule())
|
.background(.ultraThinMaterial, in: Capsule())
|
||||||
.padding(.bottom, Design.Spacing.xxxLarge + settings.ringSize)
|
.padding(.bottom, Design.Spacing.xxxLarge)
|
||||||
.transition(.move(edge: .bottom).combined(with: .opacity))
|
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||||
.animation(.spring(duration: Design.Animation.quick), value: message)
|
.animation(.spring(duration: Design.Animation.quick), value: message)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import Bedrock
|
|||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@Bindable var viewModel: SettingsViewModel
|
@Bindable var viewModel: SettingsViewModel
|
||||||
|
@Binding var showPaywall: Bool
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -20,9 +21,6 @@ struct SettingsView: View {
|
|||||||
// Color Preset
|
// Color Preset
|
||||||
colorPresetSection
|
colorPresetSection
|
||||||
|
|
||||||
// Brightness Slider
|
|
||||||
brightnessSlider
|
|
||||||
|
|
||||||
// MARK: - Camera Section
|
// MARK: - Camera Section
|
||||||
|
|
||||||
SettingsSectionHeader(title: "Camera", systemImage: "camera")
|
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"))
|
.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
|
// MARK: - Sync Section
|
||||||
|
|
||||||
SettingsSectionHeader(title: "iCloud Sync", systemImage: "icloud")
|
SettingsSectionHeader(title: "iCloud Sync", systemImage: "icloud")
|
||||||
@ -162,44 +166,6 @@ struct SettingsView: View {
|
|||||||
.padding(.vertical, Design.Spacing.xSmall)
|
.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
|
// MARK: - Timer Picker
|
||||||
|
|
||||||
private var timerPicker: some View {
|
private var timerPicker: some View {
|
||||||
@ -211,6 +177,49 @@ struct SettingsView: View {
|
|||||||
.accessibilityLabel(String(localized: "Select self-timer duration"))
|
.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
|
// MARK: - iCloud Sync Section
|
||||||
|
|
||||||
private var iCloudSyncSection: some View {
|
private var iCloudSyncSection: some View {
|
||||||
@ -335,6 +344,6 @@ private struct ColorPresetButton: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
SettingsView(viewModel: SettingsViewModel())
|
SettingsView(viewModel: SettingsViewModel(), showPaywall: .constant(false))
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,12 +101,6 @@ final class SettingsViewModel: RingLightConfigurable {
|
|||||||
set { updateSettings { $0.lightColorId = newValue } }
|
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)
|
/// Whether front flash is enabled (hides preview during capture)
|
||||||
var isFrontFlashEnabled: Bool {
|
var isFrontFlashEnabled: Bool {
|
||||||
get { cloudSync.data.isFrontFlashEnabled }
|
get { cloudSync.data.isFrontFlashEnabled }
|
||||||
@ -218,7 +212,7 @@ final class SettingsViewModel: RingLightConfigurable {
|
|||||||
// MARK: - Validation
|
// MARK: - Validation
|
||||||
|
|
||||||
var isValidConfiguration: Bool {
|
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\".",
|
"comment" : "Description of a timer option when the user selects \"10 seconds\".",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Adjusts the size of the light ring around the camera preview" : {
|
||||||
"comment" : "A description of the ring size slider in the settings view.",
|
"comment" : "A description of the ring size slider in the settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -49,6 +53,14 @@
|
|||||||
"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
|
||||||
},
|
},
|
||||||
|
"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%" : {
|
"Best Value • Save 33%" : {
|
||||||
"comment" : "A promotional text displayed below an annual subscription package, highlighting its value.",
|
"comment" : "A promotional text displayed below an annual subscription package, highlighting its value.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -73,6 +85,22 @@
|
|||||||
"comment" : "A label describing the currently selected capture mode. The placeholder is replaced with the actual mode name.",
|
"comment" : "A label describing the currently selected capture mode. The placeholder is replaced with the actual mode name.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Cool Lavender" : {
|
||||||
"comment" : "Name of a ring light color preset.",
|
"comment" : "Name of a ring light color preset.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -93,6 +121,14 @@
|
|||||||
"comment" : "The text for a button that dismisses a view. In this case, it dismisses the settings view.",
|
"comment" : "The text for a button that dismisses a view. In this case, it dismisses the settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Go Pro" : {
|
||||||
"comment" : "The title of the \"Go Pro\" button in the Pro paywall.",
|
"comment" : "The title of the \"Go Pro\" button in the Pro paywall.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -101,8 +137,8 @@
|
|||||||
"comment" : "Text displayed in a settings toggle for showing a grid overlay to help compose your shot.",
|
"comment" : "Text displayed in a settings toggle for showing a grid overlay to help compose your shot.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Higher brightness = brighter ring light effect" : {
|
"Hides preview during capture for a flash effect" : {
|
||||||
"comment" : "A description of how to adjust the brightness of the screen.",
|
"comment" : "Subtitle for the \"Front Flash\" toggle in the Settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Ice Blue" : {
|
"Ice Blue" : {
|
||||||
@ -116,6 +152,14 @@
|
|||||||
"comment" : "A label displayed above a section of the settings view related to light colors.",
|
"comment" : "A label displayed above a section of the settings view related to light colors.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"No Watermarks • Ad-Free" : {
|
||||||
"comment" : "Description of a benefit that comes with the Pro subscription.",
|
"comment" : "Description of a benefit that comes with the Pro subscription.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -167,6 +211,14 @@
|
|||||||
"comment" : "A button that restores purchases.",
|
"comment" : "A button that restores purchases.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Ring size" : {
|
||||||
"comment" : "An accessibility label for the ring size slider in the settings view.",
|
"comment" : "An accessibility label for the ring size slider in the settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -175,12 +227,8 @@
|
|||||||
"comment" : "The label for the ring size slider in the settings view.",
|
"comment" : "The label for the ring size slider in the settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Screen brightness" : {
|
"Saved to Photos" : {
|
||||||
"comment" : "An accessibility label for the screen brightness setting in the settings view.",
|
"comment" : "Text shown as a toast message when a photo is successfully saved to Photos.",
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Screen Brightness" : {
|
|
||||||
"comment" : "A label displayed above the brightness slider in the settings view.",
|
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Select self-timer duration" : {
|
"Select self-timer duration" : {
|
||||||
@ -195,6 +243,10 @@
|
|||||||
"comment" : "The title of the settings screen.",
|
"comment" : "The title of the settings screen.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Share" : {
|
||||||
|
"comment" : "Title for a button that shares the captured media.",
|
||||||
|
"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
|
||||||
@ -214,6 +266,9 @@
|
|||||||
"Skin Smoothing" : {
|
"Skin Smoothing" : {
|
||||||
"comment" : "A toggle that enables or disables real-time skin smoothing.",
|
"comment" : "A toggle that enables or disables real-time skin smoothing.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Smoothing" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Soft Pink" : {
|
"Soft Pink" : {
|
||||||
"comment" : "Name of a ring light color preset.",
|
"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.",
|
"comment" : "Description of a benefit that comes with the Pro subscription, specifically related to the boomerang tool.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Upgrade to Pro" : {
|
||||||
"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
|
||||||
},
|
},
|
||||||
|
"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" : {
|
"Video" : {
|
||||||
"comment" : "Display name for the \"Video\" capture mode.",
|
"comment" : "Display name for the \"Video\" capture mode.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -298,6 +361,10 @@
|
|||||||
"comment" : "A color option for the ring light, named after a warm, creamy shade of white.",
|
"comment" : "A color option for the ring light, named after a warm, creamy shade of white.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"When enabled, the preview is not mirrored" : {
|
||||||
"comment" : "Accessibility hint for the \"True Mirror\" setting in the Settings view.",
|
"comment" : "Accessibility hint for the \"True Mirror\" setting in the Settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|||||||
@ -11,9 +11,6 @@ protocol RingLightConfigurable {
|
|||||||
/// The color of the ring light
|
/// The color of the ring light
|
||||||
var lightColor: Color { get }
|
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)
|
/// Whether front flash is enabled (hides preview during capture)
|
||||||
var isFrontFlashEnabled: Bool { get set }
|
var isFrontFlashEnabled: Bool { get set }
|
||||||
|
|
||||||
|
|||||||
@ -35,9 +35,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
|||||||
/// ID of the selected light color preset
|
/// ID of the selected light color preset
|
||||||
var lightColorId: String = "pureWhite"
|
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)
|
/// Whether front flash is enabled (hides preview during capture)
|
||||||
var isFrontFlashEnabled: Bool = true
|
var isFrontFlashEnabled: Bool = true
|
||||||
|
|
||||||
@ -77,7 +74,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
|||||||
init(
|
init(
|
||||||
ringSize: CGFloat,
|
ringSize: CGFloat,
|
||||||
lightColorId: String,
|
lightColorId: String,
|
||||||
lightIntensity: Double,
|
|
||||||
isFrontFlashEnabled: Bool,
|
isFrontFlashEnabled: Bool,
|
||||||
isMirrorFlipped: Bool,
|
isMirrorFlipped: Bool,
|
||||||
isSkinSmoothingEnabled: Bool,
|
isSkinSmoothingEnabled: Bool,
|
||||||
@ -89,7 +85,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
|||||||
) {
|
) {
|
||||||
self.ringSizeValue = Double(ringSize)
|
self.ringSizeValue = Double(ringSize)
|
||||||
self.lightColorId = lightColorId
|
self.lightColorId = lightColorId
|
||||||
self.lightIntensity = lightIntensity
|
|
||||||
self.isFrontFlashEnabled = isFrontFlashEnabled
|
self.isFrontFlashEnabled = isFrontFlashEnabled
|
||||||
self.isMirrorFlipped = isMirrorFlipped
|
self.isMirrorFlipped = isMirrorFlipped
|
||||||
self.isSkinSmoothingEnabled = isSkinSmoothingEnabled
|
self.isSkinSmoothingEnabled = isSkinSmoothingEnabled
|
||||||
@ -108,7 +103,6 @@ struct SyncedSettings: PersistableData, Sendable {
|
|||||||
case lastModified
|
case lastModified
|
||||||
case ringSizeValue
|
case ringSizeValue
|
||||||
case lightColorId
|
case lightColorId
|
||||||
case lightIntensity
|
|
||||||
case isFrontFlashEnabled
|
case isFrontFlashEnabled
|
||||||
case isMirrorFlipped
|
case isMirrorFlipped
|
||||||
case isSkinSmoothingEnabled
|
case isSkinSmoothingEnabled
|
||||||
@ -126,7 +120,6 @@ extension SyncedSettings: Equatable {
|
|||||||
static func == (lhs: SyncedSettings, rhs: SyncedSettings) -> Bool {
|
static func == (lhs: SyncedSettings, rhs: SyncedSettings) -> Bool {
|
||||||
lhs.ringSizeValue == rhs.ringSizeValue &&
|
lhs.ringSizeValue == rhs.ringSizeValue &&
|
||||||
lhs.lightColorId == rhs.lightColorId &&
|
lhs.lightColorId == rhs.lightColorId &&
|
||||||
lhs.lightIntensity == rhs.lightIntensity &&
|
|
||||||
lhs.isFrontFlashEnabled == rhs.isFrontFlashEnabled &&
|
lhs.isFrontFlashEnabled == rhs.isFrontFlashEnabled &&
|
||||||
lhs.isMirrorFlipped == rhs.isMirrorFlipped &&
|
lhs.isMirrorFlipped == rhs.isMirrorFlipped &&
|
||||||
lhs.isSkinSmoothingEnabled == rhs.isSkinSmoothingEnabled &&
|
lhs.isSkinSmoothingEnabled == rhs.isSkinSmoothingEnabled &&
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user