// update_dialog.dart — 「发现新版本」更新提示弹窗。 // // 点「下载更新」走 App 内下载安装(core/update/app_updater.dart,带进度框,不再开 // 浏览器);Android 装 APK、Windows 跑安装包、macOS 解压提示拖入、iOS 降级外链。 // force_update=true 时不可关闭(无「稍后」按钮、点遮罩/返回也关不掉)。 import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/update/app_updater.dart'; import '../l10n/app_text.dart'; import '../pangolin_theme.dart'; import '../state/app_providers.dart'; import '../state/update_provider.dart'; import 'pangolin_icons.dart'; /// 展示更新弹窗。调用方(设置页「检查更新」)在拿到 `info.hasUpdate == true` 时调用。 Future showUpdateDialog(BuildContext context, AppText t, AppUpdateInfo info) { return showDialog( context: context, barrierDismissible: !info.forceUpdate, builder: (ctx) => PopScope( canPop: !info.forceUpdate, child: _UpdateDialog(t: t, info: info), ), ); } class _UpdateDialog extends StatelessWidget { const _UpdateDialog({required this.t, required this.info}); final AppText t; final AppUpdateInfo info; @override Widget build(BuildContext context) { final c = context.pangolin; return AlertDialog( backgroundColor: c.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(PangolinRadius.xl)), title: Row(children: [ Container( width: 34, height: 34, decoration: BoxDecoration(color: c.accentSubtle, shape: BoxShape.circle), child: Icon(PangolinIcons.zap, size: 18, color: c.accent), ), const SizedBox(width: 12), Expanded( child: Text( t.updateAvailableTitle(info.latestVersion), overflow: TextOverflow.ellipsis, style: PangolinText.body.copyWith(color: c.fg1, fontWeight: FontWeight.w700), ), ), ]), content: SingleChildScrollView( child: Text( info.releaseNotes.isEmpty ? t.updateNotesFallback : info.releaseNotes, style: PangolinText.sm.copyWith(color: c.fg2, height: 1.5), ), ), actions: [ if (!info.forceUpdate) TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(t.updateLater, style: PangolinText.sm.copyWith(color: c.fg2, fontWeight: FontWeight.w600)), ), TextButton( onPressed: () { // 先关本弹窗,用 Navigator 自身 context(关后仍挂载)启动 App 内下载 // (它自带进度框)。 final nav = Navigator.of(context); nav.pop(); unawaited(startInAppUpdate(nav.context, t, info)); }, child: Text(t.updateDownload, style: PangolinText.sm.copyWith(color: c.accent, fontWeight: FontWeight.w700)), ), ], ); } } /// jiu 式顶部更新横条(非强制更新用;强制更新仍走不可关闭弹窗 showUpdateDialog)。 /// 「立即更新」App 内下载安装;「×」忽略此版本(banner 隐藏,更新版本会重现)。 class UpdateBanner extends ConsumerWidget { const UpdateBanner({super.key, required this.info}); final AppUpdateInfo info; @override Widget build(BuildContext context, WidgetRef ref) { final c = context.pangolin; final t = ref.watch(appTextProvider); return Material( color: c.accentSubtle, child: SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.fromLTRB(14, 8, 6, 8), child: Row(children: [ Icon(PangolinIcons.zap, size: 17, color: c.accent), const SizedBox(width: 10), Expanded( child: Text( t.updateAvailableTitle(info.latestVersion), style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w700), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), TextButton( onPressed: () => unawaited(startInAppUpdate(context, t, info)), style: TextButton.styleFrom( backgroundColor: c.accent, foregroundColor: c.fgOnAccent, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(PangolinRadius.full)), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Text(t.lang.updateNow, style: PangolinText.caption .copyWith(fontWeight: FontWeight.w700, fontSize: 12.5)), ), IconButton( onPressed: () => ref.read(dismissedUpdateVersionProvider.notifier).state = info.latestVersion, icon: Icon(Icons.close, size: 18, color: c.fg3), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 36, minHeight: 36), tooltip: t.updateLater, ), ]), ), ), ); } }