This commit is contained in:
gitea
2026-01-02 19:56:50 +01:00
parent 9f06568eab
commit 68af4bf379
8 changed files with 341 additions and 76 deletions
+2
View File
@@ -477,6 +477,8 @@ class _AttemptScreenState extends State<AttemptScreen> {
// Add attempt to history
final historyEntry = AttemptHistoryEntry.fromAttemptResult(
result: result.result,
totalQuestionsInDeck: result.updatedDeck.numberOfQuestions,
knownCount: result.updatedDeck.knownCount,
timestamp: endTime,
);
final updatedDeckWithHistory = result.updatedDeck.copyWith(
+33 -22
View File
@@ -193,7 +193,7 @@ class _DeckListScreenState extends State<DeckListScreen> {
void _useDefaultDeck() async {
// Check if default deck already exists
final baseDeck = DefaultDeck.deck;
final deckExists = _deckStorage.hasDeckSync(baseDeck.id);
final deckExists = await _deckStorage.hasDeck(baseDeck.id);
// Show dialog to ask if user wants mock data (only if deck doesn't exist)
bool? includeMockData = false;
@@ -214,36 +214,47 @@ class _DeckListScreenState extends State<DeckListScreen> {
? DefaultDeck.deckWithMockData
: DefaultDeck.deck;
if (deckExists) {
// If it exists, create a copy with a new ID
final clonedDeck = defaultDeck.copyWith(
id: '${defaultDeck.id}_${DateTime.now().millisecondsSinceEpoch}',
);
_deckStorage.saveDeckSync(clonedDeck);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Default deck added successfully'),
backgroundColor: Colors.green,
),
try {
if (deckExists) {
// If it exists, create a copy with a new ID
final clonedDeck = defaultDeck.copyWith(
id: '${defaultDeck.id}_${DateTime.now().millisecondsSinceEpoch}',
);
await _deckStorage.saveDeck(clonedDeck);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Default deck added successfully'),
backgroundColor: Colors.green,
),
);
}
} else {
// Save default deck to storage
await _deckStorage.saveDeck(defaultDeck);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Default deck added successfully'),
backgroundColor: Colors.green,
),
);
}
}
} else {
// Save default deck to storage
_deckStorage.saveDeckSync(defaultDeck);
_loadDecks();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Default deck added successfully'),
backgroundColor: Colors.green,
SnackBar(
content: Text('Error adding deck: $e'),
backgroundColor: Colors.red,
),
);
}
}
_loadDecks();
}
@override
+103 -8
View File
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:practice_engine/practice_engine.dart';
import '../routes.dart';
import '../services/deck_storage.dart';
import '../widgets/attempts_chart.dart';
class DeckOverviewScreen extends StatefulWidget {
const DeckOverviewScreen({super.key});
@@ -283,6 +282,105 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
}
}
Widget _buildProgressStats(BuildContext context) {
if (_deck == null) return const SizedBox.shrink();
final totalQuestions = _deck!.numberOfQuestions;
final knownCount = _deck!.knownCount;
final unknownCount = totalQuestions - knownCount;
final unknownPercentage = totalQuestions > 0
? (unknownCount / totalQuestions * 100.0)
: 0.0;
final knownPercentage = totalQuestions > 0
? (knownCount / totalQuestions * 100.0)
: 0.0;
// Get progress trend if we have history
// Attempt history is stored chronologically (oldest first), so first is oldest, last is newest
String? trendText;
if (_deck!.attemptHistory.isNotEmpty) {
final oldestAttempt = _deck!.attemptHistory.first;
final newestAttempt = _deck!.attemptHistory.last;
final oldestUnknown = oldestAttempt.unknownPercentage;
final newestUnknown = newestAttempt.unknownPercentage;
final improvement = oldestUnknown - newestUnknown;
if (improvement > 0) {
trendText = 'Improved by ${improvement.toStringAsFixed(1)}% since first attempt';
} else if (improvement < 0) {
trendText = '${improvement.abs().toStringAsFixed(1)}% more to learn since first attempt';
} else {
trendText = 'No change since first attempt';
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Main stat: Unknown percentage
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Questions to Learn',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
),
),
const SizedBox(height: 4),
Text(
'${unknownPercentage.toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
Text(
'$unknownCount of $totalQuestions questions',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'Questions Known',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
),
),
const SizedBox(height: 4),
Text(
'${knownPercentage.toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.secondary,
),
),
Text(
'$knownCount of $totalQuestions questions',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
),
if (trendText != null) ...[
const SizedBox(height: 12),
Text(
trendText,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],
],
);
}
@override
Widget build(BuildContext context) {
@@ -462,15 +560,12 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
const Divider(),
const SizedBox(height: 8),
Text(
'Progress Chart',
'Learning Progress',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
// Progress chart
AttemptsChart(
attempts: _deck!.attemptHistory,
maxDisplayItems: 15,
),
const SizedBox(height: 12),
// Current progress
_buildProgressStats(context),
],
],
),