diff --git a/health_app/android/app/src/main/res/values-night/styles.xml b/health_app/android/app/src/main/res/values-night/styles.xml index 06952be..51b3023 100644 --- a/health_app/android/app/src/main/res/values-night/styles.xml +++ b/health_app/android/app/src/main/res/values-night/styles.xml @@ -1,18 +1,11 @@ - - - - diff --git a/health_app/lib/app.dart b/health_app/lib/app.dart index c15495a..6fb6537 100644 --- a/health_app/lib/app.dart +++ b/health_app/lib/app.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -5,7 +6,9 @@ import 'package:shadcn_ui/shadcn_ui.dart'; import 'core/app_router.dart'; import 'core/app_theme.dart'; import 'core/navigation_provider.dart'; +import 'pages/splash_page.dart'; import 'providers/auth_provider.dart'; +import 'providers/data_providers.dart'; /// 健康管家 App 根组件 class HealthApp extends ConsumerWidget { @@ -25,9 +28,64 @@ class HealthApp extends ConsumerWidget { supportedLocales: const [Locale('zh', 'CN'), Locale('zh')], locale: const Locale('zh'), home: const _RootNavigator(), - // 注入 ShadTheme,让所有页面都能用 shadcn 组件 - builder: (context, child) => - ShadTheme(data: AppTheme.shadTheme, child: child!), + // 注入 ShadTheme + 启动闸门:Splash 盖在最上层,直到首页今日健康卡数据就绪 + builder: (context, child) => ShadTheme( + data: AppTheme.shadTheme, + child: _BootGate(child: child!), + ), + ); + } +} + +/// 启动是否就绪:auth 判定完成,且(已登录的普通用户)今日健康卡数据已到位。 +/// 未登录或医生/管理员无需等待健康卡。 +final appReadyProvider = Provider((ref) { + final auth = ref.watch(authProvider); + if (auth.isLoading) return false; // 还在判登录态 → 继续盖 Splash + if (!auth.isLoggedIn) return true; // 未登录 → 就绪,露出登录页 + final role = auth.user?.role ?? 'User'; + if (role != 'User') return true; // 医生/管理员首页不依赖今日健康卡 + // 普通用户:等今日健康卡片数据(成功或失败都算就绪,避免卡死) + final health = ref.watch(latestHealthProvider); + return health.hasValue || health.hasError; +}); + +/// 启动闸门——就绪前在最上层覆盖 Splash,盖住登录页闪现与首页对话流初始态。 +/// 带 8 秒安全兜底,避免离线时数据永不返回导致卡在启动页。 +class _BootGate extends ConsumerStatefulWidget { + final Widget child; + const _BootGate({required this.child}); + + @override + ConsumerState<_BootGate> createState() => _BootGateState(); +} + +class _BootGateState extends ConsumerState<_BootGate> { + bool _timedOut = false; + Timer? _timer; + + @override + void initState() { + super.initState(); + _timer = Timer(const Duration(seconds: 8), () { + if (mounted) setState(() => _timedOut = true); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ready = ref.watch(appReadyProvider) || _timedOut; + return Stack( + children: [ + widget.child, + if (!ready) const Positioned.fill(child: SplashPage()), + ], ); } } diff --git a/health_app/lib/pages/splash_page.dart b/health_app/lib/pages/splash_page.dart new file mode 100644 index 0000000..4ebee25 --- /dev/null +++ b/health_app/lib/pages/splash_page.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +/// 启动页——盖在整个 App 最上层,直到「今日健康卡片」数据就绪后才撤掉, +/// 用于遮住启动期间的登录页闪现与首页对话流初始态。始终亮色白底。 +class SplashPage extends StatelessWidget { + const SplashPage({super.key}); + + @override + Widget build(BuildContext context) { + return const ColoredBox( + color: Colors.white, + child: Center( + child: Image( + image: AssetImage('assets/branding/health_splash_master.png'), + fit: BoxFit.contain, + ), + ), + ); + } +} diff --git a/health_app/lib/providers/auth_provider.dart b/health_app/lib/providers/auth_provider.dart index ca2ae17..4c1163e 100644 --- a/health_app/lib/providers/auth_provider.dart +++ b/health_app/lib/providers/auth_provider.dart @@ -35,13 +35,18 @@ class AuthNotifier extends Notifier { @override AuthState build() { _checkAuth(); - return const AuthState(isLoggedIn: false, isLoading: false); + // 初始为"加载中",让启动闸门显示 Splash 盖住登录页/首页初始态 + return const AuthState(isLoggedIn: false, isLoading: true); } Future _checkAuth() async { final db = ref.read(localDbProvider); final refresh = await db.read('refresh_token'); - if (refresh == null) return; // 无token,保持 isLoggedIn: false + if (refresh == null) { + // 无 token:判定完成、未登录 + state = const AuthState(isLoggedIn: false, isLoading: false); + return; + } state = const AuthState(isLoading: true); try {