170 lines
5.1 KiB
Dart
170 lines
5.1 KiB
Dart
/// Offline-first manager for connectivity monitoring and operation queueing
|
|
library offline_manager;
|
|
|
|
import 'dart:async';
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
import '../storage/pending_operations_store.dart';
|
|
import '../utils/logger.dart' show AppLogger;
|
|
|
|
/// Status of network connectivity
|
|
enum ConnectivityStatus {
|
|
online,
|
|
offline,
|
|
unknown,
|
|
}
|
|
|
|
/// Offline manager that monitors connectivity and manages offline operations
|
|
@singleton
|
|
class OfflineManager {
|
|
final Connectivity _connectivity;
|
|
final PendingOperationsStore _operationsStore;
|
|
|
|
ConnectivityStatus _currentStatus = ConnectivityStatus.unknown;
|
|
final _statusController = StreamController<ConnectivityStatus>.broadcast();
|
|
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
|
|
|
OfflineManager(
|
|
this._connectivity,
|
|
this._operationsStore,
|
|
) {
|
|
_initConnectivityMonitoring();
|
|
}
|
|
|
|
/// Current connectivity status
|
|
ConnectivityStatus get currentStatus => _currentStatus;
|
|
|
|
/// Stream of connectivity status changes
|
|
Stream<ConnectivityStatus> get statusStream => _statusController.stream;
|
|
|
|
/// Check if device is currently online
|
|
Future<bool> get isOnline async {
|
|
final result = await _connectivity.checkConnectivity();
|
|
return result.any((r) => r != ConnectivityResult.none);
|
|
}
|
|
|
|
/// Initialize connectivity monitoring
|
|
void _initConnectivityMonitoring() {
|
|
// Check initial connectivity
|
|
_connectivity.checkConnectivity().then((result) {
|
|
_updateStatus(result);
|
|
});
|
|
|
|
// Listen for connectivity changes
|
|
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(
|
|
_updateStatus,
|
|
onError: (error) {
|
|
AppLogger.error('Connectivity monitoring error', error: error);
|
|
_updateStatus([ConnectivityResult.none]);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Update connectivity status
|
|
void _updateStatus(List<ConnectivityResult> results) {
|
|
final isConnected = results.any((r) => r != ConnectivityResult.none);
|
|
final newStatus = isConnected
|
|
? ConnectivityStatus.online
|
|
: ConnectivityStatus.offline;
|
|
|
|
if (newStatus != _currentStatus) {
|
|
final previousStatus = _currentStatus;
|
|
_currentStatus = newStatus;
|
|
_statusController.add(newStatus);
|
|
|
|
AppLogger.info('Connectivity changed: $previousStatus → $newStatus');
|
|
|
|
// When back online, process pending operations
|
|
if (newStatus == ConnectivityStatus.online &&
|
|
previousStatus == ConnectivityStatus.offline) {
|
|
_processPendingOperations();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Queue an operation for later retry when offline
|
|
Future<void> queueOperation({
|
|
required String operationType,
|
|
required String endpoint,
|
|
required Map<String, dynamic> data,
|
|
Map<String, String>? headers,
|
|
}) async {
|
|
try {
|
|
await _operationsStore.addPendingOperation(
|
|
operationType: operationType,
|
|
endpoint: endpoint,
|
|
data: data,
|
|
headers: headers,
|
|
);
|
|
AppLogger.info('Operation queued: $operationType on $endpoint');
|
|
} catch (e) {
|
|
AppLogger.error('Failed to queue operation', error: e);
|
|
}
|
|
}
|
|
|
|
/// Process all pending operations when back online
|
|
Future<void> _processPendingOperations() async {
|
|
AppLogger.info('Processing pending operations...');
|
|
|
|
try {
|
|
final operations = await _operationsStore.getPendingOperations();
|
|
|
|
if (operations.isEmpty) {
|
|
AppLogger.info('No pending operations to process');
|
|
return;
|
|
}
|
|
|
|
AppLogger.info('Found ${operations.length} pending operations');
|
|
|
|
// Process operations one by one
|
|
for (final operation in operations) {
|
|
try {
|
|
// Note: Actual retry logic is delegated to the calling code
|
|
// This manager only provides the queuing mechanism
|
|
AppLogger.info('Pending operation ready for retry: ${operation['operationType']}');
|
|
} catch (e) {
|
|
AppLogger.error('Error processing pending operation', error: e);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('Failed to process pending operations', error: e);
|
|
}
|
|
}
|
|
|
|
/// Manually trigger processing of pending operations
|
|
Future<void> retryPendingOperations() async {
|
|
if (_currentStatus == ConnectivityStatus.online) {
|
|
await _processPendingOperations();
|
|
} else {
|
|
AppLogger.warning('Cannot retry pending operations while offline');
|
|
}
|
|
}
|
|
|
|
/// Clear all pending operations
|
|
Future<void> clearPendingOperations() async {
|
|
try {
|
|
await _operationsStore.clearAll();
|
|
AppLogger.info('Pending operations cleared');
|
|
} catch (e) {
|
|
AppLogger.error('Failed to clear pending operations', error: e);
|
|
}
|
|
}
|
|
|
|
/// Get count of pending operations
|
|
Future<int> getPendingOperationsCount() async {
|
|
try {
|
|
final operations = await _operationsStore.getPendingOperations();
|
|
return operations.length;
|
|
} catch (e) {
|
|
AppLogger.error('Failed to get pending operations count', error: e);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// Dispose resources
|
|
void dispose() {
|
|
_connectivitySubscription?.cancel();
|
|
_statusController.close();
|
|
}
|
|
}
|