0b86138b55
Deploy Client / build-windows (push) Successful in 1m47s
Deploy Client / build-android (push) Has been cancelled
Deploy Client / build-macos (push) Has been cancelled
Deploy Client / build-ios (push) Has been cancelled
Deploy Client / release-deploy (push) Has been cancelled
按需求去掉居中弹窗,改成不打断的顶部 banner(对照 jiu 黄条): - 非强制更新 → home_shell 顶部 UpdateBanner「发现新版本 vX + 立即更新 + ×」; 「立即更新」App 内下载安装,「×」忽略此版本(banner 隐藏,更新版本会重现) - 强制更新(force_update)→ 仍走不可关闭弹窗 showUpdateDialog - 忽略状态改 dismissedUpdateVersionProvider(按版本号,响应式);去掉 notifier 的 _dismissed - 设置页「检查更新」有更新时:清忽略版本→banner 显示 + toast,不再弹窗 - 新增 6 语「立即更新」文案。flutter analyze 仅 2 个既有 withOpacity info Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
140 lines
5.2 KiB
Dart
140 lines
5.2 KiB
Dart
// 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<void> showUpdateDialog(BuildContext context, AppText t, AppUpdateInfo info) {
|
|
return showDialog<void>(
|
|
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,
|
|
),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|