lum_splash_video/example/lib/main.dart

716 lines
22 KiB
Dart

import 'package:flutter/material.dart';
import 'package:splash_video/splash_video.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Initialize SplashVideo: defer first frame & setup MediaKit
// This is safe because our first screen IS a SplashVideoPage
SplashVideoPage.initialize();
runApp(const MyApp());
}
// Cyberpunk color palette
class CyberpunkColors {
static const neonPink = Color(0xFFFF006E);
static const neonCyan = Color(0xFF00F5FF);
static const neonPurple = Color(0xFFB76CFF);
static const neonYellow = Color(0xFFFFED4E);
static const darkBg = Color(0xFF0A0E27);
static const darkerBg = Color(0xFF050816);
static const glowBlue = Color(0xFF1B1464);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Splash Video Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
scaffoldBackgroundColor: CyberpunkColors.darkBg,
colorScheme: ColorScheme.dark(
primary: CyberpunkColors.neonCyan,
secondary: CyberpunkColors.neonPink,
// surface: CyberpunkColors.darkBg,
surface: CyberpunkColors.glowBlue,
),
textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.white, fontWeight: FontWeight.w300),
bodyMedium: TextStyle(color: Colors.white70, fontWeight: FontWeight.w300),
),
useMaterial3: true,
),
// Show splash video first, then auto-navigate to selector
home: const InitialSplashExample(),
);
}
}
/// Initial splash screen shown on app startup
class InitialSplashExample extends StatelessWidget {
const InitialSplashExample({super.key});
@override
Widget build(BuildContext context) {
return SplashVideoPage(
source: AssetSource(ExampleSelector.kFilePath),
backgroundColor: Colors.black,
nextScreen: const ExampleSelector(),
videoConfig: const VideoConfig(
videoVisibilityEnum: VisibilityEnum.useFullScreen,
),
onVideoError: (error) {
debugPrint('Initial Splash Video Error: $error');
// Navigate to selector even on error
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const ExampleSelector(),
),
);
},
);
}
}
/// Example selector screen to choose different splash examples
class ExampleSelector extends StatelessWidget {
const ExampleSelector({super.key});
static const kFilePath = 'assets/splash.mp4';
static const backgroundColor = Colors.white;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: CyberpunkColors.darkerBg,
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
CyberpunkColors.darkerBg,
CyberpunkColors.darkBg,
CyberpunkColors.glowBlue.withValues(alpha: 0.3),
],
),
),
child: SafeArea(
child: Column(
children: [
const SizedBox(height: 40),
// Cyberpunk header
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [CyberpunkColors.neonCyan, CyberpunkColors.neonPink],
).createShader(bounds),
child: const Text(
'⚡ SPLASH VIDEO ⚡',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w900,
color: Colors.white,
letterSpacing: 3,
),
),
),
const SizedBox(height: 10),
const Text(
'CYBERPUNK EDITION',
style: TextStyle(
fontSize: 14,
color: CyberpunkColors.neonYellow,
letterSpacing: 4,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 40),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 20),
children: [
_buildCyberpunkTile(
context,
'AUTO-NAVIGATE',
'Video plays then auto-navigates to home',
Icons.rocket_launch,
CyberpunkColors.neonCyan,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const AutoNavigateExample(),
),
),
),
const SizedBox(height: 16),
_buildCyberpunkTile(
context,
'MANUAL CONTROL',
'Use onVideoComplete for custom logic',
Icons.control_camera,
CyberpunkColors.neonPurple,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ManualControlExample(),
),
),
),
const SizedBox(height: 16),
_buildCyberpunkTile(
context,
'INFINITE LOOP',
'Video loops until user taps skip',
Icons.all_inclusive,
CyberpunkColors.neonPink,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const LoopingExample(),
),
),
),
const SizedBox(height: 16),
_buildCyberpunkTile(
context,
'OVERLAY MODE',
'Title, footer, and custom overlays',
Icons.layers,
CyberpunkColors.neonYellow,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const OverlayExample(),
),
),
),
],
),
),
],
),
),
),
);
}
Widget _buildCyberpunkTile(
BuildContext context,
String title,
String subtitle,
IconData icon,
Color accentColor,
VoidCallback onTap,
) {
return InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: CyberpunkColors.darkBg.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: accentColor.withValues(alpha: 0.5),
width: 2,
),
boxShadow: [
BoxShadow(
color: accentColor.withValues(alpha: 0.3),
blurRadius: 20,
spreadRadius: -5,
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: accentColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: accentColor.withValues(alpha: 0.5),
blurRadius: 15,
),
],
),
child: Icon(
icon,
color: accentColor,
size: 32,
),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: accentColor,
letterSpacing: 1.5,
),
),
const SizedBox(height: 6),
Text(
subtitle,
style: const TextStyle(
fontSize: 13,
color: Colors.white70,
fontWeight: FontWeight.w300,
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
color: accentColor.withValues(alpha: 0.7),
size: 20,
),
],
),
),
);
}
}
/// Example 1: Auto-navigate to next screen when video completes
class AutoNavigateExample extends StatelessWidget {
const AutoNavigateExample({super.key});
@override
Widget build(BuildContext context) {
return SplashVideoPage(
source: AssetSource(ExampleSelector.kFilePath),
nextScreen: const HomeScreen(title: 'Auto-Navigate Example'),
backgroundColor: Colors.black,
videoConfig: const VideoConfig(
videoVisibilityEnum: VisibilityEnum.useFullScreen,
),
onVideoError: (error) {
debugPrint('Video Error in AutoNavigateExample: $error');
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const HomeScreen(title: 'Auto-Navigate (Error)'),
),
);
},
);
}
}
/// Example 2: Manual control with onVideoComplete callback
class ManualControlExample extends StatelessWidget {
const ManualControlExample({super.key});
@override
Widget build(BuildContext context) {
return SplashVideoPage(
source: AssetSource(ExampleSelector.kFilePath),
backgroundColor: Colors.black,
onVideoError: (error) {
debugPrint('Video Error in ManualControlExample: $error');
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const HomeScreen(title: 'Manual Control (Error)'),
),
);
},
onVideoComplete: () {
// Custom logic before navigation
debugPrint('Video completed!');
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const HomeScreen(title: 'Manual Control Example'),
),
);
},
);
}
}
/// Example 3: Looping video with manual skip
class LoopingExample extends StatefulWidget {
const LoopingExample({super.key});
@override
State<LoopingExample> createState() => _LoopingExampleState();
}
class _LoopingExampleState extends State<LoopingExample> {
late final SplashVideoController controller;
@override
void initState() {
super.initState();
controller = SplashVideoController(loopVideo: true);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
void _onSkip() {
controller.skip();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const HomeScreen(title: 'Looping Example'),
),
);
}
@override
Widget build(BuildContext context) {
return SplashVideoPage(
source: AssetSource(ExampleSelector.kFilePath),
controller: controller,
backgroundColor: Colors.black,
onVideoError: (error) {
debugPrint('LoopingExample - Video Error: $error');
// Navigate to home screen on error
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const HomeScreen(title: 'Looping Example (Error)'),
),
);
},
footerWidget: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [CyberpunkColors.neonCyan, CyberpunkColors.neonPurple],
),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: CyberpunkColors.neonCyan.withValues(alpha: 0.5),
blurRadius: 20,
spreadRadius: 2,
),
],
),
child: ElevatedButton.icon(
onPressed: _onSkip,
icon: const Icon(Icons.skip_next, color: Colors.black),
label: const Text(
'SKIP',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
),
),
),
),
),
);
}
}
/// Example 4: Video with title, footer, and custom overlay
class OverlayExample extends StatelessWidget {
const OverlayExample({super.key});
@override
Widget build(BuildContext context) {
return SplashVideoPage(
source: AssetSource(ExampleSelector.kFilePath),
nextScreen: const HomeScreen(title: 'Overlay Example'),
backgroundColor: Colors.black,
onVideoError: (error) {
debugPrint('OverlayExample - Video Error: $error');
// Navigate to home screen on error
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const HomeScreen(title: 'Overlay Example (Error)'),
),
);
},
titleWidget: const Padding(
padding: EdgeInsets.all(20.0),
child: Center(
child: _CyberpunkTitle(),
),
),
footerWidget: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [CyberpunkColors.neonPink, CyberpunkColors.neonCyan],
),
borderRadius: BorderRadius.circular(50),
),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: CyberpunkColors.darkerBg,
borderRadius: BorderRadius.circular(50),
),
child: const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(CyberpunkColors.neonCyan),
strokeWidth: 3,
),
),
),
),
),
overlayBuilder: (context) => Positioned(
right: 20,
top: 100,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
CyberpunkColors.neonPurple.withValues(alpha: 0.3),
CyberpunkColors.neonCyan.withValues(alpha: 0.3),
],
),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: CyberpunkColors.neonCyan.withValues(alpha: 0.5),
width: 1,
),
boxShadow: [
BoxShadow(
color: CyberpunkColors.neonCyan.withValues(alpha: 0.5),
blurRadius: 10,
),
],
),
child: const Text(
'v1.0.0',
style: TextStyle(
color: CyberpunkColors.neonCyan,
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 1,
),
),
),
),
);
}
}
/// Home screen that's shown after splash
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: CyberpunkColors.darkerBg,
body: Container(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment.center,
radius: 1.5,
colors: [
CyberpunkColors.glowBlue.withValues(alpha: 0.3),
CyberpunkColors.darkerBg,
],
),
),
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Success icon with glow
Container(
padding: const EdgeInsets.all(30),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
CyberpunkColors.neonCyan.withValues(alpha: 0.3),
Colors.transparent,
],
),
boxShadow: [
BoxShadow(
color: CyberpunkColors.neonCyan.withValues(alpha: 0.5),
blurRadius: 50,
spreadRadius: 10,
),
],
),
child: const Icon(
Icons.check_circle,
size: 100,
color: CyberpunkColors.neonCyan,
),
),
const SizedBox(height: 40),
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [CyberpunkColors.neonCyan, CyberpunkColors.neonPink],
).createShader(bounds),
child: Text(
title.toUpperCase(),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 2,
),
),
),
const SizedBox(height: 20),
Text(
'SPLASH VIDEO COMPLETED',
style: TextStyle(
fontSize: 16,
color: CyberpunkColors.neonYellow,
letterSpacing: 3,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 60),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [CyberpunkColors.neonPink, CyberpunkColors.neonPurple],
),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: CyberpunkColors.neonPink.withValues(alpha: 0.5),
blurRadius: 20,
spreadRadius: 2,
),
],
),
child: ElevatedButton.icon(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back, color: Colors.black),
label: const Text(
'BACK TO EXAMPLES',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 18),
),
),
),
],
),
),
),
),
);
}
}
// Animated cyberpunk title widget
class _CyberpunkTitle extends StatefulWidget {
const _CyberpunkTitle();
@override
State<_CyberpunkTitle> createState() => _CyberpunkTitleState();
}
class _CyberpunkTitleState extends State<_CyberpunkTitle>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
CyberpunkColors.neonCyan.withValues(alpha: 0.2 + _controller.value * 0.3),
CyberpunkColors.neonPink.withValues(alpha: 0.2 + _controller.value * 0.3),
],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: CyberpunkColors.neonCyan.withValues(alpha: 0.5 + _controller.value * 0.5),
width: 2,
),
boxShadow: [
BoxShadow(
color: CyberpunkColors.neonCyan.withValues(alpha: 0.3 + _controller.value * 0.4),
blurRadius: 20 + _controller.value * 10,
spreadRadius: 5,
),
],
),
child: ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [CyberpunkColors.neonCyan, CyberpunkColors.neonPink],
).createShader(bounds),
child: const Text(
'⚡ WELCOME TO THE FUTURE ⚡',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
color: Colors.white,
letterSpacing: 2,
),
),
),
);
},
);
}
}