Files
flutterApp/lib/features/auth/presentation/pages/splash_page.dart

115 lines
3.6 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_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/router/route_paths.dart';
import '../bloc/auth_cubit.dart';
import '../bloc/auth_state.dart';
/// 启动页:展示 App Logo 并循环播放「呼吸缩放」动画。
///
/// 该页面作为 App 的第一个路由,衔接原生启动屏(静态 Logo)。
/// 动画会一直循环,直到 [AuthCubit.appStarted] 完成、登录态确定
/// ([AuthAuthenticated] / [AuthUnauthenticated])这一「就绪信号」到来,
/// 才停止动画并跳转到首页或登录页。
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage>
with SingleTickerProviderStateMixin {
/// 呼吸动画控制器(循环往复)
late final AnimationController _breathController;
/// 就绪信号是否已到达
bool _ready = false;
/// 就绪后的目标登录态
AuthState? _resolvedState;
/// 防止重复跳转
bool _navigated = false;
@override
void initState() {
super.initState();
_breathController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 900),
)..repeat(reverse: true);
// 处理进入 splash 时登录态已经确定的情况(appStarted 先于首帧完成)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _onReady(context.read<AuthCubit>().state);
});
}
@override
void dispose() {
_breathController.dispose();
super.dispose();
}
/// 记录就绪信号,并尝试跳转
void _onReady(AuthState state) {
if (state is AuthAuthenticated || state is AuthUnauthenticated) {
_resolvedState = state;
_ready = true;
_maybeNavigate();
}
// AuthInitial:尚未就绪,继续停留在启动页播放动画
}
/// 就绪信号到达即离开启动页,不额外增加打开时间
void _maybeNavigate() {
if (_navigated || !mounted) return;
if (!_ready || _resolvedState == null) return;
_navigated = true;
_breathController.stop();
if (_resolvedState is AuthAuthenticated) {
context.go(RoutePaths.home);
} else {
context.go(RoutePaths.login);
}
}
@override
Widget build(BuildContext context) {
return BlocListener<AuthCubit, AuthState>(
listenWhen: (prev, cur) =>
cur is AuthAuthenticated || cur is AuthUnauthenticated,
listener: (context, state) => _onReady(state),
child: Scaffold(
// 与原生启动屏白底保持一致,避免切换时闪色
backgroundColor: Colors.white,
body: Center(
child: AnimatedBuilder(
animation: _breathController,
builder: (context, child) {
// easeInOut 让呼吸更柔和;scale 从 1.0 起始以贴合原生屏尺寸
final t = Curves.easeInOut.transform(_breathController.value);
final scale = 1.0 - 0.22 * t; // 1.0 -> 0.78,幅度更大更明显
final opacity = 1.0 - 0.55 * t; // 1.0 -> 0.45
return Opacity(
opacity: opacity,
child: Transform.scale(scale: scale, child: child),
);
},
// 透明底应用图标,避免白底上出现浅灰"阴影"底块
child: Image.asset(
'assets/images/app_logo_gray_transparent.png',
width: 180,
height: 180,
fit: BoxFit.contain,
),
),
),
),
);
}
}