feat(mobile): replace splash screen with video player
- Add video_player dependency for video playback - Create assets/videos directory for splash video - Implement video splash screen with: - Full-screen video playback (splash.mp4) - Skip button appears after 1 second - Fallback to logo view if video fails to load - Auto-navigate when video completes - Video file should be placed at: assets/videos/splash.mp4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3644c04521
commit
7bf88cd6a8
|
|
@ -1,14 +1,14 @@
|
||||||
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 '../../../../core/constants/app_constants.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';
|
||||||
|
|
||||||
/// 开屏页面 - 应用启动时显示的第一个页面
|
/// 开屏页面 - 应用启动时显示的第一个页面
|
||||||
/// 显示 Logo 和应用名称,同时检查用户认证状态
|
/// 播放开屏视频,视频结束后检查用户认证状态并跳转
|
||||||
class SplashPage extends ConsumerStatefulWidget {
|
class SplashPage extends ConsumerStatefulWidget {
|
||||||
const SplashPage({super.key});
|
const SplashPage({super.key});
|
||||||
|
|
||||||
|
|
@ -17,20 +17,92 @@ class SplashPage extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SplashPageState extends ConsumerState<SplashPage> {
|
class _SplashPageState extends ConsumerState<SplashPage> {
|
||||||
|
/// 视频播放控制器
|
||||||
|
VideoPlayerController? _videoController;
|
||||||
|
|
||||||
|
/// 视频是否已初始化
|
||||||
|
bool _isVideoInitialized = false;
|
||||||
|
|
||||||
|
/// 是否显示跳过按钮
|
||||||
|
bool _showSkipButton = false;
|
||||||
|
|
||||||
|
/// 是否已经开始跳转(防止重复跳转)
|
||||||
|
bool _isNavigating = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initializeApp();
|
_initializeVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 初始化应用并检查认证状态
|
@override
|
||||||
Future<void> _initializeApp() async {
|
void dispose() {
|
||||||
|
_videoController?.removeListener(_onVideoStateChanged);
|
||||||
|
_videoController?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 初始化视频播放器
|
||||||
|
Future<void> _initializeVideo() async {
|
||||||
|
try {
|
||||||
|
// 从 assets 加载视频
|
||||||
|
_videoController = VideoPlayerController.asset('assets/videos/splash.mp4');
|
||||||
|
|
||||||
|
await _videoController!.initialize();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isVideoInitialized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置视频播放完成监听
|
||||||
|
_videoController!.addListener(_onVideoStateChanged);
|
||||||
|
|
||||||
|
// 开始播放视频
|
||||||
|
await _videoController!.play();
|
||||||
|
|
||||||
|
// 1秒后显示跳过按钮
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_showSkipButton = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[SplashPage] 视频初始化失败: $e');
|
||||||
|
// 如果视频加载失败,直接进行跳转
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 导航到下一个页面
|
||||||
|
Future<void> _navigateToNextPage() async {
|
||||||
|
// 防止重复跳转
|
||||||
|
if (_isNavigating) return;
|
||||||
|
_isNavigating = true;
|
||||||
|
|
||||||
// 初始化遥测服务(需要 BuildContext)
|
// 初始化遥测服务(需要 BuildContext)
|
||||||
await initializeTelemetry(context);
|
await initializeTelemetry(context);
|
||||||
|
|
||||||
// 等待开屏动画展示
|
|
||||||
await Future.delayed(AppConstants.splashDuration);
|
|
||||||
|
|
||||||
// 检查认证状态
|
// 检查认证状态
|
||||||
await ref.read(authProvider.notifier).checkAuthStatus();
|
await ref.read(authProvider.notifier).checkAuthStatus();
|
||||||
|
|
||||||
|
|
@ -68,89 +140,128 @@ class _SplashPageState extends ConsumerState<SplashPage> {
|
||||||
debugPrint('[SplashPage] 已看过向导但未创建钱包 → 跳转到创建账户页面');
|
debugPrint('[SplashPage] 已看过向导但未创建钱包 → 跳转到创建账户页面');
|
||||||
context.go(RoutePaths.onboarding);
|
context.go(RoutePaths.onboarding);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注意:更新检查已移至 HomeShellPage,在主页面加载后执行
|
|
||||||
// 这里不再调用 checkForAppUpdate,因为 context.go() 后 context 已失效
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Container(
|
backgroundColor: Colors.black,
|
||||||
width: double.infinity,
|
body: Stack(
|
||||||
height: double.infinity,
|
fit: StackFit.expand,
|
||||||
// 渐变背景 - 从浅米色到金黄色
|
children: [
|
||||||
decoration: const BoxDecoration(
|
// 视频播放器(全屏覆盖)
|
||||||
gradient: LinearGradient(
|
if (_isVideoInitialized && _videoController != null)
|
||||||
begin: Alignment.topCenter,
|
SizedBox.expand(
|
||||||
end: Alignment.bottomCenter,
|
child: FittedBox(
|
||||||
colors: [
|
fit: BoxFit.cover,
|
||||||
Color(0xFFFFF5E6), // 浅米色
|
child: SizedBox(
|
||||||
Color(0xFFFFE4B5), // 金黄色
|
width: _videoController!.value.size.width,
|
||||||
],
|
height: _videoController!.value.size.height,
|
||||||
),
|
child: VideoPlayer(_videoController!),
|
||||||
),
|
),
|
||||||
child: Column(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
)
|
||||||
children: [
|
else
|
||||||
// Logo 图片容器
|
// 视频加载中显示 Logo
|
||||||
_buildLogo(),
|
_buildLoadingView(),
|
||||||
const SizedBox(height: 24),
|
|
||||||
// 应用名称
|
// 跳过按钮
|
||||||
_buildAppTitle(),
|
if (_showSkipButton)
|
||||||
],
|
Positioned(
|
||||||
),
|
top: MediaQuery.of(context).padding.top + 16,
|
||||||
|
right: 16,
|
||||||
|
child: _buildSkipButton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建 Logo 组件
|
/// 构建加载中视图(视频未加载完成时显示)
|
||||||
/// 深色背景上的皇冠图标
|
Widget _buildLoadingView() {
|
||||||
Widget _buildLogo() {
|
|
||||||
return Container(
|
return Container(
|
||||||
width: 128,
|
width: double.infinity,
|
||||||
height: 152,
|
height: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: const Color(0xFF2D2A26), // 深棕色背景
|
gradient: LinearGradient(
|
||||||
borderRadius: BorderRadius.circular(8),
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Color(0xFFFFF5E6),
|
||||||
|
Color(0xFFFFE4B5),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// 皇冠图标
|
// Logo
|
||||||
Container(
|
Container(
|
||||||
width: 64,
|
width: 128,
|
||||||
height: 48,
|
height: 152,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFD4A84B), // 金色
|
color: const Color(0xFF2D2A26),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: Column(
|
||||||
Icons.workspace_premium,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
size: 32,
|
children: [
|
||||||
color: Color(0xFF2D2A26),
|
Container(
|
||||||
|
width: 64,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFD4A84B),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.workspace_premium,
|
||||||
|
size: 32,
|
||||||
|
color: Color(0xFF2D2A26),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'DURIAN',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 2,
|
||||||
|
color: Color(0xFFD4A84B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
const Text(
|
||||||
|
'QUEEN',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
letterSpacing: 1.5,
|
||||||
|
color: Color(0xFFD4A84B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 24),
|
||||||
// DURIAN 文字
|
|
||||||
const Text(
|
const Text(
|
||||||
'DURIAN',
|
'榴莲女皇',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.w600,
|
fontFamily: 'Noto Sans SC',
|
||||||
letterSpacing: 2,
|
fontWeight: FontWeight.w700,
|
||||||
color: Color(0xFFD4A84B),
|
height: 1.25,
|
||||||
|
letterSpacing: 1.6,
|
||||||
|
color: Color(0xFF4A3F35),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 24),
|
||||||
// QUEEN 文字
|
// 加载指示器
|
||||||
const Text(
|
const SizedBox(
|
||||||
'QUEEN',
|
width: 24,
|
||||||
style: TextStyle(
|
height: 24,
|
||||||
fontSize: 10,
|
child: CircularProgressIndicator(
|
||||||
fontWeight: FontWeight.w400,
|
strokeWidth: 2,
|
||||||
letterSpacing: 1.5,
|
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||||||
color: Color(0xFFD4A84B),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -158,17 +269,40 @@ class _SplashPageState extends ConsumerState<SplashPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建应用标题
|
/// 构建跳过按钮
|
||||||
Widget _buildAppTitle() {
|
Widget _buildSkipButton() {
|
||||||
return const Text(
|
return GestureDetector(
|
||||||
'榴莲女皇',
|
onTap: _skipVideo,
|
||||||
style: TextStyle(
|
child: Container(
|
||||||
fontSize: 32,
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
fontFamily: 'Noto Sans SC',
|
decoration: BoxDecoration(
|
||||||
fontWeight: FontWeight.w700,
|
color: Colors.black.withValues(alpha: 0.5),
|
||||||
height: 1.25,
|
borderRadius: BorderRadius.circular(20),
|
||||||
letterSpacing: 1.6,
|
border: Border.all(
|
||||||
color: Color(0xFF4A3F35), // 深棕色文字
|
color: Colors.white.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'跳过',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Icon(
|
||||||
|
Icons.skip_next,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
import video_player_avfoundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
|
|
@ -31,4 +32,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -621,6 +629,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1570,6 +1586,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
video_player:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: video_player
|
||||||
|
sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.1"
|
||||||
|
video_player_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_android
|
||||||
|
sha256: d74b66f283afff135d5be0ceccca2ca74dff7df1e9b1eaca6bd4699875d3ae60
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.8.22"
|
||||||
|
video_player_avfoundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_avfoundation
|
||||||
|
sha256: e4d33b79a064498c6eb3a6a492b6a5012573d4943c28d566caf1a6c0840fe78d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.8.8"
|
||||||
|
video_player_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_platform_interface
|
||||||
|
sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.0"
|
||||||
|
video_player_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: video_player_web
|
||||||
|
sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ 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
|
||||||
|
|
@ -111,3 +112,4 @@ flutter:
|
||||||
- assets/icons/tokens/
|
- assets/icons/tokens/
|
||||||
- assets/icons/actions/
|
- assets/icons/actions/
|
||||||
- assets/lottie/
|
- assets/lottie/
|
||||||
|
- assets/videos/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue