169 lines
4.7 KiB
Dart
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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|