import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; /// Service to track and manage user online presence class UserPresenceService { static const String _presenceTableName = 'user_presence'; static const int _presenceTimeout = 60; // seconds final _supabase = Supabase.instance.client; Timer? _heartbeatTimer; String? _userId; bool _isInitialized = false; final StreamController _onlineUsersController = StreamController.broadcast(); /// Stream of currently online users count Stream get onlineUsersStream => _onlineUsersController.stream; /// Current count of online users int _onlineUsersCount = 0; int get onlineUsersCount => _onlineUsersCount; /// Initialize the presence service Future initialize() async { if (_isInitialized) return; try { final currentUser = _supabase.auth.currentUser; if (currentUser == null) { debugPrint('UserPresence: No authenticated user'); return; } _userId = currentUser.id; // Start heartbeat timer _startHeartbeat(); // Initial presence update await updatePresence(); // Get initial online users count await _fetchOnlineUsersCount(); _isInitialized = true; debugPrint('UserPresence: Service initialized for user $_userId'); } catch (e) { debugPrint('UserPresence: Error initializing - $e'); } } /// Start heartbeat timer to keep presence alive void _startHeartbeat() { _heartbeatTimer?.cancel(); // Update presence every 30 seconds _heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) { updatePresence(); _fetchOnlineUsersCount(); }); } /// Update user presence Future updatePresence() async { if (_userId == null) return; try { final currentUser = _supabase.auth.currentUser; if (currentUser == null) return; final now = DateTime.now().toIso8601String(); // Update the database record await _supabase.from(_presenceTableName).upsert({ 'id': _userId!, 'user_id': _userId!, 'email': currentUser.email ?? '', 'last_seen_at': now, 'is_online': true, }); debugPrint('UserPresence: Updated presence for user $_userId'); } catch (e) { debugPrint('UserPresence: Error updating presence - $e'); } } /// Fetch the count of online users Future _fetchOnlineUsersCount() async { try { final now = DateTime.now(); final cutoffTime = now.subtract(Duration(seconds: _presenceTimeout)); // First, get all online users final response = await _supabase .from(_presenceTableName) .select() .gt('last_seen_at', cutoffTime.toIso8601String()) .eq('is_online', true); // Calculate count from the response final List users = response; final count = users.length; if (_onlineUsersCount != count) { _onlineUsersCount = count; _onlineUsersController.add(count); debugPrint( 'UserPresence: Online users count updated: $_onlineUsersCount', ); } } catch (e) { debugPrint('UserPresence: Error fetching online users count - $e'); } } /// Get all currently online users Future>> getOnlineUsers() async { try { final now = DateTime.now(); final cutoffTime = now.subtract(Duration(seconds: _presenceTimeout)); final response = await _supabase .from(_presenceTableName) .select('id, user_id, email, last_seen_at') .gt('last_seen_at', cutoffTime.toIso8601String()) .eq('is_online', true); return List>.from(response); } catch (e) { debugPrint('UserPresence: Error getting online users - $e'); return []; } } /// Clean up resources void dispose() { _heartbeatTimer?.cancel(); _onlineUsersController.close(); _isInitialized = false; // Set user as offline in the database if (_userId != null) { try { _supabase .from(_presenceTableName) .update({'is_online': false}) .eq('id', _userId!) .then((_) { debugPrint('UserPresence: User set to offline'); }); } catch (e) { debugPrint('UserPresence: Error setting user offline - $e'); } } debugPrint('UserPresence: Service disposed'); } }