import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; import 'dart:async'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tugas_akhir_supabase/core/constants/app_constants.dart'; import 'package:tugas_akhir_supabase/di/service_locator.dart'; import 'package:tugas_akhir_supabase/core/routes/app_routes.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:tugas_akhir_supabase/services/session_manager.dart'; import 'package:tugas_akhir_supabase/services/user_presence_service.dart'; import 'package:tugas_akhir_supabase/widgets/session_expired_dialog.dart'; import 'package:get_it/get_it.dart'; import 'package:tugas_akhir_supabase/widgets/session_guard_wrapper.dart'; // Tambahkan listener untuk hot reload bool _hasDoneHotReloadSetup = false; // Global navigator key for accessing navigation from anywhere final GlobalKey navigatorKey = GlobalKey(); // Fungsi isolate terpisah untuk inisialisasi Supabase Future _initializeSupabase() async { try { await Supabase.initialize( url: AppConstants.supabaseUrl, anonKey: AppConstants.supabaseAnonKey, debug: false, ); debugPrint('Supabase initialized successfully'); } catch (e) { debugPrint('Error initializing Supabase: $e'); rethrow; } } void main() async { // Langsung memulai aplikasi utama try { // Initialize Flutter binding WidgetsFlutterBinding.ensureInitialized(); // Tambahkan penanganan error global PlatformDispatcher.instance.onError = (error, stack) { debugPrint('Global error handler: $error'); debugPrint('Stack trace: $stack'); // Return true to prevent the error from being reported to the framework return true; }; // Tambahkan dukungan untuk hot reload if (!_hasDoneHotReloadSetup) { _hasDoneHotReloadSetup = true; // Set debug flags debugPrint('======= Setting up hot reload support ======='); // Pastikan semua yang memblokir hot reload dibersihkan final binding = WidgetsFlutterBinding.ensureInitialized(); binding.addPostFrameCallback((_) { // Execute after first frame is rendered debugPrint( '======= First frame rendered, hot reload should work =======', ); }); } // Debug log untuk pelacakan splash screen debugPrint('======= App Start: Loading TaniSMART application ======='); // Set orientation to portrait await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); // Set up error handlers FlutterError.onError = (FlutterErrorDetails details) { debugPrint('Flutter error: ${details.exception}'); }; // Initialize date formatting await initializeDateFormatting('id_ID'); await initializeDateFormatting('en_US'); // Initialize Supabase dengan timeout try { // Gunakan timeout untuk mencegah blocking terlalu lama await _initializeSupabase().timeout( const Duration(seconds: 5), onTimeout: () { debugPrint('Supabase initialization timed out, continuing startup'); throw TimeoutException('Supabase initialization timed out'); }, ); } catch (e) { // Lanjutkan meskipun ada error, akan ditangani nanti debugPrint('Continuing after Supabase initialization issue: $e'); } // Initialize service locator dengan timeout try { await initServiceLocator().timeout( const Duration(seconds: 3), onTimeout: () { debugPrint('Service locator initialization timed out, continuing'); throw TimeoutException('Service locator initialization timed out'); }, ); } catch (e) { debugPrint('Continuing after service locator issue: $e'); } // Initialize session management dengan timeout try { await SessionManager.initializeSession().timeout( const Duration(seconds: 3), onTimeout: () { debugPrint('Session initialization timed out, continuing'); throw TimeoutException('Session initialization timed out'); }, ); // Initialize user presence service if user is logged in if (Supabase.instance.client.auth.currentUser != null) { try { if (GetIt.instance.isRegistered()) { await GetIt.instance().initialize(); debugPrint('User presence service initialized'); } } catch (e) { debugPrint('Error initializing user presence service: $e'); } } } catch (e) { debugPrint('Continuing after session initialization issue: $e'); } // Debug log sebelum menjalankan aplikasi debugPrint( '======= App initialized: Running TaniSMART application =======', ); // Run the app runApp(const RealApp()); } catch (e, stack) { debugPrint('Error starting full app: $e\n$stack'); // Show error screen runApp( MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('TaniSMART Error'), backgroundColor: Colors.red, ), body: Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, color: Colors.red, size: 48), const SizedBox(height: 16), const Text( 'Aplikasi tidak dapat dimulai', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Error: ${e.toString()}', style: const TextStyle(color: Colors.red), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton( onPressed: () { main(); }, child: const Text('Coba Lagi'), ), ], ), ), ), ), ), ); } } class RealApp extends StatefulWidget { const RealApp({super.key}); @override State createState() => _RealAppState(); } class _RealAppState extends State with WidgetsBindingObserver { bool _showingSessionExpiredDialog = false; bool _isInitialLaunch = true; // Flag untuk menandai initial launch Timer? _initialLaunchTimer; StreamSubscription? _sessionSubscription; Timer? _sessionCheckTimer; // Timer untuk memeriksa sesi secara berkala bool _hasSetupSessionMonitoring = false; // Flag baru untuk menandai setup monitoring @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // Penundaan yang lebih lama untuk pemasangan listener agar aplikasi bisa dimuat sepenuhnya Future.delayed(Duration(seconds: 7), () { if (!mounted) return; // Hanya setup jika aplikasi masih berjalan dan belum setup if (!_hasSetupSessionMonitoring) { _setupSessionMonitoring(); } }); // Delay pemeriksaan session sampai aplikasi benar-benar siap // Beri waktu splash screen menyelesaikan animasinya dan navigasi selesai _initialLaunchTimer = Timer(Duration(seconds: 10), () { if (mounted) { setState(() { _isInitialLaunch = false; // Reset flag setelah initial launch benar-benar selesai debugPrint('App: Initial launch phase completed'); }); // Hanya setup jika aplikasi masih berjalan dan belum setup if (!_hasSetupSessionMonitoring) { _setupSessionMonitoring(); } } }); } // Metode terpisah untuk setup monitoring sesi void _setupSessionMonitoring() { if (_hasSetupSessionMonitoring) return; // Hindari setup duplikat _hasSetupSessionMonitoring = true; debugPrint('App: Setting up session expiration listener'); _sessionSubscription = SessionManager.sessionExpiredStream.listen(( expired, ) { debugPrint('App: Session expired event received: $expired'); if (expired && !_showingSessionExpiredDialog) { debugPrint('App: Showing session expired dialog from stream'); _showSessionExpiredDialog(); } }); // Mulai pemeriksaan sesi berkala yang lebih agresif, tapi hanya jika sudah tidak dalam initial launch if (!_isInitialLaunch) { _startAggressiveSessionChecking(); } } // Mulai pemeriksaan sesi yang lebih agresif void _startAggressiveSessionChecking() { debugPrint('App: Starting aggressive session checking'); // Batalkan timer yang ada jika ada _sessionCheckTimer?.cancel(); // Periksa sesi setiap 15 detik _sessionCheckTimer = Timer.periodic(Duration(seconds: 15), (timer) { // Skip jika masih dalam fase initial launch if (_isInitialLaunch) { debugPrint('App: Skipping aggressive check during initial launch'); return; } // Periksa apakah pengguna sudah login terlebih dahulu final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint( 'App: No authenticated user, skipping aggressive session check', ); return; } debugPrint('App: Running aggressive session check'); _checkSessionValidity(); }); } // Check session validity on startup and periodically Future _checkSessionValidity() async { // Jangan periksa session selama initial launch phase if (_isInitialLaunch) { debugPrint('App: Skipping session check during initial launch'); return; } try { debugPrint('App: Checking session validity...'); // Periksa apakah pengguna sudah login terlebih dahulu final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint('App: No authenticated user found, skipping session check'); return; } final isValid = await SessionManager.isSessionValid(); debugPrint('App: Session validity check result: $isValid'); if (!isValid && !_showingSessionExpiredDialog) { debugPrint('App: Session is invalid, showing expired dialog'); _showSessionExpiredDialog(); } } catch (e) { debugPrint('App: Error checking session validity: $e'); } } @override void dispose() { _initialLaunchTimer?.cancel(); _sessionSubscription?.cancel(); _sessionCheckTimer?.cancel(); WidgetsBinding.instance.removeObserver(this); SessionManager.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { debugPrint('App lifecycle state changed to: $state'); switch (state) { case AppLifecycleState.paused: case AppLifecycleState.inactive: case AppLifecycleState.detached: // App went to background debugPrint('App: Going to background, calling onAppBackground'); SessionManager.onAppBackground(); break; case AppLifecycleState.resumed: // App came to foreground debugPrint('App: Coming to foreground, calling onAppForeground'); SessionManager.onAppForeground().then((_) { debugPrint( 'App: After foreground transition, expired = ${SessionManager.isExpired}', ); // Periksa apakah pengguna sudah login terlebih dahulu final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint( 'App: No authenticated user after foreground, skipping session check', ); return; } if (SessionManager.isExpired && !_showingSessionExpiredDialog) { debugPrint('App: Session expired after coming to foreground'); _showSessionExpiredDialog(); } else { // Periksa sesi secara manual untuk memastikan _checkSessionValidity(); } }); break; default: break; } } void _showSessionExpiredDialog() { debugPrint('App: Attempting to show session expired dialog'); // Jangan tampilkan dialog jika masih dalam fase initial launch if (_isInitialLaunch) { debugPrint( 'App: Still in initial launch phase, skipping session expired dialog', ); return; } if (_showingSessionExpiredDialog) { debugPrint('App: Dialog already showing, skipping'); return; } _showingSessionExpiredDialog = true; // Pastikan context tersedia if (navigatorKey.currentContext == null) { debugPrint('App: Navigator context not available, using delayed dialog'); // Coba lagi setelah beberapa saat Future.delayed(Duration(milliseconds: 500), () { if (mounted) _showSessionExpiredDialog(); }); _showingSessionExpiredDialog = false; return; } debugPrint('App: Showing session expired dialog now'); showDialog( context: navigatorKey.currentContext!, barrierDismissible: false, builder: (context) => const SessionExpiredDialog(), ).then((_) { debugPrint('App: Session expired dialog closed'); _showingSessionExpiredDialog = false; }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'TaniSMART', navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, theme: ThemeData( primaryColor: const Color(0xFF2E7D32), colorScheme: ColorScheme.fromSwatch( primarySwatch: Colors.green, accentColor: const Color(0xFF66BB6A), ), scaffoldBackgroundColor: const Color.fromARGB(255, 255, 255, 255), visualDensity: VisualDensity.adaptivePlatformDensity, useMaterial3: true, inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: const BorderSide(color: Colors.black26, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: const BorderSide(color: Colors.black26, width: 1.5), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: const BorderSide(color: Colors.black, width: 2.5), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: const BorderSide(color: Colors.red, width: 1.5), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: const BorderSide(color: Colors.red, width: 2.5), ), ), dividerColor: Colors.black, ), routes: AppRoutes.routes, initialRoute: '/', // Add navigation observer to clear SnackBars when navigating navigatorObservers: [ _SnackBarClearingNavigatorObserver(), _UserInteractionObserver(), ], // Add router to intercept navigation when session is expired builder: (context, child) { // Force login screen if session is expired if (SessionManager.isExpired && child != null && !_isInitialLaunch && Supabase.instance.client.auth.currentUser != null) { debugPrint('App: Session expired, forcing login screen'); // Show session expired dialog if not already showing if (!_showingSessionExpiredDialog) { // Use a post-frame callback to avoid build phase issues WidgetsBinding.instance.addPostFrameCallback((_) { _showSessionExpiredDialog(); }); } // Return a restricted UI that prevents interaction return Material( child: Stack( children: [ // Blur the background content Opacity(opacity: 0.3, child: child), // Show a loading indicator or message if (!_showingSessionExpiredDialog) const Center(child: CircularProgressIndicator()), ], ), ); } // Normal app flow return GestureDetector( onTap: () => _updateUserInteraction(), onPanDown: (_) => _updateUserInteraction(), onScaleStart: (_) => _updateUserInteraction(), behavior: HitTestBehavior.translucent, child: child!, ); }, ); } // Method untuk memperbarui timestamp interaksi pengguna void _updateUserInteraction() { debugPrint('App: User interaction detected'); SessionManager.updateLastUserInteraction(); } } // Custom navigator observer to clear SnackBars when navigating to new screens class _SnackBarClearingNavigatorObserver extends NavigatorObserver { @override void didPush(Route route, Route? previousRoute) { super.didPush(route, previousRoute); _clearSnackBars(); } @override void didReplace({Route? newRoute, Route? oldRoute}) { super.didReplace(newRoute: newRoute, oldRoute: oldRoute); _clearSnackBars(); } @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); _clearSnackBars(); } void _clearSnackBars() { if (navigatorKey.currentContext != null) { ScaffoldMessenger.of(navigatorKey.currentContext!).hideCurrentSnackBar(); ScaffoldMessenger.of(navigatorKey.currentContext!).clearSnackBars(); } } } // Observer baru untuk mendeteksi navigasi pengguna sebagai bentuk interaksi class _UserInteractionObserver extends NavigatorObserver { @override void didPush(Route route, Route? previousRoute) { super.didPush(route, previousRoute); debugPrint('App: Navigation interaction detected (push)'); SessionManager.updateLastUserInteraction(); } @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); debugPrint('App: Navigation interaction detected (pop)'); SessionManager.updateLastUserInteraction(); } } // Add a utility function to show the debug FAB from anywhere void showDebugFAB(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { // Use the navigator key to navigate safely navigatorKey.currentState?.pushNamed('/image-test'); }); }