|
|
|
@ -3,6 +3,7 @@ import 'package:practice_engine/practice_engine.dart';
|
|
|
|
import '../routes.dart';
|
|
|
|
import '../routes.dart';
|
|
|
|
import '../widgets/status_chip.dart';
|
|
|
|
import '../widgets/status_chip.dart';
|
|
|
|
import '../services/deck_storage.dart';
|
|
|
|
import '../services/deck_storage.dart';
|
|
|
|
|
|
|
|
import '../utils/top_snackbar.dart';
|
|
|
|
|
|
|
|
|
|
|
|
class AttemptResultScreen extends StatefulWidget {
|
|
|
|
class AttemptResultScreen extends StatefulWidget {
|
|
|
|
const AttemptResultScreen({super.key});
|
|
|
|
const AttemptResultScreen({super.key});
|
|
|
|
@ -82,6 +83,42 @@ class _AttemptResultScreenState extends State<AttemptResultScreen> {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
|
|
'Marked as needs practice',
|
|
|
|
|
|
|
|
backgroundColor: Colors.blue,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
if (_deck == null || _result == null) {
|
|
|
|
if (_deck == null || _result == null) {
|
|
|
|
@ -217,6 +254,8 @@ class _AttemptResultScreenState extends State<AttemptResultScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
..._result!.incorrectQuestions.map((answerResult) {
|
|
|
|
..._result!.incorrectQuestions.map((answerResult) {
|
|
|
|
|
|
|
|
final currentQ = _currentQuestionInDeck(answerResult.question.id);
|
|
|
|
|
|
|
|
final isFlagged = currentQ?.isFlagged ?? false;
|
|
|
|
return Card(
|
|
|
|
return Card(
|
|
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
|
|
child: Padding(
|
|
|
|
child: Padding(
|
|
|
|
@ -233,23 +272,42 @@ class _AttemptResultScreenState extends State<AttemptResultScreen> {
|
|
|
|
children: [
|
|
|
|
children: [
|
|
|
|
StatusChip(statusChange: answerResult.statusChange),
|
|
|
|
StatusChip(statusChange: answerResult.statusChange),
|
|
|
|
const Spacer(),
|
|
|
|
const Spacer(),
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
'Your answer${answerResult.userAnswerIndices.length > 1 ? 's' : ''}: ${answerResult.userAnswerIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}',
|
|
|
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
|
|
color: Colors.red,
|
|
|
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
Text(
|
|
|
|
Text(
|
|
|
|
'Your answer${answerResult.userAnswerIndices.length > 1 ? 's' : ''}: ${answerResult.userAnswerIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}',
|
|
|
|
'Correct answer${answerResult.question.correctIndices.length > 1 ? 's' : ''}: ${answerResult.question.correctIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}',
|
|
|
|
style: TextStyle(
|
|
|
|
style: TextStyle(
|
|
|
|
color: Colors.red,
|
|
|
|
color: Colors.green,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
),
|
|
|
|
Row(
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
|
|
Text(
|
|
|
|
children: [
|
|
|
|
'Correct answer${answerResult.question.correctIndices.length > 1 ? 's' : ''}: ${answerResult.question.correctIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}',
|
|
|
|
TextButton.icon(
|
|
|
|
style: TextStyle(
|
|
|
|
onPressed: () => _markNeedsPractice(answerResult.question.id),
|
|
|
|
color: Colors.green,
|
|
|
|
icon: const Icon(Icons.replay, size: 20),
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
label: const Text('Needs practice'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
IconButton(
|
|
|
|
|
|
|
|
onPressed: () => _toggleFlag(answerResult.question.id),
|
|
|
|
|
|
|
|
icon: Icon(
|
|
|
|
|
|
|
|
isFlagged ? Icons.flag : Icons.outlined_flag,
|
|
|
|
|
|
|
|
color: isFlagged ? Colors.red : null,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
tooltip: isFlagged ? 'Unflag' : 'Flag for review',
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
@ -265,6 +323,8 @@ class _AttemptResultScreenState extends State<AttemptResultScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
..._result!.allResults.map((answerResult) {
|
|
|
|
..._result!.allResults.map((answerResult) {
|
|
|
|
|
|
|
|
final currentQ = _currentQuestionInDeck(answerResult.question.id);
|
|
|
|
|
|
|
|
final isFlagged = currentQ?.isFlagged ?? false;
|
|
|
|
return Card(
|
|
|
|
return Card(
|
|
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
|
|
child: ListTile(
|
|
|
|
child: ListTile(
|
|
|
|
@ -274,7 +334,26 @@ class _AttemptResultScreenState extends State<AttemptResultScreen> {
|
|
|
|
? 'Correct'
|
|
|
|
? 'Correct'
|
|
|
|
: 'Incorrect - Selected: ${answerResult.userAnswerIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}',
|
|
|
|
: 'Incorrect - Selected: ${answerResult.userAnswerIndices.map((idx) => answerResult.question.answers[idx]).join(', ')}',
|
|
|
|
),
|
|
|
|
),
|
|
|
|
trailing: StatusChip(statusChange: answerResult.statusChange),
|
|
|
|
trailing: Row(
|
|
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
StatusChip(statusChange: answerResult.statusChange),
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
|