import 'package:flutter/material.dart'; import 'package:practice_engine/practice_engine.dart'; import '../services/deck_storage.dart'; import '../routes.dart'; class DeckConfigScreen extends StatefulWidget { const DeckConfigScreen({super.key}); @override State createState() => _DeckConfigScreenState(); } class _DeckConfigScreenState extends State { Deck? _deck; DeckConfig? _config; late TextEditingController _consecutiveController; late TextEditingController _attemptSizeController; late TextEditingController _priorityIncreaseController; late TextEditingController _priorityDecreaseController; late TextEditingController _timeLimitHoursController; late TextEditingController _timeLimitMinutesController; late TextEditingController _timeLimitSecondsController; late bool _immediateFeedback; late bool _includeKnownInAttempts; bool _timeLimitEnabled = false; String? _lastDeckId; int _configHashCode = 0; @override void initState() { super.initState(); _deck = null; _lastDeckId = null; _configHashCode = 0; // Initialize controllers with default values _timeLimitHoursController = TextEditingController(); _timeLimitMinutesController = TextEditingController(); _timeLimitSecondsController = TextEditingController(); } void _initializeFromDeck(Deck deck) { // Dispose old controllers if they exist if (_deck != null) { _consecutiveController.dispose(); _attemptSizeController.dispose(); _priorityIncreaseController.dispose(); _priorityDecreaseController.dispose(); _timeLimitHoursController.dispose(); _timeLimitMinutesController.dispose(); _timeLimitSecondsController.dispose(); } setState(() { _deck = deck; _config = deck.config; _lastDeckId = deck.id; _configHashCode = deck.config.hashCode; // Create new controllers with current config values _consecutiveController = TextEditingController( text: _config!.requiredConsecutiveCorrect.toString(), ); _attemptSizeController = TextEditingController( text: _config!.defaultAttemptSize.toString(), ); _priorityIncreaseController = TextEditingController( text: _config!.priorityIncreaseOnIncorrect.toString(), ); _priorityDecreaseController = TextEditingController( text: _config!.priorityDecreaseOnCorrect.toString(), ); _immediateFeedback = _config!.immediateFeedbackEnabled; _includeKnownInAttempts = _config!.includeKnownInAttempts; // Initialize time limit controllers _timeLimitEnabled = _config!.timeLimitSeconds != null; final totalSeconds = _config!.timeLimitSeconds ?? 0; final hours = totalSeconds ~/ 3600; final minutes = (totalSeconds % 3600) ~/ 60; final seconds = totalSeconds % 60; _timeLimitHoursController = TextEditingController( text: hours > 0 ? hours.toString() : '', ); _timeLimitMinutesController = TextEditingController( text: minutes > 0 ? minutes.toString() : '', ); _timeLimitSecondsController = TextEditingController( text: seconds > 0 ? seconds.toString() : '', ); }); } @override void didChangeDependencies() { super.didChangeDependencies(); // Get deck from route arguments final args = ModalRoute.of(context)?.settings.arguments; final newDeck = args is Deck ? args : _createSampleDeck(); // Check if we need to update: first load, different deck, or config changed final needsUpdate = _deck == null || _lastDeckId != newDeck.id || _configHashCode != newDeck.config.hashCode; if (needsUpdate) { _initializeFromDeck(newDeck); } } Deck _createSampleDeck() { return Deck( id: 'sample', title: 'Sample', description: 'Sample', questions: [], config: const DeckConfig(), ); } @override void dispose() { _consecutiveController.dispose(); _attemptSizeController.dispose(); _priorityIncreaseController.dispose(); _priorityDecreaseController.dispose(); _timeLimitHoursController.dispose(); _timeLimitMinutesController.dispose(); _timeLimitSecondsController.dispose(); super.dispose(); } void _save() { if (_deck == null || _config == null) return; final consecutive = int.tryParse(_consecutiveController.text); final attemptSize = int.tryParse(_attemptSizeController.text); final priorityIncrease = int.tryParse(_priorityIncreaseController.text); final priorityDecrease = int.tryParse(_priorityDecreaseController.text); if (consecutive == null || attemptSize == null || priorityIncrease == null || priorityDecrease == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Please enter valid numbers for all fields'), backgroundColor: Colors.red, ), ); return; } if (consecutive < 1 || attemptSize < 1 || priorityIncrease < 0 || priorityDecrease < 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Values must be positive (priority changes >= 0)'), backgroundColor: Colors.red, ), ); return; } // Calculate time limit in seconds int? timeLimitSeconds; if (_timeLimitEnabled) { final hours = int.tryParse(_timeLimitHoursController.text) ?? 0; final minutes = int.tryParse(_timeLimitMinutesController.text) ?? 0; final seconds = int.tryParse(_timeLimitSecondsController.text) ?? 0; final totalSeconds = hours * 3600 + minutes * 60 + seconds; if (totalSeconds > 0) { timeLimitSeconds = totalSeconds; } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Time limit must be greater than 0'), backgroundColor: Colors.red, ), ); return; } } final updatedConfig = DeckConfig( requiredConsecutiveCorrect: consecutive, defaultAttemptSize: attemptSize, priorityIncreaseOnIncorrect: priorityIncrease, priorityDecreaseOnCorrect: priorityDecrease, immediateFeedbackEnabled: _immediateFeedback, includeKnownInAttempts: _includeKnownInAttempts, timeLimitSeconds: timeLimitSeconds, ); final updatedDeck = _deck!.copyWith(config: updatedConfig); // Show success message ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Deck configuration saved successfully'), backgroundColor: Colors.green, duration: Duration(seconds: 1), ), ); // Pop with the updated deck Navigator.pop(context, updatedDeck); } void _cancel() { Navigator.pop(context); } void _editDeck() { if (_deck == null) return; Navigator.pushNamed( context, Routes.deckEdit, arguments: _deck, ).then((updatedDeck) { if (updatedDeck != null && updatedDeck is Deck && mounted) { // Reload the deck from storage to get the latest state final deckStorage = DeckStorage(); final refreshedDeck = deckStorage.getDeckSync(updatedDeck.id); if (refreshedDeck != null) { setState(() { _deck = refreshedDeck; _initializeFromDeck(refreshedDeck); }); } } }); } Future _resetDeck() async { if (_deck == null) return; final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Reset Deck'), content: const Text( 'Are you sure you want to reset this deck? This will erase all attempt data, progress, and statistics. This action cannot be undone.', ), 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) { // Reset the deck: clear all progress, attempt history, and reset attempt counts final resetDeck = DeckService.resetDeck( deck: _deck!, resetAttemptCounts: true, clearAttemptHistory: true, ); // Save the reset deck to storage final deckStorage = DeckStorage(); deckStorage.saveDeckSync(resetDeck); // Show success message if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Deck has been reset successfully'), backgroundColor: Colors.green, duration: Duration(seconds: 2), ), ); } // Return the reset deck to update the parent screen if (mounted) { Navigator.pop(context, resetDeck); } } } @override Widget build(BuildContext context) { if (_deck == null || _config == null) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar( title: const Text('Deck Configuration'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Practice Settings', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 24), // Required Consecutive Correct TextField( controller: _consecutiveController, decoration: const InputDecoration( labelText: 'Required Consecutive Correct', helperText: 'Number of correct answers in a row to mark as known', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), const SizedBox(height: 16), // Default Attempt Size TextField( controller: _attemptSizeController, decoration: const InputDecoration( labelText: 'Default Questions Per Attempt', helperText: 'Number of questions to include in each attempt', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), const SizedBox(height: 16), // Priority Increase TextField( controller: _priorityIncreaseController, decoration: const InputDecoration( labelText: 'Priority Increase on Incorrect', helperText: 'Priority points added when answered incorrectly', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), const SizedBox(height: 16), // Priority Decrease TextField( controller: _priorityDecreaseController, decoration: const InputDecoration( labelText: 'Priority Decrease on Correct', helperText: 'Priority points removed when answered correctly', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), const SizedBox(height: 16), // Immediate Feedback Toggle SwitchListTile( title: const Text('Immediate Feedback'), subtitle: const Text( 'Show correct/incorrect immediately after answering', ), value: _immediateFeedback, onChanged: (value) { setState(() { _immediateFeedback = value; }); }, ), const SizedBox(height: 16), // Include Known in Attempts Toggle SwitchListTile( title: const Text('Include Known in Attempts'), subtitle: const Text( 'Include questions marked as known in practice attempts', ), value: _includeKnownInAttempts, onChanged: (value) { setState(() { _includeKnownInAttempts = value; }); }, ), const SizedBox(height: 16), // Time Limit Section SwitchListTile( title: const Text('Enable Time Limit'), subtitle: const Text( 'Set a time limit for attempts', ), value: _timeLimitEnabled, onChanged: (value) { setState(() { _timeLimitEnabled = value; if (!value) { _timeLimitHoursController.clear(); _timeLimitMinutesController.clear(); _timeLimitSecondsController.clear(); } }); }, ), if (_timeLimitEnabled) ...[ const SizedBox(height: 16), Row( children: [ Expanded( child: TextField( controller: _timeLimitHoursController, decoration: const InputDecoration( labelText: 'Hours', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _timeLimitMinutesController, decoration: const InputDecoration( labelText: 'Minutes', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _timeLimitSecondsController, decoration: const InputDecoration( labelText: 'Seconds', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), ), ], ), ], ], ), ), ), const SizedBox(height: 24), // Edit Deck Section Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Deck Management', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), Text( 'Edit the deck name, description, and questions.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _editDeck, icon: const Icon(Icons.edit), label: const Text('Edit Deck'), ), ), ], ), ), ), const SizedBox(height: 24), // Reset Deck Section Card( color: Theme.of(context).colorScheme.errorContainer.withValues(alpha: 0.1), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.warning_amber_rounded, color: Theme.of(context).colorScheme.error, size: 20, ), const SizedBox(width: 8), Text( 'Danger Zone', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Theme.of(context).colorScheme.error, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 12), Text( 'Reset this deck to erase all progress, attempt history, and statistics. This action cannot be undone.', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _resetDeck, icon: const Icon(Icons.refresh), label: const Text('Reset Deck'), style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.error, side: BorderSide(color: Theme.of(context).colorScheme.error), ), ), ), ], ), ), ), const SizedBox(height: 24), // Action Buttons Row( children: [ Expanded( child: OutlinedButton( onPressed: _cancel, child: const Text('Cancel'), ), ), const SizedBox(width: 16), Expanded( child: FilledButton( onPressed: _save, child: const Text('Save'), ), ), ], ), ], ), ), ); } }