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:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../../application/auth/auth_bloc.dart'; import '../../../application/auth/auth_bloc.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart'; import '../../../common/theme/theme.dart';
import '../../../injection.dart';
import '../../components/assets/assets.gen.dart'; import '../../components/assets/assets.gen.dart';
import '../../router/app_router.gr.dart'; import '../../router/app_router.gr.dart';
@ -20,64 +17,70 @@ class SplashPage extends StatefulWidget {
class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin { class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
late AnimationController _logoController; late AnimationController _logoController;
late AnimationController _versionController; late AnimationController _taglineController;
late AnimationController _backgroundController; late AnimationController _poweredByController;
late Animation<double> _logoAnimation; late Animation<double> _logoFadeAnimation;
late Animation<double> _versionAnimation; late Animation<double> _logoScaleAnimation;
late Animation<double> _backgroundAnimation; late Animation<double> _taglineFadeAnimation;
late Animation<Offset> _taglineSlideAnimation;
late Animation<double> _poweredByFadeAnimation;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Initialize animation controllers
_logoController = AnimationController( _logoController = AnimationController(
duration: const Duration(milliseconds: 1200), duration: const Duration(milliseconds: 1000),
vsync: this, vsync: this,
); );
_versionController = AnimationController( _taglineController = AnimationController(
duration: const Duration(milliseconds: 800), duration: const Duration(milliseconds: 800),
vsync: this, vsync: this,
); );
_backgroundController = AnimationController( _poweredByController = AnimationController(
duration: const Duration(milliseconds: 1500), duration: const Duration(milliseconds: 600),
vsync: this, vsync: this,
); );
// Create animations _logoFadeAnimation = Tween<double>(
_logoAnimation = Tween<double>(begin: 0.0, end: 1.0).animate( begin: 0.0,
CurvedAnimation(parent: _logoController, curve: Curves.elasticOut), 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( _taglineFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _versionController, curve: Curves.easeInOut), CurvedAnimation(parent: _taglineController, curve: Curves.easeOut),
); );
_backgroundAnimation = Tween<double>(begin: 0.0, end: 1.0).animate( _taglineSlideAnimation =
CurvedAnimation(parent: _backgroundController, curve: Curves.easeInOut), 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(); _startAnimations();
} }
void _startAnimations() async { void _startAnimations() async {
// Start background animation immediately
_backgroundController.forward();
// Wait a bit, then start logo animation
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
if (mounted) _logoController.forward(); if (mounted) _logoController.forward();
// Start version animation after logo starts
await Future.delayed(const Duration(milliseconds: 600)); 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: 400));
await Future.delayed(const Duration(milliseconds: 2000)); if (mounted) _poweredByController.forward();
await Future.delayed(const Duration(milliseconds: 1500));
if (mounted) { if (mounted) {
context.read<AuthBloc>().add(const AuthEvent.fetchCurrentUser()); context.read<AuthBloc>().add(const AuthEvent.fetchCurrentUser());
} }
@ -86,14 +89,13 @@ class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
@override @override
void dispose() { void dispose() {
_logoController.dispose(); _logoController.dispose();
_versionController.dispose(); _taglineController.dispose();
_backgroundController.dispose(); _poweredByController.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final packageInfo = getIt<PackageInfo>();
return BlocListener<AuthBloc, AuthState>( return BlocListener<AuthBloc, AuthState>(
listenWhen: (previous, current) => previous.status != current.status, listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) { listener: (context, state) {
@ -104,86 +106,91 @@ class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
} }
}, },
child: Scaffold( child: Scaffold(
body: AnimatedBuilder( body: Container(
animation: Listenable.merge([ width: double.infinity,
_logoController, height: double.infinity,
_versionController, decoration: const BoxDecoration(
_backgroundController, gradient: LinearGradient(
]), begin: Alignment.topCenter,
builder: (context, child) { end: Alignment.bottomCenter,
// Clamp values to prevent opacity errors colors: [Color(0xFFE81E1E), Color(0xFFC40202)],
final logoOpacity = _logoAnimation.value.clamp(0.0, 1.0); ),
final versionOpacity = _versionAnimation.value.clamp(0.0, 1.0); ),
final backgroundOpacity = _backgroundAnimation.value.clamp( child: SafeArea(
0.0, child: Column(
1.0, children: [
); // Main content - Logo & Tagline di tengah
Expanded(
return Container( child: Column(
decoration: BoxDecoration( mainAxisAlignment: MainAxisAlignment.center,
gradient: LinearGradient( children: [
begin: Alignment.topCenter, // Logo
end: Alignment.bottomCenter, AnimatedBuilder(
colors: [ animation: _logoController,
AppColor.primaryWithOpacity(backgroundOpacity), builder: (context, child) {
AppColor.primaryWithOpacity(backgroundOpacity * 0.8), return Opacity(
], opacity: _logoFadeAnimation.value.clamp(0.0, 1.0),
), child: Transform.scale(
), scale: _logoScaleAnimation.value,
child: Stack( child: child,
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,
), ),
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 // Tagline
Positioned( SlideTransition(
bottom: 60, position: _taglineSlideAnimation,
left: 0, child: FadeTransition(
right: 0, opacity: _taglineFadeAnimation,
child: Transform.translate( child: Text(
offset: Offset(0, 20 * (1 - versionOpacity)), 'Pantau kondisi bisnismu, dari genggaman',
child: Opacity( style: AppStyle.lg.copyWith(
opacity: versionOpacity, color: AppColor.white.withOpacity(0.9),
child: Text( fontWeight: FontWeight.w400,
'${context.lang.version} ${packageInfo.version}+${packageInfo.buildNumber}', letterSpacing: 0.3,
style: AppStyle.md.copyWith( ),
color: AppColor.textLight, 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,
),
),
],
),
), ),
), ),
], ),
), ],
); ),
}, ),
), ),
), ),
); );