import 'package:flutter/material.dart'; import 'package:practice_engine/practice_engine.dart'; import '../routes.dart'; import '../widgets/status_chip.dart'; import '../services/deck_storage.dart'; import '../utils/top_snackbar.dart'; class AttemptResultScreen extends StatefulWidget { const AttemptResultScreen({super.key}); @override State createState() => _AttemptResultScreenState(); } class _AttemptResultScreenState extends State { Deck? _deck; AttemptResult? _result; Attempt? _completedAttempt; final DeckStorage _deckStorage = DeckStorage(); @override void initState() { super.initState(); } @override void didChangeDependencies() { super.didChangeDependencies(); if (_deck == null) { // Get data from route arguments final args = ModalRoute.of(context)?.settings.arguments as Map?; _deck = args?['deck'] as Deck? ?? _createSampleDeck(); _result = args?['result'] as AttemptResult? ?? _createSampleResult(); _completedAttempt = args?['attempt'] as Attempt?; } } Deck _createSampleDeck() { return Deck( id: 'sample', title: 'Sample', description: 'Sample', questions: [], config: const DeckConfig(), ); } AttemptResult _createSampleResult() { return AttemptResult( totalQuestions: 10, correctCount: 7, percentageCorrect: 70.0, timeSpent: 300000, incorrectQuestions: [], allResults: [], ); } String _formatTime(int milliseconds) { final seconds = milliseconds ~/ 1000; final minutes = seconds ~/ 60; final remainingSeconds = seconds % 60; return '${minutes}m ${remainingSeconds}s'; } void _repeatSameAttempt() { if (_deck == null) return; // Pass the completed attempt so the attempt screen uses the same questions in the same order if (_completedAttempt != null && _completedAttempt!.questions.isNotEmpty) { Navigator.pushReplacementNamed( context, Routes.attempt, arguments: {'deck': _deck, 'repeatAttempt': _completedAttempt}, ); } else { Navigator.pushReplacementNamed(context, Routes.attempt, arguments: _deck); } } void _newAttempt() { if (_deck == null) return; Navigator.pushReplacementNamed(context, Routes.attempt, arguments: _deck); } void _done() { if (_deck == null) return; // Save the updated deck to storage _deckStorage.saveDeckSync(_deck!); // Navigate back to this deck's overview (decks screen), not the home deck list Navigator.popUntil(context, (route) => route.settings.name == Routes.deckOverview); Navigator.pushReplacementNamed( context, Routes.deckOverview, arguments: _deck, ); } Question? _currentQuestionInDeck(String questionId) { if (_deck == null) return null; try { return _deck!.questions.firstWhere((q) => q.id == questionId); } catch (_) { return null; } } void _toggleFlag(String questionId) { if (_deck == null) return; setState(() { _deck = DeckService.toggleQuestionFlag(deck: _deck!, questionId: questionId); }); _deckStorage.saveDeckSync(_deck!); final q = _currentQuestionInDeck(questionId); showTopSnackBar( context, message: q?.isFlagged == true ? 'Question flagged for review' : 'Question unflagged', backgroundColor: q?.isFlagged == true ? Colors.orange : null, ); } void _markNeedsPractice(String questionId) { if (_deck == null) return; setState(() { _deck = DeckService.markQuestionAsNeedsPractice(deck: _deck!, questionId: questionId); }); _deckStorage.saveDeckSync(_deck!); showTopSnackBar( context, message: 'Marked as needs practice', backgroundColor: Colors.blue, ); } void _showQuestionDetail(AnswerResult answerResult) { final q = answerResult.question; final correctSet = q.correctIndices.toSet(); final userSet = answerResult.userAnswerIndices.toSet(); showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Question'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( q.prompt, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 16), ...q.answers.asMap().entries.map((e) { final i = e.key; final answer = e.value; final isCorrect = correctSet.contains(i); final isUser = userSet.contains(i); String? label; Color? color; if (isCorrect && isUser) { label = 'Correct (your answer)'; color = Colors.green; } else if (isCorrect) { label = 'Correct'; color = Colors.green; } else if (isUser) { label = 'Your answer'; color = Colors.red; } return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 24, child: isCorrect ? Icon(Icons.check_circle, color: Colors.green, size: 20) : isUser ? Icon(Icons.cancel, color: Colors.red, size: 20) : const Icon(Icons.radio_button_unchecked, size: 20, color: Colors.grey), ), const SizedBox(width: 8), Expanded( child: Text.rich( TextSpan( children: [ if (label != null) TextSpan( text: '$label: ', style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: Theme.of(context).textTheme.bodyLarge?.fontSize, ), ), TextSpan( text: answer, style: TextStyle( color: color, fontWeight: label != null ? FontWeight.bold : null, fontSize: Theme.of(context).textTheme.bodyLarge?.fontSize, ), ), ], ), ), ), ], ), ); }), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Close'), ), ], ), ); } @override Widget build(BuildContext context) { if (_deck == null || _result == null) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar( title: const Text('Attempt Results'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 100% Completion Celebration if (_deck != null && _deck!.knownCount == _deck!.numberOfQuestions && _deck!.numberOfQuestions > 0) ...[ Card( color: Theme.of(context).colorScheme.primaryContainer, child: Padding( padding: const EdgeInsets.all(24), child: Column( children: [ Icon( Icons.celebration, size: 64, color: Theme.of(context).colorScheme.onPrimaryContainer, ), const SizedBox(height: 16), Text( 'Deck Completed!', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 8), Text( 'Congratulations! You\'ve mastered all ${_deck!.numberOfQuestions} questions in this deck!', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onPrimaryContainer.withValues(alpha: 0.9), ), ), ], ), ), ), const SizedBox(height: 24), ], // Summary Card Card( child: Padding( padding: const EdgeInsets.all(24), child: Column( children: [ Text( 'Results', style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 24), CircularProgressIndicator( value: _result!.percentageCorrect / 100, strokeWidth: 8, ), const SizedBox(height: 16), Text( '${_result!.percentageCorrect.toStringAsFixed(1)}%', style: Theme.of(context).textTheme.displayMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( '${_result!.correctCount} of ${_result!.totalQuestions} correct', style: Theme.of(context).textTheme.titleMedium, ), // Show deck progress if available if (_deck != null && _deck!.numberOfQuestions > 0) ...[ const SizedBox(height: 16), Divider(), const SizedBox(height: 16), Text( 'Deck Progress', style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 8), Text( '${_deck!.knownCount} of ${_deck!.numberOfQuestions} questions known', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: _deck!.knownCount == _deck!.numberOfQuestions ? Colors.green : null, ), ), ], const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _StatItem( label: 'Time', value: _formatTime(_result!.timeSpent), icon: Icons.timer, ), _StatItem( label: 'Correct', value: '${_result!.correctCount}', icon: Icons.check_circle, ), _StatItem( label: 'Incorrect', value: '${_result!.totalQuestions - _result!.correctCount}', icon: Icons.cancel, ), ], ), ], ), ), ), const SizedBox(height: 24), // Incorrect Questions if (_result!.incorrectQuestions.isNotEmpty) ...[ Text( 'Incorrect Questions', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), ..._result!.incorrectQuestions.map((answerResult) { final currentQ = _currentQuestionInDeck(answerResult.question.id); final isFlagged = currentQ?.isFlagged ?? false; return Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( answerResult.question.prompt, style: Theme.of(context).textTheme.titleMedium, overflow: TextOverflow.ellipsis, maxLines: 3, ), const SizedBox(height: 8), Text( answerResult.userAnswerIndices.isEmpty ? 'No answer' : 'Your answer${answerResult.userAnswerIndices.length > 1 ? 's' : ''}: ${answerResult.userAnswerIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}', style: TextStyle( color: Colors.red, fontWeight: FontWeight.bold, fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, ), overflow: TextOverflow.ellipsis, maxLines: 2, ), const SizedBox(height: 4), Text( 'Correct answer${answerResult.question.correctIndices.length > 1 ? 's' : ''}: ${answerResult.question.correctIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}', style: TextStyle( color: Colors.green, fontWeight: FontWeight.bold, fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, ), overflow: TextOverflow.ellipsis, maxLines: 2, ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StatusChip(statusChange: answerResult.statusChange), Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( onPressed: () => _showQuestionDetail(answerResult), icon: const Icon(Icons.visibility, size: 22), tooltip: 'View question details', ), IconButton( onPressed: () => _markNeedsPractice(answerResult.question.id), icon: const Icon(Icons.replay, size: 22), tooltip: 'Mark as needs practice', ), IconButton( onPressed: () => _toggleFlag(answerResult.question.id), icon: Icon( isFlagged ? Icons.flag : Icons.outlined_flag, color: isFlagged ? Colors.red : null, size: 22, ), tooltip: isFlagged ? 'Unflag' : 'Flag for review', ), ], ), ], ), ], ), ), ); }), const SizedBox(height: 24), ], // All Results with Status Text( 'Question Status Changes', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), ..._result!.allResults.map((answerResult) { final currentQ = _currentQuestionInDeck(answerResult.question.id); final isFlagged = currentQ?.isFlagged ?? false; return Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( answerResult.question.prompt, style: Theme.of(context).textTheme.titleMedium, overflow: TextOverflow.ellipsis, maxLines: 3, ), const SizedBox(height: 4), Text( answerResult.isCorrect ? 'Correct' : answerResult.userAnswerIndices.isEmpty ? 'Incorrect - No answer' : 'Incorrect - Selected: ${answerResult.userAnswerIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}', style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.ellipsis, maxLines: 2, ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StatusChip(statusChange: answerResult.statusChange), Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( onPressed: () => _showQuestionDetail(answerResult), icon: const Icon(Icons.visibility, size: 22), tooltip: 'View question details', ), IconButton( onPressed: () => _markNeedsPractice(answerResult.question.id), icon: const Icon(Icons.replay, size: 22), tooltip: 'Mark as needs practice', ), IconButton( onPressed: () => _toggleFlag(answerResult.question.id), icon: Icon( isFlagged ? Icons.flag : Icons.outlined_flag, color: isFlagged ? Colors.red : null, size: 22, ), tooltip: isFlagged ? 'Unflag' : 'Flag for review', ), ], ), ], ), ], ), ), ); }), const SizedBox(height: 24), // Action Buttons FilledButton.icon( onPressed: _repeatSameAttempt, icon: const Icon(Icons.repeat), label: const Text('Repeat Same Attempt'), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), ), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: _newAttempt, icon: const Icon(Icons.refresh), label: const Text('New Attempt'), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: _done, icon: const Icon(Icons.check), label: const Text('Done'), ), ], ), ), ); } } class _StatItem extends StatelessWidget { final String label; final String value; final IconData icon; const _StatItem({ required this.label, required this.value, required this.icon, }); @override Widget build(BuildContext context) { return Column( children: [ Icon(icon, size: 32), const SizedBox(height: 8), Text( value, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), Text( label, style: Theme.of(context).textTheme.bodySmall, ), ], ); } }