MIF_E31222656/lib/screens/intro/animation_splash_screen.dart

379 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:tugas_akhir_supabase/services/auth_services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:tugas_akhir_supabase/di/service_locator.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:tugas_akhir_supabase/services/session_manager.dart';
import 'dart:async';
// Import HomeScreen
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _slideAnimation;
late AuthServices _authServices;
final AudioPlayer _audioPlayer = AudioPlayer();
bool _isAudioPlayed = false;
@override
void initState() {
super.initState();
// Get auth service instance using the sl instance from service_locator.dart
try {
_authServices = sl<AuthServices>();
} catch (e) {
debugPrint('Error getting AuthServices from GetIt: $e');
// Create a local instance as fallback if GetIt fails
_authServices = AuthServices();
}
_animationController = AnimationController(
duration: const Duration(milliseconds: 2000), // Increased duration
vsync: this,
);
_fadeAnimation = TweenSequence<double>([
TweenSequenceItem(tween: Tween<double>(begin: 0.0, end: 1.0), weight: 20),
TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.0), weight: 60),
TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 0.0), weight: 20),
]).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_scaleAnimation = TweenSequence<double>([
TweenSequenceItem(
tween: Tween<double>(
begin: 0.5,
end: 1.0,
).chain(CurveTween(curve: Curves.elasticOut)),
weight: 40,
),
TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.0), weight: 40),
TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.2), weight: 20),
]).animate(_animationController);
_slideAnimation = Tween<double>(begin: 50.0, end: 0.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
),
);
// Delay animation start for native splash to finish properly
Future.delayed(const Duration(milliseconds: 900), () {
if (mounted) {
_animationController.forward();
}
});
// Play sound after a short delay
Timer(const Duration(milliseconds: 1000), () {
_playDeepMaleVoice();
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// Langsung navigasi tanpa delay tambahan
if (mounted) {
_checkAuthAndNavigate();
}
}
});
}
Future<void> _playDeepMaleVoice() async {
if (_isAudioPlayed) return;
_isAudioPlayed = true;
debugPrint('Playing intro voice...');
try {
// Gunakan Future.delayed untuk memastikan tidak memblokir thread utama
Future.delayed(const Duration(milliseconds: 300), () {
// Bungkus dalam try-catch terpisah untuk mengisolasi error audio
_safePlayAudio();
});
} catch (e) {
debugPrint('Error scheduling audio playback: $e');
// Jangan biarkan error audio mengganggu flow aplikasi
}
}
// Metode terpisah untuk memutar audio dengan aman
Future<void> _safePlayAudio() async {
try {
await _audioPlayer.setReleaseMode(ReleaseMode.release);
// Gunakan timeout untuk mencegah blocking
await _audioPlayer
.play(AssetSource('audio/introVoice.mp3'), volume: 0.8)
.timeout(
const Duration(seconds: 3),
onTimeout: () {
debugPrint('Timeout playing intro voice');
return;
},
);
debugPrint('Intro voice started playing');
} catch (assetError) {
debugPrint('Could not play intro voice: $assetError');
// Fallback to regular welcome audio
try {
debugPrint('Falling back to regular welcome audio...');
await _audioPlayer
.play(AssetSource('audio/welcome.mp3'), volume: 0.8)
.timeout(
const Duration(seconds: 2),
onTimeout: () {
debugPrint('Timeout playing welcome audio');
return;
},
);
} catch (welcomeError) {
debugPrint('Could not play welcome audio: $welcomeError');
// Ignore audio errors to prevent app crash
}
}
}
// Improved auth checking with timeout handling
Future<void> _checkAuthAndNavigate() async {
// Gunakan flag untuk menghindari multiple calls
bool isNavigating = false;
try {
// Tambahkan timeout untuk menghindari hang
bool isLoggedIn = false;
bool isSessionValid = false;
try {
// Bungkus dalam Future.delayed untuk memastikan UI tetap responsif
await Future.delayed(const Duration(milliseconds: 100), () async {
// Check if user is logged in AND session is valid (not timed out)
try {
isLoggedIn = _authServices.isUserLoggedIn();
} catch (e) {
debugPrint('Error checking login status: $e');
isLoggedIn = false;
}
});
// Berikan jeda sebelum memeriksa session
await Future.delayed(const Duration(milliseconds: 200));
// Periksa session dengan timeout
// Pada splash screen kita hanya perlu tau apakah user login,
// bukan apakah session valid - ini akan diperiksa di SessionManager nanti
try {
// Sebagai fallback, gunakan status login karena di splash screen
// kita hanya butuh tahu apakah harus ke intro atau home
isSessionValid = isLoggedIn;
// Lakukan pemeriksaan sederhana untuk user saat ini
final user = _authServices.getCurrentUser();
isSessionValid = user != null;
} catch (e) {
debugPrint('Error checking user: $e');
isSessionValid = isLoggedIn; // Fallback ke status login
}
debugPrint(
'Auth check: isLoggedIn=$isLoggedIn, isSessionValid=$isSessionValid',
);
} catch (authError) {
debugPrint('Error checking auth status: $authError');
// Default ke false jika ada error
isLoggedIn = false;
isSessionValid = false;
}
if (!mounted) return;
// Hindari multiple navigation
if (isNavigating) {
debugPrint('Already navigating, skipping duplicate navigation');
return;
}
isNavigating = true;
if (isLoggedIn && isSessionValid) {
// Valid session, navigate to home
debugPrint('Navigating to home screen - valid session');
Navigator.pushReplacementNamed(context, '/home');
} else {
// Session expired or no session, go to intro
debugPrint('Navigating to intro screen - no valid session');
Navigator.pushReplacementNamed(context, '/intro');
}
} catch (e) {
// Handle any errors by directing to login
if (mounted && !isNavigating) {
debugPrint('Auth error in splash screen: $e');
Navigator.pushReplacementNamed(context, '/intro');
}
}
}
@override
void dispose() {
_animationController.dispose();
_audioPlayer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
resizeToAvoidBottomInset: true,
body: SafeArea(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black,
Colors.black87,
const Color(0xFF056839).withOpacity(0.3),
],
stops: const [0.0, 0.5, 1.0],
),
),
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Transform.translate(
offset: Offset(0, _slideAnimation.value),
child: Container(
width: 180,
height: 180,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
),
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 160,
height: 160,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(
0xFF056839,
).withOpacity(0.8),
const Color(0xFF056839),
],
),
boxShadow: [
BoxShadow(
color: const Color(
0xFF056839,
).withOpacity(0.3),
blurRadius: 20,
spreadRadius: 5,
),
],
),
),
Image.asset(
'assets/images/logo.png',
width: 120,
height: 120,
color: Colors.white,
),
],
),
),
),
const SizedBox(height: 40),
Transform.translate(
offset: Offset(0, _slideAnimation.value),
child: Text(
'TaniSM4RT',
style: GoogleFonts.poppins(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 2,
shadows: [
Shadow(
color: const Color(
0xFF056839,
).withOpacity(0.5),
offset: const Offset(0, 4),
blurRadius: 15,
),
],
),
),
),
const SizedBox(height: 16),
Transform.translate(
offset: Offset(0, _slideAnimation.value),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 8,
),
decoration: BoxDecoration(
color: const Color(0xFF056839).withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: const Color(
0xFF056839,
).withOpacity(0.3),
width: 1,
),
),
child: Text(
'Smart Farming Solution',
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.white.withOpacity(0.9),
letterSpacing: 1,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
),
),
),
);
},
),
),
),
);
}
}