feat: update ui splash
Some checks are pending
Build & Deploy iOS to TestFlight / build-and-deploy (push) Waiting to run

This commit is contained in:
Efril 2026-06-23 21:40:08 +07:00
parent b98462ee8c
commit 7137cd2335

View File

@ -1,12 +1,9 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../../application/auth/auth_bloc.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
import '../../../injection.dart';
import '../../components/assets/assets.gen.dart';
import '../../router/app_router.gr.dart';
@ -20,64 +17,70 @@ class SplashPage extends StatefulWidget {
class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
late AnimationController _logoController;
late AnimationController _versionController;
late AnimationController _backgroundController;
late AnimationController _taglineController;
late AnimationController _poweredByController;
late Animation<double> _logoAnimation;
late Animation<double> _versionAnimation;
late Animation<double> _backgroundAnimation;
late Animation<double> _logoFadeAnimation;
late Animation<double> _logoScaleAnimation;
late Animation<double> _taglineFadeAnimation;
late Animation<Offset> _taglineSlideAnimation;
late Animation<double> _poweredByFadeAnimation;
@override
void initState() {
super.initState();
// Initialize animation controllers
_logoController = AnimationController(
duration: const Duration(milliseconds: 1200),
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_versionController = AnimationController(
_taglineController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_backgroundController = AnimationController(
duration: const Duration(milliseconds: 1500),
_poweredByController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
// Create animations
_logoAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _logoController, curve: Curves.elasticOut),
_logoFadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(parent: _logoController, curve: Curves.easeOut));
_logoScaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: _logoController, curve: Curves.easeOutBack),
);
_versionAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _versionController, curve: Curves.easeInOut),
_taglineFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _taglineController, curve: Curves.easeOut),
);
_backgroundAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _backgroundController, curve: Curves.easeInOut),
_taglineSlideAnimation =
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
CurvedAnimation(parent: _taglineController, curve: Curves.easeOut),
);
_poweredByFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _poweredByController, curve: Curves.easeIn),
);
// Start animations
_startAnimations();
}
void _startAnimations() async {
// Start background animation immediately
_backgroundController.forward();
// Wait a bit, then start logo animation
await Future.delayed(const Duration(milliseconds: 300));
if (mounted) _logoController.forward();
// Start version animation after logo starts
await Future.delayed(const Duration(milliseconds: 600));
if (mounted) _versionController.forward();
if (mounted) _taglineController.forward();
// Navigate to home screen after all animations complete
await Future.delayed(const Duration(milliseconds: 2000));
await Future.delayed(const Duration(milliseconds: 400));
if (mounted) _poweredByController.forward();
await Future.delayed(const Duration(milliseconds: 1500));
if (mounted) {
context.read<AuthBloc>().add(const AuthEvent.fetchCurrentUser());
}
@ -86,14 +89,13 @@ class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
@override
void dispose() {
_logoController.dispose();
_versionController.dispose();
_backgroundController.dispose();
_taglineController.dispose();
_poweredByController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final packageInfo = getIt<PackageInfo>();
return BlocListener<AuthBloc, AuthState>(
listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) {
@ -104,86 +106,91 @@ class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
}
},
child: Scaffold(
body: AnimatedBuilder(
animation: Listenable.merge([
_logoController,
_versionController,
_backgroundController,
]),
builder: (context, child) {
// Clamp values to prevent opacity errors
final logoOpacity = _logoAnimation.value.clamp(0.0, 1.0);
final versionOpacity = _versionAnimation.value.clamp(0.0, 1.0);
final backgroundOpacity = _backgroundAnimation.value.clamp(
0.0,
1.0,
);
return Container(
decoration: BoxDecoration(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColor.primaryWithOpacity(backgroundOpacity),
AppColor.primaryWithOpacity(backgroundOpacity * 0.8),
],
colors: [Color(0xFFE81E1E), Color(0xFFC40202)],
),
),
child: Stack(
child: SafeArea(
child: Column(
children: [
// Logo di tengah
Center(
// Main content - Logo & Tagline di tengah
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
AnimatedBuilder(
animation: _logoController,
builder: (context, child) {
return Opacity(
opacity: _logoFadeAnimation.value.clamp(0.0, 1.0),
child: Transform.scale(
scale: logoOpacity,
child: Opacity(
opacity: logoOpacity,
child: Container(
width: 200,
height: 150,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 30,
offset: const Offset(0, 15),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(
AppValue.radius,
),
child: Assets.images.logo.image(fit: BoxFit.cover),
),
),
scale: _logoScaleAnimation.value,
child: child,
),
);
},
child: SizedBox(
width: 180,
child: Assets.images.logo.image(fit: BoxFit.contain),
),
),
const SizedBox(height: 32),
// Version di bagian bawah
Positioned(
bottom: 60,
left: 0,
right: 0,
child: Transform.translate(
offset: Offset(0, 20 * (1 - versionOpacity)),
child: Opacity(
opacity: versionOpacity,
// Tagline
SlideTransition(
position: _taglineSlideAnimation,
child: FadeTransition(
opacity: _taglineFadeAnimation,
child: Text(
'${context.lang.version} ${packageInfo.version}+${packageInfo.buildNumber}',
style: AppStyle.md.copyWith(
color: AppColor.textLight,
'Pantau kondisi bisnismu, dari genggaman',
style: AppStyle.lg.copyWith(
color: AppColor.white.withOpacity(0.9),
fontWeight: FontWeight.w400,
letterSpacing: 0.3,
),
textAlign: TextAlign.center,
),
),
),
],
),
),
// Powered by di bawah
FadeTransition(
opacity: _poweredByFadeAnimation,
child: Padding(
padding: const EdgeInsets.only(bottom: 24),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: AppStyle.sm.copyWith(
color: AppColor.white.withOpacity(0.7),
),
children: [
const TextSpan(text: 'powered by '),
TextSpan(
text: 'PT Selalu Ada Keberuntungan',
style: AppStyle.sm.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w700,
),
),
],
),
);
},
),
),
),
],
),
),
),
),
);