refactor(game-over): split game_over_page into sub-widgets (<150 lines)

Extract GameOverHeader, GameOverStats (+ reusable _StatBox), HingeDivider,
GameOverActions. game_over_page.dart 338 -> 100 lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 11:47:08 +02:00
co-authored by Claude Opus 4.8
parent 2f0d7e1e36
commit bdbd2fcc6a
5 changed files with 240 additions and 289 deletions
+51 -289
View File
@@ -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<GameOverPage> {
@override
Widget build(BuildContext context) {
final Map<String, dynamic>? args =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
final String pokemonImage = args?['pokemonImage'] ?? '';
final String pokemonName = args?['pokemonName'] ?? 'Unknown';
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
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),
),
),
),
],
),
),
),
],
],
),
),
),
),
);
}
}