feat: enhance user avatar display with S3Image and initial-based fallback, improve socket reconnection logic, and update splash screen layout, assets, and Android permissions.

This commit is contained in:
pradeepkumar
2026-03-27 15:39:38 +05:30
parent ca4315b09d
commit 625db4bb99
7 changed files with 132 additions and 56 deletions

View File

@@ -20,6 +20,9 @@ class SocketService {
io.Socket? _socket;
bool _isConnected = false;
bool _intentionalDisconnect = false;
bool _isReconnecting = false;
int _reconnectAttempts = 0;
static const _maxReconnectAttempts = 3;
String? _currentToken;
// Stream controllers for events
@@ -81,6 +84,8 @@ class SocketService {
_socket!.onConnect((_) {
_log.i('Socket connected: ${_socket!.id}');
_isConnected = true;
_reconnectAttempts = 0; // Reset on successful connect
_isReconnecting = false;
_connectionController.add(true);
});
@@ -89,9 +94,14 @@ class SocketService {
_isConnected = false;
_connectionController.add(false);
if (reason == 'io server disconnect' && !_intentionalDisconnect) {
_log.w('Server rejected connection - token likely expired');
_reconnectWithFreshToken();
if (reason == 'io server disconnect' && !_intentionalDisconnect && !_isReconnecting) {
if (_reconnectAttempts < _maxReconnectAttempts) {
_reconnectAttempts++;
_log.w('Server rejected connection (attempt $_reconnectAttempts/$_maxReconnectAttempts) - refreshing token...');
_reconnectWithFreshToken();
} else {
_log.e('Max reconnect attempts reached - giving up');
}
}
});
@@ -170,31 +180,52 @@ class SocketService {
}
Future<void> _reconnectWithFreshToken() async {
// Wait briefly for any in-flight REST token refresh to complete
await Future.delayed(const Duration(milliseconds: 500));
_isReconnecting = true;
// Wait with exponential backoff
final delay = Duration(seconds: _reconnectAttempts * 2);
_log.i('Waiting ${delay.inSeconds}s before reconnect attempt...');
await Future.delayed(delay);
final token = await SecureStorage.getAccessToken();
if (token == null) return;
if (token == null) {
_log.w('No access token available for socket reconnection');
return;
}
// If token hasn't changed, try triggering a refresh via a lightweight API call
if (token == _currentToken) {
_log.i('Token unchanged, triggering refresh via /auth/me...');
try {
// Make a lightweight call that triggers the API client's 401 interceptor
// which auto-refreshes the token
final dio = ApiClient.instance.dio;
await dio.get('/auth/me');
final refreshedToken = await SecureStorage.getAccessToken();
if (refreshedToken == null || refreshedToken == _currentToken) return;
_currentToken = refreshedToken;
} catch (_) {
return; // Token refresh failed — user will need to re-login
if (refreshedToken == null || refreshedToken == _currentToken) {
_log.w('Token refresh did not produce a new token');
// Still try reconnecting — token might be valid but socket had a glitch
} else {
_log.i('Got fresh token after refresh');
}
} catch (e) {
_log.e('Token refresh failed: $e');
// Still try reconnecting with existing token
}
} else {
_currentToken = token;
}
_socket?.io.options?['auth'] = {'token': _currentToken};
_socket?.connect();
// Get the latest token (may have been refreshed)
final freshToken = await SecureStorage.getAccessToken();
if (freshToken == null) return;
_log.i('Reconnecting socket with fresh token...');
// Full reconnect — dispose old socket and create new one
_socket?.disconnect();
_socket?.dispose();
_socket = null;
_isConnected = false;
_currentToken = null;
// Small delay before reconnecting
await Future.delayed(const Duration(milliseconds: 500));
await connect();
}
/// Ensure socket is connected — reconnect if needed.
@@ -205,6 +236,8 @@ class SocketService {
void disconnect() {
_intentionalDisconnect = true;
_isReconnecting = false;
_reconnectAttempts = 0;
_socket?.disconnect();
_socket?.dispose();
_socket = null;