diff --git a/KisaniCal/ISSUES.md b/KisaniCal/ISSUES.md index 1109051..eb5e0f4 100644 --- a/KisaniCal/ISSUES.md +++ b/KisaniCal/ISSUES.md @@ -2029,3 +2029,36 @@ offset from an unrelated comment, verified via stash — not introduced by this change). `OverdueCard`'s wiring is technically unreachable today since `overdueTasks` already excludes recurring tasks by design — wired anyway for consistency in case that changes. + +--- + +## KC-60 — Habit reminders also introduced in the in-app tutorial, not just onboarding + +**Status:** Implemented (not build-verified) +**Reported by:** User: "do we have the behavior reminders as part of onboarding +and also in the tutorial" + +### Description +Habit reminders (KC-55) were added to Onboarding, but **not** to the app's +separate tutorial system (`TutorialManager` — the spotlight/coachmark walkthrough +shown per-tab: intro tour, Today tips, Workout tips). Someone who skips or +doesn't linger on onboarding's toggles had no second chance to discover the +feature exists. + +### Change +- New fourth tutorial track in `TutorialManager`: `settingsTips` / + `settingsStep` / `settingsDone` (persisted key `kisani.tutorial.settings.v1`), + following the exact same pattern as the existing Today/Workout tip tracks + (`advanceSettings()`/`skipSettings()`). +- One tip: "Habit Reminders" — introduces Prayer/Bed/Coffee and the private + streak via the notification's "Done" action. +- Wired into `SettingsView` via the same `InViewTutorialCard` component + Today/Workout already use — shown once, the first time Settings is opened. + +Files: `TutorialManager.swift`, `SettingsView.swift`. + +### Note +Not build-verified (no iOS runtime here). Self-reviewed: balanced braces/ +parens confirmed; wiring mirrors `TodayView`/`WorkoutView`'s existing +`tm.xIsActive` + `InViewTutorialCard` pattern exactly, reusing the same shared +component rather than introducing a new one. diff --git a/KisaniCal/Managers/TutorialManager.swift b/KisaniCal/Managers/TutorialManager.swift index 27eec35..44bfa14 100644 --- a/KisaniCal/Managers/TutorialManager.swift +++ b/KisaniCal/Managers/TutorialManager.swift @@ -104,4 +104,24 @@ final class TutorialManager: ObservableObject { else { withAnimation(KisaniSpring.entrance) { workoutStep += 1 } } } func skipWorkout() { withAnimation(KisaniSpring.exit) { workoutDone = true } } + + // MARK: - Settings view tutorial + @AppStorage("kisani.tutorial.settings.v1") var settingsDone: Bool = false + @Published var settingsStep: Int = 0 + + let settingsTips: [PageTip] = [ + .init(title: "Habit Reminders", + body: "Prayer, making your bed, coffee — under Habits, set daily nudges for the small routines you don't want to forget. Tap Done on the notification to keep a private streak.", + icon: "hands.sparkles"), + ] + + var settingsIsActive: Bool { !settingsDone } + var settingsCurrent: PageTip { settingsTips[min(settingsStep, settingsTips.count - 1)] } + var settingsIsLast: Bool { settingsStep == settingsTips.count - 1 } + + func advanceSettings() { + if settingsIsLast { withAnimation(KisaniSpring.exit) { settingsDone = true } } + else { withAnimation(KisaniSpring.entrance) { settingsStep += 1 } } + } + func skipSettings() { withAnimation(KisaniSpring.exit) { settingsDone = true } } } diff --git a/KisaniCal/Views/SettingsView.swift b/KisaniCal/Views/SettingsView.swift index 1140048..ed3809d 100644 --- a/KisaniCal/Views/SettingsView.swift +++ b/KisaniCal/Views/SettingsView.swift @@ -15,6 +15,7 @@ struct SettingsView: View { @EnvironmentObject var taskVM: TaskViewModel @ObservedObject private var auth = AuthManager.shared @ObservedObject private var calStore = CalendarStore.shared + @ObservedObject private var tm = TutorialManager.shared // Persisted settings @AppStorage("appearanceMode") private var appearanceMode = 0 @@ -189,6 +190,21 @@ struct SettingsView: View { Spacer().frame(height: 40) } } + + if tm.settingsIsActive { + VStack { + Spacer() + InViewTutorialCard( + tips: tm.settingsTips, + step: $tm.settingsStep, + onAdvance: { tm.advanceSettings() }, + onSkip: { tm.skipSettings() } + ) + .padding(.bottom, 84) + } + .transition(.opacity) + .zIndex(10) + } } .sheet(isPresented: $showProfile) { ProfileStatsSheet().environmentObject(taskVM).environmentObject(workoutVM).presentationDetents([.large]).presentationDragIndicator(.visible) } .sheet(isPresented: $showTabBar) { TabBarSheet(showMatrix: $showMatrixTab, showWorkout: $showWorkoutTab).presentationDetents([.height(320)]).presentationDragIndicator(.visible) }