Files
jiu/client/lib/providers/update_provider.dart
T
wangjia 36c7ad8b43
Deploy Client / build-client-web (push) Successful in 38s
Deploy Client / build-windows (push) Successful in 1m52s
Deploy Client / build-macos (push) Successful in 1m55s
Deploy Client / build-android (push) Successful in 1m0s
Deploy Client / build-ios (push) Successful in 2m47s
Deploy Client / release-deploy-client (push) Successful in 1m21s
chore: release client-v1.0.61
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 08:56:02 +08:00

164 lines
5.3 KiB
Dart

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';
import '../core/config/app_constants.dart';
// ── 数据模型 ────────────────────────────────────────────────
class AppUpdateInfo {
final String latestVersion;
final int buildNumber;
final bool forceUpdate;
final String releaseNotes;
final Map<String, String> 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, AppUpdateInfo?>(UpdateNotifier.new);
class UpdateNotifier extends AsyncNotifier<AppUpdateInfo?> {
String get _checkUrl => AppConfig.versionUrl;
Timer? _timer;
bool _dismissed = false; // 用户已手动关闭非强制更新提示
final _dio = Dio(BaseOptions(
connectTimeout: AppConstants.updateCheckTimeout,
receiveTimeout: AppConstants.updateCheckTimeout,
));
@override
Future<AppUpdateInfo?> build() async {
ref.onDispose(() {
_timer?.cancel();
_dio.close(force: true);
});
// 启动后延迟首次检查(避免与登录请求竞争)
await Future.delayed(AppConstants.updateInitialDelay);
final result = await _check();
// 定期检查更新
_timer = Timer.periodic(AppConstants.updatePollInterval, (_) async {
_dismissed = false;
final r = await _check();
state = AsyncValue.data(r);
});
return result;
}
/// 手动触发一次检查(供设置页"检查更新"按钮调用)
Future<void> 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<AppUpdateInfo?> _check() async {
try {
final resp = await _dio.get(_checkUrl);
final data = resp.data as Map<String, dynamic>;
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<String, dynamic>? ?? {};
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<int> _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<String>((ref) async {
final info = await PackageInfo.fromPlatform();
return 'v${info.version}';
});
// ── 打开下载链接工具函数 ────────────────────────────────────
Future<void> launchUpdateUrl(Map<String, String> 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);
}
}