316 lines
9.9 KiB
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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|