import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/config/app_constants.dart'; import '../../domain/game/game_state.dart'; import '../providers/gen_filter_provider.dart'; import '../providers/game_provider.dart'; import '../providers/navigation_provider.dart'; import '../widgets/pokemon_image.dart'; class GuessPage extends ConsumerStatefulWidget { const GuessPage({Key? key}) : super(key: key); @override ConsumerState createState() => _GuessPageState(); } class _GuessPageState extends ConsumerState { final TextEditingController _guessController = TextEditingController(); bool _started = false; bool _genFilterOpen = false; @override void initState() { super.initState(); // Démarre la partie après le premier frame (le provider est prêt). WidgetsBinding.instance.addPostFrameCallback((_) { if (!_started) { _started = true; ref.read(gameProvider.notifier).startNewGame(); } }); } @override void dispose() { _guessController.dispose(); super.dispose(); } Future _onGuess() async { final result = await ref.read(gameProvider.notifier).submitGuess(_guessController.text); if (!mounted) return; final state = ref.read(gameProvider); switch (result) { case GuessResult.correct: ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(state.isShiny ? '✨ SHINY! You caught ${state.currentPokemon!.formatedName}! (+20 pts) ✨' : 'Correct! You caught ${state.currentPokemon!.formatedName}!'), backgroundColor: state.isShiny ? Colors.amber[800] : Colors.green, )); break; case GuessResult.wrong: ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Wrong guess! Try again.'), backgroundColor: Colors.orange)); break; case GuessResult.gameOver: await _showGameOver(); break; case GuessResult.invalid: break; } _guessController.clear(); } Future _showGameOver() async { final state = ref.read(gameProvider); final playAgain = await Navigator.pushNamed( context, '/game-over', arguments: { 'pokemonName': state.currentPokemon!.formatedName, 'score': state.currentScore, 'streak': state.sessionCorrectCount, 'pokemonImage': state.currentPokemon!.imageUrl, }, ) as bool?; if (!mounted) return; if (playAgain == false) { ref.read(selectedTabProvider.notifier).set(0); // onglet LIST } // true (Try Again), false (Back to Pokédex) et null (geste retour système) // relancent tous une nouvelle partie pour ne pas rester bloqué en game over. await ref.read(gameProvider.notifier).startNewGame(); } String _maskedName(String name) { if (name.length <= 2) return name; // trop court pour masquer utilement return '${name[0]}${List.filled(name.length - 2, '_').join()}${name[name.length - 1]}'; } @override Widget build(BuildContext context) { final state = ref.watch(gameProvider); if (state.status == GameStatus.loading) { return const Center(child: CircularProgressIndicator()); } if (state.currentPokemon == null || state.status == GameStatus.error) { return const Center(child: Text("Error loading Pokémon")); } final pokemon = state.currentPokemon!; final isGuessed = state.status == GameStatus.roundWon; return Container( decoration: const BoxDecoration(color: Color(0xFFC8D1D8)), child: Stack( children: [ Positioned.fill( child: ListView.builder( itemCount: 100, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => Container( height: 4, margin: const EdgeInsets.only(bottom: 4), color: Colors.black.withAlpha(2), ), ), ), SingleChildScrollView( child: Column( children: [ // Silhouette screen Container( height: 250, width: double.infinity, margin: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF3B6EE3), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF1B2333), width: 8), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Padding( padding: const EdgeInsets.all(16.0), child: isGuessed ? PokemonImage( imageUrl: state.isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl, fallbackUrl: pokemon.imageUrl, fit: BoxFit.contain, ) : PokemonImage( imageUrl: state.isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl, fallbackUrl: pokemon.imageUrl, fit: BoxFit.contain, color: state.isShiny ? Colors.yellow[700]! : Colors.black, colorBlendMode: BlendMode.srcIn, ), ), ), Container( color: const Color(0xFF1B2333), width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 8), child: Text( state.isShiny ? "✨ SHINY POKÉMON DETECTED! ✨" : "WHO'S THAT POKÉMON?", textAlign: TextAlign.center, style: TextStyle( color: state.isShiny ? Colors.yellow[400] : Colors.white, fontSize: state.isShiny ? 18 : 22, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), ) ], ), ), // Gen filter Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () => setState(() => _genFilterOpen = !_genFilterOpen), icon: Icon( _genFilterOpen ? Icons.expand_less : Icons.filter_list, color: const Color(0xFF1B2333), ), label: const Text( 'GEN FILTER', style: TextStyle( color: Color(0xFF1B2333), fontWeight: FontWeight.bold, fontSize: 16, letterSpacing: 2, ), ), style: OutlinedButton.styleFrom( side: const BorderSide(color: Color(0xFF1B2333), width: 2), padding: const EdgeInsets.symmetric(vertical: 12), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), ), ), AnimatedSize( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, child: _genFilterOpen ? _GenFilterPanel( selectedGens: ref.watch(genFilterProvider), onToggle: (i) => ref.read(genFilterProvider.notifier).toggle(i), ) : const SizedBox.shrink(), ), ], ), ), const SizedBox(height: 12), // Lives Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(AppConstants.startingLives, (index) { return Icon( index < state.lives ? Icons.favorite : Icons.favorite_border, color: Colors.red, size: 32, ); }), ), const SizedBox(height: 16), // Guess section Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("IDENTIFICATION INPUT", style: TextStyle(color: Colors.black54, fontSize: 12, fontWeight: FontWeight.bold)), const SizedBox(height: 4), if (state.isHintUsed) Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: Colors.amber[100], border: Border.all(color: Colors.amber[600]!, width: 2), borderRadius: BorderRadius.circular(8), ), child: Text( "HINT: ${_maskedName(pokemon.formatedName)}", textAlign: TextAlign.center, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.amber[900], letterSpacing: 4), ), ), Container( decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey[400]!), ), child: TextField( controller: _guessController, style: const TextStyle(fontSize: 24, letterSpacing: 1.5), decoration: const InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), border: InputBorder.none, hintText: 'Enter Pokémon name...', ), onSubmitted: (_) => _onGuess(), ), ), const SizedBox(height: 16), if (isGuessed) SizedBox( width: double.infinity, height: 60, child: ElevatedButton( onPressed: () => ref.read(gameProvider.notifier).loadNextPokemon(), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), child: const Text("CONTINUE", style: TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2)), ), ) else ...[ SizedBox( width: double.infinity, height: 60, child: ElevatedButton( onPressed: _onGuess, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF3B6EE3), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), child: const Text("GUESS!", style: TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2)), ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: (state.isHintUsed || state.hints <= 0) ? null : () => ref.read(gameProvider.notifier).useHint(), icon: const Icon(Icons.lightbulb, color: Colors.black87), label: Text("HINT (${state.hints})", style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)), style: ElevatedButton.styleFrom( backgroundColor: Colors.amber, padding: const EdgeInsets.symmetric(vertical: 16), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( onPressed: state.skips > 0 ? () => ref.read(gameProvider.notifier).useSkip() : null, icon: const Icon(Icons.skip_next, color: Colors.black87), label: Text("SKIP (${state.skips})", style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], padding: const EdgeInsets.symmetric(vertical: 16), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), ), ), ), ], ), ], const SizedBox(height: 24), // Score Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withAlpha(153), border: Border.all(color: Colors.black12), borderRadius: BorderRadius.circular(8), ), child: Column( children: [ const Text("CURRENT SCORE", style: TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold)), Text("${state.currentScore}", style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Color(0xFF3B6EE3))), const Divider(height: 24), Text("PERSONAL BEST: ${state.bestScore}", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87)), ], ), ), ], ), ), const SizedBox(height: 32), ], ), ), ], ), ); } } class _GenFilterPanel extends StatelessWidget { final Set selectedGens; final void Function(int) onToggle; const _GenFilterPanel({required this.selectedGens, required this.onToggle}); static const _genNames = ['Gen I', 'Gen II', 'Gen III', 'Gen IV', 'Gen V', 'Gen VI', 'Gen VII', 'Gen VIII', 'Gen IX']; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(top: 4), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: const Color(0xFF1B2333), width: 2), ), child: Column( children: List.generate(AppConstants.genRanges.length, (i) { final range = AppConstants.genRanges[i]; final isSelected = selectedGens.contains(i); final isLast = i == AppConstants.genRanges.length - 1; return InkWell( onTap: () => onToggle(i), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: isSelected ? const Color(0xFF1B2333).withAlpha(12) : Colors.transparent, border: isLast ? null : Border(bottom: BorderSide(color: Colors.grey.shade200)), ), child: Row( children: [ AnimatedContainer( duration: const Duration(milliseconds: 150), width: 20, height: 20, decoration: BoxDecoration( color: isSelected ? const Color(0xFF3B6EE3) : Colors.transparent, border: Border.all( color: isSelected ? const Color(0xFF3B6EE3) : Colors.grey.shade400, width: 2, ), borderRadius: BorderRadius.circular(3), ), child: isSelected ? const Icon(Icons.check, size: 13, color: Colors.white) : null, ), const SizedBox(width: 12), Expanded( child: Text( _genNames[i], style: TextStyle( fontSize: 16, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? const Color(0xFF1B2333) : Colors.black54, ), ), ), Text( '#${range.$1}–#${range.$2}', style: TextStyle(fontSize: 12, color: Colors.grey.shade500), ), ], ), ), ); }), ), ); } }