import 'dart:async'; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import '../../theming/app_theme.dart'; import '../../theming/light_app_theme.dart'; class PostLoginSplash extends StatefulWidget { const PostLoginSplash({super.key, required this.onComplete}); static const String _darkAsset = 'assets/logo/logo-anim-white.json'; static const String _lightAsset = 'assets/logo/logo-anim-red.json'; static LottieComposition? _darkComposition; static LottieComposition? _lightComposition; static Future precache() async { try { _darkComposition ??= await AssetLottie(_darkAsset).load(); _lightComposition ??= await AssetLottie(_lightAsset).load(); } catch (_) {} } final VoidCallback onComplete; @override State createState() => _PostLoginSplashState(); } class _PostLoginSplashState extends State with TickerProviderStateMixin { static const Duration _holdAfterLogo = Duration(milliseconds: 450); late final AnimationController _intro; late final AnimationController _outro; late final AnimationController _logo; late final Animation _introCurve; late final Animation _outroCurve; Timer? _safety; Timer? _hold; LottieComposition? _composition; bool _started = false; bool _outroStarted = false; bool _completed = false; @override void initState() { super.initState(); _intro = AnimationController( vsync: this, duration: const Duration(milliseconds: 900), ); _outro = AnimationController( vsync: this, duration: const Duration(milliseconds: 850), ); _introCurve = CurvedAnimation(parent: _intro, curve: Curves.easeInOut); _outroCurve = CurvedAnimation(parent: _outro, curve: Curves.easeInOutCubic); _logo = AnimationController(vsync: this); _logo.addStatusListener((status) { if (status == AnimationStatus.completed) _scheduleOutro(); }); _safety = Timer(const Duration(seconds: 6), _startOutro); } @override void didChangeDependencies() { super.didChangeDependencies(); if (_started || _outroStarted) return; final isDark = AppTheme.isDarkMode(context); final cached = isDark ? PostLoginSplash._darkComposition : PostLoginSplash._lightComposition; if (cached != null) { _begin(cached); return; } final asset = isDark ? PostLoginSplash._darkAsset : PostLoginSplash._lightAsset; AssetLottie(asset) .load() .then((c) { if (mounted) _begin(c); }) .catchError((_) {}); } void _begin(LottieComposition composition) { if (_started || _outroStarted || !mounted) return; _started = true; _logo.duration = composition.duration * 0.9; setState(() => _composition = composition); _intro.forward(from: 0); _logo.forward(from: 0); } void _scheduleOutro() { if (_outroStarted || _hold != null) return; _hold = Timer(_holdAfterLogo, _startOutro); } void _startOutro() { if (_outroStarted) return; _outroStarted = true; _safety?.cancel(); _hold?.cancel(); _outro.forward().whenComplete(() { if (_completed || !mounted) return; _completed = true; widget.onComplete(); }); } @override void dispose() { _safety?.cancel(); _hold?.cancel(); _intro.dispose(); _outro.dispose(); _logo.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final surface = Theme.of(context).scaffoldBackgroundColor; return AbsorbPointer( child: AnimatedBuilder( animation: Listenable.merge([_introCurve, _outroCurve]), builder: (context, _) { final intro = _introCurve.value; final outro = _outroCurve.value; final background = Color.lerp( LightAppTheme.marianumRed, surface, intro, ); return Opacity( opacity: 1 - outro, child: ColoredBox( color: background ?? surface, child: Center( child: _composition == null ? const SizedBox.shrink() : Opacity( opacity: intro, child: Transform.scale( scale: (0.94 + 0.06 * intro) * (1 + 0.05 * outro), child: Lottie( composition: _composition, controller: _logo, width: 220, height: 220, fit: BoxFit.contain, ), ), ), ), ), ); }, ), ); } }