diff --git a/lib/presentation/pages/game_over_page.dart b/lib/presentation/pages/game_over_page.dart index 0248ef3..3e8088f 100644 --- a/lib/presentation/pages/game_over_page.dart +++ b/lib/presentation/pages/game_over_page.dart @@ -2,8 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/config/app_constants.dart'; import '../providers/repository_provider.dart'; -import '../widgets/pokemon_image.dart'; +import '../widgets/game_over/game_over_header.dart'; +import '../widgets/game_over/game_over_stats.dart'; +import '../widgets/game_over/game_over_actions.dart'; +import '../widgets/game_over/hinge_divider.dart'; +/// Écran de fin de partie. Affiche le Pokémon manqué, les stats, et propose de rejouer. class GameOverPage extends ConsumerStatefulWidget { const GameOverPage({Key? key}) : super(key: key); @@ -33,306 +37,64 @@ class _GameOverPageState extends ConsumerState { @override Widget build(BuildContext context) { - final Map? args = - ModalRoute.of(context)!.settings.arguments as Map?; - - final String pokemonImage = args?['pokemonImage'] ?? ''; - final String pokemonName = args?['pokemonName'] ?? 'Unknown'; + final args = ModalRoute.of(context)!.settings.arguments as Map?; + final pokemonImage = args?['pokemonImage'] ?? ''; + final pokemonName = args?['pokemonName'] ?? 'Unknown'; final int streak = args?['streak'] ?? 0; final int score = args?['score'] ?? 0; - - // Pad streak with zeroes to 3 digits as in mockup (e.g. 004) - final String streakText = streak.toString().padLeft(3, '0'); - // Define color palette from mockup - const Color pokedexRed = Color(0xFFD32F2F); - const Color darkRed = Color(0xFF9E1B1B); - const Color silverBg = Color(0xFFC8D1D8); - const Color messageBoxBg = Color(0xFF1B2333); - const Color statBoxBg = Color(0xFFD9E0E5); // slightly lighter/different silver for stats - const Color tryAgainBtn = Color(0xFF2962FF); // Blue - const Color backBtn = Color(0xFFA66A00); // Brown + const darkRed = Color(0xFF9E1B1B); + const silverBg = Color(0xFFC8D1D8); + const messageBoxBg = Color(0xFF1B2333); return Scaffold( - backgroundColor: pokedexRed, - body: _isLoading - ? const Center(child: CircularProgressIndicator(color: Colors.white)) - : SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - // Top Box: Pokemon Silhouette & GAME OVER - Container( - decoration: BoxDecoration( - color: darkRed, // Border color - border: Border.all(color: darkRed, width: 4), - ), - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16), - color: silverBg, - child: Column( - children: [ - // GAME OVER Banner - Container( - color: darkRed, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), - child: const Text( - "GAME OVER", - style: TextStyle( - fontSize: 26, - color: Colors.yellow, - fontWeight: FontWeight.bold, - letterSpacing: 2, - shadows: [ - Shadow( - offset: Offset(1.5, 1.5), - color: Colors.black, - ), - ], - ), - ), - ), - const SizedBox(height: 16), - // Pokemon Image and Name - if (pokemonImage.isNotEmpty) - SizedBox( - height: 140, - child: PokemonImage( - imageUrl: pokemonImage, - fit: BoxFit.contain, - ), - ), - const SizedBox(height: 12), - Text( - "It was $pokemonName!", - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Color(0xFF1B2333), - letterSpacing: 1, - ), - ), - const SizedBox(height: 8), - ], - ), - ), - ), - - // Divider between boxes - Padding( - padding: const EdgeInsets.symmetric(vertical: 24.0), - child: Row( - children: [ - Expanded( - child: Container(height: 2, color: darkRed), - ), - const SizedBox(width: 8), - Row( - mainAxisSize: MainAxisSize.min, - children: List.generate(3, (index) => + backgroundColor: const Color(0xFFD32F2F), + body: _isLoading + ? const Center(child: CircularProgressIndicator(color: Colors.white)) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + GameOverHeader(pokemonImage: pokemonImage, pokemonName: pokemonName), + const HingeDivider(), + Container( + decoration: BoxDecoration(color: darkRed, border: Border.all(color: darkRed, width: 4)), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + color: silverBg, + child: Column( + children: [ Container( - width: 8, - height: 8, - margin: const EdgeInsets.symmetric(horizontal: 4), - decoration: const BoxDecoration( - color: darkRed, - shape: BoxShape.circle, + width: double.infinity, + color: messageBoxBg, + padding: const EdgeInsets.all(24), + child: const Text( + "\"Looks like your journey\nends here. You've run out\nof energy!\"", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white, fontSize: 18, height: 1.5), ), - ) - ), + ), + const SizedBox(height: 16), + GameOverStats( + streak: streak.toString().padLeft(3, '0'), + seen: "$_seenCount/${AppConstants.totalPokemon}", + score: "$score", + ), + const SizedBox(height: 16), + GameOverActions( + onTryAgain: () => Navigator.pop(context, true), + onBack: () => Navigator.pop(context, false), + ), + ], ), - const SizedBox(width: 8), - Expanded( - child: Container(height: 2, color: darkRed), - ), - ], - ), - ), - - // Bottom Box: Message, Stats, Buttons - Container( - decoration: BoxDecoration( - color: darkRed, - border: Border.all(color: darkRed, width: 4), - ), - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - color: silverBg, - child: Column( - children: [ - // Message Box - Container( - width: double.infinity, - color: messageBoxBg, - padding: const EdgeInsets.all(24), - child: const Text( - "\"Looks like your journey\nends here. You've run out\nof energy!\"", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.normal, - height: 1.5, - ), - ), - ), - const SizedBox(height: 16), - - // Stats Row - Row( - children: [ - Expanded( - child: Container( - color: statBoxBg, - padding: const EdgeInsets.symmetric(vertical: 12), - child: Column( - children: [ - const Text( - "STREAK", - style: TextStyle( - color: Colors.red, - fontSize: 10, - fontWeight: FontWeight.bold, - letterSpacing: 1, - ), - ), - const SizedBox(height: 4), - Text( - streakText, - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Container( - color: statBoxBg, - padding: const EdgeInsets.symmetric(vertical: 12), - child: Column( - children: [ - const Text( - "SEEN", - style: TextStyle( - color: Colors.red, - fontSize: 10, - fontWeight: FontWeight.bold, - letterSpacing: 1, - ), - ), - const SizedBox(height: 4), - Text( - "$_seenCount/${AppConstants.totalPokemon}", - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Container( - color: statBoxBg, - padding: const EdgeInsets.symmetric(vertical: 12), - child: Column( - children: [ - const Text( - "SCORE", - style: TextStyle( - color: Colors.red, - fontSize: 10, - fontWeight: FontWeight.bold, - letterSpacing: 1, - ), - ), - const SizedBox(height: 4), - Text( - "$score", - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 16), - - // Try Again Button - SizedBox( - width: double.infinity, - height: 60, - child: ElevatedButton.icon( - onPressed: () { - Navigator.pop(context, true); - }, - icon: const Icon(Icons.refresh, color: Colors.white, size: 24), - label: const Text( - "TRY AGAIN", - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - letterSpacing: 2, - ), - ), - style: ElevatedButton.styleFrom( - backgroundColor: tryAgainBtn, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - ), - ), - ), - const SizedBox(height: 16), - - // Back to Pokedex Button - SizedBox( - width: double.infinity, - height: 60, - child: ElevatedButton.icon( - onPressed: () { - Navigator.pop(context, false); - }, - icon: const Icon(Icons.menu_book, color: Colors.white, size: 24), - label: const Text( - "BACK TO POKEDEX", - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - letterSpacing: 2, - ), - ), - style: ElevatedButton.styleFrom( - backgroundColor: backBtn, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - ), - ), - ), - ], ), ), - ), - ], + ], + ), ), ), - ), ); } } diff --git a/lib/presentation/widgets/game_over/game_over_actions.dart b/lib/presentation/widgets/game_over/game_over_actions.dart new file mode 100644 index 0000000..a847a11 --- /dev/null +++ b/lib/presentation/widgets/game_over/game_over_actions.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +/// Boutons d'action de l'écran game over : rejouer ou retourner au Pokédex. +class GameOverActions extends StatelessWidget { + final VoidCallback onTryAgain; + final VoidCallback onBack; + + const GameOverActions({super.key, required this.onTryAgain, required this.onBack}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _button(label: "TRY AGAIN", icon: Icons.refresh, color: const Color(0xFF2962FF), onPressed: onTryAgain), + const SizedBox(height: 16), + _button(label: "BACK TO POKEDEX", icon: Icons.menu_book, color: const Color(0xFFA66A00), onPressed: onBack), + ], + ); + } + + Widget _button({ + required String label, + required IconData icon, + required Color color, + required VoidCallback onPressed, + }) { + return SizedBox( + width: double.infinity, + height: 60, + child: ElevatedButton.icon( + onPressed: onPressed, + icon: Icon(icon, color: Colors.white, size: 24), + label: Text( + label, + style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 2), + ), + style: ElevatedButton.styleFrom( + backgroundColor: color, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/game_over/game_over_header.dart b/lib/presentation/widgets/game_over/game_over_header.dart new file mode 100644 index 0000000..a7a31cc --- /dev/null +++ b/lib/presentation/widgets/game_over/game_over_header.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import '../pokemon_image.dart'; + +/// Bloc supérieur de l'écran game over : bannière "GAME OVER", image et nom du Pokémon. +class GameOverHeader extends StatelessWidget { + final String pokemonImage; + final String pokemonName; + + const GameOverHeader({super.key, required this.pokemonImage, required this.pokemonName}); + + static const _darkRed = Color(0xFF9E1B1B); + static const _silverBg = Color(0xFFC8D1D8); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration(color: _darkRed, border: Border.all(color: _darkRed, width: 4)), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16), + color: _silverBg, + child: Column( + children: [ + Container( + color: _darkRed, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + child: const Text( + "GAME OVER", + style: TextStyle( + fontSize: 26, + color: Colors.yellow, + fontWeight: FontWeight.bold, + letterSpacing: 2, + shadows: [Shadow(offset: Offset(1.5, 1.5), color: Colors.black)], + ), + ), + ), + const SizedBox(height: 16), + if (pokemonImage.isNotEmpty) + SizedBox(height: 140, child: PokemonImage(imageUrl: pokemonImage, fit: BoxFit.contain)), + const SizedBox(height: 12), + Text( + "It was $pokemonName!", + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Color(0xFF1B2333), + letterSpacing: 1, + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/game_over/game_over_stats.dart b/lib/presentation/widgets/game_over/game_over_stats.dart new file mode 100644 index 0000000..c6818a4 --- /dev/null +++ b/lib/presentation/widgets/game_over/game_over_stats.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +/// Rangée de statistiques de fin de partie (STREAK / SEEN / SCORE). +class GameOverStats extends StatelessWidget { + final String streak; + final String seen; + final String score; + + const GameOverStats({super.key, required this.streak, required this.seen, required this.score}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded(child: _StatBox(label: "STREAK", value: streak)), + const SizedBox(width: 16), + Expanded(child: _StatBox(label: "SEEN", value: seen)), + const SizedBox(width: 16), + Expanded(child: _StatBox(label: "SCORE", value: score)), + ], + ); + } +} + +/// Une case de statistique : un libellé rouge au-dessus d'une valeur. +class _StatBox extends StatelessWidget { + final String label; + final String value; + + const _StatBox({required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + return Container( + color: const Color(0xFFD9E0E5), + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + children: [ + Text( + label, + style: const TextStyle(color: Colors.red, fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: 1), + ), + const SizedBox(height: 4), + Text( + value, + style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/game_over/hinge_divider.dart b/lib/presentation/widgets/game_over/hinge_divider.dart new file mode 100644 index 0000000..4a327de --- /dev/null +++ b/lib/presentation/widgets/game_over/hinge_divider.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +/// Séparateur décoratif (deux traits + trois points) entre les blocs de l'écran game over. +class HingeDivider extends StatelessWidget { + const HingeDivider({super.key}); + + static const _darkRed = Color(0xFF9E1B1B); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: Row( + children: [ + Expanded(child: Container(height: 2, color: _darkRed)), + const SizedBox(width: 8), + Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 3, + (index) => Container( + width: 8, + height: 8, + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: const BoxDecoration(color: _darkRed, shape: BoxShape.circle), + ), + ), + ), + const SizedBox(width: 8), + Expanded(child: Container(height: 2, color: _darkRed)), + ], + ), + ); + } +}