import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../widgets/app_background.dart'; class LoginAnimationScreen extends StatefulWidget { final bool isLogin; final bool isSuccess; const LoginAnimationScreen({ super.key, required this.isLogin, required this.isSuccess, }); @override State createState() => _LoginAnimationScreenState(); } class _LoginAnimationScreenState extends State with TickerProviderStateMixin { late AnimationController ringController; late AnimationController progressController; late Animation ringAnimation; late Animation progressAnimation; // ERROR pulse animation late AnimationController errorPulseController; late Animation errorPulseAnimation; // SUCCESS one-time pulse late AnimationController successPulseController; late Animation successPulseAnimation; bool showResult = false; @override void initState() { super.initState(); // MAIN loading ripple animations ringController = AnimationController( vsync: this, duration: const Duration(seconds: 1), ); progressController = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); ringAnimation = Tween( begin: 0, end: 1, ).animate(CurvedAnimation(parent: ringController, curve: Curves.easeOut)); progressAnimation = Tween(begin: 0, end: 1).animate( CurvedAnimation(parent: progressController, curve: Curves.linear), ); // ERROR pulse animation errorPulseController = AnimationController( vsync: this, duration: const Duration(seconds: 1), ); errorPulseAnimation = Tween(begin: 0, end: 1).animate( CurvedAnimation(parent: errorPulseController, curve: Curves.easeOut), ); // SUCCESS one-time pulse successPulseController = AnimationController( vsync: this, duration: const Duration(seconds: 1), ); successPulseAnimation = Tween(begin: 0, end: 1).animate( CurvedAnimation(parent: successPulseController, curve: Curves.easeOut), ); startAnimations(); // MAIN DELAY (same as before) Future.delayed(const Duration(seconds: 2), () { if (mounted) { setState(() => showResult = true); ringController.stop(); progressController.stop(); if (!widget.isSuccess) { startErrorPulse(); } else { successPulseController.forward(); // ONE-TIME BEAT Future.delayed(const Duration(seconds: 2), () { if (mounted) Navigator.of(context).pop(); }); } } }); } void startAnimations() { progressController.repeat(); startPulseAnimation(); } void startPulseAnimation() { ringController.forward().then((_) { ringController.reset(); if (!showResult) startPulseAnimation(); }); } void startErrorPulse() { errorPulseController.forward().then((_) { errorPulseController.reset(); if (showResult && !widget.isSuccess) startErrorPulse(); }); } @override void dispose() { ringController.dispose(); progressController.dispose(); errorPulseController.dispose(); successPulseController.dispose(); super.dispose(); } Widget _buildIcon() { if (!showResult) { return SvgPicture.asset( "assets/images/finger_print.svg", key: const ValueKey("fingerprint"), width: 70, height: 70, ); } else if (widget.isSuccess) { return SizedBox( width: 120, height: 120, child: Image.asset( "assets/images/tick.png", key: const ValueKey("success"), fit: BoxFit.contain, ), ); } else { return SizedBox( width: 120, height: 120, child: Image.asset( "assets/images/error.png", key: const ValueKey('error'), fit: BoxFit.contain, ), ); } } @override Widget build(BuildContext context) { return Scaffold( body: AppBackground( child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( widget.isLogin ? "تسجيل الدخول" : "تسجيل خروج", style: const TextStyle( color: Colors.white, fontSize: 30, fontWeight: FontWeight.bold, decoration: TextDecoration.none, ), ), const SizedBox(height: 100), Container( width: 280, height: 400, decoration: BoxDecoration( color: const Color(0xFFEEFFFA), borderRadius: BorderRadius.circular(38), boxShadow: [ BoxShadow( color: const Color(0x34000000), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 160, height: 160, child: Stack( alignment: Alignment.center, children: [ // GREY CIRCLE (base) Container( width: 120, height: 120, decoration: const BoxDecoration( color: Color(0xFFE5E5E5), shape: BoxShape.circle, ), ), // MAIN ICON SWITCHER AnimatedSwitcher( duration: const Duration(milliseconds: 500), child: _buildIcon(), ), // LOADING MODE if (!showResult) Positioned.fill( child: AnimatedBuilder( animation: progressAnimation, builder: (_, __) => CustomPaint( painter: _ProgressRingPainter( progress: progressAnimation.value, ), ), ), ), if (!showResult) Positioned.fill( child: AnimatedBuilder( animation: ringAnimation, builder: (_, __) => CustomPaint( painter: _PulseRingsPainter( progress: ringAnimation.value, ), ), ), ), // ERROR ANIMATION if (showResult && !widget.isSuccess) Positioned.fill( child: AnimatedBuilder( animation: errorPulseAnimation, builder: (_, __) => CustomPaint( painter: _ErrorPulsePainter( progress: errorPulseAnimation.value, ), ), ), ), // SUCCESS ONE-TIME PULSE if (showResult && widget.isSuccess) Positioned.fill( child: AnimatedBuilder( animation: successPulseAnimation, builder: (_, __) => CustomPaint( painter: _SuccessPulsePainter( progress: successPulseAnimation.value, ), ), ), ), ], ), ), const SizedBox(height: 40), AnimatedSwitcher( duration: const Duration(milliseconds: 500), child: showResult ? Text( widget.isSuccess ? (widget.isLogin ? "تم تسجيل دخولك بنجاح" : "تم تسجيل خروجك بنجاح") : "تم رفض تسجيل الدخول", key: ValueKey("text_${widget.isSuccess}"), style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w600, decoration: TextDecoration.none, ), ) : Text( widget.isLogin ? "يتم تسجيل الدخول..." : "يتم تسجيل الخروج...", key: const ValueKey("loading_text"), style: const TextStyle( fontSize: 16, decoration: TextDecoration.none, ), ), ), // RETRY BUTTON if (showResult && !widget.isSuccess) ...[ const SizedBox(height: 20), GestureDetector( onTap: () { setState(() => showResult = false); errorPulseController.stop(); successPulseController.reset(); startAnimations(); Future.delayed(const Duration(seconds: 2), () { if (!mounted) return; setState(() => showResult = true); ringController.stop(); progressController.stop(); if (!widget.isSuccess) { startErrorPulse(); } else { successPulseController.forward(); Future.delayed(const Duration(seconds: 2), () { if (mounted) Navigator.of(context).pop(); }); } }); }, child: const Text( "أعد المحاولة", style: TextStyle( color: Color(0xFFB00020), fontSize: 16, fontWeight: FontWeight.w600, decoration: TextDecoration.underline, ), ), ), ], ], ), ), ], ), ), ), ); } } // ------------------------- PAINTERS -------------------------- // class _ProgressRingPainter extends CustomPainter { final double progress; _ProgressRingPainter({required this.progress}); @override void paint(Canvas canvas, Size size) { final center = size.center(Offset.zero); final radius = 50.0; final bgPaint = Paint() ..color = const Color(0x0032C599) ..style = PaintingStyle.stroke ..strokeWidth = 3; canvas.drawCircle(center, radius, bgPaint); final fgPaint = Paint() ..color = const Color(0xC40A4433) ..style = PaintingStyle.stroke ..strokeWidth = 3 ..strokeCap = StrokeCap.round; final sweep = 2 * 3.1415926 * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -1.5708, sweep, false, fgPaint, ); } @override bool shouldRepaint(_) => true; } class _PulseRingsPainter extends CustomPainter { final double progress; _PulseRingsPainter({required this.progress}); @override void paint(Canvas canvas, Size size) { final center = size.center(Offset.zero); const baseRadius = 60.0; const maxRadius = 130.0; for (final phase in [0.0, 0.25, 0.5]) { final rp = (progress - phase).clamp(0.0, 1.0); if (rp > 0) { final radius = baseRadius + (maxRadius - baseRadius) * rp; final opacity = (1 - rp) * 0.45; final paint = Paint() ..color = const Color(0xFF32C59A).withOpacity(opacity) ..style = PaintingStyle.stroke ..strokeWidth = 2; canvas.drawCircle(center, radius, paint); } } } @override bool shouldRepaint(_) => true; } // ERROR PAINTER class _ErrorPulsePainter extends CustomPainter { final double progress; _ErrorPulsePainter({required this.progress}); @override void paint(Canvas canvas, Size size) { final center = size.center(Offset.zero); // static ring final staticPaint = Paint() ..color = const Color(0xFFB00020).withOpacity(0.20) ..style = PaintingStyle.stroke ..strokeWidth = 3; canvas.drawCircle(center, 70, staticPaint); // pulse ring final radius = 70 + (20 * progress); final opacity = (1 - progress) * 0.45; final pulsePaint = Paint() ..color = const Color(0xFFB00020).withOpacity(opacity) ..style = PaintingStyle.stroke ..strokeWidth = 3; canvas.drawCircle(center, radius, pulsePaint); } @override bool shouldRepaint(_) => true; } // SUCCESS one-time pulse class _SuccessPulsePainter extends CustomPainter { final double progress; _SuccessPulsePainter({required this.progress}); @override void paint(Canvas canvas, Size size) { final center = size.center(Offset.zero); final radius = 70 + (20 * progress); final opacity = (1 - progress) * 0.40; final paint = Paint() ..color = const Color(0xFF32C59A).withOpacity(opacity) ..style = PaintingStyle.stroke ..strokeWidth = 3; canvas.drawCircle(center, radius, paint); } @override bool shouldRepaint(_) => true; }