diff --git a/assets/images/error.svg b/assets/images/error.svg new file mode 100644 index 0000000..f594fa0 --- /dev/null +++ b/assets/images/error.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/images/finger_print.svg b/assets/images/finger_print.svg new file mode 100644 index 0000000..cfdc0c3 --- /dev/null +++ b/assets/images/finger_print.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/tick.svg b/assets/images/tick.svg new file mode 100644 index 0000000..f202eae --- /dev/null +++ b/assets/images/tick.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/screens/attendence_screen.dart b/lib/screens/attendence_screen.dart index be9fee9..40b13d1 100644 --- a/lib/screens/attendence_screen.dart +++ b/lib/screens/attendence_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../widgets/login_animation_screen.dart'; // Import the LoginAnimationScreen class AttendanceScreen extends StatelessWidget { const AttendanceScreen({super.key}); @@ -88,10 +89,6 @@ class AttendanceScreen extends StatelessWidget { top: 70, left: 24, child: _ShadowedCard( - child: _FingerButton( - icon: "assets/images/login.svg", - label: "تسجيل الدخول", - ), shadow: const [ // Strong BLACK shadow bottom-right BoxShadow( @@ -101,6 +98,21 @@ class AttendanceScreen extends StatelessWidget { offset: Offset(5, 5), ), ], + child: _FingerButton( + icon: "assets/images/login.svg", + label: "تسجيل الدخول", + onTap: () { + // Navigate to LoginAnimationScreen with success state + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LoginAnimationScreen( + isLogin: true, + isSuccess: true, // Set to true for success, false for error + ), + ), + ); + }, + ), ), ), @@ -109,12 +121,6 @@ class AttendanceScreen extends StatelessWidget { bottom: 10, right: 24, child: _ShadowedCard( - child: _FingerButton( - icon: "assets/images/logout.svg", - label: "تسجيل خروج", - ), - - // GREEN glow top-left shadow: const [ // Strong BLACK shadow bottom-right BoxShadow( @@ -137,6 +143,21 @@ class AttendanceScreen extends StatelessWidget { offset: Offset(5, 5), ), ], + child: _FingerButton( + icon: "assets/images/logout.svg", + label: "تسجيل خروج", + onTap: () { + // Navigate to LoginAnimationScreen with success state + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LoginAnimationScreen( + isLogin: false, + isSuccess: true, // Set to true for success, false for error + ), + ), + ); + }, + ), ), ), ], @@ -176,33 +197,41 @@ class _ShadowedCard extends StatelessWidget { class _FingerButton extends StatelessWidget { final String icon; final String label; + final VoidCallback onTap; // Added onTap callback - const _FingerButton({required this.icon, required this.label}); + const _FingerButton({ + required this.icon, + required this.label, + required this.onTap, // Added this parameter + }); @override Widget build(BuildContext context) { - return Container( - height: 160, - width: 160, - decoration: BoxDecoration( - color: const Color(0xFFEAFBF3), - borderRadius: BorderRadius.circular(32), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset(icon, width: 62, height: 62), - const SizedBox(height: 10), - Text( - label, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.black, + return GestureDetector( + onTap: onTap, // Added gesture detection + child: Container( + height: 160, + width: 160, + decoration: BoxDecoration( + color: const Color(0xFFEAFBF3), + borderRadius: BorderRadius.circular(32), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(icon, width: 62, height: 62), + const SizedBox(height: 10), + Text( + label, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black, + ), ), - ), - ], + ], + ), ), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/login_animation_screen.dart b/lib/widgets/login_animation_screen.dart new file mode 100644 index 0000000..812dda7 --- /dev/null +++ b/lib/widgets/login_animation_screen.dart @@ -0,0 +1,354 @@ +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; + + bool showResult = false; + + @override + void initState() { + super.initState(); + + ringController = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), + ); + + progressController = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + ); + + ringAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: ringController, curve: Curves.easeOut)); + + progressAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: progressController, curve: Curves.linear), + ); + + startAnimations(); + + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + showResult = true; + }); + + ringController.stop(); + progressController.stop(); + + if (widget.isSuccess) { + 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(); + }); + } + + @override + void dispose() { + ringController.dispose(); + progressController.dispose(); + super.dispose(); + } + + @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: [ + Container( + width: 120, + height: 120, + decoration: const BoxDecoration( + color: Color(0xFFE5E5E5), + shape: BoxShape.circle, + ), + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: _buildIcon( + showResult, + widget.isSuccess, + key: ValueKey( + "icon_${showResult}_${widget.isSuccess}", + ), + ), + ), + if (!showResult) + Positioned.fill( + child: AnimatedBuilder( + animation: progressAnimation, + builder: (_, __) { + return CustomPaint( + painter: _ProgressRingPainter( + progress: progressAnimation.value, + ), + ); + }, + ), + ), + + if (!showResult) + Positioned.fill( + child: AnimatedBuilder( + animation: ringAnimation, + builder: (_, __) { + return CustomPaint( + painter: _PulseRingsPainter( + progress: ringAnimation.value, + ), + ); + }, + ), + ), + ], + ), + ), + + const SizedBox(height: 40), + + AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: + showResult + ? Text( + widget.isSuccess + ? "تم تسجيل دخولك بنجاح" + : "تم رفض تسجيل الدخول ", + key: ValueKey("text_${widget.isSuccess}"), + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ) + : const Text( + "يتم تسجيل الدخول ...", + key: ValueKey("loading_text"), + style: TextStyle( + fontSize: 16, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + /// >>> FIXED ICON BUILDER <<< + Widget _buildIcon(bool showResult, bool isSuccess, {required Key key}) { + if (showResult) { + if (isSuccess) { + return SizedBox( + key: key, + width: 70, + height: 70, + child: SvgPicture.asset( + "assets/images/tick.svg", + width: 70, + height: 70, + placeholderBuilder: + (context) => Icon( + Icons.check_circle, + size: 70, + color: const Color(0xFF32C59A), + ), + ), + ); + } else { + return SizedBox( + key: key, + width: 70, + height: 70, + child: SvgPicture.asset( + "assets/images/error.svg", + width: 70, + height: 70, + placeholderBuilder: + (context) => Icon(Icons.error, size: 70, color: Colors.red), + ), + ); + } + } + + return SizedBox( + key: key, + width: 70, + height: 70, + child: SvgPicture.asset( + "assets/images/finger_print.svg", + width: 70, + height: 70, + placeholderBuilder: + (context) => Icon( + Icons.fingerprint, + size: 70, + color: const Color(0xFF102D25), + ), + ), + ); + } +} + +class _ProgressRingPainter extends CustomPainter { + final double progress; + + _ProgressRingPainter({required this.progress}); + + @override + void paint(Canvas canvas, Size size) { + final center = size.center(Offset.zero); + + // radius slightly bigger than gray circle (60) + final radius = 50.0; + + // background circle (very subtle) + final bgPaint = + Paint() + ..color = const Color(0x0032C599) + ..style = PaintingStyle.stroke + ..strokeWidth = 3.0; + + canvas.drawCircle(center, radius, bgPaint); + + // foreground arc that animates + final progressPaint = + Paint() + ..color = const Color(0xC40A4433) + ..style = PaintingStyle.stroke + ..strokeWidth = 3.0 + ..strokeCap = StrokeCap.round; + + const startAngle = -1.5708; // -90° in radians + final sweepAngle = 2 * 3.1415926 * progress; + + canvas.drawArc( + Rect.fromCircle(center: center, radius: radius), + startAngle, + sweepAngle, + false, + progressPaint, + ); + } + + @override + bool shouldRepaint(_ProgressRingPainter oldDelegate) => + oldDelegate.progress != progress; +} + +class _PulseRingsPainter extends CustomPainter { + final double progress; + + _PulseRingsPainter({required this.progress}); + + @override + void paint(Canvas canvas, Size size) { + final center = size.center(Offset.zero); + + final baseRadius = 60.0; // start at grey circle + final maxRadius = 130.0; // outermost ripple + + final ringPhases = [0.0, 0.25, 0.5]; + + for (final phase in ringPhases) { + final ringProgress = (progress - phase).clamp(0.0, 1.0); + + if (ringProgress > 0) { + final radius = baseRadius + (maxRadius - baseRadius) * ringProgress; + final opacity = (1.0 - ringProgress) * 0.45; + + final paint = + Paint() + ..color = const Color(0xFF32C59A).withOpacity(opacity) + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + canvas.drawCircle(center, radius, paint); + } + } + } + + @override + bool shouldRepaint(_PulseRingsPainter oldDelegate) => + oldDelegate.progress != progress; +} diff --git a/pubspec.yaml b/pubspec.yaml index ba1a38a..211081e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,12 @@ flutter: uses-material-design: true assets: - assets/images/ + - assets/images/finger_print.svg + - assets/images/login.svg + - assets/images/logout.svg + - assets/images/tick.svg + - assets/images/error.svg + fonts: - family: AbdElRady fonts: