From 398ac5af63fee50d835b5fd6b44679360747bbce Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Sat, 23 Aug 2025 22:51:33 +0900 Subject: [PATCH] Implement persistent state across navigation views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../pages/main_navigation_page.dart | 43 ++++++++++++++++- lib/presentation/pages/scoring_page.dart | 48 +++++++++++++++---- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/lib/presentation/pages/main_navigation_page.dart b/lib/presentation/pages/main_navigation_page.dart index dbc9405..9724a49 100644 --- a/lib/presentation/pages/main_navigation_page.dart +++ b/lib/presentation/pages/main_navigation_page.dart @@ -12,8 +12,47 @@ class MainNavigationPage extends StatefulWidget { class _MainNavigationPageState extends State { int _selectedIndex = 0; - final List _pages = [ - const ScoringPage(), + // Persistent scoring state + int _arrowsPerEnd = 3; + int _endsPerSet = 12; + List> _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> newScores) { + setState(() { + _scores = newScores; + }); + } + + List get _pages => [ + ScoringPage( + initialArrowsPerEnd: _arrowsPerEnd, + initialEndsPerSet: _endsPerSet, + initialScores: _scores, + onArrowsPerEndChanged: _onArrowsPerEndChanged, + onScoresUpdated: _onScoresUpdated, + ), const SettingsPage(), ]; diff --git a/lib/presentation/pages/scoring_page.dart b/lib/presentation/pages/scoring_page.dart index 9a20924..34eff05 100644 --- a/lib/presentation/pages/scoring_page.dart +++ b/lib/presentation/pages/scoring_page.dart @@ -4,18 +4,31 @@ import '../widgets/scoring_grid/scoring_keypad.dart'; import '../widgets/common/toggle_switches.dart'; class ScoringPage extends StatefulWidget { - const ScoringPage({super.key}); + final int initialArrowsPerEnd; + final int initialEndsPerSet; + final List> initialScores; + final Function(int) onArrowsPerEndChanged; + final Function(List>) onScoresUpdated; + + const ScoringPage({ + super.key, + required this.initialArrowsPerEnd, + required this.initialEndsPerSet, + required this.initialScores, + required this.onArrowsPerEndChanged, + required this.onScoresUpdated, + }); @override State createState() => _ScoringPageState(); } class _ScoringPageState extends State with SingleTickerProviderStateMixin { - int arrowsPerEnd = 3; - int endsPerSet = 12; - int selectedRow = 0; - int selectedColumn = 0; - List> scores = []; + late int arrowsPerEnd; + late int endsPerSet; + int selectedRow = -1; + int selectedColumn = -1; + late List> scores; bool isKeypadVisible = false; int? activeEndIndex; late AnimationController _animationController; @@ -24,6 +37,11 @@ class _ScoringPageState extends State with SingleTickerProviderStat @override void initState() { super.initState(); + // Initialize with values from parent + arrowsPerEnd = widget.initialArrowsPerEnd; + endsPerSet = widget.initialEndsPerSet; + scores = _deepCopyScores(widget.initialScores); + _animationController = AnimationController( duration: const Duration(milliseconds: 250), vsync: this, @@ -32,7 +50,10 @@ class _ScoringPageState extends State with SingleTickerProviderStat parent: _animationController, curve: Curves.easeInOut, ); - _initializeScores(); + } + + List> _deepCopyScores(List> originalScores) { + return originalScores.map((end) => List.from(end)).toList(); } @override @@ -104,6 +125,10 @@ class _ScoringPageState extends State with SingleTickerProviderStat } else { _initializeScores(); } + + // Notify parent of changes + widget.onArrowsPerEndChanged(arrows); + widget.onScoresUpdated(scores); }); } else { // No scores exist, just switch modes normally @@ -112,6 +137,10 @@ class _ScoringPageState extends State with SingleTickerProviderStat // Infer ends per set based on ruleset endsPerSet = arrows == 3 ? 10 : 6; _initializeScores(); + + // Notify parent of changes + widget.onArrowsPerEndChanged(arrows); + widget.onScoresUpdated(scores); }); } } @@ -155,6 +184,9 @@ class _ScoringPageState extends State with SingleTickerProviderStat // Update scores array scores[endIndex] = sortedScores; + + // Notify parent of score changes + widget.onScoresUpdated(scores); } int _findFirstEmptyCellInEnd(int endIndex) { @@ -214,7 +246,7 @@ class _ScoringPageState extends State with SingleTickerProviderStat // Clear the selected cell if (selectedColumn < scores[selectedRow].length && scores[selectedRow][selectedColumn].isNotEmpty) { scores[selectedRow][selectedColumn] = ''; - _sortEndScores(selectedRow); + _sortEndScores(selectedRow); // This already notifies parent // Retract keypad and disarm after clearing _animationController.reverse().then((_) { setState(() {