amoriai/lib/screens/splash_screen.dart

316 lines
9.9 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../services/firebase_service.dart';
import 'auth/login_screen.dart';
import 'home/home_screen.dart';
import 'onboarding_screen.dart';
import 'onboarding/early_setup_screen.dart';
class SplashScreen extends ConsumerStatefulWidget {
const SplashScreen({super.key});
@override
ConsumerState<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends ConsumerState<SplashScreen>
with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
_initializeApp();
}
Future<void> _initializeApp() async {
try {
debugPrint('🚀 Starting app initialization...');
// Start minimum timer
final minTimer = Future.delayed(const Duration(seconds: 3));
// Attempt to load resources with a timeout limit
try {
await Future.any([
Future.wait([
_checkConnectivity(),
_checkServerHealth(),
_preloadData(),
_precacheNetworkImages(),
]),
Future.delayed(const Duration(seconds: 5)),
]);
} catch (e) {
debugPrint('Resource loading error/timeout: $e');
}
// Ensure minimum splash time has passed
await minTimer;
debugPrint('✅ Splash timer finished, navigating...');
if (mounted) {
// Check if onboarding has been completed
final prefs = await SharedPreferences.getInstance();
final isFirstLaunch = prefs.getBool('is_first_launch') ?? true;
if (isFirstLaunch) {
// FIRST LAUNCH: Always show Onboarding, sign out any stale sessions
debugPrint('🆕 First launch -> Showing Onboarding');
final user = FirebaseService().currentUser;
if (user != null) {
debugPrint('⚠️ Stale auth session found -> Signing out');
await FirebaseService().signOut();
}
if (mounted) {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (_, __, ___) => const OnboardingScreen(),
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder: (_, a, __, c) =>
FadeTransition(opacity: a, child: c),
),
);
}
return;
}
final user = FirebaseService().currentUser;
debugPrint(
'👤 User status: ${user != null ? "Logged In (${user.uid})" : "Guest"}',
);
if (user != null) {
// Check if user data exists and setup is complete
final userData = await FirebaseService().getUserData(user.uid);
if (userData == null) {
debugPrint('⚠️ User data missing -> Forcing Sign Out');
await FirebaseService().signOut();
if (mounted) {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (_, __, ___) => const OnboardingScreen(),
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder: (_, a, __, c) =>
FadeTransition(opacity: a, child: c),
),
);
}
} else if (!userData.isSetupComplete) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const EarlySetupScreen()),
);
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
}
} else {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const OnboardingScreen(),
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation.drive(
Tween<double>(
begin: 0.0,
end: 1.0,
).chain(CurveTween(curve: Curves.easeOut)),
),
child: child,
);
},
),
);
}
}
} catch (e) {
debugPrint('Splash initialization error: $e');
if (mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const LoginScreen()),
);
}
}
}
/// Check network connectivity silently in background
Future<bool> _checkConnectivity() async {
try {
final result = await Connectivity().checkConnectivity();
final isConnected = result != ConnectivityResult.none;
debugPrint(
'🌐 Network connectivity: ${isConnected ? "Connected" : "Offline"}',
);
return isConnected;
} catch (e) {
debugPrint('⚠️ Connectivity check failed: $e');
return false;
}
}
/// Verify server health and API accessibility
Future<bool> _checkServerHealth() async {
try {
// Get API URL from config
final apiUrl = 'https://dev-api.muningkofie.com';
// Send health check request with timeout
final response = await http
.get(Uri.parse('$apiUrl/health'))
.timeout(
const Duration(seconds: 5),
onTimeout: () {
debugPrint('⚠️ Server health check timeout');
return http.Response('Timeout', 408);
},
);
final isHealthy = response.statusCode == 200;
debugPrint(
'🏥 Server health: ${isHealthy ? "OK" : "Unavailable (${response.statusCode})"}',
);
return isHealthy;
} catch (e) {
debugPrint('⚠️ Server health check failed: $e');
return false;
}
}
/// Preload essential data and resources
Future<void> _preloadData() async {
try {
debugPrint('📦 Preloading essential resources...');
// Preload user preferences from local storage
await _loadUserPreferences();
// Cache important assets
await _precacheAssets();
debugPrint('✅ Resources preloaded successfully');
} catch (e) {
debugPrint('⚠️ Resource preloading failed: $e');
}
}
/// Load user preferences from SharedPreferences
Future<void> _loadUserPreferences() async {
try {
final sharedPrefs = await SharedPreferences.getInstance();
// Check if first launch
final isFirstLaunch = sharedPrefs.getBool('is_first_launch') ?? true;
debugPrint('📱 First launch: $isFirstLaunch');
} catch (e) {
debugPrint('⚠️ Failed to load preferences: $e');
}
}
/// Precache important image assets
Future<void> _precacheAssets() async {
try {
if (mounted) {
// Precache commonly used images to improve performance
// Currently only GIF is being used, but can add more
await precacheImage(
const AssetImage('assets/animations/loginsplashscreen.gif'),
context,
);
}
} catch (e) {
debugPrint('⚠️ Asset precaching failed: $e');
}
}
/// Precache network images for faster loading on subsequent screens
Future<void> _precacheNetworkImages() async {
try {
if (mounted) {
debugPrint('🌐 Precaching network images...');
// Google logo for login screen - use ImageStream to ensure full load
const googleLogoUrl =
'https://developers.google.com/identity/images/g-logo.png';
final imageProvider = NetworkImage(googleLogoUrl);
final completer = Completer<void>();
final imageStream = imageProvider.resolve(ImageConfiguration.empty);
late ImageStreamListener listener;
listener = ImageStreamListener(
(ImageInfo info, bool synchronousCall) {
debugPrint(
'✅ Google logo loaded: ${info.image.width}x${info.image.height}',
);
imageStream.removeListener(listener);
if (!completer.isCompleted) completer.complete();
},
onError: (exception, stackTrace) {
debugPrint('⚠️ Failed to load Google logo: $exception');
imageStream.removeListener(listener);
if (!completer.isCompleted) completer.complete();
},
);
imageStream.addListener(listener);
// Wait for image to fully load
await completer.future;
// Also precache using Flutter's built-in method for caching
if (mounted) {
await precacheImage(imageProvider, context);
}
debugPrint('✅ Network images precached successfully');
}
} catch (e) {
debugPrint('⚠️ Network image precaching failed: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 300,
height: 300,
child: Image.asset(
'assets/animations/loginsplashscreen.gif',
fit: BoxFit.contain,
),
)
.animate()
.fade(duration: 800.ms)
.scale(
delay: 200.ms,
duration: 800.ms,
curve: Curves.easeOutBack,
),
],
),
),
),
);
}
}