192 lines
6.2 KiB
Dart
Raw Normal View History

2025-08-12 15:18:38 +07:00
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
2025-08-16 17:48:49 +07:00
import 'package:flutter_bloc/flutter_bloc.dart';
2025-08-13 14:08:07 +07:00
import 'package:package_info_plus/package_info_plus.dart';
2025-08-12 15:18:38 +07:00
2025-08-16 17:48:49 +07:00
import '../../../application/auth/auth_bloc.dart';
2025-08-13 01:17:00 +07:00
import '../../../common/extension/extension.dart';
2025-08-12 17:15:16 +07:00
import '../../../common/theme/theme.dart';
2025-08-13 14:08:07 +07:00
import '../../../injection.dart';
2025-08-12 17:15:16 +07:00
import '../../components/assets/assets.gen.dart';
import '../../router/app_router.gr.dart';
2025-08-12 16:12:36 +07:00
2025-08-12 15:18:38 +07:00
@RoutePage()
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
2025-08-12 16:12:36 +07:00
class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
late AnimationController _logoController;
late AnimationController _versionController;
late AnimationController _backgroundController;
late Animation<double> _logoAnimation;
late Animation<double> _versionAnimation;
late Animation<double> _backgroundAnimation;
@override
void initState() {
super.initState();
// Initialize animation controllers
_logoController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_versionController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_backgroundController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// Create animations
_logoAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _logoController, curve: Curves.elasticOut),
);
_versionAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _versionController, curve: Curves.easeInOut),
);
_backgroundAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _backgroundController, curve: Curves.easeInOut),
);
// 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();
// Navigate to home screen after all animations complete
await Future.delayed(const Duration(milliseconds: 2000));
if (mounted) {
2025-08-16 17:48:49 +07:00
context.read<AuthBloc>().add(const AuthEvent.fetchCurrentUser());
2025-08-12 16:12:36 +07:00
}
}
@override
void dispose() {
_logoController.dispose();
_versionController.dispose();
_backgroundController.dispose();
super.dispose();
}
2025-08-12 15:18:38 +07:00
@override
Widget build(BuildContext context) {
2025-08-13 14:08:07 +07:00
final packageInfo = getIt<PackageInfo>();
2025-08-16 17:48:49 +07:00
return BlocListener<AuthBloc, AuthState>(
listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) {
if (state.isAuthenticated) {
context.router.replace(const MainRoute());
} else {
context.router.replace(const LoginRoute());
}
},
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),
],
),
2025-08-12 16:12:36 +07:00
),
2025-08-16 17:48:49 +07:00
child: Stack(
children: [
// Logo di tengah
Center(
child: Transform.scale(
scale: logoOpacity,
child: Opacity(
opacity: logoOpacity,
child: Container(
width: 150,
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,
2025-08-12 16:12:36 +07:00
),
2025-08-16 17:48:49 +07:00
child: Assets.images.logo.image(fit: BoxFit.cover),
),
2025-08-12 16:12:36 +07:00
),
),
),
),
2025-08-16 17:48:49 +07:00
// 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,
),
textAlign: TextAlign.center,
),
2025-08-12 16:12:36 +07:00
),
),
),
2025-08-16 17:48:49 +07:00
],
),
);
},
),
2025-08-12 15:18:38 +07:00
),
);
}
}