Files
App.K12.Student.Miniapp_Flu…/lib/main.dart
T
崔宝新 c0c673e2d2 feat: 新增我的报告模块,集成inappwebview支持H5详情页
1.  添加flutter_inappwebview依赖并配置各平台插件注册
2.  新增我的报告列表页、详情WebView页
3.  新增报告模块API和数据模型
4.  配置路由跳转,修复我的菜单跳转逻辑
5.  添加AGP9兼容补丁修复插件构建问题
6.  注释掉登录成功的SnackBar提示
2026-09-09 11:08:47 +08:00

131 lines
7.5 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 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:new_learn/core/app.dart';
import 'package:new_learn/core/network/dio_client.dart';
import 'package:new_learn/core/providers/privacy_provider.dart';
import 'package:new_learn/core/storage/database/app_database.dart';
import 'package:new_learn/core/storage/privacy_storage.dart';
/// App 启动入口 —— 真正的业务代码都在 app.dart / pages/ 里
///
/// 〇、环境 / 品牌参数(--dart-define 编译期注入,详见 core/config/app_config.dart):
/// 日常开发(默认正式环境 + 爱学蝶变,零参数):
/// flutter run
/// 连测试环境调试:
/// flutter run --dart-define=ENV=test
/// 打楚才正式包:
/// flutter build apk --release --dart-define=ENV=prod --dart-define=BRAND=chucai
/// 打豫志测试包:
/// flutter build apk --release --dart-define=ENV=test --dart-define=BRAND=yuzhi
/// BRAND 可选:aixue / chucai / yule / yuzhi / qinxiao / wansi / shucai
/// (品牌表见 core/config/app_brand.dart;不传默认 aixue=爱学蝶变)
///
/// 一、启动顺序(每步都是插件初始化,必须 runApp 前 await):
/// 1. WidgetsFlutterBinding.ensureInitialized():插件通道必须在 runApp 前就绪;
/// 2. await DioClient.restoreToken():冷启动把磁盘上的 Token 回填内存,
/// 这样即便没进登录页,后续请求也自动带 Authorization;
/// 3. await PrivacyStorage.readAccepted():恢复启动隐私协议同意状态;
/// 未同意时,App 首屏会先被 StartupGate + 路由守卫拦住;
/// 4. await AppDatabase.instance.init():初始化本地业务数据库(Isar,
/// 题库/错题/播放记录集合;web 调试模式自动跳过,见 app_database.dart);
/// 5. runApp:ProviderScope 包根(Riverpod 全局状态容器),进入 UI。
///
/// 二、完整路由运行流程(从 main() 到页面渲染):
///
/// main()
/// │
/// ├─ 1. WidgetsFlutterBinding.ensureInitialized()
/// ├─ 2. GoRouter.optionURLReflectsImperativeAPIs = true
/// ├─ 3. await DioClient.restoreToken() ← 恢复登录态
/// ├─ 4. privacyAcceptedNotifier.value = ... ← 恢复隐私同意状态
/// ├─ 5. await AppDatabase.instance.init() ← 初始化 Isar
/// │
/// └─ 6. runApp(ProviderScope(child: TabApp()))
/// │
/// └─ MaterialApp.router(routerConfig: appRouter)
/// │
/// ├─ builder: PrivacyStartupGate ← 视觉第一拦
/// │ ├─ 未同意隐私 → 全屏盖 PrivacyConsentPage
/// │ └─ 已同意隐私 → 放行 child(路由树)
/// │
/// └─ appRouter (GoRouter)
/// │
/// ├─ redirect: _guardLogin ← 每次导航都过一遍
/// │ │
/// │ │ 判断逻辑(按顺序):
/// │ │ ① 未同意隐私 且不在隐私页 → 重定向 /privacy-consent
/// │ │ ② 已同意隐私 且在隐私页 → 已登录去 /,未登录去 /login
/// │ │ ③ 未登录 且不在登录页 → 重定向 /login
/// │ │ ④ 已登录 且在登录页 → 重定向 /(首页)
/// │ │ ⑤ 以上都不满足 → return null(放行)
/// │ │
/// │ └─ 触发时机:
/// │ • 每次 context.push/go 导航时
/// │ • authTick 变化时(登录/登出 → 自动 refresh)
/// │ • privacyAcceptedNotifier 变化时
/// │
/// └─ routes 路由表:
/// │
/// ├─ ShellRoute (MainTabPage) ← 底部 4 tab 壳
/// │ ├─ / → TaskPage (任务)
/// │ ├─ /recite → RecitePage (背诵)
/// │ ├─ /class → ClassPage (班级)
/// │ └─ /my → MyPage (我的)
/// │ │
/// │ └─ MainTabPage.initState
/// │ └─ addPostFrameCallback
/// │ └─ _checkVersion() ← 版本检查(仅冷启动一次)
/// │
/// ├─ /privacy-consent → PrivacyConsentPage
/// ├─ /user-agreement → LegalDocumentPage
/// ├─ /privacy-policy → LegalDocumentPage
/// ├─ /login → LoginPage
/// └─ /myPoint → MyPointPage
///
/// 三、用户正常流程:
///
/// 首次安装:
/// 启动 → PrivacyStartupGate 拦住 → 隐私协议页 → 同意
/// → redirect 重定向 /login → 登录成功 → authTick 变化
/// → redirect 重定向 / → ShellRoute → MainTabPage → 版本检查
///
/// 老用户(已登录):
/// 启动 → 已同意隐私 + 有 Token → redirect 直接放行
/// → / → ShellRoute → MainTabPage → 版本检查
///
/// 四、关键文件对照:
/// main.dart = 启动脚本(本文件)
/// core/app.dart = 根组件(ScreenUtil + MaterialApp + PrivacyStartupGate)
/// core/router/app_router.dart = 路由表 + 登录守卫(对标 vue-router/index.ts)
/// core/router/main_tab_page.dart = 底部 tab 壳 + 版本检查触发点
/// core/router/router_keys.dart = 全局 NavigatorKey
///
/// 想单独跑某个学习 Demo 时,临时改这里,例如:
/// runApp(const ProviderScope(child: MaterialApp(home: DioDemoPage())));
/// 跑完记得改回 runApp(const ProviderScope(child: TabApp()));
Future<void> main() async {
// 需要在启动 App 之前读取本地存储、调用原生插件,必须写在 runApp 之前!
// 作用: 初始化 Flutter 和原生的绑定桥梁
WidgetsFlutterBinding.ensureInitialized();
// flutter_inappwebview 平台实现注册(Chrome Web / Windows desktop 必须显式触发)
// 否则会报 InAppWebViewPlatform.instance != null 断言错误
InAppWebViewPlatform.instance;
// go_router 17.x 大坑:命令式导航(context.push/go)默认不写回浏览器
// 地址栏(optionURLReflectsImperativeAPIs 默认 false)。必须在使用
// appRouter(runApp)之前全局开启,否则 web 调试 URL 不随页面变化。
GoRouter.optionURLReflectsImperativeAPIs = true;
// 冷启动恢复登录态(Token 从安全存储回填内存)
await DioClient.instance.restoreToken();
// 冷启动恢复"是否已同意启动隐私协议"状态。
// 这个值会同时驱动:
// 1. app.dart 里的 StartupGate(视觉上先拦住);
// 2. app_router.dart 里的 redirect(路由层再兜底一次)。
privacyAcceptedNotifier.value = await PrivacyStorage.instance.readAccepted();
// 初始化本地业务数据库(Isar 题库/错题/播放记录;web 调试自动跳过)
await AppDatabase.instance.init();
// ProviderScope:Riverpod 所有 provider 的容器,必须包在根
runApp(const ProviderScope(child: TabApp()));
}