diff --git a/lib/data/default_deck.dart b/lib/data/default_deck.dart index 557a045..2a47cb2 100644 --- a/lib/data/default_deck.dart +++ b/lib/data/default_deck.dart @@ -17,18 +17,21 @@ class DefaultDeck { prompt: 'What is the capital city of Australia?', answers: ['Sydney', 'Melbourne', 'Canberra', 'Perth'], correctAnswerIndices: [2], + explanation: 'Canberra was chosen as the capital in 1908 as a compromise between Sydney and Melbourne.', ), Question( id: 'gk_2', prompt: 'Which planet is known as the Red Planet?', answers: ['Venus', 'Mars', 'Jupiter', 'Saturn'], correctAnswerIndices: [1], + explanation: 'Mars appears red due to iron oxide (rust) on its surface.', ), Question( id: 'gk_3', prompt: 'What is the largest ocean on Earth?', answers: ['Atlantic Ocean', 'Indian Ocean', 'Arctic Ocean', 'Pacific Ocean'], correctAnswerIndices: [3], + explanation: 'The Pacific Ocean covers about 63 million square miles and is larger than all of Earth\'s land area combined.', ), Question( id: 'gk_4', @@ -203,6 +206,7 @@ class DefaultDeck { prompt: 'What is the capital city of Australia?', answers: ['Sydney', 'Melbourne', 'Canberra', 'Perth'], correctAnswerIndices: [2], + explanation: 'Canberra was chosen as the capital in 1908 as a compromise between Sydney and Melbourne.', consecutiveCorrect: 3, isKnown: true, priorityPoints: 0, @@ -216,6 +220,7 @@ class DefaultDeck { prompt: 'Which planet is known as the Red Planet?', answers: ['Venus', 'Mars', 'Jupiter', 'Saturn'], correctAnswerIndices: [1], + explanation: 'Mars appears red due to iron oxide (rust) on its surface.', consecutiveCorrect: 3, isKnown: true, priorityPoints: 0, @@ -227,6 +232,7 @@ class DefaultDeck { Question( id: 'gk_3', prompt: 'What is the largest ocean on Earth?', + explanation: 'The Pacific Ocean covers about 63 million square miles and is larger than all of Earth\'s land area combined.', answers: ['Atlantic Ocean', 'Indian Ocean', 'Arctic Ocean', 'Pacific Ocean'], correctAnswerIndices: [3], consecutiveCorrect: 3, diff --git a/lib/screens/attempt_result_screen.dart b/lib/screens/attempt_result_screen.dart index a395e4f..dc98228 100644 --- a/lib/screens/attempt_result_screen.dart +++ b/lib/screens/attempt_result_screen.dart @@ -209,6 +209,22 @@ class _AttemptResultScreenState extends State { ), ); }), + if (q.explanation != null && q.explanation!.trim().isNotEmpty) ...[ + const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 8), + Text( + 'Explanation', + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + q.explanation!, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], ], ), ), diff --git a/lib/screens/deck_edit_screen.dart b/lib/screens/deck_edit_screen.dart index 51cafa9..751a81e 100644 --- a/lib/screens/deck_edit_screen.dart +++ b/lib/screens/deck_edit_screen.dart @@ -161,6 +161,7 @@ class _DeckEditScreenState extends State { final list = questions.map((q) => { 'id': q.id, 'prompt': q.prompt, + if (q.explanation != null && q.explanation!.isNotEmpty) 'explanation': q.explanation, 'answers': q.answers, 'correctAnswerIndices': q.correctAnswerIndices, }).toList(); @@ -378,6 +379,16 @@ class _QuestionEditorCardState extends State { ), maxLines: 2, ), + const SizedBox(height: 12), + TextField( + controller: widget.editor.explanationController, + decoration: const InputDecoration( + labelText: 'Explanation (optional)', + helperText: 'Shown when viewing question details after an attempt', + border: OutlineInputBorder(), + ), + maxLines: 3, + ), const SizedBox(height: 16), // Answers @@ -474,12 +485,14 @@ class _QuestionEditorCardState extends State { class QuestionEditor { final TextEditingController promptController; + final TextEditingController explanationController; final List answerControllers; Set correctAnswerIndices; final String? originalId; QuestionEditor({ required this.promptController, + required this.explanationController, required this.answerControllers, Set? correctAnswerIndices, this.originalId, @@ -488,6 +501,7 @@ class QuestionEditor { factory QuestionEditor.fromQuestion(Question question) { return QuestionEditor( promptController: TextEditingController(text: question.prompt), + explanationController: TextEditingController(text: question.explanation ?? ''), answerControllers: question.answers .map((answer) => TextEditingController(text: answer)) .toList(), @@ -499,6 +513,7 @@ class QuestionEditor { factory QuestionEditor.empty() { return QuestionEditor( promptController: TextEditingController(), + explanationController: TextEditingController(), answerControllers: [ TextEditingController(), TextEditingController(), @@ -521,9 +536,11 @@ class QuestionEditor { } Question toQuestion() { + final explanationText = explanationController.text.trim(); return Question( id: originalId ?? DateTime.now().millisecondsSinceEpoch.toString(), prompt: promptController.text.trim(), + explanation: explanationText.isEmpty ? null : explanationText, answers: answerControllers.map((c) => c.text.trim()).toList(), correctAnswerIndices: correctAnswerIndices.toList()..sort(), ); @@ -531,6 +548,7 @@ class QuestionEditor { void dispose() { promptController.dispose(); + explanationController.dispose(); for (final controller in answerControllers) { controller.dispose(); } diff --git a/lib/screens/deck_import_screen.dart b/lib/screens/deck_import_screen.dart index 98a631a..5438306 100644 --- a/lib/screens/deck_import_screen.dart +++ b/lib/screens/deck_import_screen.dart @@ -64,6 +64,7 @@ class _DeckImportScreenState extends State { return Question( id: questionMap['id'] as String? ?? '', prompt: questionMap['prompt'] as String? ?? '', + explanation: questionMap['explanation'] as String?, answers: (questionMap['answers'] as List?) ?.map((e) => e.toString()) .toList() ?? @@ -643,6 +644,7 @@ class _DeckImportScreenState extends State { const Text('• answers: array of strings (required)'), const Text('• correctAnswerIndex: number (deprecated, use correctAnswerIndices)'), const Text('• correctAnswerIndices: array of numbers (for multiple correct answers)'), + const Text('• explanation: string (optional, shown when viewing question details)'), const Text('• isKnown: boolean (optional)'), const Text('• consecutiveCorrect: number (optional)'), const Text('• priorityPoints: number (optional)'), diff --git a/lib/screens/flagged_questions_screen.dart b/lib/screens/flagged_questions_screen.dart index 91f5940..8360ace 100644 --- a/lib/screens/flagged_questions_screen.dart +++ b/lib/screens/flagged_questions_screen.dart @@ -156,8 +156,10 @@ class _FlaggedQuestionsScreenState extends State { if (id == null) continue; final existing = questionMap[id]; if (existing == null) continue; + final explanationText = editor.explanationController.text.trim(); final updated = existing.copyWith( prompt: editor.promptController.text.trim(), + explanation: explanationText.isEmpty ? null : explanationText, answers: editor.answerControllers.map((c) => c.text.trim()).toList(), correctAnswerIndices: editor.correctAnswerIndices.toList()..sort(), ); @@ -203,6 +205,7 @@ class _FlaggedQuestionsScreenState extends State { final list = questions.map((q) => { 'id': q.id, 'prompt': q.prompt, + if (q.explanation != null && q.explanation!.isNotEmpty) 'explanation': q.explanation, 'answers': q.answers, 'correctAnswerIndices': q.correctAnswerIndices, }).toList(); diff --git a/lib/services/deck_storage.dart b/lib/services/deck_storage.dart index c84c9d0..f3b9563 100644 --- a/lib/services/deck_storage.dart +++ b/lib/services/deck_storage.dart @@ -66,6 +66,7 @@ class DeckStorage { 'questions': deck.questions.map((q) => { 'id': q.id, 'prompt': q.prompt, + if (q.explanation != null && q.explanation!.isNotEmpty) 'explanation': q.explanation, 'answers': q.answers, 'correctAnswerIndices': q.correctAnswerIndices, 'consecutiveCorrect': q.consecutiveCorrect, @@ -133,6 +134,7 @@ class DeckStorage { return Question( id: questionMap['id'] as String? ?? '', prompt: questionMap['prompt'] as String? ?? '', + explanation: questionMap['explanation'] as String?, answers: (questionMap['answers'] as List?) ?.map((e) => e.toString()) .toList() ?? diff --git a/packages/practice_engine/lib/models/question.dart b/packages/practice_engine/lib/models/question.dart index 1834f4b..a785b1d 100644 --- a/packages/practice_engine/lib/models/question.dart +++ b/packages/practice_engine/lib/models/question.dart @@ -6,6 +6,9 @@ class Question { /// The question prompt. final String prompt; + /// Optional explanation shown when viewing question details (e.g. after an attempt). + final String? explanation; + /// List of possible answers. final List answers; @@ -42,6 +45,7 @@ class Question { Question({ required this.id, required this.prompt, + this.explanation, required this.answers, List? correctAnswerIndices, @Deprecated('Use correctAnswerIndices instead') int? correctAnswerIndex, @@ -60,6 +64,7 @@ class Question { Question copyWith({ String? id, String? prompt, + String? explanation, List? answers, List? correctAnswerIndices, @Deprecated('Use correctAnswerIndices instead') int? correctAnswerIndex, @@ -74,6 +79,7 @@ class Question { return Question( id: id ?? this.id, prompt: prompt ?? this.prompt, + explanation: explanation ?? this.explanation, answers: answers ?? this.answers, correctAnswerIndices: correctAnswerIndices ?? this.correctAnswerIndices, correctAnswerIndex: correctAnswerIndex ?? this.correctAnswerIndex, @@ -119,6 +125,7 @@ class Question { runtimeType == other.runtimeType && id == other.id && prompt == other.prompt && + explanation == other.explanation && answers.toString() == other.answers.toString() && correctAnswerIndices.toString() == other.correctAnswerIndices.toString() && consecutiveCorrect == other.consecutiveCorrect && @@ -133,6 +140,7 @@ class Question { int get hashCode => id.hashCode ^ prompt.hashCode ^ + (explanation?.hashCode ?? 0) ^ answers.hashCode ^ correctAnswerIndices.hashCode ^ consecutiveCorrect.hashCode ^