import 'dart:math'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; // <-- IMPORT SUPABASE DITAMBAHKAN import '../constants/app_colors.dart'; import 'login_screen.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State with TickerProviderStateMixin { // Controllers late AnimationController _mainController; late AnimationController _slideController; late AnimationController _textController; late AnimationController _revealController; // Animations late Animation _logoY; late Animation _shadowScale; late Animation _shadowOpacity; late Animation _logoSlideX; late Animation _textOpacity; late Animation _revealProgress; // Typewriter State final String _fullText = 'IoT Kopi'; String _displayText = ''; int _charIndex = 0; @override void initState() { super.initState(); _setupAnimations(); _mulaiAnimasi(); } void _setupAnimations() { // Total durasi sekuens utama = 3.6 detik _mainController = AnimationController( vsync: this, duration: const Duration(milliseconds: 3600), ); // ── KOREOGRAFI LOGO DENGAN EFEK GRAVITASI ── _logoY = TweenSequence([ TweenSequenceItem(tween: ConstantTween(120.0), weight: 25), TweenSequenceItem( tween: Tween(begin: 120.0, end: -180.0).chain(CurveTween(curve: Curves.easeOut)), weight: 14, ), TweenSequenceItem( tween: TweenSequence([ TweenSequenceItem(tween: Tween(begin: -180.0, end: -188.0).chain(CurveTween(curve: Curves.easeInOut)), weight: 50), TweenSequenceItem(tween: Tween(begin: -188.0, end: -180.0).chain(CurveTween(curve: Curves.easeInOut)), weight: 50), ]), weight: 25, ), TweenSequenceItem( tween: Tween(begin: -180.0, end: 0.0).chain(CurveTween(curve: Curves.easeIn)), weight: 11, ), TweenSequenceItem(tween: Tween(begin: 0.0, end: -45.0).chain(CurveTween(curve: Curves.easeOut)), weight: 5), TweenSequenceItem(tween: Tween(begin: -45.0, end: 0.0).chain(CurveTween(curve: Curves.easeIn)), weight: 5), TweenSequenceItem(tween: Tween(begin: 0.0, end: -15.0).chain(CurveTween(curve: Curves.easeOut)), weight: 4), TweenSequenceItem(tween: Tween(begin: -15.0, end: 0.0).chain(CurveTween(curve: Curves.easeIn)), weight: 4), TweenSequenceItem(tween: ConstantTween(0.0), weight: 7), ]).animate(_mainController); // ── KOREOGRAFI UKURAN BAYANGAN ── _shadowScale = TweenSequence([ TweenSequenceItem(tween: ConstantTween(2.5), weight: 25), TweenSequenceItem(tween: Tween(begin: 2.5, end: 0.1).chain(CurveTween(curve: Curves.easeOut)), weight: 14), TweenSequenceItem(tween: ConstantTween(0.1), weight: 61), ]).animate(_mainController); // ── KOREOGRAFI TRANSPARANSI BAYANGAN ── _shadowOpacity = TweenSequence([ TweenSequenceItem(tween: ConstantTween(0.6), weight: 25), TweenSequenceItem(tween: Tween(begin: 0.6, end: 0.0).chain(CurveTween(curve: Curves.easeOut)), weight: 14), TweenSequenceItem(tween: ConstantTween(0.0), weight: 61), ]).animate(_mainController); // ── SLIDE LOGO KE KIRI ── _slideController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); _logoSlideX = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _slideController, curve: Curves.easeInOutCubic), ); // ── FADE IN OPACITY TEKS ── _textController = AnimationController( vsync: this, duration: const Duration(milliseconds: 200), ); _textOpacity = Tween(begin: 0.0, end: 1.0).animate(_textController); // ── KONTROL TRANSISI MENGECIL ── _revealController = AnimationController( vsync: this, duration: const Duration(milliseconds: 750), ); _revealProgress = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _revealController, curve: Curves.easeInOutQuad), ); } // --- FUNGSI ANIMASI YANG SUDAH DIUPDATE DENGAN PENGECEKAN LOGIN --- Future _mulaiAnimasi() async { await _mainController.forward(); await Future.delayed(const Duration(milliseconds: 200)); _slideController.forward(); await Future.delayed(const Duration(milliseconds: 300)); _textController.forward(); await Future.delayed(const Duration(milliseconds: 500)); // Typewriter _charIndex = 0; _displayText = ''; for (int i = 0; i < _fullText.length; i++) { await Future.delayed(const Duration(milliseconds: 120)); if (mounted) { setState(() { _charIndex++; _displayText = _fullText.substring(0, _charIndex); }); } } await Future.delayed(const Duration(milliseconds: 100)); if (mounted) { // 1. Jalankan animasi lingkaran mengecil internal sampai habis await _revealController.forward(); // 2. CEK SESI LOGIN DI SINI if (mounted) { final session = Supabase.instance.client.auth.currentSession; if (session != null) { // JIKA SUDAH LOGIN: Lempar ke Monitoring / Home Navigator.of(context).pushReplacementNamed('/home'); } else { // JIKA BELUM LOGIN: Lempar ke LoginScreen Navigator.of(context).pushReplacement( PageRouteBuilder( pageBuilder: (_, __, ___) => const LoginScreen(), transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, ), ); } } } } @override void dispose() { _mainController.dispose(); _slideController.dispose(); _textController.dispose(); _revealController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; const logoSize = 80.0; const textEstimatedWidth = 140.0; const gap = 16.0; const totalWidth = logoSize + gap + textEstimatedWidth; final startX = (size.width - totalWidth) / 2; final logoFinalX = startX; final logoCenterX = size.width / 2 - logoSize / 2; final centerY = size.height / 2; return AnimatedBuilder( animation: _revealProgress, builder: (context, child) { return ClipPath( clipper: CircularRevealClipper(progress: _revealProgress.value), child: Scaffold( backgroundColor: AppColors.primary, body: AnimatedBuilder( animation: Listenable.merge([_mainController, _slideController, _textController]), builder: (context, child) { final currentLogoX = logoCenterX - (_logoSlideX.value * (logoCenterX - logoFinalX)); final currentLogoY = centerY - logoSize / 2 + _logoY.value; final shadowW = logoSize * 0.75 * _shadowScale.value; final shadowH = 14.0 * _shadowScale.value; final shadowX = currentLogoX + (logoSize - shadowW) / 2; final shadowY = centerY + logoSize / 2 + 6; final textX = currentLogoX + logoSize + gap; final textY = centerY - 20; return Stack( children: [ // ── 1. BAYANGAN ELIPS ── Positioned( left: shadowX, top: shadowY, child: Opacity( opacity: _shadowOpacity.value.clamp(0.0, 1.0), child: Container( width: shadowW.clamp(0.0, logoSize * 2.5), height: shadowH.clamp(0.0, 20.0 * 2.5), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(50), ), ), ), ), // ── 2. LOGO ── Positioned( left: currentLogoX, top: currentLogoY, child: Opacity( opacity: _mainController.value > 0.25 ? 1.0 : 0.0, child: Container( width: logoSize, height: logoSize, decoration: BoxDecoration( color: AppColors.cardBg, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.15), blurRadius: 10, offset: const Offset(0, 3), ), ], ), child: ClipOval( child: Padding( padding: const EdgeInsets.all(12), child: Image.asset( 'assets/icon/kopibaru.png', fit: BoxFit.contain, ), ), ), ), ), ), // ── 3. TEKS POPPINS ── Positioned( left: textX, top: textY, child: Opacity( opacity: _textOpacity.value, child: Text( _displayText, style: GoogleFonts.poppins( color: Colors.white, fontSize: 30, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ), ), ], ); }, ), ), ); }, ); } } // ── CUSTOM CLIPPER UNTUK EFEK LINGKARAN MENGECIL TEPAT DI TENGAH LAYAR ── class CircularRevealClipper extends CustomClipper { final double progress; CircularRevealClipper({required this.progress}); @override Path getClip(Size size) { final center = Offset(size.width / 2, size.height / 2); final maxRadius = sqrt(size.width * size.width + size.height * size.height); final radius = maxRadius * progress; final path = Path(); path.addOval(Rect.fromCircle(center: center, radius: radius)); return path; } @override bool shouldReclip(CustomClipper oldClipper) => true; }