import 'dart:math'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State with TickerProviderStateMixin { late final AnimationController _logoCtrl; late final AnimationController _textCtrl; late final AnimationController _bubblesCtrl; late final Animation _logoScale; late final Animation _logoOpacity; late final Animation _textSlide; late final Animation _textOpacity; late final Animation _subtitleOpacity; @override void initState() { super.initState(); _logoCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 900), ); _textCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 700), ); _bubblesCtrl = AnimationController( vsync: this, duration: const Duration(seconds: 6), )..repeat(); _logoScale = Tween(begin: 0.2, end: 1.0).animate( CurvedAnimation(parent: _logoCtrl, curve: Curves.elasticOut), ); _logoOpacity = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _logoCtrl, curve: const Interval(0.0, 0.4, curve: Curves.easeIn), ), ); _textSlide = Tween( begin: const Offset(0, 0.4), end: Offset.zero, ).animate(CurvedAnimation(parent: _textCtrl, curve: Curves.easeOut)); _textOpacity = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _textCtrl, curve: const Interval(0.0, 0.6, curve: Curves.easeIn), ), ); _subtitleOpacity = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _textCtrl, curve: const Interval(0.4, 1.0, curve: Curves.easeIn), ), ); _runSequence(); } Future _runSequence() async { await Future.delayed(const Duration(milliseconds: 400)); if (!mounted) return; _logoCtrl.forward(); await Future.delayed(const Duration(milliseconds: 600)); if (!mounted) return; _textCtrl.forward(); await Future.delayed(const Duration(milliseconds: 2200)); if (mounted) context.go('/login'); } @override void dispose() { _logoCtrl.dispose(); _textCtrl.dispose(); _bubblesCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, stops: [0.0, 0.55, 1.0], colors: [ Color(0xFF0A4A38), Color(0xFF0F6E56), Color(0xFF1D9E75), ], ), ), child: Stack( children: [ // Burbujas decorativas animadas AnimatedBuilder( animation: _bubblesCtrl, builder: (_, _) => CustomPaint( painter: _BubblesPainter(_bubblesCtrl.value), size: Size.infinite, ), ), SafeArea( child: Column( children: [ const Spacer(flex: 3), // Logo central ScaleTransition( scale: _logoScale, child: FadeTransition( opacity: _logoOpacity, child: Container( width: 118, height: 118, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(34), border: Border.all( color: Colors.white.withValues(alpha: 0.35), width: 2, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), blurRadius: 24, offset: const Offset(0, 8), ), ], ), child: const Icon( Icons.recycling_rounded, size: 64, color: Colors.white, ), ), ), ), const SizedBox(height: 36), // Nombre de la app SlideTransition( position: _textSlide, child: FadeTransition( opacity: _textOpacity, child: const Text( 'RecolectApp', style: TextStyle( fontSize: 38, fontWeight: FontWeight.w800, color: Colors.white, letterSpacing: -1.0, height: 1.1, ), ), ), ), const SizedBox(height: 10), // Subtítulo FadeTransition( opacity: _subtitleOpacity, child: Text( 'Sistema de Recolección Inteligente', style: TextStyle( fontSize: 14, color: Colors.white.withValues(alpha: 0.8), letterSpacing: 0.4, fontWeight: FontWeight.w400, ), ), ), const Spacer(flex: 3), // Indicador de carga FadeTransition( opacity: _subtitleOpacity, child: const Padding( padding: EdgeInsets.only(bottom: 52), child: _DotsLoader(), ), ), ], ), ), ], ), ), ); } } // ── Loader de tres puntos ──────────────────────────────────────────────────── class _DotsLoader extends StatefulWidget { const _DotsLoader(); @override State<_DotsLoader> createState() => _DotsLoaderState(); } class _DotsLoaderState extends State<_DotsLoader> with SingleTickerProviderStateMixin { late final AnimationController _ctrl; @override void initState() { super.initState(); _ctrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 1400), )..repeat(); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _ctrl, builder: (_, _) { return Row( mainAxisSize: MainAxisSize.min, children: List.generate(3, (i) { final phase = (_ctrl.value - i * 0.2).clamp(0.0, 1.0); final wave = (sin(phase * pi)).clamp(0.0, 1.0); return AnimatedContainer( duration: Duration.zero, margin: const EdgeInsets.symmetric(horizontal: 4), width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withValues(alpha: 0.35 + 0.65 * wave), ), ); }), ); }, ); } } // ── Burbujas decorativas de fondo ──────────────────────────────────────────── class _BubblesPainter extends CustomPainter { final double t; _BubblesPainter(this.t); static const _bubbles = [ _Bubble(0.08, 0.15, 60, 0.0), _Bubble(0.85, 0.08, 90, 0.2), _Bubble(0.72, 0.78, 50, 0.5), _Bubble(0.15, 0.85, 70, 0.7), _Bubble(0.50, 0.05, 40, 0.35), _Bubble(0.92, 0.55, 35, 0.9), ]; @override void paint(Canvas canvas, Size size) { for (final b in _bubbles) { final phase = (t + b.phase) % 1.0; final floatY = sin(phase * 2 * pi) * 12; final paint = Paint() ..color = Colors.white.withValues(alpha: 0.04 + 0.03 * sin(phase * pi)) ..style = PaintingStyle.fill; canvas.drawCircle( Offset(b.xFrac * size.width, b.yFrac * size.height + floatY), b.radius, paint, ); final strokePaint = Paint() ..color = Colors.white.withValues(alpha: 0.07) ..style = PaintingStyle.stroke ..strokeWidth = 1.5; canvas.drawCircle( Offset(b.xFrac * size.width, b.yFrac * size.height + floatY), b.radius, strokePaint, ); } } @override bool shouldRepaint(_BubblesPainter old) => old.t != t; } class _Bubble { final double xFrac, yFrac, radius, phase; const _Bubble(this.xFrac, this.yFrac, this.radius, this.phase); }