From 7137cd2335156a59e882964c902c5b871e8ae8c1 Mon Sep 17 00:00:00 2001 From: Efril Date: Tue, 23 Jun 2026 21:40:08 +0700 Subject: [PATCH] feat: update ui splash --- .../pages/splash/splash_page.dart | 213 +++++++++--------- 1 file changed, 110 insertions(+), 103 deletions(-) diff --git a/lib/presentation/pages/splash/splash_page.dart b/lib/presentation/pages/splash/splash_page.dart index 25523f7..d0de379 100644 --- a/lib/presentation/pages/splash/splash_page.dart +++ b/lib/presentation/pages/splash/splash_page.dart @@ -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 with TickerProviderStateMixin { late AnimationController _logoController; - late AnimationController _versionController; - late AnimationController _backgroundController; + late AnimationController _taglineController; + late AnimationController _poweredByController; - late Animation _logoAnimation; - late Animation _versionAnimation; - late Animation _backgroundAnimation; + late Animation _logoFadeAnimation; + late Animation _logoScaleAnimation; + late Animation _taglineFadeAnimation; + late Animation _taglineSlideAnimation; + late Animation _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(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _logoController, curve: Curves.elasticOut), + _logoFadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _logoController, curve: Curves.easeOut)); + + _logoScaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _logoController, curve: Curves.easeOutBack), ); - _versionAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _versionController, curve: Curves.easeInOut), + _taglineFadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _taglineController, curve: Curves.easeOut), ); - _backgroundAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _backgroundController, curve: Curves.easeInOut), + _taglineSlideAnimation = + Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( + CurvedAnimation(parent: _taglineController, curve: Curves.easeOut), + ); + + _poweredByFadeAnimation = Tween(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().add(const AuthEvent.fetchCurrentUser()); } @@ -86,14 +89,13 @@ class _SplashPageState extends State 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(); return BlocListener( listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { @@ -104,86 +106,91 @@ class _SplashPageState extends State 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( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - AppColor.primaryWithOpacity(backgroundOpacity), - AppColor.primaryWithOpacity(backgroundOpacity * 0.8), - ], - ), - ), - child: Stack( - children: [ - // Logo di tengah - Center( - 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, + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFFE81E1E), Color(0xFFC40202)], + ), + ), + child: SafeArea( + child: Column( + children: [ + // 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: _logoScaleAnimation.value, + child: child, ), - child: Assets.images.logo.image(fit: BoxFit.cover), - ), + ); + }, + 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, - child: Text( - '${context.lang.version} ${packageInfo.version}+${packageInfo.buildNumber}', - style: AppStyle.md.copyWith( - color: AppColor.textLight, + // Tagline + SlideTransition( + position: _taglineSlideAnimation, + child: FadeTransition( + opacity: _taglineFadeAnimation, + child: Text( + 'Pantau kondisi bisnismu, dari genggaman', + style: AppStyle.lg.copyWith( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w400, + letterSpacing: 0.3, + ), + textAlign: TextAlign.center, ), - 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, + ), + ), + ], + ), ), ), - ], - ), - ); - }, + ), + ], + ), + ), ), ), );