tutorial: introduce habit reminders in the Settings tutorial too (KC-60)
Some checks failed
CI / build-and-test (push) Has been cancelled

Habit reminders (KC-55) were in Onboarding but not the app's separate
spotlight/coachmark tutorial system -- skipping onboarding meant no second
chance to discover Prayer/Bed/Coffee reminders exist.

Adds a fourth TutorialManager track (settingsTips/settingsStep/settingsDone),
matching the existing Today/Workout tip-track pattern exactly, and wires it
into SettingsView via the same shared InViewTutorialCard component -- shown
once, first time Settings opens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kutesir
2026-07-09 12:47:23 +03:00
parent a5f7bce09f
commit dd69a6a2e4
3 changed files with 69 additions and 0 deletions

View File

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

View File

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

View File

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