ops-console v1.0.2 (versionCode 8) 源码入库
This commit is contained in:
161
lib/utils/app_update.dart
Normal file
161
lib/utils/app_update.dart
Normal file
@@ -0,0 +1,161 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../services/api_client.dart';
|
||||
import '../services/update_service.dart';
|
||||
import '../utils/i10n.dart';
|
||||
|
||||
/// 全局「已弹过更新提示」标记,避免会话内重复弹窗。
|
||||
bool _promptedThisSession = false;
|
||||
|
||||
/// 全局当前 [PackageInfo] 缓存(首次读取后复用)。
|
||||
PackageInfo? _cachedPackageInfo;
|
||||
|
||||
/// 取得当前 App 包信息(带缓存)。
|
||||
Future<PackageInfo> get currentPackageInfo async =>
|
||||
_cachedPackageInfo ??= await PackageInfo.fromPlatform();
|
||||
|
||||
/// 检测更新并在有新版时弹窗提示。
|
||||
///
|
||||
/// - [silentIfNoUpdate]=true(如启动时):无更新或检测失败均静默;
|
||||
/// - [silentIfNoUpdate]=false(如手动「检查更新」):无更新/失败给出明确反馈。
|
||||
/// - [orgId] 当前登录用户所属组织 ID;传空时与旧行为一致。
|
||||
Future<void> checkAndPromptUpdate(
|
||||
BuildContext context, {
|
||||
bool silentIfNoUpdate = true,
|
||||
String? orgId,
|
||||
}) async {
|
||||
final t = AppLocalizations.of(context);
|
||||
try {
|
||||
final pkg = await currentPackageInfo;
|
||||
final service = UpdateService(packageName: pkg.packageName);
|
||||
final currentCode = int.tryParse(pkg.buildNumber) ?? 0;
|
||||
final result = await service.checkForUpdate(currentCode, orgId: orgId);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (result.hasUpdate && result.latest != null) {
|
||||
if (!_promptedThisSession) {
|
||||
_promptedThisSession = true;
|
||||
showUpdateDialog(context, result, pkg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!silentIfNoUpdate && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.updateNoNew)),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// 任何异常均被捕获,不抛给调用方;仅非静默模式下给用户一句友好提示。
|
||||
if (!silentIfNoUpdate && context.mounted) {
|
||||
final detail = formatApiError(e, fallback: '网络异常,请稍后重试');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('检测更新失败:$detail')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 版本更新提示对话框:展示当前 / 最新版本,并提供「立即更新」跳转下载。
|
||||
void showUpdateDialog(
|
||||
BuildContext context,
|
||||
UpdateCheckResult result,
|
||||
PackageInfo pkg,
|
||||
) {
|
||||
final t = AppLocalizations.of(context);
|
||||
final latest = result.latest!;
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(Icons.system_update_alt, color: Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
Text(t.updateAvailable),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_InfoRow(label: t.currentVersion, value: '${pkg.version} (${pkg.buildNumber})'),
|
||||
const SizedBox(height: 6),
|
||||
_InfoRow(
|
||||
label: t.latestVersion,
|
||||
value: '${latest.versionName} (${latest.versionCode})',
|
||||
),
|
||||
if (latest.apkUrl.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
_InfoRow(label: t.downloadUrl, value: latest.apkUrl),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text(t.updateLater),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(ctx);
|
||||
final url = latest.apkUrl;
|
||||
if (url.isEmpty) return;
|
||||
final uri = Uri.parse(url);
|
||||
try {
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.updateOpenFailed)),
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.updateOpenFailed)),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(t.updateNow),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 对话框内的「标签:值」单行。
|
||||
class _InfoRow extends StatelessWidget {
|
||||
const _InfoRow({required this.label, required this.value});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 84,
|
||||
child: Text(
|
||||
label,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
value,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user