diff --git a/lib/pantallas/pantalla_principal.dart b/lib/pantallas/pantalla_principal.dart index ade0ad1..0c5b811 100644 --- a/lib/pantallas/pantalla_principal.dart +++ b/lib/pantallas/pantalla_principal.dart @@ -1,13 +1,17 @@ -import 'package:flutter/material.dart'; +import 'dart:math' as math; + import 'package:farolero/l10n/generated/app_localizations.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; + import '../servicios/servicio_perfil_usuario.dart'; import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_ajustes.dart'; -import 'pantalla_seleccion_modo_juego.dart'; import 'pantalla_historial.dart'; import 'pantalla_reglas.dart'; +import 'pantalla_seleccion_modo_juego.dart'; import 'pantalla_unirse.dart'; class PantallaPrincipal extends StatelessWidget { @@ -21,54 +25,47 @@ class PantallaPrincipal extends StatelessWidget { final gamificacion = servicioPerfil.resumenGamificacion; return Scaffold( + backgroundColor: const Color(0xFF05070D), body: FondoFarolero( intenso: true, - child: SafeArea( - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 420), - child: Column( - children: [ - Row( + child: Stack( + children: [ + const Positioned.fill( + child: IgnorePointer(child: CustomPaint(painter: _InicioFondoPainter())), + ), + Positioned.fill( + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withValues(alpha: 0.05), + Colors.transparent, + Colors.black.withValues(alpha: 0.58), + ], + stops: const [0.0, 0.48, 1.0], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ), + ), + SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 18, 20, 24), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 430), + child: Column( children: [ - AvatarFarolero( - texto: perfil.nombre.substring(0, 1).toUpperCase(), - assetPath: perfil.avatarAsset, - size: 48, + _PerfilInicioPremium( + nombre: perfil.nombre, + nick: perfil.nick, + avatarAsset: perfil.avatarAsset, fuego: gamificacion.fuego, medallas: gamificacion.medallas, - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - perfil.nombre, - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - '@${perfil.nick}', - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 3), - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: LinearProgressIndicator( - value: gamificacion.fuego / 100, - minHeight: 4, - color: TemaApp.colorNaranja, - backgroundColor: Colors.black.withValues(alpha: 0.45), - ), - ), - ], - ), - ), - IconButton.filledTonal( - tooltip: l10n.settings, - onPressed: () { + onAjustes: () { Navigator.push( context, MaterialPageRoute( @@ -76,90 +73,554 @@ class PantallaPrincipal extends StatelessWidget { ), ); }, - icon: const Icon(Icons.settings), + ajustesTooltip: l10n.settings, + ).animate().fadeIn(duration: 280.ms).slideY(begin: -0.12), + const SizedBox(height: 42), + _HeroInicioPremium(subtitulo: l10n.subtitle) + .animate() + .fadeIn(delay: 120.ms, duration: 420.ms) + .scale(begin: const Offset(0.92, 0.92)), + const SizedBox(height: 46), + _BotonInicioPremium.primario( + texto: 'Jugar', + icono: Icons.play_arrow_rounded, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const PantallaSeleccionModoJuego(), + ), + ); + }, + ).animate().fadeIn(delay: 240.ms).slideY(begin: 0.16), + const SizedBox(height: 14), + _BotonInicioPremium.secundario( + texto: l10n.joinGame, + icono: Icons.bolt_rounded, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const PantallaUnirse()), + ); + }, + ).animate().fadeIn(delay: 320.ms).slideY(begin: 0.16), + const SizedBox(height: 12), + _BotonInicioPremium.oscuro( + texto: l10n.howToPlay, + icono: Icons.question_mark_rounded, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const PantallaReglas()), + ); + }, + ).animate().fadeIn(delay: 390.ms).slideY(begin: 0.16), + const SizedBox(height: 14), + _AccesoHistorialPremium( + etiqueta: 'Historial', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const PantallaHistorial()), + ); + }, + ).animate().fadeIn(delay: 470.ms).slideY(begin: 0.14), + const SizedBox(height: 28), + Text( + l10n.playersRange, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: TemaApp.colorTextoSecundario.withValues(alpha: 0.82), + letterSpacing: 0.8, + ), ), ], ), - const SizedBox(height: 38), - const LogoFarolero(size: 70), - const SizedBox(height: 12), - Text( - l10n.subtitle, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: TemaApp.colorTexto, - fontSize: 15, - ), - ), - const SizedBox(height: 54), - BotonFarolero( - texto: 'Jugar', - icono: Icons.play_arrow, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PantallaSeleccionModoJuego(), - ), - ); - }, - ), - const SizedBox(height: 12), - BotonFarolero.secundario( - texto: l10n.joinGame, - icono: Icons.bolt, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PantallaUnirse(), - ), - ); - }, - ), - const SizedBox(height: 12), - BotonFarolero.oscuro( - texto: l10n.howToPlay, - icono: Icons.question_mark, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PantallaReglas(), - ), - ); - }, - ), - const SizedBox(height: 18), - Row( - children: [ - Expanded( - child: AccesoFarolero( - etiqueta: 'Historial', - icono: Icons.history, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PantallaHistorial(), - ), - ); - }, - ), - ), - ], - ), - const SizedBox(height: 28), - Text( - l10n.playersRange, - style: Theme.of(context).textTheme.bodySmall, - ), - ], + ), ), ), ), + ], + ), + ), + ); + } +} + +class _PerfilInicioPremium extends StatelessWidget { + final String nombre; + final String nick; + final String? avatarAsset; + final int fuego; + final List medallas; + final VoidCallback onAjustes; + final String ajustesTooltip; + + const _PerfilInicioPremium({ + required this.nombre, + required this.nick, + required this.avatarAsset, + required this.fuego, + required this.medallas, + required this.onAjustes, + required this.ajustesTooltip, + }); + + @override + Widget build(BuildContext context) { + final progreso = (fuego / 100).clamp(0.0, 1.0).toDouble(); + return Container( + padding: const EdgeInsets.fromLTRB(14, 12, 10, 12), + decoration: _decoracionCristal(radius: 28, alpha: 0.82), + child: Row( + children: [ + AvatarFarolero( + texto: nombre.substring(0, 1).toUpperCase(), + assetPath: avatarAsset, + size: 58, + fuego: fuego, + medallas: medallas, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + nombre, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w900, + ), + ), + Text( + '@$nick', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: TemaApp.colorTextoSecundario, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular(999), + child: Stack( + children: [ + Container(height: 8, color: Colors.black.withValues(alpha: 0.55)), + FractionallySizedBox( + widthFactor: progreso, + child: Container( + height: 8, + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFFE53935), + TemaApp.colorNaranja, + TemaApp.colorDorado, + ], + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(width: 10), + IconButton.filledTonal( + tooltip: ajustesTooltip, + onPressed: onAjustes, + style: IconButton.styleFrom( + backgroundColor: TemaApp.colorDorado.withValues(alpha: 0.14), + foregroundColor: TemaApp.colorDorado, + side: BorderSide(color: TemaApp.colorDorado.withValues(alpha: 0.44)), + ), + icon: const Icon(Icons.settings_rounded), + ), + ], + ), + ); + } +} + +class _HeroInicioPremium extends StatelessWidget { + final String subtitulo; + + const _HeroInicioPremium({required this.subtitulo}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 230, + child: Stack( + alignment: Alignment.center, + children: [ + const Positioned.fill( + child: IgnorePointer(child: CustomPaint(painter: _HeroInicioPainter())), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 92, + height: 92, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + TemaApp.colorDorado.withValues(alpha: 0.92), + TemaApp.colorNaranja.withValues(alpha: 0.55), + Colors.black.withValues(alpha: 0.78), + ], + ), + border: Border.all(color: TemaApp.colorDorado, width: 3), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.65), + blurRadius: 42, + spreadRadius: 7, + ), + ], + ), + child: const Icon( + Icons.lightbulb_rounded, + color: Color(0xFF251304), + size: 54, + ), + ).animate(onPlay: (controller) => controller.repeat(reverse: true)).scale( + begin: const Offset(0.98, 0.98), + end: const Offset(1.04, 1.04), + duration: 1400.ms, + curve: Curves.easeInOut, + ), + const SizedBox(height: 12), + const LogoFarolero(size: 64), + const SizedBox(height: 8), + Text( + subtitulo, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorTexto, + fontSize: 17, + fontWeight: FontWeight.w800, + shadows: [ + Shadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.35), + blurRadius: 14, + ), + ], + ), + ), + const SizedBox(height: 4), + Text( + 'Descubr� al impostor antes de que sea tarde', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: TemaApp.colorTextoSecundario, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ); + } +} + +class _BotonInicioPremium extends StatelessWidget { + final String texto; + final IconData icono; + final VoidCallback onPressed; + final LinearGradient gradient; + final Color foreground; + final double height; + final bool hero; + + const _BotonInicioPremium._({ + required this.texto, + required this.icono, + required this.onPressed, + required this.gradient, + required this.foreground, + required this.height, + required this.hero, + }); + + factory _BotonInicioPremium.primario({ + required String texto, + required IconData icono, + required VoidCallback onPressed, + }) { + return _BotonInicioPremium._( + texto: texto, + icono: icono, + onPressed: onPressed, + gradient: const LinearGradient( + colors: [Color(0xFFFFE28A), TemaApp.colorDorado, TemaApp.colorNaranja], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + foreground: const Color(0xFF1C0D03), + height: 72, + hero: true, + ); + } + + factory _BotonInicioPremium.secundario({ + required String texto, + required IconData icono, + required VoidCallback onPressed, + }) { + return _BotonInicioPremium._( + texto: texto, + icono: icono, + onPressed: onPressed, + gradient: const LinearGradient( + colors: [Color(0xFF211730), Color(0xFF121B28)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + foreground: Colors.white, + height: 60, + hero: false, + ); + } + + factory _BotonInicioPremium.oscuro({ + required String texto, + required IconData icono, + required VoidCallback onPressed, + }) { + return _BotonInicioPremium._( + texto: texto, + icono: icono, + onPressed: onPressed, + gradient: const LinearGradient( + colors: [Color(0xFF101A24), Color(0xFF080D13)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + foreground: TemaApp.colorTexto, + height: 60, + hero: false, + ); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(hero ? 26 : 22), + onTap: onPressed, + child: Ink( + height: height, + decoration: BoxDecoration( + gradient: gradient, + borderRadius: BorderRadius.circular(hero ? 26 : 22), + border: Border.all( + color: hero ? const Color(0xFFFFF0B8) : TemaApp.colorDorado.withValues(alpha: 0.34), + ), + boxShadow: [ + BoxShadow( + color: (hero ? TemaApp.colorNaranja : Colors.black).withValues(alpha: hero ? 0.46 : 0.42), + blurRadius: hero ? 34 : 18, + offset: const Offset(0, 12), + ), + ], + ), + child: Row( + children: [ + SizedBox(width: hero ? 70 : 62, child: Icon(icono, color: foreground, size: hero ? 38 : 27)), + Expanded( + child: Text( + texto.toUpperCase(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: foreground, + fontSize: hero ? 28 : 18, + fontWeight: FontWeight.w900, + letterSpacing: hero ? 1.6 : 1.0, + ), + ), + ), + SizedBox(width: hero ? 70 : 62), + ], ), ), ), ); } } + +class _AccesoHistorialPremium extends StatelessWidget { + final String etiqueta; + final VoidCallback onPressed; + + const _AccesoHistorialPremium({required this.etiqueta, required this.onPressed}); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(22), + onTap: onPressed, + child: Ink( + height: 58, + decoration: _decoracionCristal(radius: 22, alpha: 0.72), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.history_rounded, color: TemaApp.colorNaranja), + const SizedBox(width: 10), + Text( + etiqueta.toUpperCase(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorDorado, + fontWeight: FontWeight.w900, + letterSpacing: 1.2, + ), + ), + ], + ), + ), + ), + ); + } +} + +BoxDecoration _decoracionCristal({required double radius, required double alpha}) { + return BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color(0xFF111C29).withValues(alpha: alpha), + const Color(0xFF160C1F).withValues(alpha: alpha - 0.08), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(radius), + border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.42)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.46), + blurRadius: 24, + offset: const Offset(0, 12), + ), + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.14), + blurRadius: 26, + ), + ], + ); +} + +class _InicioFondoPainter extends CustomPainter { + const _InicioFondoPainter(); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..isAntiAlias = true; + paint.shader = const LinearGradient( + colors: [Color(0xFF050A12), Color(0xFF0A1524), Color(0xFF15091D)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(Offset.zero & size); + canvas.drawRect(Offset.zero & size, paint); + + final farol = Offset(size.width * 0.5, size.height * 0.34); + paint.shader = RadialGradient( + colors: [ + TemaApp.colorDorado.withValues(alpha: 0.28), + TemaApp.colorNaranja.withValues(alpha: 0.12), + Colors.transparent, + ], + ).createShader(Rect.fromCircle(center: farol, radius: size.width * 0.78)); + canvas.drawCircle(farol, size.width * 0.78, paint); + paint.shader = null; + + _drawSkyline(canvas, size, paint); + _drawSparks(canvas, size); + } + + void _drawSkyline(Canvas canvas, Size size, Paint paint) { + paint.color = Colors.black.withValues(alpha: 0.38); + final base = size.height * 0.80; + final path = Path() + ..moveTo(0, size.height) + ..lineTo(0, base - 20) + ..lineTo(size.width * 0.14, base - 58) + ..lineTo(size.width * 0.26, base - 24) + ..lineTo(size.width * 0.42, base - 78) + ..lineTo(size.width * 0.58, base - 34) + ..lineTo(size.width * 0.74, base - 74) + ..lineTo(size.width * 0.90, base - 28) + ..lineTo(size.width, base - 48) + ..lineTo(size.width, size.height) + ..close(); + canvas.drawPath(path, paint); + + paint.color = TemaApp.colorNaranja.withValues(alpha: 0.18); + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter(center: Offset(size.width * 0.5, base - 108), width: 24, height: 150), + const Radius.circular(12), + ), + paint, + ); + } + + void _drawSparks(Canvas canvas, Size size) { + final palette = [TemaApp.colorDorado, TemaApp.colorNaranja, const Color(0xFFFFF2C9)]; + for (var i = 0; i < 64; i++) { + final x = (i * 67 % math.max(size.width, 1)).toDouble(); + final y = (i * 113 % math.max(size.height, 1)).toDouble(); + final paint = Paint() + ..isAntiAlias = true + ..color = palette[i % palette.length].withValues(alpha: 0.12 + (i % 4) * 0.06); + canvas.drawCircle(Offset(x, y), 1.2 + (i % 3), paint); + } + } + + @override + bool shouldRepaint(covariant _InicioFondoPainter oldDelegate) => false; +} + +class _HeroInicioPainter extends CustomPainter { + const _HeroInicioPainter(); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height * 0.45); + final paint = Paint()..isAntiAlias = true; + paint.shader = RadialGradient( + colors: [TemaApp.colorNaranja.withValues(alpha: 0.30), Colors.transparent], + ).createShader(Rect.fromCircle(center: center, radius: size.width * 0.54)); + canvas.drawCircle(center, size.width * 0.54, paint); + paint.shader = null; + + for (var i = 0; i < 24; i++) { + final angle = math.pi * 2 * i / 24; + paint.color = (i.isEven ? TemaApp.colorDorado : TemaApp.colorNaranja).withValues(alpha: i.isEven ? 0.14 : 0.08); + canvas.drawPath( + Path() + ..moveTo(center.dx + math.cos(angle - 0.035) * 45, center.dy + math.sin(angle - 0.035) * 45) + ..lineTo(center.dx + math.cos(angle) * size.width * 0.44, center.dy + math.sin(angle) * size.width * 0.44) + ..lineTo(center.dx + math.cos(angle + 0.035) * 45, center.dy + math.sin(angle + 0.035) * 45) + ..close(), + paint, + ); + } + } + + @override + bool shouldRepaint(covariant _HeroInicioPainter oldDelegate) => false; +}