mock data and persistence

This commit is contained in:
gitea
2026-01-02 16:56:23 +01:00
parent 09cc51c059
commit 9f06568eab
12 changed files with 827 additions and 58 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ class _AttemptResultScreenState extends State<AttemptResultScreen> {
void _done() {
if (_deck == null) return;
// Save the updated deck to storage
_deckStorage.saveDeck(_deck!);
_deckStorage.saveDeckSync(_deck!);
// Navigate back to deck list
Navigator.pushNamedAndRemoveUntil(
context,
+2 -2
View File
@@ -449,7 +449,7 @@ class _AttemptScreenState extends State<AttemptScreen> {
// Update deck with incomplete attempt
final updatedDeck = _deck!.copyWith(incompleteAttempt: incompleteAttempt);
_deckStorage.saveDeck(updatedDeck);
_deckStorage.saveDeckSync(updatedDeck);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
@@ -485,7 +485,7 @@ class _AttemptScreenState extends State<AttemptScreen> {
);
// Save the updated deck to storage
_deckStorage.saveDeck(updatedDeckWithHistory);
_deckStorage.saveDeckSync(updatedDeckWithHistory);
Navigator.pushReplacementNamed(
context,
+2 -2
View File
@@ -225,7 +225,7 @@ class _DeckConfigScreenState extends State<DeckConfigScreen> {
if (updatedDeck != null && updatedDeck is Deck && mounted) {
// Reload the deck from storage to get the latest state
final deckStorage = DeckStorage();
final refreshedDeck = deckStorage.getDeck(updatedDeck.id);
final refreshedDeck = deckStorage.getDeckSync(updatedDeck.id);
if (refreshedDeck != null) {
setState(() {
_deck = refreshedDeck;
@@ -272,7 +272,7 @@ class _DeckConfigScreenState extends State<DeckConfigScreen> {
// Save the reset deck to storage
final deckStorage = DeckStorage();
deckStorage.saveDeck(resetDeck);
deckStorage.saveDeckSync(resetDeck);
// Show success message
if (mounted) {
+1 -1
View File
@@ -96,7 +96,7 @@ class _DeckCreateScreenState extends State<DeckCreateScreen> {
attemptHistory: [],
);
_deckStorage.saveDeck(newDeck);
_deckStorage.saveDeckSync(newDeck);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
+1 -1
View File
@@ -118,7 +118,7 @@ class _DeckEditScreenState extends State<DeckEditScreen> {
currentAttemptIndex: _deck!.currentAttemptIndex,
);
_deckStorage.saveDeck(updatedDeck);
_deckStorage.saveDeckSync(updatedDeck);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
+72 -5
View File
@@ -109,7 +109,7 @@ class _DeckImportScreenState extends State<DeckImportScreen> {
// Save deck to storage
final deckStorage = DeckStorage();
deckStorage.saveDeck(deck);
deckStorage.saveDeckSync(deck);
// Navigate back to deck list
Navigator.pop(context);
@@ -121,12 +121,11 @@ class _DeckImportScreenState extends State<DeckImportScreen> {
}
}
void _useDefaultDeck() {
final defaultDeck = DefaultDeck.deck;
void _useDefaultDeck() async {
final deckStorage = DeckStorage();
// Check if default deck already exists
if (deckStorage.hasDeck(defaultDeck.id)) {
if (deckStorage.hasDeckSync(DefaultDeck.deck.id)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Default deck already exists'),
@@ -136,8 +135,24 @@ class _DeckImportScreenState extends State<DeckImportScreen> {
return;
}
// Show dialog to ask if user wants mock data
final includeMockData = await showDialog<bool>(
context: context,
builder: (context) => _MockDataDialog(),
);
if (includeMockData == null) {
// User cancelled
return;
}
// Get the appropriate deck version
final defaultDeck = includeMockData
? DefaultDeck.deckWithMockData
: DefaultDeck.deck;
// Save default deck to storage
deckStorage.saveDeck(defaultDeck);
deckStorage.saveDeckSync(defaultDeck);
// Navigate back to deck list
Navigator.pop(context);
@@ -420,3 +435,55 @@ class _DeckImportScreenState extends State<DeckImportScreen> {
}
}
class _MockDataDialog extends StatefulWidget {
@override
State<_MockDataDialog> createState() => _MockDataDialogState();
}
class _MockDataDialogState extends State<_MockDataDialog> {
bool _includeMockData = false;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Add Default Deck'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Would you like to include mock attempt history and progress data?',
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 16),
CheckboxListTile(
value: _includeMockData,
onChanged: (value) {
setState(() {
_includeMockData = value ?? false;
});
},
title: const Text('Include mock data'),
subtitle: const Text(
'Adds 4 sample attempts showing progress over time',
style: TextStyle(fontSize: 12),
),
contentPadding: EdgeInsets.zero,
controlAffinity: ListTileControlAffinity.leading,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, null),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(context, _includeMockData),
child: const Text('Add Deck'),
),
],
);
}
}
+90 -9
View File
@@ -22,8 +22,17 @@ class _DeckListScreenState extends State<DeckListScreen> {
}
void _loadDecks() {
// Use sync version for immediate UI update, will work once storage is initialized
setState(() {
_decks = _deckStorage.getAllDecks();
_decks = _deckStorage.getAllDecksSync();
});
// Also trigger async load to ensure we have latest data
_deckStorage.getAllDecks().then((decks) {
if (mounted) {
setState(() {
_decks = decks;
});
}
});
}
@@ -61,7 +70,7 @@ class _DeckListScreenState extends State<DeckListScreen> {
);
if (confirmed == true) {
_deckStorage.deleteDeck(deck.id);
_deckStorage.deleteDeckSync(deck.id);
_loadDecks();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -112,7 +121,7 @@ class _DeckListScreenState extends State<DeckListScreen> {
}).toList(),
);
_deckStorage.saveDeck(clonedDeck);
_deckStorage.saveDeckSync(clonedDeck);
_loadDecks();
if (mounted) {
@@ -181,16 +190,36 @@ class _DeckListScreenState extends State<DeckListScreen> {
);
}
void _useDefaultDeck() {
final defaultDeck = DefaultDeck.deck;
void _useDefaultDeck() async {
// Check if default deck already exists
if (_deckStorage.hasDeck(defaultDeck.id)) {
final baseDeck = DefaultDeck.deck;
final deckExists = _deckStorage.hasDeckSync(baseDeck.id);
// Show dialog to ask if user wants mock data (only if deck doesn't exist)
bool? includeMockData = false;
if (!deckExists) {
includeMockData = await showDialog<bool>(
context: context,
builder: (context) => _MockDataDialog(),
);
if (includeMockData == null) {
// User cancelled
return;
}
}
// Get the appropriate deck version
final defaultDeck = includeMockData
? 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.saveDeck(clonedDeck);
_deckStorage.saveDeckSync(clonedDeck);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -202,7 +231,7 @@ class _DeckListScreenState extends State<DeckListScreen> {
}
} else {
// Save default deck to storage
_deckStorage.saveDeck(defaultDeck);
_deckStorage.saveDeckSync(defaultDeck);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -421,3 +450,55 @@ class _StatChip extends StatelessWidget {
}
}
class _MockDataDialog extends StatefulWidget {
@override
State<_MockDataDialog> createState() => _MockDataDialogState();
}
class _MockDataDialogState extends State<_MockDataDialog> {
bool _includeMockData = false;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Add Default Deck'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Would you like to include mock attempt history and progress data?',
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 16),
CheckboxListTile(
value: _includeMockData,
onChanged: (value) {
setState(() {
_includeMockData = value ?? false;
});
},
title: const Text('Include mock data'),
subtitle: const Text(
'Adds 4 sample attempts showing progress over time',
style: TextStyle(fontSize: 12),
),
contentPadding: EdgeInsets.zero,
controlAffinity: ListTileControlAffinity.leading,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, null),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(context, _includeMockData),
child: const Text('Add Deck'),
),
],
);
}
}
+9 -9
View File
@@ -24,7 +24,7 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
// ALWAYS load the latest version from storage to ensure we have the most up-to-date deck
// This is critical for getting the latest incomplete attempt state
// Don't rely on route arguments - they might be stale
final storedDeck = _deckStorage.getDeck(args.id);
final storedDeck = _deckStorage.getDeckSync(args.id);
final deckToUse = storedDeck ?? args;
// Always update to get the latest state from storage
@@ -37,7 +37,7 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
void _saveDeck() {
if (_deck != null) {
_deckStorage.saveDeck(_deck!);
_deckStorage.saveDeckSync(_deck!);
}
}
@@ -50,7 +50,7 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
// Force reload from storage before checking for incomplete attempts to ensure we have latest state
Deck? deckToCheck = _deck;
if (_deck != null) {
final freshDeck = _deckStorage.getDeck(_deck!.id);
final freshDeck = _deckStorage.getDeckSync(_deck!.id);
if (freshDeck != null) {
setState(() {
_deck = freshDeck;
@@ -117,8 +117,8 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
final updatedDeck = deckToCheck.copyWith(clearIncompleteAttempt: true);
// Save to storage multiple times to ensure it's persisted
_deckStorage.saveDeck(updatedDeck);
_deckStorage.saveDeck(updatedDeck); // Save twice to be sure
_deckStorage.saveDeckSync(updatedDeck);
_deckStorage.saveDeckSync(updatedDeck); // Save twice to be sure
// Update local state immediately with cleared deck
setState(() {
@@ -128,13 +128,13 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
// Force reload from storage multiple times to verify it's cleared
Deck? finalClearedDeck = updatedDeck;
for (int i = 0; i < 3; i++) {
final verifiedDeck = _deckStorage.getDeck(updatedDeck.id);
final verifiedDeck = _deckStorage.getDeckSync(updatedDeck.id);
if (verifiedDeck != null) {
// Ensure incomplete attempt is null even if storage had it
if (verifiedDeck.incompleteAttempt != null) {
final clearedDeck = verifiedDeck.copyWith(clearIncompleteAttempt: true);
_deckStorage.saveDeck(clearedDeck);
_deckStorage.saveDeckSync(clearedDeck);
setState(() {
_deck = clearedDeck;
});
@@ -211,7 +211,7 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
// Also ensure storage has the cleared state
final deckToUse = deckToCheck.copyWith(clearIncompleteAttempt: true);
// Ensure storage also has the cleared state
_deckStorage.saveDeck(deckToUse);
_deckStorage.saveDeckSync(deckToUse);
Navigator.pushNamed(
context,
Routes.attempt,
@@ -226,7 +226,7 @@ class _DeckOverviewScreenState extends State<DeckOverviewScreen> {
void _refreshDeck() {
if (_deck != null) {
// Reload the deck from storage to get the latest state (including incomplete attempts)
final refreshedDeck = _deckStorage.getDeck(_deck!.id);
final refreshedDeck = _deckStorage.getDeckSync(_deck!.id);
if (refreshedDeck != null && mounted) {
setState(() {
_deck = refreshedDeck;