// Author: fruit.com.cn import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../core/theme/app_colors.dart'; const Color _splashBackground = Color(0xFF100C0A); const Color _markGlow = Color(0x40FF7A1A); const double _markSize = 112; const double _markRadius = 24; const double _logoGlowSize = 144; const double _logoGlowBlur = 40; const double _loaderSize = 22; const double _loaderStroke = 2; const String _markAsset = 'assets/icon/splash_mark.png'; const Duration _introDuration = Duration(milliseconds: 1200); /// 品牌开屏:深暖底上依次呈现水果标记、FruitShorts 与中文标语。 class SplashPage extends StatefulWidget { const SplashPage({super.key}); @override State createState() => _SplashPageState(); } class _SplashPageState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _logoScale; late final Animation _logoOpacity; late final Animation _brandReveal; late final Animation _taglineReveal; late final Animation _loadingReveal; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: _introDuration) ..forward(); _logoScale = Tween(begin: 0.86, end: 1).animate( CurvedAnimation( parent: _controller, curve: const Interval(0, 0.42, curve: Curves.easeOutBack), ), ); _logoOpacity = Tween(begin: 0, end: 1).animate( CurvedAnimation( parent: _controller, curve: const Interval(0, 0.42, curve: Curves.easeOut), ), ); _brandReveal = _reveal(0.125, 0.542); _taglineReveal = _reveal(0.25, 0.667); _loadingReveal = _reveal(0.417, 0.833); } Animation _reveal(double begin, double end) { return Tween(begin: 0, end: 1).animate( CurvedAnimation( parent: _controller, curve: Interval(begin, end, curve: Curves.easeOutCubic), ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnnotatedRegion( value: SystemUiOverlayStyle.light, child: ColoredBox( key: const Key('splash-screen'), color: _splashBackground, child: Stack( fit: StackFit.expand, children: [ Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ _logo(), const SizedBox(height: 24), _brand(), const SizedBox(height: 6), _tagline(), ], ), ), Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(bottom: 48), child: FadeTransition( key: const Key('splash-loading-fade'), opacity: _loadingReveal, child: const SizedBox.square( dimension: _loaderSize, child: CircularProgressIndicator( key: Key('splash-loading'), strokeWidth: _loaderStroke, color: AppColors.blue, ), ), ), ), ), ], ), ), ); } Widget _logo() { return FadeTransition( key: const Key('splash-logo-fade'), opacity: _logoOpacity, child: ScaleTransition( scale: _logoScale, child: SizedBox.square( dimension: _logoGlowSize, child: Stack( alignment: Alignment.center, children: [ ImageFiltered( key: const Key('splash-logo-glow'), imageFilter: ImageFilter.blur( sigmaX: _logoGlowBlur, sigmaY: _logoGlowBlur, ), child: Container( width: _markSize, height: _markSize, decoration: const BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [_markGlow, Color(0x00FF7A1A)], ), ), ), ), ClipRRect( borderRadius: BorderRadius.circular(_markRadius), child: Image.asset( _markAsset, key: const Key('splash-logo-mark'), width: _markSize, height: _markSize, filterQuality: FilterQuality.medium, gaplessPlayback: true, ), ), ], ), ), ), ); } Widget _brand() { return FadeTransition( key: const Key('splash-brand-fade'), opacity: _brandReveal, child: AnimatedBuilder( animation: _brandReveal, builder: (context, child) => Transform.translate( offset: Offset(0, 12 * (1 - _brandReveal.value)), child: child, ), child: const Text( 'FruitShorts', key: Key('splash-brand'), style: TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.w900, fontFamily: 'Outfit', ), ), ), ); } Widget _tagline() { return FadeTransition( key: const Key('splash-tagline-fade'), opacity: _taglineReveal, child: AnimatedBuilder( animation: _taglineReveal, builder: (context, child) => Transform.translate( offset: Offset(0, 8 * (1 - _taglineReveal.value)), child: child, ), child: const Text( '水果短剧', key: Key('splash-tagline'), style: TextStyle( color: Colors.white70, fontSize: 13, letterSpacing: 4, ), ), ), ); } }