feat(mining-app): 启动和切回前台自动检测升级

MainShell 从 StatelessWidget 转为 StatefulWidget + WidgetsBindingObserver,
参考 mobile-app HomeShellPage 模式,实现:
- initState 首帧后自动检查更新
- didChangeAppLifecycleState(resumed) 从后台恢复时自动检查
- 90-300秒随机冷却间隔防止频繁请求(static 变量跨 rebuild 保持)
- 延迟3秒启动检查,避免干扰用户操作
- 有更新时弹出 SelfHostedUpdater 对话框(支持强制更新)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-30 12:07:40 -08:00
parent f0c7cee94e
commit 53a2e64cad
1 changed files with 65 additions and 2 deletions

View File

@ -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<MainShell> createState() => _MainShellState();
}
class _MainShellState extends State<MainShell> 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<void> _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),