From f2f42aa9e04845f287198d57ac73096bc81e5482 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 3 Dec 2025 01:35:25 +0300 Subject: [PATCH] f --- .../presentation/pages/test/test_page.dart | 535 +++++++----------- 1 file changed, 190 insertions(+), 345 deletions(-) diff --git a/mnemo_cards_web_v2/lib/presentation/pages/test/test_page.dart b/mnemo_cards_web_v2/lib/presentation/pages/test/test_page.dart index 53d3e5b..6b06999 100644 --- a/mnemo_cards_web_v2/lib/presentation/pages/test/test_page.dart +++ b/mnemo_cards_web_v2/lib/presentation/pages/test/test_page.dart @@ -26,185 +26,11 @@ class _TestPageState extends State { TestDto? _test; bool _isLoading = true; String? _errorMessage; - - @override - void initState() { - super.initState(); - _loadTest(); - } - - Future _loadTest() async { - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - final appScope = ScopeProvider.of( - context, - listen: false, - ); - final userScope = appScope?.userScopeHolder.scope; - if (userScope == null) { - throw Exception('Scope not available'); - } - - log('Loading test: ${widget.testId}', name: 'TestPage'); - final test = await userScope.testsModule.testManager.loadTest(widget.testId); - - if (mounted) { - setState(() { - _test = test; - _isLoading = false; - }); - } - } catch (e, s) { - log( - 'Error loading test', - error: e, - stackTrace: s, - name: 'TestPage', - ); - if (mounted) { - setState(() { - _isLoading = false; - _errorMessage = 'Failed to load test: ${e.toString()}'; - }); - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_test?.name ?? 'Test'), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => context.pop(), - ), - ), - body: _buildBody(), - ); - } - - Widget _buildBody() { - if (_isLoading) { - return const LoadingView(message: 'Loading test...'); - } - - if (_errorMessage != null) { - return ErrorView( - title: 'Failed to load test', - message: _errorMessage!, - onRetry: _loadTest, - ); - } - - if (_test == null) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error_outline, size: 64, color: Colors.grey), - const SizedBox(height: 16), - const Text('Test not found'), - const SizedBox(height: 16), - ElevatedButton( - onPressed: _loadTest, - child: const Text('Retry'), - ), - ], - ), - ); - } - - return _buildTestIntro(); - } - - Widget _buildTestIntro() { - final test = _test!; - return SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - test.name, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 24), - Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Test Information', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 16), - _buildInfoRow(Icons.quiz, 'Questions', '${test.questions.length}'), - const SizedBox(height: 8), - _buildInfoRow(Icons.timer, 'Estimated Time', '${test.questions.length * 2} minutes'), - const SizedBox(height: 8), - _buildInfoRow(Icons.help_outline, 'Type', 'Interactive Game'), - ], - ), - ), - ), - const SizedBox(height: 24), - Text( - 'Instructions', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - const Text( - '• Answer each question as it appears\n' - '• Get instant feedback on your answers\n' - '• Complete all questions to see your results\n' - '• Your progress and statistics will be tracked', - style: TextStyle(fontSize: 16), - ), - const SizedBox(height: 32), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () => _playGame(context), - icon: const Icon(Icons.games), - label: const Text('Start Test'), - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(48), - textStyle: const TextStyle(fontSize: 18), - ), - ), - ), - ], - ), - ); - } - - Widget _buildInfoRow(IconData icon, String label, String value) { - return Row( - children: [ - Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary), - const SizedBox(width: 12), - Text( - '$label: ', - style: const TextStyle(fontWeight: FontWeight.w500), - ), - Text(value), - ], - ); - } - - void _playGame(BuildContext context) { - // Navigate to the game page - context.go('/game/${widget.testId}'); - } -} - + bool _isTestStarted = false; + bool _isTestCompleted = false; + int _currentQuestionIndex = 0; + final Map _userAnswers = {}; + TestResult? _testResult; @override void initState() { @@ -366,15 +192,15 @@ class _TestPageState extends State { const SizedBox(height: 32), Column( children: [ - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () => _playGame(context), - icon: const Icon(Icons.games), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => _playGame(context), + icon: const Icon(Icons.games), label: const Text('Play Interactive Game'), - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(48), - textStyle: const TextStyle(fontSize: 18), + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(48), + textStyle: const TextStyle(fontSize: 18), backgroundColor: Theme.of(context).colorScheme.secondary, foregroundColor: Theme.of(context).colorScheme.onSecondary, ), @@ -414,6 +240,159 @@ class _TestPageState extends State { ); } + void _playGame(BuildContext context) { + // Navigate to the game page + context.go('/game/${widget.testId}'); + } + + void _startTest() { + setState(() { + _isTestStarted = true; + _currentQuestionIndex = 0; + _userAnswers.clear(); + }); + } + + void _selectAnswer(String answer) { + setState(() { + _userAnswers[_currentQuestionIndex] = answer; + }); + } + + void _nextQuestion() { + if (_currentQuestionIndex < _test!.questions.length - 1) { + setState(() { + _currentQuestionIndex++; + }); + } else { + _finishTest(); + } + } + + void _previousQuestion() { + if (_currentQuestionIndex > 0) { + setState(() { + _currentQuestionIndex--; + }); + } + } + + Future _finishTest() async { + if (_test == null) return; + + final startTime = DateTime.now(); + final endTime = DateTime.now(); + final timeTaken = endTime.difference(startTime).inSeconds; + + // Calculate results + int correctAnswers = 0; + int incorrectAnswers = 0; + + for (int i = 0; i < _test!.questions.length; i++) { + final question = _test!.questions[i]; + final userAnswer = _userAnswers[i]; + + if (question is SimpleTestQuestionBody) { + if (userAnswer == question.answer) { + correctAnswers++; + } else { + incorrectAnswers++; + } + } else { + // For unsupported question types, count as incorrect + incorrectAnswers++; + } + } + + final result = TestResult( + testId: widget.testId, + correctAnswers: correctAnswers, + incorrectAnswers: incorrectAnswers, + totalQuestions: _test!.questions.length, + timeTaken: timeTaken, + completedAt: endTime, + ); + + // Submit results to backend + try { + final appScope = ScopeProvider.of( + context, + listen: false, + ); + final userScope = appScope?.userScopeHolder.scope; + if (userScope != null) { + // Create a simplified statistics DTO for submission + final statistics = TestStatisticsDto( + testId: int.tryParse(widget.testId) ?? 0, + words: AllWordsStatisticsDto.empty(), + attempts: 1, + ); + + await userScope.testsModule.testManager.submitTestStatistics( + widget.testId, + statistics, + ); + } + } catch (e, s) { + log( + 'Error submitting test statistics', + error: e, + stackTrace: s, + name: 'TestPage', + ); + } + + setState(() { + _isTestCompleted = true; + _testResult = result; + }); + } + + void _retakeTest() { + setState(() { + _isTestStarted = false; + _isTestCompleted = false; + _currentQuestionIndex = 0; + _userAnswers.clear(); + _testResult = null; + }); + } + + void _goBack() { + context.pop(); + } + + void _handleBack() { + if (_isTestStarted && !_isTestCompleted) { + _showExitConfirmation(); + } else { + context.pop(); + } + } + + void _showExitConfirmation() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Exit Test'), + content: const Text('Are you sure you want to exit? Your progress will be lost.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + context.pop(); + }, + child: const Text('Exit'), + ), + ], + ), + ); + } + Widget _buildTestQuestion() { final test = _test!; final question = test.questions[_currentQuestionIndex]; @@ -475,7 +454,7 @@ class _TestPageState extends State { question.text!, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontSize: 16, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), ], @@ -513,7 +492,7 @@ class _TestPageState extends State { ), borderRadius: BorderRadius.circular(8), color: isSelected - ? Theme.of(context).colorScheme.primary.withOpacity(0.1) + ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.1) : null, ), child: Row( @@ -630,7 +609,7 @@ class _TestPageState extends State { height: 120, decoration: BoxDecoration( shape: BoxShape.circle, - color: _getScoreColor(score).withOpacity(0.1), + color: _getScoreColor(score).withValues(alpha: 0.1), border: Border.all( color: _getScoreColor(score), width: 4, @@ -686,10 +665,10 @@ class _TestPageState extends State { Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: _getScoreColor(score).withOpacity(0.1), + color: _getScoreColor(score).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( - color: _getScoreColor(score).withOpacity(0.3), + color: _getScoreColor(score).withValues(alpha: 0.3), ), ), child: Row( @@ -774,158 +753,24 @@ class _TestPageState extends State { if (score >= 60) return 'Good job! You have a decent understanding, but there\'s room for improvement.'; return 'Keep studying! Review the material and try again.'; } - - void _startTest() { - setState(() { - _isTestStarted = true; - _currentQuestionIndex = 0; - _userAnswers.clear(); - }); - } - - void _playGame(BuildContext context) { - // Navigate to the game page - context.go('/game/${widget.testId}'); - } - - void _selectAnswer(String answer) { - setState(() { - _userAnswers[_currentQuestionIndex] = answer; - }); - } - - void _nextQuestion() { - if (_currentQuestionIndex < _test!.questions.length - 1) { - setState(() { - _currentQuestionIndex++; - }); - } else { - _finishTest(); - } - } - - void _previousQuestion() { - if (_currentQuestionIndex > 0) { - setState(() { - _currentQuestionIndex--; - }); - } - } - - Future _finishTest() async { - if (_test == null) return; - - final startTime = DateTime.now(); - final endTime = DateTime.now(); - final timeTaken = endTime.difference(startTime).inSeconds; - - // Calculate results - int correctAnswers = 0; - int incorrectAnswers = 0; - - for (int i = 0; i < _test!.questions.length; i++) { - final question = _test!.questions[i]; - final userAnswer = _userAnswers[i]; - - if (question is SimpleTestQuestionBody) { - if (userAnswer == question.answer) { - correctAnswers++; - } else { - incorrectAnswers++; - } - } else { - // For unsupported question types, count as incorrect - incorrectAnswers++; - } - } - - final result = TestResult( - testId: widget.testId, - correctAnswers: correctAnswers, - incorrectAnswers: incorrectAnswers, - totalQuestions: _test!.questions.length, - timeTaken: timeTaken, - completedAt: endTime, - ); - - // Submit results to backend - try { - final appScope = ScopeProvider.of( - context, - listen: false, - ); - final userScope = appScope?.userScopeHolder.scope; - if (userScope != null) { - // Create a simplified statistics DTO for submission - final statistics = TestStatisticsDto( - testId: int.tryParse(widget.testId) ?? 0, - words: AllWordsStatisticsDto.empty(), - attempts: 1, - ); - - await userScope.testsModule.testManager.submitTestStatistics( - widget.testId, - statistics, - ); - } - } catch (e, s) { - log( - 'Error submitting test statistics', - error: e, - stackTrace: s, - name: 'TestPage', - ); - } - - setState(() { - _isTestCompleted = true; - _testResult = result; - }); - } - - void _retakeTest() { - setState(() { - _isTestStarted = false; - _isTestCompleted = false; - _currentQuestionIndex = 0; - _userAnswers.clear(); - _testResult = null; - }); - } - - void _goBack() { - context.pop(); - } - - void _handleBack() { - if (_isTestStarted && !_isTestCompleted) { - _showExitConfirmation(); - } else { - context.pop(); - } - } - - void _showExitConfirmation() { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Exit Test'), - content: const Text('Are you sure you want to exit? Your progress will be lost.'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - context.pop(); - }, - child: const Text('Exit'), - ), - ], - ), - ); - } +} + +/// Result of a completed test +class TestResult { + const TestResult({ + required this.testId, + required this.correctAnswers, + required this.incorrectAnswers, + required this.totalQuestions, + required this.timeTaken, + required this.completedAt, + }); + + final String testId; + final int correctAnswers; + final int incorrectAnswers; + final int totalQuestions; + final int timeTaken; + final DateTime completedAt; }