Implement persistent state across navigation views
Lifts scoring state from ScoringPage to MainNavigationPage level to preserve scores and rule modes when switching between navigation views. State management improvements: - MainNavigationPage now manages persistent scoring state (arrows per end, scores array) - ScoringPage accepts initial state parameters and callback functions - All score modifications notify parent to maintain state consistency - Deep copying ensures proper state isolation between parent and child Persistence features: - Scores remain intact when navigating to Settings and back to Main scorer - Arrow count mode (3/6 arrows) preserved across view switches - All sophisticated end interaction behaviors maintained within scoring context - Confirmation dialogs for rule changes still work with persistent state Technical implementation: - State lifting pattern following Flutter best practices - Callback functions for bidirectional communication between parent and child - Deep copying of score arrays prevents reference sharing issues - Clean separation of navigation state from scoring logic This enhancement ensures professional UX where users can navigate between app sections without losing their scoring progress or configuration settings. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,47 @@ class MainNavigationPage extends StatefulWidget {
|
|||||||
class _MainNavigationPageState extends State<MainNavigationPage> {
|
class _MainNavigationPageState extends State<MainNavigationPage> {
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
final List<Widget> _pages = [
|
// Persistent scoring state
|
||||||
const ScoringPage(),
|
int _arrowsPerEnd = 3;
|
||||||
|
int _endsPerSet = 12;
|
||||||
|
List<List<String>> _scores = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initializeScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeScores() {
|
||||||
|
_scores = List.generate(
|
||||||
|
_endsPerSet,
|
||||||
|
(index) => List.generate(_arrowsPerEnd, (index) => ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onArrowsPerEndChanged(int arrows) {
|
||||||
|
setState(() {
|
||||||
|
_arrowsPerEnd = arrows;
|
||||||
|
_endsPerSet = arrows == 3 ? 10 : 6;
|
||||||
|
// Note: The actual score preservation dialog logic will be handled by ScoringPage
|
||||||
|
// This is just to keep the navigation-level state in sync
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onScoresUpdated(List<List<String>> newScores) {
|
||||||
|
setState(() {
|
||||||
|
_scores = newScores;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> get _pages => [
|
||||||
|
ScoringPage(
|
||||||
|
initialArrowsPerEnd: _arrowsPerEnd,
|
||||||
|
initialEndsPerSet: _endsPerSet,
|
||||||
|
initialScores: _scores,
|
||||||
|
onArrowsPerEndChanged: _onArrowsPerEndChanged,
|
||||||
|
onScoresUpdated: _onScoresUpdated,
|
||||||
|
),
|
||||||
const SettingsPage(),
|
const SettingsPage(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,31 @@ import '../widgets/scoring_grid/scoring_keypad.dart';
|
|||||||
import '../widgets/common/toggle_switches.dart';
|
import '../widgets/common/toggle_switches.dart';
|
||||||
|
|
||||||
class ScoringPage extends StatefulWidget {
|
class ScoringPage extends StatefulWidget {
|
||||||
const ScoringPage({super.key});
|
final int initialArrowsPerEnd;
|
||||||
|
final int initialEndsPerSet;
|
||||||
|
final List<List<String>> initialScores;
|
||||||
|
final Function(int) onArrowsPerEndChanged;
|
||||||
|
final Function(List<List<String>>) onScoresUpdated;
|
||||||
|
|
||||||
|
const ScoringPage({
|
||||||
|
super.key,
|
||||||
|
required this.initialArrowsPerEnd,
|
||||||
|
required this.initialEndsPerSet,
|
||||||
|
required this.initialScores,
|
||||||
|
required this.onArrowsPerEndChanged,
|
||||||
|
required this.onScoresUpdated,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ScoringPage> createState() => _ScoringPageState();
|
State<ScoringPage> createState() => _ScoringPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStateMixin {
|
class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStateMixin {
|
||||||
int arrowsPerEnd = 3;
|
late int arrowsPerEnd;
|
||||||
int endsPerSet = 12;
|
late int endsPerSet;
|
||||||
int selectedRow = 0;
|
int selectedRow = -1;
|
||||||
int selectedColumn = 0;
|
int selectedColumn = -1;
|
||||||
List<List<String>> scores = [];
|
late List<List<String>> scores;
|
||||||
bool isKeypadVisible = false;
|
bool isKeypadVisible = false;
|
||||||
int? activeEndIndex;
|
int? activeEndIndex;
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
@@ -24,6 +37,11 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// Initialize with values from parent
|
||||||
|
arrowsPerEnd = widget.initialArrowsPerEnd;
|
||||||
|
endsPerSet = widget.initialEndsPerSet;
|
||||||
|
scores = _deepCopyScores(widget.initialScores);
|
||||||
|
|
||||||
_animationController = AnimationController(
|
_animationController = AnimationController(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
@@ -32,7 +50,10 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
|
|||||||
parent: _animationController,
|
parent: _animationController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
_initializeScores();
|
}
|
||||||
|
|
||||||
|
List<List<String>> _deepCopyScores(List<List<String>> originalScores) {
|
||||||
|
return originalScores.map((end) => List<String>.from(end)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -104,6 +125,10 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
|
|||||||
} else {
|
} else {
|
||||||
_initializeScores();
|
_initializeScores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify parent of changes
|
||||||
|
widget.onArrowsPerEndChanged(arrows);
|
||||||
|
widget.onScoresUpdated(scores);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// No scores exist, just switch modes normally
|
// No scores exist, just switch modes normally
|
||||||
@@ -112,6 +137,10 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
|
|||||||
// Infer ends per set based on ruleset
|
// Infer ends per set based on ruleset
|
||||||
endsPerSet = arrows == 3 ? 10 : 6;
|
endsPerSet = arrows == 3 ? 10 : 6;
|
||||||
_initializeScores();
|
_initializeScores();
|
||||||
|
|
||||||
|
// Notify parent of changes
|
||||||
|
widget.onArrowsPerEndChanged(arrows);
|
||||||
|
widget.onScoresUpdated(scores);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,6 +184,9 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
|
|||||||
|
|
||||||
// Update scores array
|
// Update scores array
|
||||||
scores[endIndex] = sortedScores;
|
scores[endIndex] = sortedScores;
|
||||||
|
|
||||||
|
// Notify parent of score changes
|
||||||
|
widget.onScoresUpdated(scores);
|
||||||
}
|
}
|
||||||
|
|
||||||
int _findFirstEmptyCellInEnd(int endIndex) {
|
int _findFirstEmptyCellInEnd(int endIndex) {
|
||||||
@@ -214,7 +246,7 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
|
|||||||
// Clear the selected cell
|
// Clear the selected cell
|
||||||
if (selectedColumn < scores[selectedRow].length && scores[selectedRow][selectedColumn].isNotEmpty) {
|
if (selectedColumn < scores[selectedRow].length && scores[selectedRow][selectedColumn].isNotEmpty) {
|
||||||
scores[selectedRow][selectedColumn] = '';
|
scores[selectedRow][selectedColumn] = '';
|
||||||
_sortEndScores(selectedRow);
|
_sortEndScores(selectedRow); // This already notifies parent
|
||||||
// Retract keypad and disarm after clearing
|
// Retract keypad and disarm after clearing
|
||||||
_animationController.reverse().then((_) {
|
_animationController.reverse().then((_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
Reference in New Issue
Block a user