mirror of
https://github.com/jakobkordez/s5_practice.git
synced 2025-05-30 15:40:29 +00:00
Add incorrect answers on result page
Add visual improvement
This commit is contained in:
parent
0ae87202d4
commit
e0bbec0568
@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../models/category.dart';
|
||||
|
@ -36,8 +36,8 @@ class GeneratorTest extends GeneratorState {
|
||||
final Duration timerDuration;
|
||||
|
||||
const GeneratorTest({
|
||||
int questionCount = 60,
|
||||
this.timerDuration = const Duration(minutes: 90),
|
||||
questionCount = kDebugMode ? 6 : 60,
|
||||
this.timerDuration = const Duration(minutes: kDebugMode ? 9 : 90),
|
||||
}) : super(questionCount);
|
||||
|
||||
GeneratorTest copyWith({
|
||||
|
@ -14,6 +14,11 @@ class PracticeTab extends StatelessWidget {
|
||||
Widget build(BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Vaja',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
'Izberi pogročje in vpiši število vprašanj, ki jih želiš generirati. '
|
||||
'Če želiš generirati vprašanja iz vseh področij, pusti polje za področje prazno.'),
|
||||
@ -22,6 +27,7 @@ class PracticeTab extends StatelessWidget {
|
||||
builder: (context, constraints) {
|
||||
final mxW = constraints.maxWidth - 80;
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 20,
|
||||
crossAxisAlignment: WrapCrossAlignment.end,
|
||||
children: [
|
||||
@ -132,6 +138,11 @@ class TestTab extends StatelessWidget {
|
||||
Widget build(BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Preizkus uspeha',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
'Kandidati za radioamaterja razreda A opravljajo izpit, ki je '
|
||||
'sestavljen iz 60 različnih vprašanj. Vsako vprašanje ima 3 možne odgovore, od katerih je '
|
||||
@ -170,7 +181,7 @@ class TestTab extends StatelessWidget {
|
||||
revealInstantly: false,
|
||||
));
|
||||
},
|
||||
child: const Text('Začni'),
|
||||
child: const Text('Naprej'),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -22,6 +22,14 @@ class QuizState extends Equatable {
|
||||
return c;
|
||||
}
|
||||
|
||||
List<int>? get incorrectAnswers {
|
||||
final ret = <int>[];
|
||||
for (int i = 0; i < answers!.length; ++i) {
|
||||
if (answers![i] != questions[i].correct) ret.add(i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QuizState({
|
||||
required this.title,
|
||||
required this.questions,
|
||||
|
@ -6,10 +6,12 @@ import 'cubit/quiz_cubit.dart';
|
||||
|
||||
class QuestionCard extends StatelessWidget {
|
||||
final int qIndex;
|
||||
final bool forResultScreen;
|
||||
|
||||
const QuestionCard({
|
||||
Key? key,
|
||||
required this.qIndex,
|
||||
this.forResultScreen = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -22,81 +24,92 @@ class QuestionCard extends StatelessWidget {
|
||||
final theme = Theme.of(context);
|
||||
final question = state.questions[qIndex];
|
||||
|
||||
return SizedCard(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${question.id}'.padLeft(3, '0'),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
Text(
|
||||
'${qIndex + 1}. ${question.question}',
|
||||
style: theme.textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (question.image != null)
|
||||
Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 500,
|
||||
maxWidth: 500,
|
||||
),
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: question.answers != null
|
||||
? Image.asset('images/${question.image}')
|
||||
: HiddenImage(
|
||||
img: question.image!,
|
||||
isHidden: state.revealed![qIndex] == null,
|
||||
onClick: () =>
|
||||
context.read<QuizCubit>().answer(qIndex, 1),
|
||||
),
|
||||
final child = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${question.id}'.padLeft(3, '0'),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
Text(
|
||||
'${qIndex + 1}. ${question.question}',
|
||||
style: theme.textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (question.image != null)
|
||||
Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 500,
|
||||
maxWidth: 500,
|
||||
),
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: question.answers != null
|
||||
? Image.asset('images/${question.image}')
|
||||
: HiddenImage(
|
||||
img: question.image!,
|
||||
isHidden: state.revealed![qIndex] == null,
|
||||
onClick: () =>
|
||||
context.read<QuizCubit>().answer(qIndex, 1),
|
||||
),
|
||||
),
|
||||
if (question.answers != null)
|
||||
Material(
|
||||
color: Colors.white,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: question.answers!.length,
|
||||
itemBuilder: (_, aIndex) {
|
||||
if (state.revealInstantly) {
|
||||
final revealed = state.revealed![qIndex] ?? {};
|
||||
|
||||
return AnswerTile(
|
||||
text: question.answers![aIndex],
|
||||
isCorrect: aIndex == question.correct,
|
||||
isRevealed: revealed.contains(aIndex),
|
||||
isEnabled: !revealed.contains(question.correct),
|
||||
onClick: () => context
|
||||
.read<QuizCubit>()
|
||||
.answer(qIndex, aIndex),
|
||||
);
|
||||
}
|
||||
),
|
||||
if (question.answers != null)
|
||||
Material(
|
||||
color: Colors.white,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: question.answers!.length,
|
||||
itemBuilder: (_, aIndex) {
|
||||
if (state.revealInstantly) {
|
||||
final revealed = state.revealed![qIndex] ?? {};
|
||||
|
||||
return AnswerTile(
|
||||
text: question.answers![aIndex],
|
||||
isCorrect: aIndex == question.correct,
|
||||
isRevealed: false,
|
||||
isEnabled: true,
|
||||
isSelected: state.answers![qIndex] == aIndex,
|
||||
isRevealed: revealed.contains(aIndex),
|
||||
isEnabled: !revealed.contains(question.correct),
|
||||
onClick: () =>
|
||||
context.read<QuizCubit>().answer(qIndex, aIndex),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(height: 0),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
if (forResultScreen) {
|
||||
return AnswerTile(
|
||||
text: question.answers![aIndex],
|
||||
isCorrect: aIndex == question.correct,
|
||||
isRevealed: true,
|
||||
isEnabled: false,
|
||||
isSelected: state.answers![qIndex] == aIndex,
|
||||
);
|
||||
}
|
||||
|
||||
return AnswerTile(
|
||||
text: question.answers![aIndex],
|
||||
isCorrect: aIndex == question.correct,
|
||||
isRevealed: false,
|
||||
isEnabled: true,
|
||||
isSelected: state.answers![qIndex] == aIndex,
|
||||
onClick: () =>
|
||||
context.read<QuizCubit>().answer(qIndex, aIndex),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(height: 0),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
if (forResultScreen) return child;
|
||||
|
||||
return SizedCard(child: child);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -190,22 +203,29 @@ class AnswerTile extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListTile(
|
||||
leading: _icon(),
|
||||
onTap: !isRevealed && isEnabled ? onClick : null,
|
||||
title: Text(text),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
iconColor: isRevealed
|
||||
? (isCorrect ? Colors.green.shade900 : Colors.red.shade900)
|
||||
: null,
|
||||
tileColor: isRevealed
|
||||
? (isCorrect ? Colors.green.shade50 : Colors.red.shade50)
|
||||
: null,
|
||||
textColor: isRevealed
|
||||
? (isCorrect ? Colors.green.shade900 : Colors.red.shade900)
|
||||
: null,
|
||||
selected: isSelected == true,
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
final tileColor = isRevealed
|
||||
? (isCorrect ? Colors.green.shade50 : Colors.red.shade50)
|
||||
: null;
|
||||
final textColor = isRevealed
|
||||
? (isCorrect ? Colors.green.shade900 : Colors.red.shade900)
|
||||
: null;
|
||||
|
||||
return ListTile(
|
||||
leading: _icon(),
|
||||
onTap: !isRevealed && isEnabled ? onClick : null,
|
||||
title: Text(text),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
iconColor: isRevealed
|
||||
? (isCorrect ? Colors.green.shade900 : Colors.red.shade900)
|
||||
: null,
|
||||
tileColor: tileColor,
|
||||
textColor: textColor,
|
||||
selectedTileColor: tileColor,
|
||||
selectedColor: textColor,
|
||||
selected: isSelected == true,
|
||||
);
|
||||
}
|
||||
|
||||
Icon? _icon() {
|
||||
if (isSelected == true) return const Icon(Icons.radio_button_checked);
|
||||
|
@ -150,17 +150,82 @@ class _TimerCountdown extends StatelessWidget {
|
||||
|
||||
class _StartPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SizedCard(
|
||||
child: Center(
|
||||
child: ElevatedButton(
|
||||
child: const Text('Začni preizkus'),
|
||||
onPressed: () => context.read<QuizCubit>().start(),
|
||||
Widget build(BuildContext context) {
|
||||
final tTheme = Theme.of(context).textTheme;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SizedCard(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Preizkus uspeha',
|
||||
style: tTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final halfW =
|
||||
BoxConstraints(minWidth: constraints.maxWidth / 2);
|
||||
|
||||
return BlocBuilder<QuizCubit, QuizState>(
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: halfW,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${state.count}',
|
||||
style: tTheme.headlineMedium,
|
||||
),
|
||||
Text(
|
||||
'vprašanj',
|
||||
style: tTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: halfW,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${state.duration!.inMinutes}',
|
||||
style: tTheme.headlineMedium,
|
||||
),
|
||||
Text(
|
||||
'minut',
|
||||
style: tTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text('Ob pritisku na spodnji gumb, se preizkus začne. '
|
||||
'Ob izteku časa, se samodejno zaključi.'),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
child: const Text('Začni preizkus'),
|
||||
onPressed: () => context.read<QuizCubit>().start(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ResultPage extends StatelessWidget {
|
||||
@ -173,9 +238,9 @@ class _ResultPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'Rezultat',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
const SizedBox(height: 10),
|
||||
BlocBuilder<QuizCubit, QuizState>(
|
||||
builder: (context, state) {
|
||||
final correct = state.score!;
|
||||
@ -199,6 +264,44 @@ class _ResultPage extends StatelessWidget {
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
BlocBuilder<QuizCubit, QuizState>(
|
||||
builder: (context, state) {
|
||||
final wrongs = state.incorrectAnswers!;
|
||||
|
||||
if (wrongs.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: wrongs.length + 2,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 20),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return Divider(
|
||||
color: Colors.grey.shade200,
|
||||
thickness: 2,
|
||||
);
|
||||
}
|
||||
|
||||
if (index == 1) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'Napačni odgovori',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return QuestionCard(
|
||||
qIndex: wrongs[index - 2],
|
||||
forResultScreen: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user