diff --git a/lib/features/auth/presentation/pages/splash_screen.dart b/lib/features/auth/presentation/pages/splash_screen.dart new file mode 100644 index 0000000..68763cb --- /dev/null +++ b/lib/features/auth/presentation/pages/splash_screen.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:monitoring_jamur/core/theme/app_theme.dart'; +import 'package:monitoring_jamur/core/session/user_session.dart'; +import 'package:monitoring_jamur/features/auth/presentation/pages/login_page.dart'; +import 'package:monitoring_jamur/features/home/presentation/pages/main_screen.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State with SingleTickerProviderStateMixin { + late AnimationController _logoController; + late Animation _logoScale; + late Animation _logoOpacity; + + @override + void initState() { + super.initState(); + _logoController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _logoScale = Tween(begin: 0.5, end: 1.0).animate( + CurvedAnimation(parent: _logoController, curve: Curves.elasticOut), + ); + _logoOpacity = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _logoController, curve: Curves.easeIn), + ); + + _logoController.forward(); + + // Navigate to next screen after animation + Future.delayed(const Duration(milliseconds: 3500), () { + _checkSessionAndNavigate(); + }); + } + + void _checkSessionAndNavigate() { + if (!mounted) return; + + Widget nextScreen = UserSession.isLoggedIn ? const MainScreen() : const LoginPage(); + + Navigator.of(context).pushReplacement( + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => nextScreen, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition(opacity: animation, child: child); + }, + transitionDuration: const Duration(milliseconds: 800), + ), + ); + } + + @override + void dispose() { + _logoController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppTheme.backgroundBeige, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ScaleTransition( + scale: _logoScale, + child: FadeTransition( + opacity: _logoOpacity, + child: Image.asset( + 'lib/assets/mushroom.png', + height: 150, + ), + ), + ), + const SizedBox(height: 32), + _buildAnimatedTextLine("mushrooms", 1000), + const SizedBox(height: 8), + _buildAnimatedTextLine("monitoring apps", 1800, isSecondary: true), + ], + ), + ), + ); + } + + Widget _buildAnimatedTextLine(String text, int startDelayMs, {bool isSecondary = false}) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: text.split('').asMap().entries.map((entry) { + return AnimatedCharacter( + char: entry.value, + delay: Duration(milliseconds: startDelayMs + (entry.key * 70)), + isSecondary: isSecondary, + ); + }).toList(), + ); + } +} + +class AnimatedCharacter extends StatefulWidget { + final String char; + final Duration delay; + final bool isSecondary; + + const AnimatedCharacter({ + super.key, + required this.char, + required this.delay, + this.isSecondary = false, + }); + + @override + State createState() => _AnimatedCharacterState(); +} + +class _AnimatedCharacterState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _offsetAnimation; + late Animation _opacityAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 600), + vsync: this, + ); + + _offsetAnimation = Tween( + begin: const Offset(3.0, 0.0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeOutCubic, + )); + + _opacityAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeIn, + )); + + Future.delayed(widget.delay, () { + if (mounted) _controller.forward(); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _opacityAnimation, + child: SlideTransition( + position: _offsetAnimation, + child: Text( + widget.char == ' ' ? '\u00A0' : widget.char, + style: TextStyle( + fontSize: widget.isSecondary ? 18 : 32, + fontWeight: widget.isSecondary ? FontWeight.w500 : FontWeight.bold, + color: widget.isSecondary ? AppTheme.textLight : AppTheme.primaryGreen, + letterSpacing: widget.isSecondary ? 1.5 : 2.0, + ), + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 16ed18e..d11f372 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:monitoring_jamur/core/constants/supabase_config.dart'; import 'package:monitoring_jamur/core/theme/app_theme.dart'; import 'package:monitoring_jamur/features/auth/presentation/pages/login_page.dart'; +import 'package:monitoring_jamur/features/auth/presentation/pages/splash_screen.dart'; import 'package:monitoring_jamur/features/home/presentation/pages/main_screen.dart'; import 'package:monitoring_jamur/core/session/user_session.dart'; @@ -28,7 +29,7 @@ class MyApp extends StatelessWidget { title: 'Mushroom Monitor', debugShowCheckedModeBanner: false, theme: AppTheme.lightTheme, - home: UserSession.isLoggedIn ? const MainScreen() : const LoginPage(), + home: const SplashScreen(), ); } }