import 'dart:async'; import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tugas_akhir_supabase/services/user_presence_service.dart'; class SessionManager { static const String _lastActiveTimeKey = 'last_active_time'; static const String _lastUserInteractionKey = 'last_user_interaction'; static const String _sessionStateKey = 'session_state'; static const int _sessionTimeoutMinutes = 480; // Increased to 8 hours for permissive mode static Timer? _sessionCheckTimer; static Timer? _presenceUpdateTimer; static bool _isCheckingSession = false; static bool _isAppInBackground = false; static bool _isSessionExpired = false; static bool _hasLoggedInUser = false; static bool _isAppJustLaunched = true; static final StreamController _sessionExpiredController = StreamController.broadcast(); // Stream untuk mendengarkan perubahan status session static Stream get sessionExpiredStream => _sessionExpiredController.stream; // Getter untuk mendapatkan waktu timeout session dalam menit static int getSessionTimeout() { return _sessionTimeoutMinutes; } // Getter untuk status login static bool get hasLoggedInUser => _hasLoggedInUser; // Metode untuk menghindari blocking pada SharedPreferences static Future _getSafeSharedPreferences() async { try { return await SharedPreferences.getInstance().timeout( const Duration(seconds: 2), onTimeout: () { debugPrint('Session: SharedPreferences timeout'); throw TimeoutException('SharedPreferences timeout'); }, ); } catch (e) { debugPrint('Session: Error getting SharedPreferences - $e'); return null; } } // Initialize session management static Future initializeSession() async { try { _isAppJustLaunched = true; debugPrint('Session: App just launched flag set to true'); Future.delayed(Duration(seconds: 10), () { _isAppJustLaunched = false; debugPrint('Session: App just launched flag set to false after delay'); }); final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint('Session: No authenticated user found'); _hasLoggedInUser = false; return; } _hasLoggedInUser = true; await updateLastUserInteraction(); _isAppInBackground = false; _setSessionExpired(false); // Initialize user presence service try { if (GetIt.instance.isRegistered()) { await GetIt.instance().initialize(); _startPresenceUpdates(); } } catch (e) { debugPrint('Session: Error initializing presence service - $e'); } Future.delayed(Duration(seconds: 5), () { _startSessionMonitoring(); }); debugPrint( 'Session: Initialized successfully for user: ${currentUser.email}', ); } catch (e) { debugPrint('Session: Error initializing - $e'); _hasLoggedInUser = false; } } // Method baru untuk memperbarui status login static void setUserLoggedIn(bool isLoggedIn) { _hasLoggedInUser = isLoggedIn; debugPrint('Session: User login status set to: $isLoggedIn'); if (isLoggedIn) { _setSessionExpired(false); // Pastikan expired direset! _isSessionExpired = false; _sessionExpiredController.add(false); // Kirim event expired false updateLastUserInteraction(); _startSessionMonitoring(); _startPresenceUpdates(); } else { _stopSessionMonitoring(); _stopPresenceUpdates(); _setSessionExpired(false); // Reset expired saat logout juga! _isSessionExpired = false; _sessionExpiredController.add(false); } } // Tambahkan fungsi untuk reset expired state secara manual static void resetExpiredState() { _isSessionExpired = false; _sessionExpiredController.add(false); } // Start periodic presence updates static void _startPresenceUpdates() { _stopPresenceUpdates(); // Stop any existing timer // Update presence every 30 seconds _presenceUpdateTimer = Timer.periodic(Duration(seconds: 30), (timer) { if (_hasLoggedInUser && !_isSessionExpired) { try { if (GetIt.instance.isRegistered()) { GetIt.instance().updatePresence(); } } catch (e) { debugPrint('Session: Error updating presence - $e'); } } }); debugPrint('Session: Started presence updates'); } // Stop presence updates static void _stopPresenceUpdates() { _presenceUpdateTimer?.cancel(); _presenceUpdateTimer = null; } // Check if session is valid with extremely permissive logic static Future isSessionValid() async { debugPrint('Session: Checking session validity (PERMISSIVE MODE)...'); // Check if user is actually logged in final currentUser = Supabase.instance.client.auth.currentUser; final currentSession = Supabase.instance.client.auth.currentSession; if (currentUser == null || currentSession == null) { debugPrint('Session: No user or session found, returning false'); return false; } // Always return true for permissive mode if user is logged in debugPrint('Session: PERMISSIVE MODE - User logged in, returning true'); return true; } // BARU: Update timestamp interaksi pengguna terakhir static Future updateLastUserInteraction() async { try { final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint( 'Session: Cannot update interaction - user not authenticated', ); return; } SharedPreferences? prefs = await _getSafeSharedPreferences(); if (prefs == null) return; final now = DateTime.now().millisecondsSinceEpoch; try { await prefs .setInt(_lastUserInteractionKey, now) .timeout( const Duration(seconds: 2), onTimeout: () { debugPrint( 'Session: Timeout setting last user interaction time', ); return false; }, ); await updateLastActiveTime(); // Update presence when user interacts try { if (GetIt.instance.isRegistered()) { GetIt.instance().updatePresence(); } } catch (e) { debugPrint('Session: Error updating presence on interaction - $e'); } debugPrint( 'Session: User interaction recorded at ${DateTime.fromMillisecondsSinceEpoch(now)}', ); } catch (e) { debugPrint( 'Session: Error writing user interaction to SharedPreferences - $e', ); } } catch (e) { debugPrint('Session: Error updating user interaction - $e'); } } // Update last active time with better error handling static Future updateLastActiveTime() async { try { final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint('Session: Cannot update activity - user not authenticated'); return; } SharedPreferences? prefs = await _getSafeSharedPreferences(); if (prefs == null) return; final now = DateTime.now().millisecondsSinceEpoch; try { await prefs .setInt(_lastActiveTimeKey, now) .timeout( const Duration(seconds: 2), onTimeout: () { debugPrint('Session: Timeout setting last active time'); return false; }, ); await prefs .setString(_sessionStateKey, 'active') .timeout( const Duration(seconds: 2), onTimeout: () { debugPrint('Session: Timeout setting session state'); return false; }, ); debugPrint( 'Session: Activity updated at ${DateTime.fromMillisecondsSinceEpoch(now)}', ); } catch (e) { debugPrint('Session: Error writing to SharedPreferences - $e'); } } catch (e) { debugPrint('Session: Error updating activity - $e'); } } // Called when app goes to background static Future onAppBackground() async { debugPrint('Session: App entering background'); _isAppInBackground = true; await updateLastActiveTime(); _startSessionMonitoring(); } // Called when app comes to foreground static Future onAppForeground() async { debugPrint('Session: App entering foreground'); if (!_isAppInBackground) return; _isAppInBackground = false; try { final currentUser = Supabase.instance.client.auth.currentUser; if (currentUser == null) { debugPrint( 'Session: No authenticated user when returning to foreground', ); _stopSessionMonitoring(); return; } debugPrint( 'Session: Checking session validity after returning to foreground', ); // Use permissive mode - don't check timeout final isValid = await isSessionValid(); if (!isValid) { debugPrint('Session: Session invalid after background'); await clearSession(); Future.delayed(const Duration(milliseconds: 500), () { _setSessionExpired(true); }); } else { debugPrint('Session: Still valid after background'); await updateLastActiveTime(); _startSessionMonitoring(); } } catch (e) { debugPrint('Session: Error during foreground transition - $e'); } } // Set session expired state and notify listeners static void _setSessionExpired(bool value) { // Jangan trigger expired jika user memang belum login if (!_hasLoggedInUser) { debugPrint('Session: Not logged in, skip setting expired state'); return; } if (_isSessionExpired != value) { _isSessionExpired = value; debugPrint('Session: Setting expired state to $value'); _sessionExpiredController.add(value); // If session is expired, force clear session if (value) { debugPrint('Session: Session expired, clearing session data'); clearSession(); } } } // Get session status for UI components static bool get isExpired => _isSessionExpired; // Check if user is properly authenticated static bool get isAuthenticated { if (_isSessionExpired) { debugPrint('Session: Session is marked as expired, not authenticated'); return false; } if (!_hasLoggedInUser) { return false; } final currentUser = Supabase.instance.client.auth.currentUser; final currentSession = Supabase.instance.client.auth.currentSession; final isValid = currentUser != null && currentSession != null && !_isSessionExpired; // Only update state if there's a significant change, and be more conservative if (!isValid && _hasLoggedInUser) { // Double check before changing state to avoid race conditions final doubleCheckUser = Supabase.instance.client.auth.currentUser; final doubleCheckSession = Supabase.instance.client.auth.currentSession; if (doubleCheckUser == null || doubleCheckSession == null) { debugPrint( 'Session: User authentication state changed, updating flags', ); _hasLoggedInUser = false; _setSessionExpired(true); } else { debugPrint('Session: User still exists, keeping authentication state'); } } return isValid; } // Clear session data with proper cleanup static Future clearSession() async { try { _stopSessionMonitoring(); _setSessionExpired(true); _hasLoggedInUser = false; _isSessionExpired = false; _sessionExpiredController.add(false); final prefs = await SharedPreferences.getInstance(); await prefs.remove(_lastActiveTimeKey); await prefs.remove(_lastUserInteractionKey); await prefs.remove(_sessionStateKey); await Supabase.instance.client.auth.signOut(); debugPrint('Session: Cleared and signed out successfully'); } catch (e) { debugPrint('Session: Error during cleanup - $e'); _setSessionExpired(true); _hasLoggedInUser = false; _isSessionExpired = false; _sessionExpiredController.add(false); } } // Refresh session - extend if still valid static Future refreshSession() async { try { final isValid = await isSessionValid(); if (isValid) { await updateLastUserInteraction(); return true; } return false; } catch (e) { debugPrint('Session: Error refreshing - $e'); return false; } } // Fungsi baru untuk memberi tahu aplikasi tentang aktivitas penting yang harus memperbarui sesi static Future notifyImportantActivity(String activityType) async { try { debugPrint('Session: Important activity detected: $activityType'); await updateLastUserInteraction(); Timer(Duration(seconds: 2), () { debugPrint('Session: Follow-up check after important activity'); isSessionValid(); }); } catch (e) { debugPrint('Session: Error handling important activity - $e'); } } // Start session monitoring (DISABLED for permissive mode) static void _startSessionMonitoring() { _stopSessionMonitoring(); debugPrint('Session: Session monitoring DISABLED for permissive mode'); // No timer setup - completely disabled debugPrint('Session: No periodic checks will run'); } // Stop session monitoring static void _stopSessionMonitoring() { _sessionCheckTimer?.cancel(); _sessionCheckTimer = null; _stopPresenceUpdates(); debugPrint('Session: Monitoring stopped'); } // Dispose resources static void dispose() { _sessionCheckTimer?.cancel(); _presenceUpdateTimer?.cancel(); _sessionExpiredController.close(); try { if (GetIt.instance.isRegistered()) { GetIt.instance().dispose(); } } catch (e) { debugPrint('Session: Error disposing presence service - $e'); } } }