import 'package:flutter/material.dart'; import 'package:flame/game.dart'; import 'app_theme.dart'; import 'levels.dart'; import 'managers.dart'; import 'game_engine.dart'; import 'game_components.dart'; import 'skin_shop_screen.dart'; import 'settings_screens.dart'; // --- UI: Main Menu --- class MainMenuScreen extends StatelessWidget { const MainMenuScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.paperBg, body: Stack( children: [ Positioned.fill(child: CustomPaint(painter: GridPainter())), Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( "LineInkGuide", style: TextStyle( fontSize: 48, fontWeight: FontWeight.w900, color: AppTheme.inkPrimary, letterSpacing: -1, ), ), const Text( "PHYSICS NOTEBOOK", style: TextStyle( fontSize: 14, color: AppTheme.inkPrimary, letterSpacing: 4, ), ), const SizedBox(height: 80), _buildBtn( context, "START GAME", () => Navigator.push( context, MaterialPageRoute( builder: (_) => const LevelSelectScreen(), ), ), ), _buildBtn( context, "SKINS", () => Navigator.push( context, MaterialPageRoute(builder: (_) => const SkinShopScreen()), ), ), _buildBtn( context, "SETTINGS", () => Navigator.push( context, MaterialPageRoute(builder: (_) => const SettingsScreen()), ), ), _buildBtn(context, "HOW TO PLAY", () => _showHelp(context)), ], ), ), ], ), ); } Widget _buildBtn(BuildContext context, String text, VoidCallback onTap) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: SizedBox( width: 220, child: OutlinedButton( style: OutlinedButton.styleFrom( side: const BorderSide(color: AppTheme.inkPrimary, width: 2), padding: const EdgeInsets.all(16), shape: const RoundedRectangleBorder(), ), onPressed: onTap, child: Text( text, style: const TextStyle( color: AppTheme.inkPrimary, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ); } void _showHelp(BuildContext context) { showDialog( context: context, builder: (_) => AlertDialog( backgroundColor: AppTheme.paperBg, title: const Text("INSTRUCTIONS"), content: const Text( "• Draw lines to guide the ball.\n• Collect ALL stars to unlock the goal.\n• Reach the goal basket to win.\n• Conserve ink for a higher score!", ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("READY"), ), ], ), ); } } // --- UI: Level Selection --- class LevelSelectScreen extends StatefulWidget { const LevelSelectScreen({super.key}); @override State createState() => _LevelSelectScreenState(); } class _LevelSelectScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.paperBg, appBar: AppBar( title: const Text( "SELECT LEVEL", style: TextStyle( color: AppTheme.inkPrimary, fontWeight: FontWeight.bold, ), ), backgroundColor: Colors.transparent, elevation: 0, iconTheme: const IconThemeData(color: AppTheme.inkPrimary), ), body: GridView.builder( padding: const EdgeInsets.all(24), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 20, crossAxisSpacing: 20, childAspectRatio: 0.8, ), itemCount: levels.length, itemBuilder: (context, index) { int starCount = LevelManager.instance.getStars(levels[index].id); bool isUnlocked = levels[index].id <= LevelManager.instance.highestUnlockedLevel; return GestureDetector( onTap: () async { if (!isUnlocked) return; await Navigator.push( context, MaterialPageRoute( builder: (_) => GameView(config: levels[index]), ), ); setState(() {}); }, child: Container( decoration: BoxDecoration( border: Border.all( color: isUnlocked ? AppTheme.inkPrimary : AppTheme.gridLine, width: 2, ), color: isUnlocked ? null : AppTheme.gridLine.withOpacity(0.1), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "${index + 1}", style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), if (isUnlocked) Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( 5, (i) => Icon( Icons.star, size: 12, color: i < starCount ? AppTheme.accentYellow : AppTheme.gridLine, ), ), ) else const Icon(Icons.lock, size: 18, color: AppTheme.gridLine), ], ), ), ); }, ), ); } } // --- Game Container --- class GameView extends StatefulWidget { final LevelConfig config; const GameView({super.key, required this.config}); @override State createState() => _GameViewState(); } class _GameViewState extends State { late LineInkGuideGame _game; @override void initState() { super.initState(); _game = LineInkGuideGame(config: widget.config); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvoked: (bool didPop) { if (didPop) return; _showExitConfirmDialog(context); }, child: Scaffold( backgroundColor: AppTheme.paperBg, body: Stack( children: [ GameWidget(game: _game), SafeArea( child: Padding( padding: const EdgeInsets.only( left: 8, top: 8, right: 20, bottom: 20, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.close), onPressed: () => _showExitConfirmDialog(context), ), Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "INK", style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, ), ), ValueListenableBuilder( valueListenable: _game.inkNotifier, builder: (context, double val, _) => Container( width: 120, height: 8, decoration: BoxDecoration( border: Border.all( color: AppTheme.inkPrimary, width: 1.5, ), ), child: LinearProgressIndicator( value: (val / widget.config.ink).clamp(0, 1), backgroundColor: Colors.transparent, valueColor: const AlwaysStoppedAnimation( AppTheme.inkPrimary, ), ), ), ), const SizedBox(height: 4), ValueListenableBuilder( valueListenable: _game.starCountNotifier, builder: (context, int count, _) => Text( "STARS: $count / ${widget.config.stars.length}", style: const TextStyle( fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ], ), Row( mainAxisSize: MainAxisSize.min, children: [ OutlinedButton( style: OutlinedButton.styleFrom( side: const BorderSide( color: AppTheme.inkPrimary, width: 1.5, ), padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8, ), ), onPressed: () => _showResetConfirmDialog(context), child: const Text( "RESET", style: TextStyle( color: AppTheme.inkPrimary, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 8), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.inkPrimary, elevation: 0, ), onPressed: () => _game.startSimulation(), child: const Text( "START", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ], ), ], ), ), ), // Win Overlay ValueListenableBuilder( valueListenable: _game.winNotifier, builder: (context, int? stars, _) { if (stars == null) return const SizedBox.shrink(); return Container( color: Colors.black54, child: Center( child: Container( width: 300, padding: const EdgeInsets.all(40), decoration: BoxDecoration( color: AppTheme.paperBg, border: Border.all( color: AppTheme.inkPrimary, width: 4, ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "LEVEL CLEAR!", style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( 5, (i) => Icon( Icons.star, size: 40, color: i < stars ? AppTheme.accentYellow : AppTheme.gridLine, ), ), ), const SizedBox(height: 12), Text( "$stars / 5 STARS", style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 40), SizedBox( width: double.infinity, child: OutlinedButton( style: OutlinedButton.styleFrom( side: const BorderSide( color: AppTheme.inkPrimary, width: 2, ), ), onPressed: () => Navigator.pop(context), child: const Text( "CONTINUE", style: TextStyle( color: AppTheme.inkPrimary, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ); }, ), // Achievement banner Positioned( top: 40, left: 0, right: 0, child: ValueListenableBuilder( valueListenable: _game.achievementNotifier, builder: (context, AchievementDef? ach, _) { if (ach == null) return const SizedBox.shrink(); return Center( child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), border: Border.all( color: AppTheme.accentGreen, width: 2, ), boxShadow: const [ BoxShadow( color: Colors.black26, blurRadius: 6, offset: Offset(0, 3), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.emoji_events, color: AppTheme.accentYellow, size: 20, ), const SizedBox(width: 8), Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( ach.title, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, ), ), Text( '+${ach.rewardStars} stars · ${ach.description}', style: const TextStyle( fontSize: 10, color: AppTheme.inkPrimary, ), ), ], ), ], ), ), ); }, ), ), // Fail Overlay ValueListenableBuilder( valueListenable: _game.failNotifier, builder: (context, bool failed, _) { if (!failed) return const SizedBox.shrink(); return Container( color: Colors.black54, child: Center( child: Container( width: 280, padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: AppTheme.paperBg, border: Border.all( color: AppTheme.inkPrimary, width: 4, ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "TRY AGAIN?", style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), const Text( "The ball left the page.\nDo you want to replay this level?", textAlign: TextAlign.center, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 28), Row( children: [ Expanded( child: OutlinedButton( style: OutlinedButton.styleFrom( side: const BorderSide( color: AppTheme.inkPrimary, width: 2, ), ), onPressed: () { Navigator.pop(context); }, child: const Text( "QUIT", style: TextStyle( color: AppTheme.inkPrimary, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.inkPrimary, elevation: 0, ), onPressed: () { _game.resetLevel(); }, child: const Text( "RETRY", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ), ], ), ], ), ), ), ); }, ), ], ), ), ); } void _showResetConfirmDialog(BuildContext context) { showDialog( context: context, builder: (BuildContext dialogContext) => AlertDialog( backgroundColor: AppTheme.paperBg, title: const Text( "RESET LEVEL?", style: TextStyle( fontWeight: FontWeight.bold, color: AppTheme.inkPrimary, ), ), content: const Text( "This will remove all drawn lines and reset the ball position. Your progress will be lost.", style: TextStyle(color: AppTheme.inkPrimary), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text( "CANCEL", style: TextStyle( color: AppTheme.inkPrimary, fontWeight: FontWeight.bold, ), ), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.inkPrimary, elevation: 0, ), onPressed: () { Navigator.pop(dialogContext); _game.resetLevel(); }, child: const Text( "RESET", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ], ), ); } void _showExitConfirmDialog(BuildContext context) { showDialog( context: context, builder: (BuildContext dialogContext) => AlertDialog( backgroundColor: AppTheme.paperBg, title: const Text( "EXIT LEVEL?", style: TextStyle( fontWeight: FontWeight.bold, color: AppTheme.inkPrimary, ), ), content: const Text( "Are you sure you want to exit? Your current progress will be lost.", style: TextStyle(color: AppTheme.inkPrimary), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text( "CANCEL", style: TextStyle( color: AppTheme.inkPrimary, fontWeight: FontWeight.bold, ), ), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.inkPrimary, elevation: 0, ), onPressed: () { Navigator.pop(dialogContext); Navigator.pop(context); }, child: const Text( "EXIT", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ], ), ); } }