import 'dart:async'; import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/config/app_config.dart'; import '../core/config/app_constants.dart'; /// 每次从离线恢复到在线时自增。 /// 数据 provider 通过 watch 此值实现网络恢复后自动刷新。 final networkRecoveryCountProvider = StateProvider((ref) => 0); /// 是否正在进行一次「带重试的连通性检测」(供 UI 显示「重试中…」转圈)。 final connectivityCheckingProvider = StateProvider((ref) => false); final connectivityProvider = StateNotifierProvider((ref) { return ConnectivityNotifier( onRecovered: () { ref.read(networkRecoveryCountProvider.notifier).update((s) => s + 1); }, onCheckingChanged: (checking) { ref.read(connectivityCheckingProvider.notifier).state = checking; }, ); }); class ConnectivityNotifier extends StateNotifier { final void Function()? onRecovered; final void Function(bool checking)? onCheckingChanged; ConnectivityNotifier({ this.onRecovered, this.onCheckingChanged, bool skipInit = false, }) : super(true) { if (!skipInit) { // 启动首检走「带重试」:跨境冷启动 DNS+TLS 握手较慢,单次 4s 易误判离线, // 退避重试 4s→8s→12s 可显著降低首屏假离线。 _check(withRetry: true); _startOnlineTimer(); } } Timer? _timer; // 独立轻量 Dio:无拦截器。连接预算用 Future.timeout 逐次控制, // 故 BaseOptions 超时放宽到 15s 作为兜底。 final _dio = Dio(BaseOptions( connectTimeout: AppConstants.connectivityTimeout, receiveTimeout: AppConstants.connectivityTimeout, )); /// 每次尝试的超时预算,逐次拉长(越来越长)。 static const _budgets = AppConstants.connectivityBudgets; /// 在线时:轻量检测一次(单次,不重试) void _startOnlineTimer() { _timer?.cancel(); _timer = Timer.periodic(AppConstants.connectivityOnlinePoll, (_) => _check()); } /// 离线时:降低频率嗅探,节省资源 void _startOfflineTimer() { _timer?.cancel(); _timer = Timer.periodic(AppConstants.connectivityOfflinePoll, (_) => _check()); } /// 立即触发一次检测(供外部调用,如 API 请求失败 / 启动时)。单次,不重试。 Future forceCheck() => _check(); /// 用户手动重试:带退避重试,并对外广播「检测中」状态,返回最终是否在线。 Future retry() async { onCheckingChanged?.call(true); try { return await _check(withRetry: true); } finally { onCheckingChanged?.call(false); } } /// 单次 ping /health,[budget] 为本次超时预算。 Future _ping(Duration budget) async { try { await _dio.get(AppConfig.healthUrl).timeout(budget); return true; } catch (_) { return false; } } /// 连通性检测。[withRetry]=true 时按 [_budgets] 退避重试,间隔递增。 Future _check({bool withRetry = false}) async { final budgets = withRetry ? _budgets : AppConstants.connectivitySingleBudget; var ok = false; for (var i = 0; i < budgets.length; i++) { ok = await _ping(budgets[i]); if (ok) break; // 最后一次失败后不再等待;中间失败按递增间隔退避(0.6s, 1.2s…) if (i < budgets.length - 1) { await Future.delayed(AppConstants.connectivityRetryStep * (i + 1)); } } if (ok) { if (!state) { // 离线 → 在线:切回高频检测,广播恢复事件 state = true; onRecovered?.call(); _startOnlineTimer(); } } else { if (state) { // 在线 → 离线:切换为低频嗅探 state = false; _startOfflineTimer(); } } return ok; } @override void dispose() { _timer?.cancel(); _dio.close(force: true); super.dispose(); } }