Compare commits

...

2 Commits

Author SHA1 Message Date
MingNian
a479e5e95d style: 今日健康卡片顶部标题改用健康档案的紫蓝左右渐变 2026-06-21 23:37:18 +08:00
MingNian
a7fb6e5ff3 fix: 启动页遮住登录/首页初始态,消除深色模式黑闪
- 新增 SplashPage + appReadyProvider/_BootGate:启动闸门将启动页覆盖在最上层,
  直到 auth 判定完成且今日健康卡数据就绪,遮住登录页闪现与首页对话流初始态
- auth 初始状态改为 isLoading=true,无 token 时再置 false
- values-night/styles.xml 改为亮色白底,消除深色模式下 Flutter 初始化期间的黑色窗口背景
- 带 8 秒安全兜底,避免离线时卡在启动页
2026-06-21 23:32:02 +08:00
5 changed files with 98 additions and 18 deletions

View File

@@ -1,18 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<!-- 启动页强制亮色:即使系统处于深色模式,也用白底品牌图,避免黑色闪屏 -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<!-- Flutter 引擎初始化期间的窗口背景:白底,避免深色模式下闪黑 -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>

View File

@@ -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<bool>((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()),
],
);
}
}

View File

@@ -1703,7 +1703,11 @@ class ChatMessagesView extends ConsumerWidget {
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
decoration: const BoxDecoration(
gradient: AppColors.drawerGradient,
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xFFE0C3FC), Color(0xFF8EC5FC)],
),
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
child: Row(

View File

@@ -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,
),
),
);
}
}

View File

@@ -35,13 +35,18 @@ class AuthNotifier extends Notifier<AuthState> {
@override
AuthState build() {
_checkAuth();
return const AuthState(isLoggedIn: false, isLoading: false);
// 初始为"加载中",让启动闸门显示 Splash 盖住登录页/首页初始态
return const AuthState(isLoggedIn: false, isLoading: true);
}
Future<void> _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 {