El Impostor v0.1 — app Flutter completa
Juego de deducción social para 3-20 jugadores. Modo un solo móvil completamente funcional. 1000 palabras en 10 categorías. Notas privadas, votación, adivinanza, revancha. Material 3 dark theme. Package: es.freetimelab.elimpostor
This commit is contained in:
325
lib/pantallas/pantalla_ver_palabra.dart
Normal file
325
lib/pantallas/pantalla_ver_palabra.dart
Normal file
@@ -0,0 +1,325 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../modelos/palabra.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
import 'pantalla_debate.dart';
|
||||
|
||||
class PantallaVerPalabra extends StatefulWidget {
|
||||
const PantallaVerPalabra({super.key});
|
||||
|
||||
@override
|
||||
State<PantallaVerPalabra> createState() => _PantallaVerPalabraState();
|
||||
}
|
||||
|
||||
class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
|
||||
final Set<String> _hanVisto = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
|
||||
final todosHanVisto =
|
||||
partida.jugadores.every((j) => _hanVisto.contains(j.id));
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Ver tu palabra'),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Cada jugador debe ver su palabra en secreto',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Ronda ${partida.rondaActual}',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: partida.jugadores.length,
|
||||
itemBuilder: (context, index) {
|
||||
final jugador = partida.jugadores[index];
|
||||
final haVisto = _hanVisto.contains(jugador.id);
|
||||
return Card(
|
||||
color: haVisto
|
||||
? TemaApp.colorVerde.withValues(alpha: 0.2)
|
||||
: TemaApp.colorTarjeta,
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: haVisto
|
||||
? TemaApp.colorVerde
|
||||
: TemaApp.colorAcento,
|
||||
child: Text(
|
||||
haVisto ? '✓' : '${index + 1}',
|
||||
style:
|
||||
const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
title: Text(jugador.nombre),
|
||||
subtitle: Text(
|
||||
haVisto ? 'Ya ha visto su palabra' : 'Pulsa para ver',
|
||||
),
|
||||
trailing: haVisto
|
||||
? const Icon(Icons.check_circle,
|
||||
color: TemaApp.colorVerde)
|
||||
: const Icon(Icons.visibility,
|
||||
color: TemaApp.colorTextoSecundario),
|
||||
onTap: haVisto
|
||||
? null
|
||||
: () => _mostrarPalabra(context, jugador.id),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: todosHanVisto
|
||||
? () {
|
||||
estado.iniciarDebate();
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const PantallaDebate(),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.forum),
|
||||
label: Text(todosHanVisto
|
||||
? 'Todos han visto → Iniciar debate'
|
||||
: 'Faltan ${partida.jugadores.length - _hanVisto.length} jugadores'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _mostrarPalabra(BuildContext context, String jugadorId) {
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final partida = estado.partida!;
|
||||
final jugador = partida.jugadores.firstWhere((j) => j.id == jugadorId);
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => _PantallaRevelarPalabra(
|
||||
nombre: jugador.nombre,
|
||||
esImpostor: jugador.esImpostor,
|
||||
palabra: partida.palabraSecreta,
|
||||
pistaActiva: partida.config.pistaImpostor,
|
||||
categoria: partida.categoriaReal,
|
||||
onVisto: () {
|
||||
setState(() => _hanVisto.add(jugadorId));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PantallaRevelarPalabra extends StatefulWidget {
|
||||
final String nombre;
|
||||
final bool esImpostor;
|
||||
final String palabra;
|
||||
final bool pistaActiva;
|
||||
final String categoria;
|
||||
final VoidCallback onVisto;
|
||||
|
||||
const _PantallaRevelarPalabra({
|
||||
required this.nombre,
|
||||
required this.esImpostor,
|
||||
required this.palabra,
|
||||
required this.pistaActiva,
|
||||
required this.categoria,
|
||||
required this.onVisto,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_PantallaRevelarPalabra> createState() =>
|
||||
_PantallaRevelarPalabraState();
|
||||
}
|
||||
|
||||
class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
bool _manteniendo = false;
|
||||
bool _visto = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(widget.nombre)),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.nombre,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Zona de revelación
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(32),
|
||||
decoration: BoxDecoration(
|
||||
color: _manteniendo
|
||||
? (widget.esImpostor
|
||||
? TemaApp.colorAcento.withValues(alpha: 0.3)
|
||||
: TemaApp.colorVerde.withValues(alpha: 0.3))
|
||||
: TemaApp.colorTarjeta,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: _manteniendo
|
||||
? (widget.esImpostor
|
||||
? TemaApp.colorAcento
|
||||
: TemaApp.colorVerde)
|
||||
: Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: _manteniendo
|
||||
? Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.esImpostor ? '🎭' : '🔍',
|
||||
style: const TextStyle(fontSize: 48),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
widget.esImpostor
|
||||
? '¡Eres el impostor!'
|
||||
: 'Tu palabra es:',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
color: widget.esImpostor
|
||||
? TemaApp.colorAcento
|
||||
: TemaApp.colorVerde,
|
||||
),
|
||||
),
|
||||
if (!widget.esImpostor) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
widget.palabra,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineLarge
|
||||
?.copyWith(
|
||||
fontSize: 32,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
if (widget.esImpostor && widget.pistaActiva) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Pista: ${BancoPalabras.nombreBonitoCategoria(widget.categoria)}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
const Text('🔒', style: TextStyle(fontSize: 48)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Mantén pulsado para ver tu palabra',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Asegúrate de que nadie más mira',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Botón mantener pulsado
|
||||
GestureDetector(
|
||||
onLongPressStart: (_) {
|
||||
setState(() {
|
||||
_manteniendo = true;
|
||||
_visto = true;
|
||||
});
|
||||
},
|
||||
onLongPressEnd: (_) {
|
||||
setState(() => _manteniendo = false);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: _manteniendo
|
||||
? [TemaApp.colorNaranja, TemaApp.colorAcento]
|
||||
: [TemaApp.colorAcento, TemaApp.colorAcento],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
_manteniendo
|
||||
? '👁️ Mostrando...'
|
||||
: '👆 Mantén pulsado para ver',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
if (_visto)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
widget.onVisto();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(Icons.check),
|
||||
label: const Text('He visto mi palabra'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user