Add tooltip coach mark tutorial for new users
- TutorialManager: tracks 4 steps (NLP, Calendar, Matrix, Workout) persisted via AppStorage; shown once then never again - TutorialView: fullscreen overlay with even-odd spotlight cutout, animated accent border, ultraThinMaterial callout card, pill progress dots, Next/Get started button, and Skip - TutorialFrameKey: PreferenceKey collects CGRect for each anchor index; .tutorialAnchor() modifier wired to FAB (step 0) and all 4 tab buttons - Callout auto-positions above or below the spotlight based on screen half - ContentView collects frames via onPreferenceChange and layers the overlay Co-Authored-By: Kutesir <tqwyy79vzn@privaterelay.appleid.com>
This commit is contained in:
48
KisaniCal/Managers/TutorialManager.swift
Normal file
48
KisaniCal/Managers/TutorialManager.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TutorialStep {
|
||||
let title: String
|
||||
let body: String
|
||||
let icon: String
|
||||
let anchorIndex: Int
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class TutorialManager: ObservableObject {
|
||||
static let shared = TutorialManager()
|
||||
|
||||
@AppStorage("kisani.tutorial.done.v1") private(set) var completed: Bool = false
|
||||
@Published var step: Int = 0
|
||||
|
||||
let steps: [TutorialStep] = [
|
||||
.init(title: "Add Tasks with NLP",
|
||||
body: "Type naturally — \"dentist tomorrow 3pm\" or \"mum's birthday on 8th June every year\" — Kisani parses the date, time, and recurrence for you.",
|
||||
icon: "sparkles",
|
||||
anchorIndex: 0),
|
||||
.init(title: "Calendar",
|
||||
body: "See tasks and iPhone calendar events side-by-side, day by day. Nothing falls through the cracks.",
|
||||
icon: "calendar",
|
||||
anchorIndex: 1),
|
||||
.init(title: "Eisenhower Matrix",
|
||||
body: "Sort tasks into Urgent, Schedule, Delegate, or Eliminate. Focus on what actually moves the needle.",
|
||||
icon: "square.grid.2x2",
|
||||
anchorIndex: 2),
|
||||
.init(title: "Workout Tracker",
|
||||
body: "Build programs, log sets, and keep your streak alive. Your gym routine lives right here.",
|
||||
icon: "dumbbell",
|
||||
anchorIndex: 3),
|
||||
]
|
||||
|
||||
var isActive: Bool { !completed }
|
||||
var current: TutorialStep { steps[min(step, steps.count - 1)] }
|
||||
var isLast: Bool { step == steps.count - 1 }
|
||||
|
||||
func advance() {
|
||||
if isLast { withAnimation(KisaniSpring.exit) { completed = true } }
|
||||
else { withAnimation(KisaniSpring.entrance) { step += 1 } }
|
||||
}
|
||||
|
||||
func skip() {
|
||||
withAnimation(KisaniSpring.exit) { completed = true }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user