Files
base_flutter/lib/shared/presentation/providers/connectivity_providers.dart
2025-09-26 18:48:14 +07:00

345 lines
9.8 KiB
Dart

import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter/foundation.dart';
part 'connectivity_providers.g.dart';
/// Network connection type
enum NetworkConnectionType {
wifi,
mobile,
ethernet,
bluetooth,
vpn,
other,
none,
}
/// Network status data class
class NetworkStatus {
final bool isConnected;
final NetworkConnectionType connectionType;
final DateTime lastUpdated;
final String? errorMessage;
const NetworkStatus({
required this.isConnected,
required this.connectionType,
required this.lastUpdated,
this.errorMessage,
});
NetworkStatus copyWith({
bool? isConnected,
NetworkConnectionType? connectionType,
DateTime? lastUpdated,
String? errorMessage,
}) {
return NetworkStatus(
isConnected: isConnected ?? this.isConnected,
connectionType: connectionType ?? this.connectionType,
lastUpdated: lastUpdated ?? this.lastUpdated,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
String toString() {
return 'NetworkStatus{isConnected: $isConnected, connectionType: $connectionType, lastUpdated: $lastUpdated}';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is NetworkStatus &&
other.isConnected == isConnected &&
other.connectionType == connectionType;
}
@override
int get hashCode {
return isConnected.hashCode ^ connectionType.hashCode;
}
}
/// Convert ConnectivityResult to NetworkConnectionType
NetworkConnectionType _getConnectionType(List<ConnectivityResult> results) {
if (results.isEmpty || results.contains(ConnectivityResult.none)) {
return NetworkConnectionType.none;
}
if (results.contains(ConnectivityResult.wifi)) {
return NetworkConnectionType.wifi;
}
if (results.contains(ConnectivityResult.mobile)) {
return NetworkConnectionType.mobile;
}
if (results.contains(ConnectivityResult.ethernet)) {
return NetworkConnectionType.ethernet;
}
if (results.contains(ConnectivityResult.bluetooth)) {
return NetworkConnectionType.bluetooth;
}
if (results.contains(ConnectivityResult.vpn)) {
return NetworkConnectionType.vpn;
}
if (results.contains(ConnectivityResult.other)) {
return NetworkConnectionType.other;
}
return NetworkConnectionType.none;
}
/// Connectivity instance provider
@riverpod
Connectivity connectivity(ConnectivityRef ref) {
return Connectivity();
}
/// Network connectivity stream provider
@riverpod
Stream<NetworkStatus> networkConnectivityStream(NetworkConnectivityStreamRef ref) {
final connectivity = ref.watch(connectivityProvider);
return connectivity.onConnectivityChanged.map((results) {
final connectionType = _getConnectionType(results);
final isConnected = connectionType != NetworkConnectionType.none;
return NetworkStatus(
isConnected: isConnected,
connectionType: connectionType,
lastUpdated: DateTime.now(),
);
}).handleError((error) {
debugPrint('❌ Connectivity stream error: $error');
return NetworkStatus(
isConnected: false,
connectionType: NetworkConnectionType.none,
lastUpdated: DateTime.now(),
errorMessage: error.toString(),
);
});
}
/// Current network status provider
@riverpod
class NetworkStatusNotifier extends _$NetworkStatusNotifier {
@override
NetworkStatus build() {
// Start listening to connectivity changes
final streamValue = ref.watch(networkConnectivityStreamProvider);
streamValue.whenData((status) {
state = status;
_logConnectionChange(status);
});
// Get initial connectivity state
_checkInitialConnectivity();
// Return initial state
return NetworkStatus(
isConnected: false,
connectionType: NetworkConnectionType.none,
lastUpdated: DateTime.now(),
);
}
Future<void> _checkInitialConnectivity() async {
try {
final connectivity = ref.read(connectivityProvider);
final result = await connectivity.checkConnectivity();
final connectionType = _getConnectionType(result);
final isConnected = connectionType != NetworkConnectionType.none;
state = NetworkStatus(
isConnected: isConnected,
connectionType: connectionType,
lastUpdated: DateTime.now(),
);
debugPrint('📡 Initial connectivity: ${isConnected ? '✅ Connected' : '❌ Disconnected'} ($connectionType)');
} catch (error) {
debugPrint('❌ Error checking initial connectivity: $error');
state = NetworkStatus(
isConnected: false,
connectionType: NetworkConnectionType.none,
lastUpdated: DateTime.now(),
errorMessage: error.toString(),
);
}
}
void _logConnectionChange(NetworkStatus status) {
final icon = status.isConnected ? '📶' : '📵';
final statusText = status.isConnected ? 'Connected' : 'Disconnected';
debugPrint('$icon Network status changed: $statusText (${status.connectionType})');
}
/// Force refresh network status
Future<void> refresh() async {
await _checkInitialConnectivity();
}
/// Get connection strength (mock implementation)
double getConnectionStrength() {
switch (state.connectionType) {
case NetworkConnectionType.wifi:
return 1.0; // Assume strong Wi-Fi
case NetworkConnectionType.ethernet:
return 1.0; // Assume strong ethernet
case NetworkConnectionType.mobile:
return 0.7; // Assume moderate mobile
case NetworkConnectionType.bluetooth:
return 0.5; // Assume weak bluetooth
case NetworkConnectionType.vpn:
return 0.8; // Assume good VPN
case NetworkConnectionType.other:
return 0.6; // Assume moderate other
case NetworkConnectionType.none:
return 0.0; // No connection
}
}
}
/// Simple connectivity status provider
@riverpod
bool isConnected(IsConnectedRef ref) {
final networkStatus = ref.watch(networkStatusNotifierProvider);
return networkStatus.isConnected;
}
/// Connection type provider
@riverpod
NetworkConnectionType connectionType(ConnectionTypeRef ref) {
final networkStatus = ref.watch(networkStatusNotifierProvider);
return networkStatus.connectionType;
}
/// Is Wi-Fi connected provider
@riverpod
bool isWifiConnected(IsWifiConnectedRef ref) {
final networkStatus = ref.watch(networkStatusNotifierProvider);
return networkStatus.isConnected && networkStatus.connectionType == NetworkConnectionType.wifi;
}
/// Is mobile data connected provider
@riverpod
bool isMobileConnected(IsMobileConnectedRef ref) {
final networkStatus = ref.watch(networkStatusNotifierProvider);
return networkStatus.isConnected && networkStatus.connectionType == NetworkConnectionType.mobile;
}
/// Network quality indicator provider
@riverpod
String networkQuality(NetworkQualityRef ref) {
final networkStatus = ref.watch(networkStatusNotifierProvider);
if (!networkStatus.isConnected) {
return 'No Connection';
}
switch (networkStatus.connectionType) {
case NetworkConnectionType.wifi:
return 'Excellent';
case NetworkConnectionType.ethernet:
return 'Excellent';
case NetworkConnectionType.mobile:
return 'Good';
case NetworkConnectionType.vpn:
return 'Good';
case NetworkConnectionType.other:
return 'Fair';
case NetworkConnectionType.bluetooth:
return 'Poor';
case NetworkConnectionType.none:
return 'No Connection';
}
}
/// Network history provider for tracking connection changes
@riverpod
class NetworkHistoryNotifier extends _$NetworkHistoryNotifier {
static const int _maxHistorySize = 50;
@override
List<NetworkStatus> build() {
// Listen to network status changes and add to history
ref.listen(networkStatusNotifierProvider, (previous, next) {
if (previous != next) {
_addToHistory(next);
}
});
return [];
}
void _addToHistory(NetworkStatus status) {
final newHistory = [...state, status];
// Keep only the last _maxHistorySize entries
if (newHistory.length > _maxHistorySize) {
newHistory.removeRange(0, newHistory.length - _maxHistorySize);
}
state = newHistory;
}
/// Get recent connection changes
List<NetworkStatus> getRecentChanges({int count = 10}) {
return state.reversed.take(count).toList();
}
/// Get connection uptime percentage
double getUptimePercentage({Duration? period}) {
if (state.isEmpty) return 0.0;
final now = DateTime.now();
final startTime = period != null ? now.subtract(period) : state.first.lastUpdated;
final relevantEntries = state.where((status) => status.lastUpdated.isAfter(startTime)).toList();
if (relevantEntries.isEmpty) return 0.0;
final connectedCount = relevantEntries.where((status) => status.isConnected).length;
return connectedCount / relevantEntries.length;
}
/// Clear history
void clearHistory() {
state = [];
}
/// Get connection statistics
Map<String, dynamic> getConnectionStats() {
if (state.isEmpty) {
return {
'totalChanges': 0,
'uptimePercentage': 0.0,
'mostCommonConnection': 'Unknown',
'connectionTypes': <String, int>{},
};
}
final connectionTypeCounts = <NetworkConnectionType, int>{};
for (final status in state) {
connectionTypeCounts[status.connectionType] = (connectionTypeCounts[status.connectionType] ?? 0) + 1;
}
final mostCommonType = connectionTypeCounts.entries.reduce((a, b) => a.value > b.value ? a : b).key;
return {
'totalChanges': state.length,
'uptimePercentage': getUptimePercentage(),
'mostCommonConnection': mostCommonType.toString().split('.').last,
'connectionTypes': connectionTypeCounts.map((key, value) => MapEntry(key.toString().split('.').last, value)),
};
}
}