import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import '../core/config/app_config.dart'; // ── 数据模型 ──────────────────────────────────────────────── class AppUpdateInfo { final String latestVersion; final int buildNumber; final bool forceUpdate; final String releaseNotes; final Map downloadUrls; final bool hasUpdate; const AppUpdateInfo({ required this.latestVersion, required this.buildNumber, required this.forceUpdate, required this.releaseNotes, required this.downloadUrls, required this.hasUpdate, }); } // ── Provider ──────────────────────────────────────────────── /// null = 检查失败或无更新数据(不影响主流程) /// AppUpdateInfo with hasUpdate=false = 已是最新版 /// AppUpdateInfo with hasUpdate=true = 有新版本 final updateProvider = AsyncNotifierProvider(UpdateNotifier.new); class UpdateNotifier extends AsyncNotifier { String get _checkUrl => AppConfig.versionUrl; Timer? _timer; bool _dismissed = false; // 用户已手动关闭非强制更新提示 final _dio = Dio(BaseOptions( connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 5), )); @override Future build() async { ref.onDispose(() { _timer?.cancel(); _dio.close(force: true); }); // 启动后 3 秒延迟首次检查(避免与登录请求竞争) await Future.delayed(const Duration(seconds: 3)); final result = await _check(); // 每小时检查一次 _timer = Timer.periodic(const Duration(hours: 1), (_) async { _dismissed = false; final r = await _check(); state = AsyncValue.data(r); }); return result; } /// 手动触发一次检查(供设置页"检查更新"按钮调用) Future forceCheck() async { _dismissed = false; state = const AsyncValue.loading(); state = AsyncValue.data(await _check()); } /// 用户点击"稍后再说"后调用,隐藏 banner(直到下次定时刷新) void dismiss() { _dismissed = true; // 保留数据但 UI 通过 dismissed 状态判断是否显示 state = AsyncValue.data(state.valueOrNull); } bool get isDismissed => _dismissed; Future _check() async { try { final resp = await _dio.get(_checkUrl); final data = resp.data as Map; final latestVersion = data['version'] as String? ?? '0.0.0'; final buildNumber = data['build_number'] as int? ?? 0; final forceUpdate = data['force_update'] as bool? ?? false; final releaseNotes = data['release_notes'] as String? ?? ''; final rawUrls = data['download_urls'] as Map? ?? {}; final downloadUrls = rawUrls.map((k, v) => MapEntry(k, v?.toString() ?? '')); final info = await PackageInfo.fromPlatform(); final hasUpdate = _isNewer(latestVersion, info.version); return AppUpdateInfo( latestVersion: latestVersion, buildNumber: buildNumber, forceUpdate: forceUpdate, releaseNotes: releaseNotes, downloadUrls: downloadUrls, hasUpdate: hasUpdate, ); } catch (_) { return null; // 检查失败静默处理,不影响主业务 } } /// 语义化版本比较:latest > current → true bool _isNewer(String latest, String current) { final l = _parse(latest); final c = _parse(current); for (var i = 0; i < 3; i++) { if (l[i] > c[i]) return true; if (l[i] < c[i]) return false; } return false; } List _parse(String v) { final parts = v.split('.').map((s) => int.tryParse(s) ?? 0).toList(); while (parts.length < 3) parts.add(0); return parts; } } // ── 版本号 Provider(供状态栏 / 门店信息面板使用)────────── final appVersionProvider = FutureProvider((ref) async { final info = await PackageInfo.fromPlatform(); return 'v${info.version}'; }); // ── 打开下载链接工具函数 ──────────────────────────────────── Future launchUpdateUrl(Map downloadUrls) async { String? urlStr; if (kIsWeb) { urlStr = downloadUrls['web']; } else if (Platform.isMacOS) { urlStr = downloadUrls['macos']; } else if (Platform.isWindows) { urlStr = downloadUrls['windows']; } else if (Platform.isIOS) { urlStr = downloadUrls['ios']; } else if (Platform.isAndroid) { urlStr = downloadUrls['android']; } else { // Linux 等 urlStr = downloadUrls['web']; } if (urlStr == null || urlStr.isEmpty) return; final uri = Uri.parse(urlStr); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } }