import 'package:flutter/material.dart'; import 'season_title_screen.dart'; Widget _defaultNextScreen() => const SeasonTitleScreen(); /// Logo-assembly splash: four glossy blocks fly in to form a 2x2 mark, the /// wordmark fades in, then we hand off. SaveRepository is already opened in /// main() so this doubles as perceived-zero loading time. class SplashScreen extends StatefulWidget { const SplashScreen({super.key, this.nextScreen = _defaultNextScreen}); /// Built when the splash finishes; the season title card task repoints /// the default. final Widget Function() nextScreen; @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State with SingleTickerProviderStateMixin { late final AnimationController _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 1900), )..addStatusListener((status) { if (status == AnimationStatus.completed && mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => widget.nextScreen()), ); } }); /// (color, fly-in direction unit, 2x2 slot unit) per block. static const _blocks = [ (Color(0xFFFF7EB3), Offset(-1.2, -0.4), Offset(-0.5, -0.5)), (Color(0xFFFFD166), Offset(1.2, -0.4), Offset(0.5, -0.5)), (Color(0xFF6FCDF5), Offset(-1.2, 0.6), Offset(-0.5, 0.5)), (Color(0xFF7EDB9C), Offset(1.2, 0.6), Offset(0.5, 0.5)), ]; @override void initState() { super.initState(); _c.forward(); } @override void dispose() { _c.dispose(); super.dispose(); } @override Widget build(BuildContext context) { const blockSize = 46.0; const gap = 3.0; return Scaffold( backgroundColor: const Color(0xFF0E1430), body: SizedBox.expand( child: AnimatedBuilder( animation: _c, builder: (context, _) { final titleT = const Interval(0.60, 0.88, curve: Curves.easeOut) .transform(_c.value); return Stack( alignment: Alignment.center, children: [ for (var i = 0; i < _blocks.length; i++) _block(i, blockSize, gap), Transform.translate( offset: Offset(0, 78 + 12 * (1 - titleT)), child: Opacity( opacity: titleT, child: const Text( 'BLOCK SEASONS', style: TextStyle( fontSize: 26, fontWeight: FontWeight.w900, letterSpacing: 4, color: Colors.white, ), ), ), ), ], ); }, ), ), ); } Widget _block(int i, double size, double gap) { final (color, from, to) = _blocks[i]; final t = Interval(0.06 * i, 0.45 + 0.06 * i, curve: Curves.easeOutBack) .transform(_c.value); final begin = Offset(from.dx * 160, from.dy * 280); final end = Offset(to.dx * (size + gap), to.dy * (size + gap)); final pos = Offset.lerp(begin, end, t)!; return Transform.translate( offset: pos, child: Container( width: size, height: size, decoration: BoxDecoration( borderRadius: BorderRadius.circular(11), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color.lerp(color, Colors.white, 0.28)!, color, Color.lerp(color, Colors.black, 0.22)!, ], ), boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.55), blurRadius: 18, ), ], ), ), ); } }