Compare commits
No commits in common. "845367fa873942f8d9ffe366bbdb416399849047" and "d3dac86de4338e78bcfc113e19115d47b0f85783" have entirely different histories.
845367fa87
...
d3dac86de4
@ -84,15 +84,6 @@ struct SettingsView: View {
|
|||||||
SettingsSectionHeader(title: "Display", systemImage: "eye", accentColor: AppAccent.primary)
|
SettingsSectionHeader(title: "Display", systemImage: "eye", accentColor: AppAccent.primary)
|
||||||
|
|
||||||
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
||||||
|
|
||||||
SettingsToggle(
|
|
||||||
title: String(localized: "Grid Overlay"),
|
|
||||||
subtitle: String(localized: "Shows rule of thirds grid for composition"),
|
|
||||||
isOn: $viewModel.isGridVisible,
|
|
||||||
accentColor: AppAccent.primary
|
|
||||||
)
|
|
||||||
.accessibilityHint(String(localized: "Shows a grid overlay to help compose your shot"))
|
|
||||||
|
|
||||||
// True Mirror (premium)
|
// True Mirror (premium)
|
||||||
premiumToggle(
|
premiumToggle(
|
||||||
title: String(localized: "True Mirror"),
|
title: String(localized: "True Mirror"),
|
||||||
@ -101,6 +92,14 @@ struct SettingsView: View {
|
|||||||
accessibilityHint: String(localized: "Flips the camera preview horizontally")
|
accessibilityHint: String(localized: "Flips the camera preview horizontally")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SettingsToggle(
|
||||||
|
title: String(localized: "Grid Overlay"),
|
||||||
|
subtitle: String(localized: "Shows rule of thirds grid for composition"),
|
||||||
|
isOn: $viewModel.isGridVisible,
|
||||||
|
accentColor: AppAccent.primary
|
||||||
|
)
|
||||||
|
.accessibilityHint(String(localized: "Shows a grid overlay to help compose your shot"))
|
||||||
|
|
||||||
// Skin Smoothing (premium)
|
// Skin Smoothing (premium)
|
||||||
premiumToggle(
|
premiumToggle(
|
||||||
title: String(localized: "Skin Smoothing"),
|
title: String(localized: "Skin Smoothing"),
|
||||||
@ -115,6 +114,8 @@ struct SettingsView: View {
|
|||||||
SettingsSectionHeader(title: "Capture", systemImage: "photo.on.rectangle", accentColor: AppAccent.primary)
|
SettingsSectionHeader(title: "Capture", systemImage: "photo.on.rectangle", accentColor: AppAccent.primary)
|
||||||
|
|
||||||
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
||||||
|
// Timer Selection
|
||||||
|
timerPicker
|
||||||
|
|
||||||
SettingsToggle(
|
SettingsToggle(
|
||||||
title: String(localized: "Auto-Save"),
|
title: String(localized: "Auto-Save"),
|
||||||
@ -123,10 +124,6 @@ struct SettingsView: View {
|
|||||||
accentColor: AppAccent.primary
|
accentColor: AppAccent.primary
|
||||||
)
|
)
|
||||||
.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"))
|
||||||
|
|
||||||
// Timer Selection
|
|
||||||
timerPicker
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Pro Section
|
// MARK: - Pro Section
|
||||||
@ -517,12 +514,82 @@ struct SettingsView: View {
|
|||||||
// MARK: - iCloud Sync Section
|
// MARK: - iCloud Sync Section
|
||||||
|
|
||||||
private var iCloudSyncSection: some View {
|
private var iCloudSyncSection: some View {
|
||||||
iCloudSyncSettingsView(
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
viewModel: viewModel,
|
// Sync toggle
|
||||||
accentColor: AppAccent.primary,
|
Toggle(isOn: $viewModel.iCloudEnabled) {
|
||||||
successColor: AppStatus.success,
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
warningColor: AppStatus.warning
|
Text(String(localized: "Sync Settings"))
|
||||||
)
|
.font(.system(size: Design.BaseFontSize.medium, weight: .medium))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
|
Text(viewModel.iCloudAvailable
|
||||||
|
? String(localized: "Sync settings across all your devices")
|
||||||
|
: String(localized: "Sign in to iCloud to enable sync"))
|
||||||
|
.font(.system(size: Design.BaseFontSize.body))
|
||||||
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tint(AppAccent.primary)
|
||||||
|
.padding(.vertical, Design.Spacing.xSmall)
|
||||||
|
.disabled(!viewModel.iCloudAvailable)
|
||||||
|
.accessibilityHint(String(localized: "Syncs settings across all your devices via iCloud"))
|
||||||
|
|
||||||
|
// Sync status (show when enabled and available)
|
||||||
|
if viewModel.iCloudEnabled && viewModel.iCloudAvailable {
|
||||||
|
HStack(spacing: Design.Spacing.small) {
|
||||||
|
Image(systemName: syncStatusIcon)
|
||||||
|
.font(.system(size: Design.BaseFontSize.body))
|
||||||
|
.foregroundStyle(syncStatusColor)
|
||||||
|
|
||||||
|
Text(syncStatusText)
|
||||||
|
.font(.system(size: Design.BaseFontSize.caption))
|
||||||
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
viewModel.forceSync()
|
||||||
|
} label: {
|
||||||
|
Text(String(localized: "Sync Now"))
|
||||||
|
.font(.system(size: Design.BaseFontSize.caption, weight: .medium))
|
||||||
|
.foregroundStyle(AppAccent.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, Design.Spacing.xSmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Sync Status Helpers
|
||||||
|
|
||||||
|
private var syncStatusIcon: String {
|
||||||
|
if !viewModel.hasCompletedInitialSync {
|
||||||
|
return "arrow.triangle.2.circlepath"
|
||||||
|
}
|
||||||
|
return viewModel.syncStatus.isEmpty ? "checkmark.icloud" : "icloud"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var syncStatusColor: Color {
|
||||||
|
if !viewModel.hasCompletedInitialSync {
|
||||||
|
return AppStatus.warning
|
||||||
|
}
|
||||||
|
return AppStatus.success
|
||||||
|
}
|
||||||
|
|
||||||
|
private var syncStatusText: String {
|
||||||
|
if !viewModel.hasCompletedInitialSync {
|
||||||
|
return String(localized: "Syncing...")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lastSync = viewModel.lastSyncDate {
|
||||||
|
let formatter = RelativeDateTimeFormatter()
|
||||||
|
formatter.unitsStyle = .abbreviated
|
||||||
|
return String(localized: "Last synced \(formatter.localizedString(for: lastSync, relativeTo: Date()))")
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewModel.syncStatus.isEmpty
|
||||||
|
? String(localized: "Synced")
|
||||||
|
: viewModel.syncStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Acknowledgments Section
|
// MARK: - Acknowledgments Section
|
||||||
@ -613,7 +680,7 @@ struct SettingsView: View {
|
|||||||
title: "Enable Debug Premium",
|
title: "Enable Debug Premium",
|
||||||
subtitle: "Unlock all premium features for testing",
|
subtitle: "Unlock all premium features for testing",
|
||||||
isOn: $viewModel.isDebugPremiumEnabled,
|
isOn: $viewModel.isDebugPremiumEnabled,
|
||||||
accentColor: AppAccent.primary
|
accentColor: AppStatus.warning
|
||||||
)
|
)
|
||||||
|
|
||||||
// Icon Generator
|
// Icon Generator
|
||||||
|
|||||||
@ -38,7 +38,7 @@ enum TimerOption: String, CaseIterable, Identifiable {
|
|||||||
/// Premium features are automatically reset to defaults when user doesn't have premium.
|
/// Premium features are automatically reset to defaults when user doesn't have premium.
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable
|
@Observable
|
||||||
final class SettingsViewModel: RingLightConfigurable, CloudSyncable {
|
final class SettingsViewModel: RingLightConfigurable {
|
||||||
|
|
||||||
// MARK: - Ring Size Limits
|
// MARK: - Ring Size Limits
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user