import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; 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 { const HealthApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp( title: '小脉健康', debugShowCheckedModeBanner: false, theme: AppTheme.lightTheme, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('zh', 'CN'), Locale('zh')], locale: const Locale('zh'), home: const _RootNavigator(), // 注入 ShadTheme + 启动闸门:Splash 盖在最上层,直到首页今日健康卡数据就绪 // 外层包一层 GestureDetector:点击任意空白处取消输入框焦点(全 app 生效) builder: (context, child) => ShadTheme( data: AppTheme.shadTheme, child: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => FocusManager.instance.primaryFocus?.unfocus(), 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()), ], ); } } /// 根导航——根据 Riverpod 路由状态切换页面 class _RootNavigator extends ConsumerWidget { const _RootNavigator(); @override Widget build(BuildContext context, WidgetRef ref) { final stack = ref.watch(routeStackProvider); final current = stack.last; final authState = ref.watch(authProvider); final isPublicStaticText = current.name == 'staticText'; // 登录后自动跳转(在下一帧完成,无闪烁) if (authState.isLoggedIn && current.name == 'login') { WidgetsBinding.instance.addPostFrameCallback((_) { final role = authState.user?.role ?? 'User'; if (role == 'Admin') { goRoute(ref, 'adminHome'); } else if (role == 'Doctor') { goRoute(ref, 'doctorHome'); } else { goRoute(ref, 'home'); } }); } if (!authState.isLoading && !authState.isLoggedIn && current.name != 'login' && !isPublicStaticText) { WidgetsBinding.instance.addPostFrameCallback((_) { goRoute(ref, 'login'); }); } return PopScope( canPop: stack.length <= 1, onPopInvokedWithResult: (didPop, result) { if (!didPop) popRoute(ref); }, child: buildPage(current, ref), ); } }