refactor(splash): 简化开屏动画为纯帧播放,恢复监控Tab

- 重构 splash_page.dart: 移除 video_player 依赖,改用纯帧动画播放
- 帧数从53张调整为36张,添加预加载优化
- 移除 pubspec.yaml 中的 video_player 依赖
- 恢复底部导航栏"监控"Tab(原"矿机")
- 调整路由索引:0-龙虎榜, 1-监控, 2-兑换, 3-我
- 移除"我的"页面的"领取全部"按钮

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-15 03:28:53 -08:00
parent f3a475a6f4
commit 306f003679
59 changed files with 79 additions and 300 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 986 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 985 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 931 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 975 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 960 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 921 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 904 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 767 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 693 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 923 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 873 KiB

View File

@ -1,14 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:video_player/video_player.dart';
import '../../../../routes/route_paths.dart'; import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart'; import '../../../../routes/app_router.dart';
import '../../../../bootstrap.dart'; import '../../../../bootstrap.dart';
import '../providers/auth_provider.dart'; import '../providers/auth_provider.dart';
/// - /// -
/// ///
class SplashPage extends ConsumerStatefulWidget { class SplashPage extends ConsumerStatefulWidget {
const SplashPage({super.key}); const SplashPage({super.key});
@ -17,11 +16,14 @@ class SplashPage extends ConsumerStatefulWidget {
} }
class _SplashPageState extends ConsumerState<SplashPage> { class _SplashPageState extends ConsumerState<SplashPage> {
/// ///
VideoPlayerController? _videoController; static const int _totalFrames = 36;
/// /// (fps)
bool _isVideoInitialized = false; static const int _frameRate = 15;
/// (0-based)
int _currentFrameIndex = 0;
/// ///
bool _showSkipButton = false; bool _showSkipButton = false;
@ -29,117 +31,29 @@ class _SplashPageState extends ConsumerState<SplashPage> {
/// ///
bool _isNavigating = false; bool _isNavigating = false;
/// 使 fallback
bool _videoFailed = false;
///
static const int _totalFrames = 53;
/// (fps)
static const int _frameRate = 15;
///
int _currentFrameIndex = 0;
/// ///
bool _isFrameAnimationPlaying = false; bool _isPlaying = true;
///
final List<ImageProvider> _frameProviders = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeVideo(); _initializeFrames();
} }
@override ///
void dispose() { void _initializeFrames() {
_videoController?.removeListener(_onVideoStateChanged); // ImageProvider
_videoController?.dispose(); for (int i = 1; i <= _totalFrames; i++) {
super.dispose(); final frameNumber = i.toString().padLeft(3, '0');
} final framePath = 'assets/images/splash_frames/frame_$frameNumber.png';
_frameProviders.add(AssetImage(framePath));
///
Future<void> _initializeVideo() async {
debugPrint('[SplashPage] ========== 开始初始化视频 ==========');
debugPrint('[SplashPage] 视频路径: assets/videos/splash.mp4');
try {
// assets
debugPrint('[SplashPage] 创建 VideoPlayerController...');
_videoController = VideoPlayerController.asset('assets/videos/splash.mp4');
debugPrint('[SplashPage] 开始 initialize()...');
final stopwatch = Stopwatch()..start();
await _videoController!.initialize();
stopwatch.stop();
debugPrint('[SplashPage] initialize() 完成,耗时: ${stopwatch.elapsedMilliseconds}ms');
//
final value = _videoController!.value;
debugPrint('[SplashPage] 视频信息:');
debugPrint('[SplashPage] - 尺寸: ${value.size.width} x ${value.size.height}');
debugPrint('[SplashPage] - 时长: ${value.duration.inMilliseconds}ms');
debugPrint('[SplashPage] - 是否已初始化: ${value.isInitialized}');
debugPrint('[SplashPage] - 是否有错误: ${value.hasError}');
if (value.hasError) {
debugPrint('[SplashPage] - 错误信息: ${value.errorDescription}');
}
if (mounted) {
setState(() {
_isVideoInitialized = true;
});
//
_videoController!.addListener(_onVideoStateChanged);
//
debugPrint('[SplashPage] 开始播放视频...');
await _videoController!.play();
debugPrint('[SplashPage] play() 调用完成isPlaying: ${_videoController!.value.isPlaying}');
// 1
Future.delayed(const Duration(seconds: 1), () {
if (mounted) {
setState(() {
_showSkipButton = true;
});
}
});
}
} catch (e, stackTrace) {
debugPrint('[SplashPage] ========== 视频初始化失败 ==========');
debugPrint('[SplashPage] 错误类型: ${e.runtimeType}');
debugPrint('[SplashPage] 错误信息: $e');
debugPrint('[SplashPage] 堆栈跟踪:\n$stackTrace');
// controller
if (_videoController != null) {
final value = _videoController!.value;
debugPrint('[SplashPage] Controller 状态:');
debugPrint('[SplashPage] - 是否已初始化: ${value.isInitialized}');
debugPrint('[SplashPage] - 是否有错误: ${value.hasError}');
if (value.hasError) {
debugPrint('[SplashPage] - 错误描述: ${value.errorDescription}');
}
}
// fallback
_startFallbackAnimation();
} }
}
/// fallback 使
void _startFallbackAnimation() {
debugPrint('[SplashPage] 启动 fallback 帧动画,共 $_totalFrames');
setState(() {
_videoFailed = true;
_isFrameAnimationPlaying = true;
_currentFrameIndex = 0;
});
// //
_playFrameAnimation(); _playAnimation();
// 1 // 1
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
@ -151,46 +65,51 @@ class _SplashPageState extends ConsumerState<SplashPage> {
}); });
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
//
_precacheInitialFrames();
}
///
void _precacheInitialFrames() {
// 5
final framesToPrecache = _totalFrames < 5 ? _totalFrames : 5;
for (int i = 0; i < framesToPrecache; i++) {
precacheImage(_frameProviders[i], context);
}
}
/// ///
Future<void> _playFrameAnimation() async { Future<void> _playAnimation() async {
final frameDuration = Duration(milliseconds: 1000 ~/ _frameRate); final frameDuration = Duration(milliseconds: 1000 ~/ _frameRate);
while (_isFrameAnimationPlaying && _currentFrameIndex < _totalFrames && mounted) { while (_isPlaying && _currentFrameIndex < _totalFrames - 1 && mounted) {
await Future.delayed(frameDuration); await Future.delayed(frameDuration);
if (!mounted || !_isFrameAnimationPlaying) break; if (!mounted || !_isPlaying) break;
setState(() { setState(() {
_currentFrameIndex++; _currentFrameIndex++;
}); });
// 2
final nextFrameIndex = _currentFrameIndex + 2;
if (nextFrameIndex < _totalFrames) {
precacheImage(_frameProviders[nextFrameIndex], context);
}
} }
// //
if (_currentFrameIndex >= _totalFrames && mounted && _isFrameAnimationPlaying) { if (_currentFrameIndex >= _totalFrames - 1 && mounted && _isPlaying) {
_navigateToNextPage(); _navigateToNextPage();
} }
} }
/// fallback ///
void _skipFallbackAnimation() { void _skipAnimation() {
_isFrameAnimationPlaying = false; _isPlaying = false;
_navigateToNextPage();
}
///
void _onVideoStateChanged() {
if (_videoController == null) return;
//
if (_videoController!.value.position >= _videoController!.value.duration &&
_videoController!.value.duration > Duration.zero) {
_navigateToNextPage();
}
}
///
void _skipVideo() {
_videoController?.pause();
_navigateToNextPage(); _navigateToNextPage();
} }
@ -249,24 +168,8 @@ class _SplashPageState extends ConsumerState<SplashPage> {
body: Stack( body: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
// //
if (_isVideoInitialized && _videoController != null) _buildFrameAnimation(),
SizedBox.expand(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: _videoController!.value.size.width,
height: _videoController!.value.size.height,
child: VideoPlayer(_videoController!),
),
),
)
else if (_videoFailed)
// fallback
_buildFallbackAnimationView()
else
// Logo
_buildLoadingView(),
// //
if (_showSkipButton) if (_showSkipButton)
@ -280,108 +183,13 @@ class _SplashPageState extends ConsumerState<SplashPage> {
); );
} }
/// ///
Widget _buildLoadingView() { Widget _buildFrameAnimation() {
return Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
Image.asset(
'assets/images/logo/app_icon.png',
width: 128,
height: 128,
),
const SizedBox(height: 24),
const Text(
'榴莲皇后',
style: TextStyle(
fontSize: 32,
fontFamily: 'Noto Sans SC',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: 1.6,
color: Color(0xFF4A3F35),
),
),
const SizedBox(height: 24),
//
const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
),
),
],
),
);
}
/// fallback 使
Widget _buildFallbackAnimationView() {
// : frame_001.png, frame_002.png, ... frame_053.png
final frameNumber = (_currentFrameIndex + 1).toString().padLeft(3, '0');
final framePath = 'assets/images/splash_frames/frame_$frameNumber.png';
return SizedBox.expand( return SizedBox.expand(
child: Image.asset( child: Image(
framePath, image: _frameProviders[_currentFrameIndex],
fit: BoxFit.cover, fit: BoxFit.cover,
gaplessPlayback: true, // gaplessPlayback: true, //
errorBuilder: (context, error, stackTrace) {
// fallback
debugPrint('[SplashPage] 帧加载失败: $framePath, 错误: $error');
return Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo/app_icon.png',
width: 128,
height: 128,
),
const SizedBox(height: 24),
const Text(
'榴莲皇后',
style: TextStyle(
fontSize: 32,
fontFamily: 'Noto Sans SC',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: 1.6,
color: Color(0xFF4A3F35),
),
),
],
),
);
},
), ),
); );
} }
@ -389,7 +197,7 @@ class _SplashPageState extends ConsumerState<SplashPage> {
/// ///
Widget _buildSkipButton() { Widget _buildSkipButton() {
return GestureDetector( return GestureDetector(
onTap: _videoFailed ? _skipFallbackAnimation : _skipVideo, onTap: _skipAnimation,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@ -60,27 +60,27 @@ class _HomeShellPageState extends ConsumerState<HomeShellPage>
int _getCurrentIndex(BuildContext context) { int _getCurrentIndex(BuildContext context) {
final location = GoRouterState.of(context).uri.path; final location = GoRouterState.of(context).uri.path;
// Tab暂时隐藏: 0-, 1-, 2- // : 0-, 1-, 2-, 3-
if (location.startsWith(RoutePaths.ranking)) return 0; if (location.startsWith(RoutePaths.ranking)) return 0;
// if (location.startsWith(RoutePaths.mining)) return 1; // if (location.startsWith(RoutePaths.mining)) return 1;
if (location.startsWith(RoutePaths.trading)) return 1; if (location.startsWith(RoutePaths.trading)) return 2;
if (location.startsWith(RoutePaths.profile)) return 2; if (location.startsWith(RoutePaths.profile)) return 3;
return 0; return 0;
} }
void _onTabTapped(BuildContext context, int index) { void _onTabTapped(BuildContext context, int index) {
// Tab暂时隐藏: 0-, 1-, 2- // : 0-, 1-, 2-, 3-
switch (index) { switch (index) {
case 0: case 0:
context.go(RoutePaths.ranking); context.go(RoutePaths.ranking);
break; break;
// case 1: //
// context.go(RoutePaths.mining);
// break;
case 1: case 1:
context.go(RoutePaths.trading); context.go(RoutePaths.mining);
break; break;
case 2: case 2:
context.go(RoutePaths.trading);
break;
case 3:
context.go(RoutePaths.profile); context.go(RoutePaths.profile);
break; break;
} }

View File

@ -1,8 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// ///
/// Tab /// Tab
/// Tab暂时隐藏便
/// 使 LayoutBuilder /// 使 LayoutBuilder
class BottomNavBar extends StatelessWidget { class BottomNavBar extends StatelessWidget {
final int currentIndex; final int currentIndex;
@ -42,21 +41,20 @@ class BottomNavBar extends StatelessWidget {
activeIcon: Icons.leaderboard, activeIcon: Icons.leaderboard,
label: '龙虎榜', label: '龙虎榜',
), ),
// Tab暂时隐藏便
// _buildNavItem(
// index: 1,
// icon: Icons.memory_outlined,
// activeIcon: Icons.memory,
// label: '矿机',
// ),
_buildNavItem( _buildNavItem(
index: 1, // index: 2 index: 1,
icon: Icons.monitor_heart_outlined,
activeIcon: Icons.monitor_heart,
label: '监控',
),
_buildNavItem(
index: 2,
icon: Icons.swap_horiz_outlined, icon: Icons.swap_horiz_outlined,
activeIcon: Icons.swap_horiz, activeIcon: Icons.swap_horiz,
label: '兑换', label: '兑换',
), ),
_buildNavItem( _buildNavItem(
index: 2, // index: 3 index: 3,
icon: Icons.person_outline, icon: Icons.person_outline,
activeIcon: Icons.person, activeIcon: Icons.person,
label: '', label: '',

View File

@ -11,7 +11,7 @@ enum MiningStatus {
paused, // paused, //
} }
/// - /// -
/// ///
class MiningPage extends ConsumerStatefulWidget { class MiningPage extends ConsumerStatefulWidget {
const MiningPage({super.key}); const MiningPage({super.key});
@ -130,8 +130,8 @@ class _MiningPageState extends ConsumerState<MiningPage> {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('矿机说明'), title: const Text('监控说明'),
content: const Text('矿机是您参与挖矿的核心工具。\n\n' content: const Text('监控是您查看挖矿状态的工具。\n\n'
'开启挖矿后,您将开始获得收益。\n\n' '开启挖矿后,您将开始获得收益。\n\n'
'收益与您的算力和团队规模相关。'), '收益与您的算力和团队规模相关。'),
actions: [ actions: [
@ -217,7 +217,7 @@ class _MiningPageState extends ConsumerState<MiningPage> {
// //
const Expanded( const Expanded(
child: Text( child: Text(
'矿机', '监控',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontFamily: 'Inter', fontFamily: 'Inter',

View File

@ -1500,32 +1500,6 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// //
...(_pendingRewards.map((item) => _buildPendingRewardItem(item))), ...(_pendingRewards.map((item) => _buildPendingRewardItem(item))),
], ],
const SizedBox(height: 15),
//
GestureDetector(
onTap: _claimAllEarnings,
child: Container(
width: double.infinity,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37),
borderRadius: BorderRadius.circular(8),
),
child: const Center(
child: Text(
'领取全部',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.21,
color: Colors.white,
),
),
),
),
),
], ],
); );
} }

View File

@ -44,7 +44,6 @@ dependencies:
mobile_scanner: ^5.1.1 mobile_scanner: ^5.1.1
flutter_screenutil: ^5.9.0 flutter_screenutil: ^5.9.0
city_pickers: ^1.3.0 city_pickers: ^1.3.0
video_player: ^2.8.6
# 工具 # 工具
intl: ^0.20.2 intl: ^0.20.2