716 lines
22 KiB
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,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|