From 53a2e64cad8f86826684f4f616511cd744fcd224 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 30 Jan 2026 12:07:40 -0800 Subject: [PATCH] =?UTF-8?q?feat(mining-app):=20=E5=90=AF=E5=8A=A8=E5=92=8C?= =?UTF-8?q?=E5=88=87=E5=9B=9E=E5=89=8D=E5=8F=B0=E8=87=AA=E5=8A=A8=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MainShell 从 StatelessWidget 转为 StatefulWidget + WidgetsBindingObserver, 参考 mobile-app HomeShellPage 模式,实现: - initState 首帧后自动检查更新 - didChangeAppLifecycleState(resumed) 从后台恢复时自动检查 - 90-300秒随机冷却间隔防止频繁请求(static 变量跨 rebuild 保持) - 延迟3秒启动检查,避免干扰用户操作 - 有更新时弹出 SelfHostedUpdater 对话框(支持强制更新) Co-Authored-By: Claude Opus 4.5 --- .../lib/presentation/widgets/main_shell.dart | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/frontend/mining-app/lib/presentation/widgets/main_shell.dart b/frontend/mining-app/lib/presentation/widgets/main_shell.dart index 47dac9be..8a6bd8a2 100644 --- a/frontend/mining-app/lib/presentation/widgets/main_shell.dart +++ b/frontend/mining-app/lib/presentation/widgets/main_shell.dart @@ -1,19 +1,82 @@ +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../core/router/routes.dart'; import '../../core/constants/app_colors.dart'; +import '../../core/updater/update_service.dart'; +import '../../core/updater/channels/self_hosted_updater.dart'; -class MainShell extends StatelessWidget { +class MainShell extends StatefulWidget { final Widget child; const MainShell({super.key, required this.child}); + @override + State createState() => _MainShellState(); +} + +class _MainShellState extends State with WidgetsBindingObserver { + /// 下次允许检查更新的时间(static 防止 widget rebuild 重置) + static DateTime? _nextCheckAllowedTime; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkForUpdateIfNeeded(); + }); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _checkForUpdateIfNeeded(); + } + } + + Future _checkForUpdateIfNeeded() async { + final now = DateTime.now(); + + // 如果从未检查过,或者已过冷却时间,则检查 + if (_nextCheckAllowedTime == null || now.isAfter(_nextCheckAllowedTime!)) { + // 设置下次允许检查时间(90-300秒随机间隔) + final randomSeconds = 90 + Random().nextInt(211); + _nextCheckAllowedTime = now.add(Duration(seconds: randomSeconds)); + + // 延迟3秒,避免启动时干扰用户 + await Future.delayed(const Duration(seconds: 3)); + if (!mounted) return; + + try { + final result = await UpdateService.instance.checkForUpdate(); + if (!mounted) return; + + if (result.hasUpdate && result.versionInfo != null) { + await SelfHostedUpdater.show( + context, + versionInfo: result.versionInfo!, + isForceUpdate: result.versionInfo!.isForceUpdate, + ); + } + } catch (e) { + debugPrint('[MainShell] 检查更新失败: $e'); + } + } + } + @override Widget build(BuildContext context) { final isDark = AppColors.isDark(context); return Scaffold( - body: child, + body: widget.child, bottomNavigationBar: Container( decoration: BoxDecoration( color: AppColors.cardOf(context),