Files
Spanish/Conjuga/Conjuga/Views/Settings/FeatureReferenceView.swift
T
Trey t dce2cc1f51 Make Full Table level-agnostic, fix the streak system end-to-end
Full Table (issue from chat): drop the level filter — Full Table tests
regular conjugation patterns, not vocabulary recognition, so restricting
to Basic-level verbs collapsed the eligible pool to two combos
(vivir present, ir future). Pool now draws from all 1,750 verbs. Random
sampling first; if 40 attempts fail we fall through to a deterministic
shuffled scan that guarantees finding any eligible (verb, tense) combo
when one exists. Returning nil now happens only when the user's filters
genuinely produce zero eligible prompts. The view replaces its silent
blank screen with a ContentUnavailableView pointing at the settings
that need adjusting. FeatureReferenceView documents the level exception.

Streak (issue #31 follow-up): activity recording was scoped to flashcard
and Full Table reviews only, so spending an hour on textbook work,
guides, videos, or AI chat could break a "streak" that the dashboard
kept displaying as if it were intact. Three fixes:

  1. Extract ReviewStore.recordActivity(context:) — a streak-only entry
     point that any user-initiated learning action can call.
  2. Add UserProgress.validateStreakIfStale(today:context:) — resets a
     broken currentStreak to 0 immediately, called from app launch and
     dashboard appear so the displayed number is never a lie.
  3. DailyLog formatter pins POSIX locale + current timezone so the
     yyyy-MM-dd strings can't drift across locales.

Wired recordActivity into every previously-silent learning action: chat
send, story-quiz completion, textbook exercise submit, grammar exercise
completion, course-deck study finish, week test / checkpoint save,
listening + pronunciation check, cloze quiz completion, lyrics word
lookup, video stream / play / download success, sentence-builder check,
and course-vocab SRS rate (which was bypassing ReviewStore entirely).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:24:27 -05:00

253 lines
11 KiB
Swift

import SwiftUI
struct FeatureReferenceView: View {
var body: some View {
List {
Section("Verb Conjugation Practice") {
featureRow(
icon: "rectangle.stack", color: .blue,
title: "Flashcard / Typing / MC / Handwriting / Sentence Builder",
details: [
"Pulls from verb conjugation database (1,750 verbs)",
"Filtered by your Level setting",
"Filtered by your Enabled Tenses",
"Respects Include Vosotros setting",
"Due cards (SRS) shown first, then random",
]
)
featureRow(
icon: "tablecells", color: .blue,
title: "Full Table",
details: [
"Shows all 6 person forms for one verb + tense",
"Drawn from any regular verb — Level filter is ignored here on purpose, since regular conjugation patterns transfer across vocabulary",
"Random tense from your Enabled Tenses",
]
)
}
Section("Quick Actions") {
featureRow(
icon: "star.fill", color: .orange,
title: "Common Tenses",
details: [
"Restricts to 6 essential tenses: Present, Preterite, Imperfect, Future, Present Subjunctive, Imperative",
"Still filtered by your Level",
]
)
featureRow(
icon: "exclamationmark.triangle", color: .red,
title: "Weak Verbs",
details: [
"Shows verbs you've struggled with (ease factor < 2.0)",
"Only includes verbs you've reviewed at least once",
"Weakest verbs shown first",
]
)
featureRow(
icon: "wand.and.stars", color: .purple,
title: "Irregularity Drills",
details: [
"Spelling Changes: c->qu, g->gu, z->c patterns",
"Stem Changes: e->ie, o->ue, e->i patterns",
"Unique Irregulars: ser, ir, haber, etc.",
"Filtered by your Level and Enabled Tenses",
]
)
featureRow(
icon: "rectangle.stack.fill", color: .teal,
title: "Vocab Review",
details: [
"Reviews vocabulary cards that are due (SRS scheduled)",
"Cards become due after you study them in Course quizzes",
"Rate Again/Hard/Good/Easy to schedule next review",
"Uses all course vocabulary, not filtered by level",
]
)
}
Section("Practice Activities") {
featureRow(
icon: "bubble.left.and.bubble.right.fill", color: .green,
title: "Conversation",
details: [
"AI chat partner using Apple Intelligence (on-device)",
"10 scenario types (restaurant, directions, etc.)",
"AI adapts vocabulary to your Level setting",
"Corrections provided inline when you make mistakes",
"Conversations saved to iCloud for revisiting",
"Requires Apple Intelligence-capable device",
]
)
featureRow(
icon: "ear.fill", color: .blue,
title: "Listening",
details: [
"Listen & Type: hear a sentence, type what you heard",
"Pronunciation: read a sentence aloud, get scored on accuracy",
"Sentences pulled from course vocabulary examples",
"Uses all course vocab (not filtered by level)",
"Pronunciation requires microphone permission",
]
)
featureRow(
icon: "text.badge.minus", color: .indigo,
title: "Cloze Practice",
details: [
"Fill in the missing word in a Spanish sentence",
"Sentences from course vocabulary examples",
"4 multiple-choice options (1 correct + 3 distractors)",
"Distractors are other vocabulary words from same pool",
"Uses all course vocab (not filtered by level)",
]
)
featureRow(
icon: "music.note.list", color: .pink,
title: "Lyrics",
details: [
"Search and save Spanish song lyrics",
"Side-by-side Spanish and English translations",
"User-curated library, not filtered by level",
"Saved to iCloud for sync across devices",
]
)
featureRow(
icon: "book.fill", color: .teal,
title: "Stories",
details: [
"AI-generated one-paragraph Spanish stories",
"Matched to your Level and Enabled Tenses",
"Every word is tappable for definition",
"Known words use offline dictionary (175K+ verb forms)",
"Unknown words looked up via on-device AI",
"English translation hidden by default (toggle to reveal)",
"3-question comprehension quiz at the end",
"Saved to iCloud for revisiting",
"Requires Apple Intelligence-capable device",
]
)
}
Section("Guide") {
featureRow(
icon: "book", color: .brown,
title: "Tense Guides",
details: [
"Detailed explanation of each of the 20 verb tenses",
"Conjugation ending tables for -ar, -er, -ir verbs",
"Usage patterns with example sentences",
"Essential tenses marked with orange badge",
]
)
featureRow(
icon: "doc.text", color: .brown,
title: "Grammar Notes",
details: [
"23 grammar topics (ser vs estar, por vs para, etc.)",
"Interactive exercises available for 5 topics",
"Tap 'Practice This' on notes that have exercises",
"Content grouped by category with card-based layout",
]
)
}
Section("Course") {
featureRow(
icon: "list.clipboard", color: .orange,
title: "Course Quizzes",
details: [
"Vocabulary from specific course weeks",
"Multiple quiz types: MC, typing, handwriting, cloze",
"Focus Area mode for missed words",
"Not filtered by Level (uses course structure)",
]
)
featureRow(
icon: "checkmark.seal", color: .orange,
title: "Checkpoint Exams",
details: [
"Cumulative review across multiple weeks",
"Cards sampled evenly across all covered weeks",
"Choose 25, 50, or 100 questions",
]
)
}
Section("Dashboard") {
featureRow(
icon: "clock.fill", color: .mint,
title: "Study Time",
details: [
"Tracks time the app is in the foreground",
"Starts when app becomes active, stops on background",
"Shows today's time and all-time total",
"7-day bar chart of daily study time",
]
)
}
Section("Settings That Affect Practice") {
settingRow(name: "Level", affects: "Verb practice, Quick Actions, Stories, Conversation (Full Table ignores level)")
settingRow(name: "Enabled Tenses", affects: "Verb practice, Full Table, Irregularity Drills, Stories")
settingRow(name: "Include Vosotros", affects: "Verb practice, Full Table, Quick Actions")
settingRow(name: "Daily Goal", affects: "Dashboard progress tracking only")
}
}
.navigationTitle("How Features Work")
.navigationBarTitleDisplayMode(.inline)
}
// MARK: - Components
@ViewBuilder
private func featureRow(icon: String, color: Color, title: String, details: [String]) -> some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 10) {
Image(systemName: icon)
.font(.body)
.foregroundStyle(color)
.frame(width: 24)
Text(title)
.font(.subheadline.weight(.semibold))
}
VStack(alignment: .leading, spacing: 4) {
ForEach(details, id: \.self) { detail in
HStack(alignment: .top, spacing: 6) {
Text("")
.font(.caption)
.foregroundStyle(.secondary)
Text(detail)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.padding(.leading, 34)
}
.padding(.vertical, 4)
}
@ViewBuilder
private func settingRow(name: String, affects: String) -> some View {
VStack(alignment: .leading, spacing: 2) {
Text(name)
.font(.subheadline.weight(.semibold))
Text(affects)
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.vertical, 2)
}
}