1.2.0: contextual notifications + in-onboarding auto-fill (aha moment)

- New "Week Ready" onboarding step: auto-fills the first week inline right
  after dish creation, then pitches the Sunday reminder with context before
  requesting OS notification permission (no more cold prompt on Home)
- Notifications section in Settings: weekly reminder toggle, deep-link to
  system settings when permission is denied
- NotificationService: remindersEnabled preference, requestPermission(),
  authorizationStatus(), cancelAllReminders()
- Post-onboarding premium prompt deferred to 2nd session (paywall was just
  shown as the final onboarding step — 2s-later alert was prompt fatigue)
- Back navigation blocked after onboarding commit to avoid duplicate dishes
- 10 new strings × 6 languages

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtWT52pAE91D7mbDda1cAM
This commit is contained in:
alexandrev-tibco
2026-07-03 10:08:04 +02:00
parent 13ece5f2e7
commit 73c2039338
13 changed files with 415 additions and 49 deletions
@@ -1,15 +1,45 @@
import SwiftUI
import SwiftData
import EventKit
import UserNotifications
struct SettingsView: View {
@Environment(\.modelContext) private var context
@Query private var allSettings: [AppSettings]
@StateObject private var viewModel = SettingsViewModel()
@State private var showResetAllDataAlert = false
@State private var notificationAuthStatus: UNAuthorizationStatus = .notDetermined
@State private var remindersEnabled = NotificationService.shared.remindersEnabled
private var settings: AppSettings? { allSettings.first }
private func refreshNotificationStatus() async {
notificationAuthStatus = await NotificationService.shared.authorizationStatus()
remindersEnabled = NotificationService.shared.remindersEnabled
}
private func handleReminderToggle(_ enabled: Bool, settings: AppSettings) {
Task {
if enabled {
let granted = await NotificationService.shared.requestPermission()
NotificationService.shared.remindersEnabled = true
remindersEnabled = true
notificationAuthStatus = await NotificationService.shared.authorizationStatus()
AnalyticsService.logNotificationsToggled(enabled: granted)
if granted {
NotificationService.shared.schedulePlanningReminderIfNeeded(
nextWeekPlan: nil,
language: settings.languageEnum.resolved()
)
}
} else {
NotificationService.shared.remindersEnabled = false
remindersEnabled = false
AnalyticsService.logNotificationsToggled(enabled: false)
}
}
}
var body: some View {
ZStack {
Color.mealMoodBackground.ignoresSafeArea()
@@ -147,6 +177,37 @@ struct SettingsView: View {
}
.listRowBackground(Color.mealMoodSurface)
// Notifications section
Section {
Toggle("settings_weekly_reminder", isOn: Binding(
get: { remindersEnabled && notificationAuthStatus != .denied },
set: { newValue in handleReminderToggle(newValue, settings: settings) }
))
.tint(.mealMoodCoral)
.disabled(notificationAuthStatus == .denied)
if notificationAuthStatus == .denied {
VStack(alignment: .leading, spacing: 8) {
Text("settings_notifications_denied_hint")
.font(.mealMoodSmall)
.foregroundColor(.mealMoodTextSecondary)
Button {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
} label: {
Text("settings_open_system_settings")
.font(.mealMoodSmall)
.foregroundColor(.mealMoodCoral)
}
}
}
} header: {
Label("settings_notifications_header", systemImage: "bell")
}
.listRowBackground(Color.mealMoodSurface)
.task { await refreshNotificationStatus() }
// Tags section
Section {
NavigationLink(destination: TagListView()) {