222 lines
9.0 KiB
Swift
222 lines
9.0 KiB
Swift
import SwiftUI
|
|
import Bedrock
|
|
import UserNotifications
|
|
|
|
struct SettingsView: View {
|
|
@Bindable var store: SettingsStore
|
|
var ritualStore: RitualStore?
|
|
|
|
private let focusOptions: [(String, FocusStyle)] = FocusStyle.allCases.map { ($0.title, $0) }
|
|
|
|
var body: some View {
|
|
ScrollView(.vertical, showsIndicators: false) {
|
|
VStack(alignment: .leading, spacing: Design.Spacing.large) {
|
|
SettingsSectionHeader(
|
|
title: String(localized: "Preferences"),
|
|
systemImage: "gearshape",
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
|
SettingsToggle(
|
|
title: String(localized: "Daily reminders"),
|
|
subtitle: reminderSubtitle,
|
|
isOn: $store.remindersEnabled,
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
if store.remindersEnabled {
|
|
HStack {
|
|
Text(String(localized: "Reminder time"))
|
|
.foregroundStyle(AppTextColors.primary)
|
|
Spacer()
|
|
DatePicker(
|
|
"",
|
|
selection: $store.reminderTime,
|
|
displayedComponents: .hourAndMinute
|
|
)
|
|
.labelsHidden()
|
|
.tint(AppAccent.primary)
|
|
}
|
|
.padding(.vertical, Design.Spacing.small)
|
|
}
|
|
|
|
SettingsToggle(
|
|
title: String(localized: "Haptics"),
|
|
subtitle: String(localized: "Vibrate when completing habits"),
|
|
isOn: $store.hapticsEnabled,
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
SettingsToggle(
|
|
title: String(localized: "Sound"),
|
|
subtitle: String(localized: "Play subtle completion sounds"),
|
|
isOn: $store.soundEnabled,
|
|
accentColor: AppAccent.primary
|
|
)
|
|
}
|
|
|
|
SettingsSectionHeader(
|
|
title: String(localized: "Rituals Pro"),
|
|
systemImage: "crown.fill",
|
|
accentColor: AppAccent.secondary
|
|
)
|
|
|
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
|
SettingsNavigationRow(
|
|
title: String(localized: "Upgrade to Pro"),
|
|
subtitle: String(localized: "Unlock unlimited rituals and more"),
|
|
backgroundColor: AppSurface.primary
|
|
) {
|
|
ProUpgradeView()
|
|
}
|
|
}
|
|
|
|
SettingsSectionHeader(
|
|
title: String(localized: "Ritual pacing"),
|
|
systemImage: "timer",
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
|
SettingsSegmentedPicker(
|
|
title: String(localized: "Focus style"),
|
|
subtitle: String(localized: "Choose the intensity of your arc"),
|
|
options: focusOptions,
|
|
selection: $store.focusStyle,
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
SettingsSlider(
|
|
title: String(localized: "Ritual length"),
|
|
subtitle: String(localized: "Adjust arc duration"),
|
|
value: $store.ritualLengthDays,
|
|
in: AppMetrics.RitualLength.minimumDays...AppMetrics.RitualLength.maximumDays,
|
|
step: AppMetrics.RitualLength.stepDays,
|
|
format: SliderFormat.integer(unit: String(localized: "days")),
|
|
accentColor: AppAccent.primary,
|
|
leadingIcon: Image(systemName: "calendar"),
|
|
trailingIcon: Image(systemName: "calendar.circle.fill")
|
|
)
|
|
}
|
|
|
|
SettingsSectionHeader(
|
|
title: String(localized: "iCloud Sync"),
|
|
systemImage: "icloud",
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
|
iCloudSyncSettingsView(
|
|
viewModel: store,
|
|
accentColor: AppAccent.primary,
|
|
successColor: AppStatus.success,
|
|
warningColor: AppStatus.warning
|
|
)
|
|
}
|
|
|
|
SettingsSectionHeader(
|
|
title: String(localized: "About"),
|
|
systemImage: "info.circle",
|
|
accentColor: AppAccent.primary
|
|
)
|
|
|
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
|
SettingsNavigationRow(
|
|
title: String(localized: "Rituals mission"),
|
|
subtitle: String(localized: "Why arcs keep habits grounded"),
|
|
backgroundColor: AppSurface.primary
|
|
) {
|
|
SettingsAboutView()
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
SettingsSectionHeader(
|
|
title: String(localized: "Debug"),
|
|
systemImage: "ant.fill",
|
|
accentColor: AppStatus.error
|
|
)
|
|
|
|
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
|
SettingsNavigationRow(
|
|
title: String(localized: "Icon Generator"),
|
|
subtitle: String(localized: "Generate the app icon"),
|
|
backgroundColor: AppSurface.primary
|
|
) {
|
|
IconGeneratorView(config: .rituals, appName: "Rituals")
|
|
}
|
|
|
|
SettingsNavigationRow(
|
|
title: String(localized: "Branding Preview"),
|
|
subtitle: String(localized: "Preview launch and icon"),
|
|
backgroundColor: AppSurface.primary
|
|
) {
|
|
BrandingPreviewView(
|
|
iconConfig: .rituals,
|
|
launchConfig: .rituals,
|
|
appName: "Rituals"
|
|
)
|
|
}
|
|
|
|
SettingsRow(
|
|
systemImage: "arrow.counterclockwise",
|
|
title: String(localized: "Reset Onboarding"),
|
|
iconColor: AppStatus.warning
|
|
) {
|
|
UserDefaults.standard.removeObject(forKey: "hasCompletedOnboarding")
|
|
}
|
|
|
|
if let ritualStore {
|
|
SettingsRow(
|
|
systemImage: "calendar.badge.plus",
|
|
title: String(localized: "Preload 6 Months Demo Data"),
|
|
iconColor: AppStatus.info
|
|
) {
|
|
ritualStore.preloadDemoData()
|
|
}
|
|
|
|
SettingsRow(
|
|
systemImage: "trash",
|
|
title: String(localized: "Clear All Completions"),
|
|
iconColor: AppStatus.error
|
|
) {
|
|
ritualStore.clearAllCompletions()
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Spacer(minLength: Design.Spacing.xxxLarge)
|
|
}
|
|
.padding(.horizontal, Design.Spacing.large)
|
|
}
|
|
.background(AppSurface.primary)
|
|
.navigationTitle(String(localized: "Settings"))
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Computed Properties
|
|
|
|
extension SettingsView {
|
|
private var reminderSubtitle: String {
|
|
switch store.notificationAuthStatus {
|
|
case .denied:
|
|
return String(localized: "Notifications disabled in Settings")
|
|
case .notDetermined:
|
|
return String(localized: "Get a gentle check-in each morning")
|
|
case .authorized, .provisional, .ephemeral:
|
|
return String(localized: "Get a gentle check-in each morning")
|
|
@unknown default:
|
|
return String(localized: "Get a gentle check-in each morning")
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
SettingsView(store: SettingsStore.preview, ritualStore: nil)
|
|
}
|
|
}
|