Compare commits

..

No commits in common. "845367fa873942f8d9ffe366bbdb416399849047" and "d3dac86de4338e78bcfc113e19115d47b0f85783" have entirely different histories.

2 changed files with 88 additions and 21 deletions

View File

@ -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

View File

@ -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