YSHOP ADMINSTRATION SCREENSHOTS
import 'dart:ui';
import 'dart:math' as math;
import 'package:flutter/material.dart';
/// 🎬 ULTRA SLOW CINEMATIC BURGER ASSEMBLY
/// Features:
/// - HEAVY layer assembly with strict sequencing
/// - Each layer takes its full time
/// - Smooth cross-fade between angles
/// - Products appear ONLY after full6 is 100%
class BurgerAssemblyWidget extends StatefulWidget {
final ValueNotifier<double> scrollNotifier;
final double height;
final String? storeName;
final VoidCallback? onAssembled;
final ValueNotifier<double>? welcomeOpacityNotifier;
const BurgerAssemblyWidget({
Key? key,
required this.scrollNotifier,
this.height = 300,
this.storeName,
this.onAssembled,
this.welcomeOpacityNotifier,
}) : super(key: key);
@override
State<BurgerAssemblyWidget> createState() => _BurgerAssemblyWidgetState();
}
class _BurgerAssemblyWidgetState extends State<BurgerAssemblyWidget>
with TickerProviderStateMixin {
// 🎬 ANIMATION CONTROLLERS
late AnimationController _breathingController;
late AnimationController _shimmerController;
late AnimationController _particleController;
late AnimationController _cameraRotationController;
late AnimationController _transitionController;
late List<AnimationController> _layerControllers;
// 📊 STATE MANAGEMENT
double _scrollOffset = 0.0;
double _assemblyProgress = 0.0;
bool _isAssembled = false;
bool _isTransitioning = false;
bool _showFinalBurger = false;
bool _animationStarted = false; // ✅ هل بدأت الـ animation
late ValueNotifier<double> initialWelcomeOpacityNotifier;
late List<double> _layerProgresses;
late List<bool> _layerLocked; // ✅ طبقة مقفلة = انتهت ولا ترجع للخلف
late List<Particle> _particles;
// 🎨 LAYER DEFINITIONS
final List<BurgerLayer> _layers = [
BurgerLayer(asset: 'assets/images/burger3D/bottom_bun.png', entryAngle: math.pi * 0.25, glowColor: Color(0xFFFFD700), label: 'Golden Base'),
BurgerLayer(asset: 'assets/images/burger3D/beef_patty.png', entryAngle: -math.pi * 0.3, glowColor: Color(0xFFFF4500), label: 'Fresh Beef'),
BurgerLayer(asset: 'assets/images/burger3D/cheese_slice.png', entryAngle: math.pi * 0.4, glowColor: Color(0xFFFFA500), label: 'Melted Cheese'),
BurgerLayer(asset: 'assets/images/burger3D/lettuce.png', entryAngle: -math.pi * 0.2, glowColor: Color(0xFF32CD32), label: 'Fresh Lettuce'),
BurgerLayer(asset: 'assets/images/burger3D/sauce_layer.png', entryAngle: math.pi * 0.35, glowColor: Color(0xFFFF6347), label: 'Special Sauce'),
BurgerLayer(asset: 'assets/images/burger3D/top_bun.png', entryAngle: -math.pi * 0.15, glowColor: Color(0xFFFFD700), label: 'Perfect Crown'),
];
@override
void initState() {
super.initState();
initialWelcomeOpacityNotifier = ValueNotifier<double>(1.0);
_initializeAnimations();
_initializeParticles();
widget.scrollNotifier.addListener(_handleScroll);
}
void _initializeAnimations() {
_breathingController = AnimationController(vsync: this, duration: const Duration(milliseconds: 4000))..repeat(reverse: true);
_shimmerController = AnimationController(vsync: this, duration: const Duration(milliseconds: 5000))..repeat();
_particleController = AnimationController(vsync: this, duration: const Duration(milliseconds: 2000));
_cameraRotationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 5000));
_transitionController = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000));
_layerControllers = List.generate(_layers.length, (i) => AnimationController(vsync: this, duration: Duration(milliseconds: 2500)));
_layerProgresses = List.filled(_layers.length, 0.0);
_layerLocked = List.filled(_layers.length, false); // ✅ في البداية لا شيء مقفول
for (int i = 0; i < _layerControllers.length; i++) {
_layerControllers[i].addListener(() {
if (mounted) {
setState(() {
_layerProgresses[i] = _layerControllers[i].value.clamp(0.0, 1.0);
_updateAssemblyProgress();
});
}
});
}
}
void _initializeParticles() {
final random = math.Random();
_particles = List.generate(14, (i) => Particle(
x: random.nextDouble() * 2 - 1,
y: random.nextDouble() * 2 - 1,
speed: 0.12 + random.nextDouble() * 0.36,
size: 1.0 + random.nextDouble() * 3.0,
opacity: 0.4 + random.nextDouble() * 0.4,
color: _layers[random.nextInt(_layers.length)].glowColor,
));
}
// ✅ تشغيل الـ animation التلقائية (بدون ربط مع scroll)
void _startAutomaticAnimation() {
if (_animationStarted) return;
_animationStarted = true;
for (int i = 0; i < _layers.length; i++) {
// كل طبقة تبدأ بعد انتهاء الطبقة السابقة
final delayMs = i * 600; // تأخير 600ms لكل طبقة
Future.delayed(Duration(milliseconds: delayMs), () {
if (!_isAssembled && mounted) {
_layerControllers[i].animateTo(1.0, duration: Duration(milliseconds: 600), curve: Curves.easeOutCubic);
setState(() {
_layerLocked[i] = true;
});
}
});
}
// بعد انتهاء جميع الطبقات (6 * 600 = 3600ms)
Future.delayed(Duration(milliseconds: 3600 + 200), () {
if (!_isAssembled && mounted) {
setState(() {
_isTransitioning = true;
});
// الآن نبدأ الـ fade والبرجر 3D
_transitionController.animateTo(1.0, duration: Duration(milliseconds: 1000));
}
});
// بعد الـ transition، نشغل camera rotation
Future.delayed(Duration(milliseconds: 4800), () {
if (!_isAssembled && mounted) {
setState(() {
_showFinalBurger = true;
});
_cameraRotationController.animateTo(0.83, duration: Duration(milliseconds: 1500), curve: Curves.easeInOutCubic);
}
});
// full6
Future.delayed(Duration(milliseconds: 6400), () {
if (!_isAssembled && mounted) {
_cameraRotationController.animateTo(1.0, duration: Duration(milliseconds: 800), curve: Curves.easeOutCubic);
}
});
// Products
Future.delayed(Duration(milliseconds: 7300), () {
if (!_isAssembled && mounted) {
setState(() { _isAssembled = true; });
widget.onAssembled?.call();
}
});
}
void _handleScroll() {
if (_isAssembled) return;
final scroll = widget.scrollNotifier.value;
setState(() {
_scrollOffset = scroll;
initialWelcomeOpacityNotifier.value = (1.0 - (scroll / 80.0)).clamp(0.0, 1.0);
widget.welcomeOpacityNotifier?.value = initialWelcomeOpacityNotifier.value;
});
// ✅ أول scroll = ابدأ الـ animation التلقائية
if (!_animationStarted && scroll > 0) {
_startAutomaticAnimation();
}
// لا نتحكم في الطبقات من خلال scroll، الـ animation تتحكم بكل شيء
}
void _updateAssemblyProgress() {
final avg = _layerProgresses.reduce((a, b) => a + b) / _layerProgresses.length;
_assemblyProgress = avg.clamp(0.0, 1.0);
}
@override
void dispose() {
widget.scrollNotifier.removeListener(_handleScroll);
initialWelcomeOpacityNotifier.dispose();
_breathingController.dispose();
_shimmerController.dispose();
_particleController.dispose();
_cameraRotationController.dispose();
_transitionController.dispose();
for (var c in _layerControllers) c.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: widget.height * 1.8,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
_buildBackground(),
_buildParticleSystem(),
if (_assemblyProgress < 0.1) _buildInitialWelcomeText(),
AnimatedBuilder(
animation: _transitionController,
builder: (context, child) => Opacity(opacity: 1.0 - _transitionController.value, child: _buildAssemblyView()),
),
if (_showFinalBurger) _build3DBurgerWithCameraRotation(),
if (_isAssembled) _buildWelcomeText(),
],
),
);
}
Widget _buildBackground() {
return AnimatedBuilder(
animation: _shimmerController,
builder: (context, child) {
final intensity = _assemblyProgress < 0.95 ? _assemblyProgress * 0.25 : _assemblyProgress * 0.5;
return Container(
width: widget.height * 2,
height: widget.height * 2,
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
Color(0xFF6366F1).withOpacity(0.08 * intensity),
Color(0xFF8B5CF6).withOpacity(0.04 * intensity),
Colors.transparent,
],
),
),
child: CustomPaint(painter: BackgroundPainter(progress: _shimmerController.value, intensity: intensity)),
);
},
);
}
Widget _buildParticleSystem() {
return AnimatedBuilder(
animation: _particleController,
builder: (context, child) => CustomPaint(
size: Size(widget.height * 2, widget.height * 2),
painter: ParticlePainter(particles: _particles, progress: _particleController.value, assemblyProgress: _assemblyProgress),
),
);
}
Widget _buildAssemblyView() {
return Transform.scale(
scale: 1.0 + (_assemblyProgress * 0.15),
child: Stack(
alignment: Alignment.center,
children: List.generate(_layers.length, (i) => _buildLayer(i)),
),
);
}
Widget _buildLayer(int index) {
final progress = _layerProgresses[index].clamp(0.0, 1.0);
final stackOrder = _layers.length - 1 - index;
final layerThickness = 12.0;
final totalStackHeight = (_layers.length - 1) * layerThickness;
final baseTop = -(totalStackHeight / 2);
final targetY = baseTop + (stackOrder * layerThickness);
final startHeight = 700.0;
final descendCurve = Curves.easeOutCubic.transform(progress);
final currentY = startHeight * (1 - descendCurve) + targetY * descendCurve;
final scale = (0.85 + (progress * 0.15));
return AnimatedBuilder(
animation: _breathingController,
builder: (context, child) {
final breathOffset = progress >= 0.98 ? math.sin(_breathingController.value * math.pi * 2) * 2.0 : 0.0;
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..translate(0.0, currentY + breathOffset)..scale(scale),
child: Stack(
alignment: Alignment.center,
children: [
if (progress > 0.6 && progress < 0.95)
Container(
width: widget.height * 0.7,
height: widget.height * 0.7,
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [BoxShadow(color: _layers[index].glowColor.withOpacity(0.08 * (progress - 0.6)), blurRadius: 15 * (progress - 0.6), spreadRadius: 2 * (progress - 0.6))],
),
),
Image.asset(_layers[index].asset, width: widget.height * 1.5, fit: BoxFit.contain),
// Layer label - يظهر فقط عندما تكون الطبقة وحدها
if (progress >= 0.4 && progress <= 0.65)
Positioned(
top: 80,
right: 20,
child: AnimatedOpacity(
opacity: progress >= 0.4 && progress <= 0.65 ? 1.0 : 0.0,
duration: Duration(milliseconds: 200),
child: ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [_layers[index].glowColor, _layers[index].glowColor.withOpacity(0.6)],
).createShader(bounds),
child: Text(
_layers[index].label,
style: TextStyle(
fontFamily: 'TenorSans',
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: 2,
color: Colors.white,
),
),
),
),
),
],
),
);
},
);
}
Widget _build3DBurgerWithCameraRotation() {
final burgerAngles = [
'assets/images/burger3D/full1.png',
'assets/images/burger3D/full2.png',
'assets/images/burger3D/full3.png',
'assets/images/burger3D/full4.png',
'assets/images/burger3D/full5.png',
'assets/images/burger3D/full6.png',
];
return AnimatedBuilder(
animation: Listenable.merge([_breathingController, _cameraRotationController, _transitionController]),
builder: (context, child) {
final rotation = Curves.easeInOutCubic.transform(_cameraRotationController.value);
final breathScale = 1.0 + (math.sin(_breathingController.value * math.pi * 2) * 0.015);
final floatY = math.sin(_breathingController.value * math.pi * 2) * 5.0;
final fadeIn = Curves.easeOut.transform(_transitionController.value);
return Opacity(
opacity: fadeIn,
child: Transform.translate(
offset: Offset(0, floatY - 50),
child: Transform.scale(
scale: breathScale,
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: widget.height * 1.0,
height: widget.height * 1.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
Color(0xFFFF6B35).withOpacity(0.15 * rotation),
Color(0xFFFFC837).withOpacity(0.08 * rotation),
Colors.transparent,
],
),
boxShadow: [BoxShadow(color: Color(0xFFFF6B35).withOpacity(0.2 * rotation), blurRadius: 40, spreadRadius: 5)],
),
),
...List.generate(burgerAngles.length, (i) => _buildBurgerAngleLayer(burgerAngles[i], i, burgerAngles.length, rotation)),
if (rotation > 0.85)
CustomPaint(
size: Size(widget.height * 2, widget.height * 2),
painter: LightRayPainter(progress: _breathingController.value, intensity: (rotation - 0.85) / 0.15),
),
],
),
),
),
);
},
);
}
Widget _buildBurgerAngleLayer(String path, int index, int total, double rotation) {
double opacity = 0.0;
if (total == 1) {
opacity = 1.0;
} else {
final segmentSize = 1.0 / (total - 1);
final myStart = index * segmentSize;
final myEnd = (index + 1) * segmentSize;
final fadeDuration = segmentSize * 0.25; // أقل من السابق للظهور الأسرع
if (index == 0) {
// First image: ظهور دائم ثم تلاشي سلس (نفس منطق الطبقات)
if (rotation <= myStart + fadeDuration) {
opacity = 1.0;
} else if (rotation < myEnd) {
opacity = 1.0 - Curves.easeInOut.transform(((rotation - (myStart + fadeDuration)) / fadeDuration).clamp(0.0, 1.0));
}
} else if (index == total - 1) {
// Last image: ظهور سلس ثم دائم (نفس منطق الطبقات)
if (rotation < myStart - fadeDuration) {
opacity = 0.0;
} else if (rotation < myStart + fadeDuration) {
opacity = Curves.easeInOut.transform(((rotation - (myStart - fadeDuration)) / (fadeDuration * 2)).clamp(0.0, 1.0));
} else {
opacity = 1.0;
}
} else {
// Middle images: ظهور سلس → دائم → تلاشي سلس (نفس منطق الطبقات)
if (rotation < myStart - fadeDuration) {
opacity = 0.0;
} else if (rotation < myStart + fadeDuration) {
opacity = Curves.easeInOut.transform(((rotation - (myStart - fadeDuration)) / (fadeDuration * 2)).clamp(0.0, 1.0));
} else if (rotation < myEnd - fadeDuration) {
opacity = 1.0;
} else if (rotation < myEnd + fadeDuration) {
opacity = 1.0 - Curves.easeInOut.transform(((rotation - (myEnd - fadeDuration)) / (fadeDuration * 2)).clamp(0.0, 1.0));
}
}
}
return Opacity(opacity: opacity.clamp(0.0, 1.0), child: Image.asset(path, width: widget.height * 1.5, fit: BoxFit.contain));
}
Widget _buildWelcomeText() {
return Positioned(
bottom: 40,
child: AnimatedOpacity(
opacity: 1.0,
duration: const Duration(milliseconds: 800),
child: Column(
children: [
ShaderMask(
shaderCallback: (bounds) => LinearGradient(colors: [Color(0xFFFF6B35), Color(0xFFFFC837)]).createShader(bounds),
child: Text('CRAFTED WITH PASSION', style: TextStyle(fontFamily: 'TenorSans', fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 4, color: Colors.white)),
),
SizedBox(height: 12),
Text('Welcome to ${widget.storeName ?? "Paradise"}', style: TextStyle(fontFamily: 'TenorSans', fontSize: 32, fontWeight: FontWeight.w900, color: Colors.white)),
],
),
),
);
}
Widget _buildInitialWelcomeText() {
return ValueListenableBuilder<double>(
valueListenable: initialWelcomeOpacityNotifier,
builder: (context, opacity, _) => AnimatedBuilder(
animation: _shimmerController,
builder: (context, child) {
final shimmer = (math.sin(_shimmerController.value * math.pi * 2) * 0.5) + 0.5;
return Opacity(
opacity: opacity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
begin: Alignment(-1.5 + (shimmer * 3), -0.2),
end: Alignment(1.5 + (shimmer * 3), 0.2),
colors: [Color(0xFF6366F1).withOpacity(0.2), Color(0xFFFF6B35).withOpacity(0.8), Color(0xFFFFC837), Color(0xFFFF6B35).withOpacity(0.8), Color(0xFF6366F1).withOpacity(0.2)],
stops: [0.0, 0.35, 0.5, 0.65, 1.0],
).createShader(bounds),
child: Text('DISCOVER EXCELLENCE', style: TextStyle(fontFamily: 'TenorSans', fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 5, color: Colors.white)),
),
SizedBox(height: 20),
ShaderMask(
shaderCallback: (bounds) => LinearGradient(colors: [Colors.white, Color(0xFFFFC837), Colors.white]).createShader(bounds),
child: Text('Scroll Down to Explore', style: TextStyle(fontFamily: 'TenorSans', fontSize: 28, fontWeight: FontWeight.w700, color: Colors.white)),
),
SizedBox(height: 30),
Transform.translate(offset: Offset(0, shimmer * 10), child: Icon(Icons.keyboard_arrow_down_rounded, size: 28, color: Colors.white)),
],
),
);
},
),
);
}
}
// PAINTERS
class BackgroundPainter extends CustomPainter {
final double progress, intensity;
BackgroundPainter({required this.progress, required this.intensity});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..style = PaintingStyle.stroke..strokeWidth = 1.5;
final center = Offset(size.width / 2, size.height / 2);
for (int i = 0; i < 4; i++) {
final phase = (progress + (i * 0.2));
final offset = (math.sin(phase * math.pi * 2) * 0.5) + 0.5;
final radius = size.width * 0.2 + (i * size.width * 0.08) + (offset * size.width * 0.1);
paint.color = Color(0xFF8B5CF6).withOpacity(((1 - offset) * intensity * 0.12).clamp(0.0, 1.0));
canvas.drawCircle(center, radius, paint);
}
}
@override
bool shouldRepaint(BackgroundPainter old) => true;
}
class ParticlePainter extends CustomPainter {
final List<Particle> particles;
final double progress, assemblyProgress;
ParticlePainter({required this.particles, required this.progress, required this.assemblyProgress});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
for (var p in particles) {
final angle = p.x * math.pi * 2 + progress * math.pi * 2;
final radius = (p.y.abs() * size.width * 0.4);
final paint = Paint()
..color = p.color.withOpacity(p.opacity * assemblyProgress * 0.5)
..maskFilter = MaskFilter.blur(BlurStyle.normal, p.size);
canvas.drawCircle(Offset(center.dx + math.cos(angle) * radius, center.dy + math.sin(angle) * radius), p.size, paint);
}
}
@override
bool shouldRepaint(ParticlePainter old) => true;
}
class LightRayPainter extends CustomPainter {
final double progress, intensity;
LightRayPainter({required this.progress, required this.intensity});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final paint = Paint()..style = PaintingStyle.fill..maskFilter = MaskFilter.blur(BlurStyle.normal, 12);
for (int i = 0; i < 6; i++) {
final angle = (i / 6) * math.pi * 2 + progress * math.pi * 0.3;
final length = size.width * 0.25 * intensity;
paint.shader = RadialGradient(colors: [Color(0xFFFFC837).withOpacity(0.15 * intensity), Colors.transparent]).createShader(Rect.fromCircle(center: center, radius: length));
final path = Path()
..moveTo(center.dx, center.dy)
..lineTo(center.dx + math.cos(angle) * length, center.dy + math.sin(angle) * length)
..lineTo(center.dx + math.cos(angle + 0.08) * length, center.dy + math.sin(angle + 0.08) * length)
..close();
canvas.drawPath(path, paint);
}
}
@override
bool shouldRepaint(LightRayPainter old) => true;
}
// MODELS
class BurgerLayer {
final String asset;
final double entryAngle;
final Color glowColor;
final String label;
BurgerLayer({required this.asset, required this.entryAngle, required this.glowColor, required this.label});
}
class Particle {
final double x, y, speed, size, opacity;
final Color color;
Particle({required this.x, required this.y, required this.speed, required this.size, required this.opacity, required this.color});
}