Files
feature-next-arch/lib/main.dart
Songzex 938aac2db9 添加修复大包更新安装覆盖的-待测试
添加修复了更新接口的令狐适配
添加了新的服务端地址-
2026-05-27 16:05:21 +08:00

362 lines
15 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_patcher/flutter_patcher.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/infrastructure/logging/app_bloc_observer.dart';
import 'package:maibu_satabot_v2/core/logging/i_logger_service.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/core/update/update_cubit.dart';
import 'package:maibu_satabot_v2/core/update/version_check_service.dart';
import 'package:maibu_satabot_v2/core/update/update_dialog.dart';
import 'package:maibu_satabot_v2/core/update/update_state.dart';
import 'package:maibu_satabot_v2/features/auth/presentation/bloc/auth_cubit.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
import 'package:maibu_satabot_v2/features/main_container/presentation/cubit/tab_config_cubit.dart';
import 'package:maibu_satabot_v2/features/main_container/presentation/main_wrapper.dart';
import 'core/di/injection.dart';
import 'core/localization/app_localizations.dart';
import 'core/localization/locale_cubit.dart';
import 'features/auth/presentation/bloc/auth_state.dart';
import 'features/auth/presentation/bloc/login_cubit.dart';
import 'features/devices/presentation/bloc/device_status_bloc.dart';
import 'features/home/presentation/bloc/permission_request_bloc.dart';
// 🔥 定义全局 Navigator Key
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
runZonedGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterPatcher.init();
await init();
final logger = sl<ILoggerService>();
await logger.init();
Bloc.observer = AppBlocObserver(logger);
runApp(const MyApp());
},
(error, stack) {
sl<ILoggerService>().captureException(error, stackTrace: stack);
},
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
sl<AuthCubit>().appStarted();
final deviceStatusBloc = sl<DeviceStatusBloc>();
final localeCubit = sl<LocaleCubit>();
return MultiBlocProvider(
providers: [
BlocProvider<AuthCubit>(create: (_) => sl<AuthCubit>()),
BlocProvider<AppUserCubit>(create: (_) => sl<AppUserCubit>()),
BlocProvider<LoginCubit>(create: (_) => sl<LoginCubit>()),
BlocProvider<DevicesCubit>(
create: (_) {
final user = sl<AppUserCubit>().state.user;
final devicesCubit = sl<DevicesCubit>();
if (user != null) devicesCubit.fetchAllDevices(user.username);
return devicesCubit;
},
),
BlocProvider<DeviceStatusBloc>.value(value: deviceStatusBloc),
BlocProvider<LocaleCubit>.value(value: localeCubit),
BlocProvider<TabConfigCubit>.value(value: sl<TabConfigCubit>()),
BlocProvider<PermissionRequestBloc>(create: (_) => sl<PermissionRequestBloc>()),
BlocProvider<UpdateCubit>(create: (_) => UpdateCubit(VersionCheckService())),
],
child: BlocBuilder<LocaleCubit, Locale>(
bloc: localeCubit,
builder: (context, locale) {
return MaterialApp.router(
title: 'Maibu Satabot',
locale: locale,
supportedLocales: const [Locale('zh', 'CN'), Locale('en', 'US')],
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: AppTheme.lightTheme,
routerConfig: sl<GoRouter>(),
builder: (context, child) {
return _LifecycleListener(child: _UpdateChecker(child: child!));
},
);
},
),
);
}
}
class _LifecycleListener extends StatefulWidget {
final Widget? child;
const _LifecycleListener({required this.child});
@override
State<_LifecycleListener> createState() => _LifecycleListenerState();
}
class _LifecycleListenerState extends State<_LifecycleListener> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
debugPrint('📱 [生命周期] 应用恢复到前台');
_handleResume();
}
}
Future<void> _handleResume() async {
try {
final authCubit = context.read<AuthCubit>();
if (authCubit.state is! AuthAuthenticated) return;
await Future.delayed(const Duration(milliseconds: 500));
await authCubit.reconnectAfterResume();
} catch (e) {
debugPrint('❌ [生命周期] 重连错误:$e');
}
}
@override
Widget build(BuildContext context) => widget.child!;
}
class _UpdateChecker extends StatefulWidget {
final Widget child;
const _UpdateChecker({required this.child});
@override
State<_UpdateChecker> createState() => _UpdateCheckerState();
}
class _UpdateCheckerState extends State<_UpdateChecker> {
@override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 2), () {
if (mounted) context.read<UpdateCubit>().checkUpdate();
});
}
@override
Widget build(BuildContext context) {
return BlocConsumer<UpdateCubit, UpdateState>(
listener: (context, state) {
if (state is UpdateFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('更新失败: ${state.error}'), backgroundColor: Colors.red),
);
}
},
builder: (context, state) {
return Stack(
children: [
widget.child,
// 🔥 核心修正:直接在 UI 树中渲染更新提示,完全不依赖 Navigator
if (state is UpdateAvailable)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.6), // 半透明遮罩
child: Center(
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 30),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('🎉 发现新版本',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
Text('版本号: ${state.versionInfo.version}',
style: const TextStyle(fontSize: 16)),
Text('更新类型: ${state.versionInfo.updateType == "patch" ? "差量更新" : "整包更新"}',
style: const TextStyle(fontSize: 16, color: Colors.grey)),
if (state.versionInfo.updateDesc.isNotEmpty) ...[
const SizedBox(height: 8),
const Text('更新内容:', style: TextStyle(fontWeight: FontWeight.bold)),
Text(state.versionInfo.updateDesc),
],
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (!state.versionInfo.forceUpdate)
TextButton(
onPressed: () => context.read<UpdateCubit>().cancelUpdate(),
child: const Text('稍后'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () {
final info = state.versionInfo;
if (info.patchUrl != null) {
// 🔥 执行差量更新
context.read<UpdateCubit>().applyPatch(
info.patchUrl!,
info.version,
info.versionCode,
md5: info.patchMd5,
);
} else if (info.apkUrl != null) {
// 🔥 执行整包更新
context.read<UpdateCubit>().downloadAndInstallApk(info.apkUrl!);
}
},
child: const Text('立即更新'),
),
],
),
],
),
),
),
),
),
),
// 🔥 下载/安装进度弹窗
if (state is UpdateDownloading || state is UpdateInstalling)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.7),
child: Center(
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 40),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (state is UpdateDownloading)
const Text('⬇️ 正在下载更新...',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
if (state is UpdateInstalling)
const Text('🔧 正在应用更新...',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
if (state is UpdateDownloading)
LinearProgressIndicator(
value: state.progress,
minHeight: 8,
borderRadius: BorderRadius.circular(4),
),
if (state is UpdateDownloading)
const SizedBox(height: 8),
if (state is UpdateDownloading)
Text('${(state.progress * 100).toInt()}%',
style: const TextStyle(fontSize: 14, color: Colors.grey)),
if (state is UpdateInstalling)
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(
state is UpdateDownloading ? '请稍候,正在下载补丁文件' : '正在应用补丁,请勿关闭应用',
style: const TextStyle(fontSize: 14, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
),
),
),
),
// 🔥 更新成功提示
if (state is UpdateSuccess)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.7),
child: Center(
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 40),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.check_circle, color: Colors.green, size: 60),
const SizedBox(height: 16),
const Text('✅ 更新已完成',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
const Text(
'补丁已成功应用!\n\n请完全关闭 App 后重新打开,\n即可体验新版本功能。',
style: TextStyle(fontSize: 15),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 提示用户手动重启
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请从后台划掉 App,然后重新打开'),
duration: Duration(seconds: 3),
),
);
context.read<UpdateCubit>().cancelUpdate();
},
child: const Text('我知道了'),
),
],
),
),
),
),
),
),
// 调试面板
Positioned(
top: 40,
right: 10,
child: Material(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('状态: ${state.runtimeType.toString().replaceAll('Update', '')}',
style: const TextStyle(color: Colors.white, fontSize: 12)),
// 🔥 暂时关闭刷新按钮和点击阴影
// IconButton(
// icon: const Icon(Icons.refresh, color: Colors.white),
// onPressed: () => context.read<UpdateCubit>().checkUpdate(),
// ),
],
),
),
),
),
],
);
},
);
}
}