MIF_E31222656/lib/services/user_presence_service.dart

162 lines
4.5 KiB
Dart

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<int> _onlineUsersController =
StreamController<int>.broadcast();
/// Stream of currently online users count
Stream<int> get onlineUsersStream => _onlineUsersController.stream;
/// Current count of online users
int _onlineUsersCount = 0;
int get onlineUsersCount => _onlineUsersCount;
/// Initialize the presence service
Future<void> 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<void> 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<void> _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<dynamic> 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<List<Map<String, dynamic>>> 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<Map<String, dynamic>>.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');
}
}