This commit is contained in:
gitea
2025-12-12 14:06:56 +01:00
commit 5644bfde61
38 changed files with 2268 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
class AnswerOption extends StatelessWidget {
final String text;
final bool isSelected;
final VoidCallback onTap;
final bool? isCorrect;
const AnswerOption({
super.key,
required this.text,
required this.isSelected,
required this.onTap,
this.isCorrect,
});
@override
Widget build(BuildContext context) {
Color? backgroundColor;
Color? borderColor;
IconData? icon;
if (isCorrect != null) {
if (isCorrect == true) {
backgroundColor = Colors.green.withValues(alpha: 0.1);
borderColor = Colors.green;
icon = Icons.check_circle;
} else {
backgroundColor = Colors.red.withValues(alpha: 0.1);
borderColor = Colors.red;
icon = Icons.cancel;
}
} else if (isSelected) {
backgroundColor = Theme.of(context).colorScheme.primaryContainer;
borderColor = Theme.of(context).colorScheme.primary;
}
return Card(
color: backgroundColor,
elevation: isSelected ? 4 : 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: borderColor != null
? BorderSide(color: borderColor, width: 2)
: BorderSide.none,
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, color: borderColor),
const SizedBox(width: 12),
],
Expanded(
child: Text(
text,
style: Theme.of(context).textTheme.bodyLarge,
),
),
if (isSelected && icon == null)
Icon(
Icons.radio_button_checked,
color: Theme.of(context).colorScheme.primary,
)
else if (!isSelected)
Icon(
Icons.radio_button_unchecked,
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5),
),
],
),
),
),
);
}
}
+28
View File
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class PracticeProgressIndicator extends StatelessWidget {
final double percentage;
const PracticeProgressIndicator({
super.key,
required this.percentage,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
CircularProgressIndicator(
value: percentage / 100,
strokeWidth: 8,
),
const SizedBox(height: 8),
Text(
'${percentage.toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.titleLarge,
),
],
);
}
}
+48
View File
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:practice_engine/practice_engine.dart';
class QuestionCard extends StatelessWidget {
final Question question;
const QuestionCard({
super.key,
required this.question,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.quiz,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'Question',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
Text(
question.prompt,
style: Theme.of(context).textTheme.titleLarge,
),
],
),
),
);
}
}
+44
View File
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:practice_engine/practice_engine.dart';
class StatusChip extends StatelessWidget {
final QuestionStatusChange statusChange;
const StatusChip({
super.key,
required this.statusChange,
});
@override
Widget build(BuildContext context) {
String label;
IconData icon;
Color color;
switch (statusChange) {
case QuestionStatusChange.improved:
label = 'Improved';
icon = Icons.trending_up;
color = Colors.green;
break;
case QuestionStatusChange.regressed:
label = 'Regressed';
icon = Icons.trending_down;
color = Colors.red;
break;
case QuestionStatusChange.unchanged:
label = 'Unchanged';
icon = Icons.remove;
color = Colors.grey;
break;
}
return Chip(
label: Text(label),
avatar: Icon(icon, size: 18, color: color),
backgroundColor: color.withValues(alpha: 0.1),
side: BorderSide(color: color),
);
}
}