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 SplashVideo.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 SplashVideo( source: AssetSource(ExampleSelector.kFilePath), backgroundColor: Colors.black, nextScreen: const ExampleSelector(), videoConfig: const VideoConfig( scale: VideoScaleMode.cover, // Fill screen, maintain aspect, may crop edges ), 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, 'SCALE MODES DEMO', 'Compare all 7 video scale modes', Icons.aspect_ratio, CyberpunkColors.neonYellow, () => Navigator.push( context, MaterialPageRoute( builder: (_) => const ScaleModesExample(), ), ), ), 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 SplashVideo( source: AssetSource(ExampleSelector.kFilePath), nextScreen: const HomeScreen(title: 'Auto-Navigate Example'), backgroundColor: Colors.black, videoConfig: const VideoConfig( scale: VideoScaleMode.cover, ), 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 SplashVideo( 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 createState() => _LoopingExampleState(); } class _LoopingExampleState extends State { 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 SplashVideo( 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: Compare all video scale modes class ScaleModesExample extends StatefulWidget { const ScaleModesExample({super.key}); @override State createState() => _ScaleModesExampleState(); } class _ScaleModesExampleState extends State { VideoScaleMode currentScaleMode = VideoScaleMode.cover; final controller = SplashVideoController(loopVideo: true); final List<(VideoScaleMode, String, String)> scaleModes = [ (VideoScaleMode.cover, 'COVER', 'Fill screen, maintain aspect, may crop'), (VideoScaleMode.contain, 'CONTAIN', 'Scale to fit inside, maintain aspect, letterbox'), (VideoScaleMode.fill, 'FILL', 'Stretch to fill (ignores aspect)'), (VideoScaleMode.fitWidth, 'SCALE WIDTH', 'Match width, maintain aspect'), (VideoScaleMode.fitHeight, 'SCALE HEIGHT', 'Match height, maintain aspect'), (VideoScaleMode.scaleDown, 'SCALE DOWN', 'Like contain, but no upscaling'), (VideoScaleMode.none, 'NONE', 'Original size, centered'), ]; @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // Video player with current scale mode SplashVideo( key: ValueKey(currentScaleMode), // Force rebuild when mode changes source: AssetSource(ExampleSelector.kFilePath), controller: controller, backgroundColor: Colors.black, videoConfig: VideoConfig( scale: currentScaleMode, playImmediately: true, ), ), // Scale mode selector overlay Positioned( bottom: 0, left: 0, right: 0, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withValues(alpha: 0.9), ], ), ), padding: const EdgeInsets.all(20), child: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'VIDEO SCALE MODES', style: TextStyle( color: CyberpunkColors.neonCyan, fontSize: 16, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), const SizedBox(height: 16), SizedBox( height: 120, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: scaleModes.length, itemBuilder: (context, index) { final (mode, title, desc) = scaleModes[index]; final isSelected = mode == currentScaleMode; return Padding( padding: const EdgeInsets.only(right: 12), child: GestureDetector( onTap: () => setState(() => currentScaleMode = mode), child: Container( width: 140, decoration: BoxDecoration( border: Border.all( color: isSelected ? CyberpunkColors.neonCyan : Colors.white24, width: isSelected ? 2 : 1, ), borderRadius: BorderRadius.circular(12), color: isSelected ? CyberpunkColors.neonCyan.withValues(alpha: 0.2) : Colors.white.withValues(alpha: 0.05), ), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( title, style: TextStyle( color: isSelected ? CyberpunkColors.neonCyan : Colors.white, fontSize: 14, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( desc, style: TextStyle( color: Colors.white70, fontSize: 11, ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ], ), ), ), ); }, ), ), ], ), ), ), ), // Back button Positioned( top: 40, left: 16, child: SafeArea( child: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), style: IconButton.styleFrom( backgroundColor: Colors.black.withValues(alpha: 0.5), ), ), ), ), ], ), ); } } /// Example 4: Video with title, footer, and custom overlay class OverlayExample extends StatelessWidget { const OverlayExample({super.key}); @override Widget build(BuildContext context) { return SplashVideo( 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(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, ), ), ), ); }, ); } }