You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
8.5 KiB
300 lines
8.5 KiB
import 'package:flutter/material.dart';
|
|
import 'package:practice_engine/practice_engine.dart';
|
|
import '../routes.dart';
|
|
|
|
class DeckOverviewScreen extends StatefulWidget {
|
|
const DeckOverviewScreen({super.key});
|
|
|
|
@override
|
|
State<DeckOverviewScreen> createState() => _DeckOverviewScreenState();
|
|
}
|
|
|
|
class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
|
|
Deck? _deck;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
// Get deck from route arguments or create sample
|
|
final args = ModalRoute.of(context)?.settings.arguments;
|
|
if (args is Deck) {
|
|
setState(() {
|
|
_deck = args;
|
|
});
|
|
} else if (_deck == null) {
|
|
// Only create sample if we don't have a deck yet
|
|
setState(() {
|
|
_deck = _createSampleDeck();
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
Deck _createSampleDeck() {
|
|
const config = DeckConfig();
|
|
final questions = List.generate(20, (i) {
|
|
return Question(
|
|
id: 'q$i',
|
|
prompt: 'Sample Question $i?',
|
|
answers: ['Answer A', 'Answer B', 'Answer C', 'Answer D'],
|
|
correctAnswerIndex: i % 4,
|
|
isKnown: i < 5, // First 5 are known
|
|
);
|
|
});
|
|
|
|
return Deck(
|
|
id: 'sample-deck',
|
|
title: 'Sample Practice Deck',
|
|
description: 'This is a sample deck for practicing. It contains various questions to help you learn.',
|
|
questions: questions,
|
|
config: config,
|
|
);
|
|
}
|
|
|
|
void _startAttempt() {
|
|
if (_deck == null || _deck!.questions.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Cannot start attempt: No questions in deck'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
Navigator.pushNamed(
|
|
context,
|
|
Routes.attempt,
|
|
arguments: _deck!,
|
|
);
|
|
}
|
|
|
|
void _openConfig() {
|
|
if (_deck == null) return;
|
|
|
|
Navigator.pushNamed(context, Routes.deckConfig, arguments: _deck)
|
|
.then((updatedDeck) {
|
|
if (updatedDeck != null && updatedDeck is Deck) {
|
|
setState(() {
|
|
_deck = updatedDeck;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _resetDeck() async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Reset Deck Progress'),
|
|
content: const Text(
|
|
'Are you sure you want to reset all progress? This will reset streaks, known status, and priorities for all questions.',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('Cancel'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
),
|
|
child: const Text('Reset'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true && _deck != null) {
|
|
setState(() {
|
|
_deck = DeckService.resetDeck(deck: _deck!, resetAttemptCounts: false);
|
|
});
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Deck progress has been reset'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_deck == null) {
|
|
return const Scaffold(
|
|
body: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(_deck!.title),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
tooltip: 'Reset Progress',
|
|
onPressed: _resetDeck,
|
|
),
|
|
],
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Deck Description
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Description',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
_deck!.description,
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Practice Progress
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
'Practice Progress',
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
const SizedBox(height: 24),
|
|
CircularProgressIndicator(
|
|
value: _deck!.practicePercentage / 100,
|
|
strokeWidth: 8,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'${_deck!.practicePercentage.toStringAsFixed(1)}%',
|
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'${_deck!.knownCount} of ${_deck!.numberOfQuestions} questions known',
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Statistics
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Statistics',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_StatItem(
|
|
label: 'Total Questions',
|
|
value: '${_deck!.numberOfQuestions}',
|
|
icon: Icons.quiz,
|
|
),
|
|
_StatItem(
|
|
label: 'Known',
|
|
value: '${_deck!.knownCount}',
|
|
icon: Icons.check_circle,
|
|
),
|
|
_StatItem(
|
|
label: 'Needs Practice',
|
|
value: '${_deck!.numberOfQuestions - _deck!.knownCount}',
|
|
icon: Icons.school,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Action Buttons
|
|
FilledButton.icon(
|
|
onPressed: _startAttempt,
|
|
icon: const Icon(Icons.play_arrow),
|
|
label: const Text('Start Attempt'),
|
|
style: FilledButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
OutlinedButton.icon(
|
|
onPressed: _openConfig,
|
|
icon: const Icon(Icons.settings),
|
|
label: const Text('Configure Deck'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|