Add swipe-in side navigation with Main/Settings pages

Implements professional navigation structure with Material Design drawer:

- MainNavigationPage: Central navigation hub with AppBar and Drawer
- SettingsPage: Placeholder settings page for future configuration options
- Drawer navigation: Swipe-in side menu with Main (Scorer) and Settings options
- App title updates dynamically based on current page selection
- Clean architecture separating navigation from scoring logic

Navigation features:
- Material Design drawer with branded header showing 'Archery Bookkeeper Alpha 0.1'
- Icons for visual clarity (sports for scorer, settings for configuration)
- Selected state highlighting for current page
- Automatic drawer close on navigation selection
- Professional app description in drawer footer

Updated main.dart to use MainNavigationPage as entry point
Modified ScoringPage to work as embedded page without its own AppBar
Maintained all sophisticated end interaction behaviors within scoring context

This establishes the foundation for additional pages and features while
preserving the advanced scoring functionality implemented in the previous release.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2025-08-23 22:42:32 +09:00
parent 074f827c8b
commit c57418ea8b
4 changed files with 154 additions and 6 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'presentation/pages/scoring_page.dart';
import 'presentation/pages/main_navigation_page.dart';
void main() {
runApp(const ArcheryBookkeeperApp());
@@ -19,7 +19,7 @@ class ArcheryBookkeeperApp extends StatelessWidget {
),
useMaterial3: true,
),
home: const ScoringPage(),
home: const MainNavigationPage(),
);
}
}
@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'scoring_page.dart';
import 'settings_page.dart';
class MainNavigationPage extends StatefulWidget {
const MainNavigationPage({super.key});
@override
State<MainNavigationPage> createState() => _MainNavigationPageState();
}
class _MainNavigationPageState extends State<MainNavigationPage> {
int _selectedIndex = 0;
final List<Widget> _pages = [
const ScoringPage(),
const SettingsPage(),
];
final List<String> _pageTitles = [
'Main (Scorer)',
'Settings',
];
void _onNavigationItemSelected(int index) {
setState(() {
_selectedIndex = index;
});
Navigator.pop(context); // Close drawer
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_pageTitles[_selectedIndex]),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: _pages[_selectedIndex],
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.inversePrimary,
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'Archery Bookkeeper',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Alpha 0.1',
style: TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
],
),
),
ListTile(
leading: const Icon(Icons.sports),
title: const Text('Main (Scorer)'),
selected: _selectedIndex == 0,
onTap: () => _onNavigationItemSelected(0),
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
selected: _selectedIndex == 1,
onTap: () => _onNavigationItemSelected(1),
),
const Divider(),
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Professional archery scoring with sophisticated end interaction system.',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
),
],
),
),
);
}
}
+1 -4
View File
@@ -204,6 +204,7 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
animation: _keypadAnimation,
builder: (context, child) {
// Calculate viewport heights based on animation progress
// AppBar is in parent navigation, account for status bar and safe area
final availableHeight = (MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
kToolbarHeight).clamp(200.0, double.infinity); // Minimum 200px
@@ -212,10 +213,6 @@ class _ScoringPageState extends State<ScoringPage> with SingleTickerProviderStat
final upperViewportHeight = (availableHeight - keypadHeight).clamp(100.0, availableHeight);
return Scaffold(
appBar: AppBar(
title: const Text('Archery Scoring'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: GestureDetector(
onTap: _onBackgroundTapped,
behavior: HitTestBehavior.opaque,
+52
View File
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: const Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Settings',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 24),
Text(
'This is a placeholder settings page.',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
SizedBox(height: 16),
Text(
'Future settings will include:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 8),
Text('• Color scheme preferences'),
Text('• Default arrow counts'),
Text('• Score input preferences'),
Text('• Export settings'),
Text('• Session management'),
],
),
),
);
}
}