Files
Client/lib/view/login/post_login_splash.dart
T

169 lines
4.7 KiB
Dart

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<void> precache() async {
try {
_darkComposition ??= await AssetLottie(_darkAsset).load();
_lightComposition ??= await AssetLottie(_lightAsset).load();
} catch (_) {}
}
final VoidCallback onComplete;
@override
State<PostLoginSplash> createState() => _PostLoginSplashState();
}
class _PostLoginSplashState extends State<PostLoginSplash>
with TickerProviderStateMixin {
static const Duration _holdAfterLogo = Duration(milliseconds: 450);
late final AnimationController _intro;
late final AnimationController _outro;
late final AnimationController _logo;
late final Animation<double> _introCurve;
late final Animation<double> _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,
),
),
),
),
),
);
},
),
);
}
}