import 'dart:async'; import 'package:flutter/foundation.dart'; class NetworkSpeedMonitor { NetworkSpeedMonitor({required this._totalRxBytes, DateTime Function()? now}) : _now = now ?? DateTime.now; final Future Function() _totalRxBytes; final DateTime Function() _now; final ValueNotifier speed = ValueNotifier(0); final ValueNotifier available = ValueNotifier(false); Timer? _timer; int _lastBytes = 0; int? _latestTotalBytes; int? get latestTotalBytes => _latestTotalBytes; DateTime? _lastSampleAt; int _sampleSessionId = 0; bool _disposed = false; bool _sampleInFlight = false; void start() { if (_disposed) return; _stopActiveSampling(resetValue: true); final sessionId = _sampleSessionId; _timer = Timer.periodic(const Duration(seconds: 1), (_) { if (_disposed || sessionId != _sampleSessionId || _sampleInFlight) { return; } _sampleInFlight = true; unawaited(_sample(sessionId)); }); } void stop() { _stopActiveSampling(resetValue: true); } Future _sample(int sessionId) async { try { final totalBytes = await _totalRxBytes(); if (_disposed || sessionId != _sampleSessionId) return; if (totalBytes == null) { available.value = false; speed.value = 0.0; return; } available.value = true; final sampled = _sampleSpeedFromTotalBytes(totalBytes); if (sampled != null) { speed.value = sampled; } } finally { if (!_disposed && sessionId == _sampleSessionId) { _sampleInFlight = false; } } } double? _sampleSpeedFromTotalBytes(int totalBytes) { final now = _now(); final lastSampleAt = _lastSampleAt; final lastBytes = _lastBytes; _lastBytes = totalBytes; _latestTotalBytes = totalBytes; _lastSampleAt = now; if (lastSampleAt == null) return null; if (totalBytes < lastBytes) return 0.0; final elapsedMs = now.difference(lastSampleAt).inMilliseconds; final deltaBytes = totalBytes - lastBytes; if (elapsedMs <= 0 || deltaBytes <= 0) return 0.0; return deltaBytes * 1000.0 / elapsedMs; } void _stopActiveSampling({required bool resetValue}) { _timer?.cancel(); _timer = null; _sampleSessionId++; _sampleInFlight = false; _lastBytes = 0; _latestTotalBytes = null; _lastSampleAt = null; if (resetValue && !_disposed) { available.value = false; speed.value = 0; } } void dispose() { if (_disposed) return; _stopActiveSampling(resetValue: false); _disposed = true; speed.dispose(); available.dispose(); } }