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 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 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 _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 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 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 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 getConnectionStats() { if (state.isEmpty) { return { 'totalChanges': 0, 'uptimePercentage': 0.0, 'mostCommonConnection': 'Unknown', 'connectionTypes': {}, }; } final connectionTypeCounts = {}; 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)), }; } }