enum LearningGoal { dailyLife, travel, workStarter } enum PlacementLevel { beginner, someBasics, simpleConversation } enum AppTab { home, learn, dialogue, review, progress } enum LessonStep { preview, listening, speaking, reading, writing, dialogue, independent, complete, } enum MasteryStatus { newItem, recognize, recall, use, master, needsReview } enum EvidenceKind { exposure, assisted, independentSuccess, languageError, pending, } class AttemptEvidence { const AttemptEvidence({ required this.id, required this.itemId, required this.taskId, required this.skill, required this.inputMode, required this.outcome, required this.createdAt, this.rawAnswer, this.recordingPath, this.assisted = false, this.variantIndex = 0, this.originalTranscript, this.transcriptConfirmed = false, this.transcriptEdited = false, }); final String id; final String itemId; final String taskId; final String skill; final String inputMode; final EvidenceKind outcome; final DateTime createdAt; final String? rawAnswer; /// Local-only file reference. It is absent unless the learner opted to keep /// recordings; no path or audio is sent to an AI provider. final String? recordingPath; final bool assisted; final int variantIndex; /// The device STT result before the learner edits it. This local-only field /// distinguishes confirmed spoken input from a subsequent typed rewrite. final String? originalTranscript; final bool transcriptConfirmed; final bool transcriptEdited; } enum AiProviderType { mock, gemini, openAi, compatible } enum AssessmentSkill { listening, speaking, reading, writing } enum ContentStatus { draft, validated, approved, rejected } enum ContentSource { builtInOriginal, aiGenerated, importedReference } /// A validated, bounded suggestion from an AI conversation provider. /// The client still owns task completion and mastery decisions. class DialogueAiResponse { const DialogueAiResponse({ required this.reply, required this.slots, required this.evidence, required this.suggestsComplete, this.feedback, }); final String reply; final Map slots; final List evidence; final bool suggestsComplete; final String? feedback; } /// A bounded suggestion only. It never changes completion, mastery, or a /// stage result; those decisions remain on-device and evidence-based. class WritingAiFeedback { const WritingAiFeedback({ required this.verdict, required this.feedback, required this.suggestion, required this.missing, }); /// `accepted`, `rewrite`, or `uncertain`. final String verdict; final String feedback; final String? suggestion; final List missing; } class AssessmentRecord { const AssessmentRecord({ required this.packId, required this.completedAt, required this.results, this.pendingSkills = const {}, }); final String packId; final DateTime completedAt; final Map results; final Set pendingSkills; bool get passed => AssessmentSkill.values.every((skill) => results[skill] == true); List get failedSkills => AssessmentSkill.values .where( (skill) => results[skill] != true && !pendingSkills.contains(skill), ) .toList(); } class AssessmentDraft { const AssessmentDraft({ required this.packId, required this.taskIndex, required this.results, }); final String packId; final int taskIndex; final Map results; } class DialogueDraft { const DialogueDraft({ required this.lessonId, required this.stage, required this.turns, required this.usedHelp, }); final String lessonId; final int stage; final List turns; final bool usedHelp; } class LessonSummary { const LessonSummary({ required this.number, required this.title, required this.outcome, this.isComplete = false, this.isCurrent = false, }); final int number; final String title; final String outcome; final bool isComplete; final bool isCurrent; } class VocabularyItem { const VocabularyItem({ required this.id, required this.word, required this.meaning, required this.example, required this.exampleMeaning, this.ipa, }); final String id; final String word; final String meaning; final String example; final String exampleMeaning; final String? ipa; } /// A non-core explanation returned by a configured text provider for a word /// or phrase that is not present in the reviewed course lexicon. These /// entries deliberately never participate in review scheduling or mastery. class TemporaryLexiconEntry { const TemporaryLexiconEntry({ required this.query, required this.definition, required this.provider, required this.model, required this.createdAt, }); final String query; final String definition; final String provider; final String model; final DateTime createdAt; } class ReviewItem { const ReviewItem({ required this.id, required this.target, required this.prompt, required this.hint, required this.dueAt, required this.skill, this.attempts = 0, this.successfulReviews = 0, this.variantIndex = 0, this.lastProgressedAt, this.isAiGenerated = false, }); final String id; final String target; final String prompt; final String hint; final DateTime dueAt; final String skill; final int attempts; final int successfulReviews; final int variantIndex; final DateTime? lastProgressedAt; final bool isAiGenerated; ReviewItem copyWith({ DateTime? dueAt, int? attempts, int? successfulReviews, int? variantIndex, String? prompt, String? hint, String? skill, DateTime? lastProgressedAt, bool? isAiGenerated, }) => ReviewItem( id: id, target: target, prompt: prompt ?? this.prompt, hint: hint ?? this.hint, dueAt: dueAt ?? this.dueAt, skill: skill ?? this.skill, attempts: attempts ?? this.attempts, successfulReviews: successfulReviews ?? this.successfulReviews, variantIndex: variantIndex ?? this.variantIndex, lastProgressedAt: lastProgressedAt ?? this.lastProgressedAt, isAiGenerated: isAiGenerated ?? this.isAiGenerated, ); } class MasteryItem { const MasteryItem({ required this.id, required this.label, required this.status, required this.evidence, this.needsReview = false, this.checkpoint = 0, this.firstTaughtAt, }); final String id; final String label; final MasteryStatus status; final List evidence; final bool needsReview; final int checkpoint; final DateTime? firstTaughtAt; MasteryItem copyWith({ MasteryStatus? status, List? evidence, bool? needsReview, int? checkpoint, DateTime? firstTaughtAt, }) => MasteryItem( id: id, label: label, status: status ?? this.status, evidence: evidence ?? this.evidence, needsReview: needsReview ?? this.needsReview, checkpoint: checkpoint ?? this.checkpoint, firstTaughtAt: firstTaughtAt ?? this.firstTaughtAt, ); } class DialogueTurn { const DialogueTurn({required this.text, required this.isLearner}); final String text; final bool isLearner; } /// Ephemeral presentation data for a completed controlled dialogue. The /// related recap review is separately persisted in AppState. class DialogueSummaryData { const DialogueSummaryData({ required this.completedTasks, required this.personalSentence, required this.usedHelp, }); final List completedTasks; final String personalSentence; final bool usedHelp; }