You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			383 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Dart
		
	
		
		
			
		
	
	
			383 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Dart
		
	
| 
											1 month ago
										 | import 'dart:async'; | ||
|  | 
 | ||
|  | import 'package:flutter/material.dart'; | ||
|  | import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||
|  | import 'package:hmg_patient_app_new/core/app_export.dart'; | ||
|  | import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||
|  | import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; | ||
|  | import 'package:hmg_patient_app_new/theme/colors.dart'; | ||
|  | import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; | ||
|  | import 'package:lottie/lottie.dart'; | ||
|  | 
 | ||
|  | class SplashAnimationScreen extends StatefulWidget { | ||
|  |   final Widget? routeWidget; | ||
|  | 
 | ||
|  |   SplashAnimationScreen({super.key, this.routeWidget}); | ||
|  | 
 | ||
|  |   @override | ||
|  |   _SplashAnimationScreenState createState() { | ||
|  |     return _SplashAnimationScreenState(); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | class _SplashAnimationScreenState extends State<SplashAnimationScreen> with SingleTickerProviderStateMixin { | ||
|  |   late final AnimationController _controller; | ||
|  | 
 | ||
|  |   @override | ||
|  |   void initState() { | ||
|  |     super.initState(); | ||
|  |     _controller = AnimationController(vsync: this); | ||
|  |     _controller.addListener(() { | ||
|  |       if (_controller.status == AnimationStatus.completed) { | ||
|  |         Navigator.of(context).pushReplacement(FadePage(page: widget.routeWidget ?? LoginScreen())); | ||
|  |       } | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   void dispose() { | ||
|  |     _controller.dispose(); | ||
|  |     super.dispose(); | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   Widget build(BuildContext context) { | ||
|  |     return Scaffold( | ||
|  |       backgroundColor: AppColors.whiteColor, | ||
|  |       body: Stack( | ||
|  |         children: [ | ||
|  |           Lottie.asset(AppAnimations.splashLaunching, controller: _controller, onLoaded: (composition) { | ||
|  |             _controller | ||
|  |               ..duration = composition.duration | ||
|  |               ..forward(); // Start the animation
 | ||
|  |           }, repeat: false, reverse: false, frameRate: FrameRate(60), fit: BoxFit.fill) | ||
|  |               .center, | ||
|  |         ], | ||
|  |       ), | ||
|  |     ); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | // todo: do-not remove this code,as animation need to test on multiple screen sizes
 | ||
|  | 
 | ||
|  | class AnimatedScreen extends StatefulWidget { | ||
|  |   const AnimatedScreen({super.key}); | ||
|  | 
 | ||
|  |   @override | ||
|  |   State<AnimatedScreen> createState() => _AnimatedScreenState(); | ||
|  | } | ||
|  | 
 | ||
|  | class _AnimatedScreenState extends State<AnimatedScreen> with TickerProviderStateMixin { | ||
|  |   late AnimationController _moveController; | ||
|  |   late Animation<Offset> _positionAnimation; | ||
|  |   late AnimationController _expandController; | ||
|  |   late Animation<double> _expandAnimation; | ||
|  | 
 | ||
|  |   bool isRipple = false; | ||
|  |   late final AnimationController _controller; | ||
|  | 
 | ||
|  |   @override | ||
|  |   void initState() { | ||
|  |     super.initState(); | ||
|  |     _controller = AnimationController(vsync: this); | ||
|  |     _controller.addListener(() { | ||
|  |       if (_controller.status == AnimationStatus.completed) { | ||
|  |         Navigator.of(context).pushReplacement( | ||
|  |           FadePage( | ||
|  |             page: LoginScreen(), | ||
|  |           ), | ||
|  |         ); | ||
|  |       } | ||
|  |     }); | ||
|  | 
 | ||
|  |     // Step 1: Move circle from bottom-left to top-right
 | ||
|  |     _moveController = AnimationController(vsync: this, duration: const Duration(seconds: 1)); | ||
|  |     _positionAnimation = Tween<Offset>( | ||
|  |       begin: const Offset(-1, 1), | ||
|  |       end: const Offset(1, -1), | ||
|  |     ).animate( | ||
|  |       CurvedAnimation(parent: _moveController, curve: const Cubic(0.82, -0.01, 0.58, 1)), | ||
|  |     ); | ||
|  | 
 | ||
|  |     // Step 2: Expand white circle from center
 | ||
|  |     _expandController = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000)); | ||
|  |     _expandAnimation = Tween<double>( | ||
|  |       begin: 0.0, | ||
|  |       end: 4.0, | ||
|  |     ).animate(CurvedAnimation(parent: _expandController, curve: Curves.easeOut)); | ||
|  | 
 | ||
|  |     // Trigger the animations in sequence
 | ||
|  |     _moveController.forward().whenComplete(() { | ||
|  |       setState(() { | ||
|  |         isRipple = true; | ||
|  |       }); | ||
|  |       _expandController.forward().whenComplete(() { | ||
|  |         setState(() { | ||
|  |           isRipple = false; | ||
|  |         }); | ||
|  |       }); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   void dispose() { | ||
|  |     _controller.dispose(); | ||
|  |     _moveController.dispose(); | ||
|  |     _expandController.dispose(); | ||
|  |     super.dispose(); | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   Widget build(BuildContext context) { | ||
|  |     final screenSize = MediaQuery.of(context).size; | ||
|  | 
 | ||
|  |     return Scaffold( | ||
|  |       backgroundColor: AppColors.whiteColor, | ||
|  |       body: Stack( | ||
|  |         children: [ | ||
|  |           // Moving rotated ellipse
 | ||
|  | 
 | ||
|  |           Lottie.asset(AppAnimations.splashLaunching, controller: _controller, onLoaded: (composition) { | ||
|  |             _controller | ||
|  |               ..duration = composition.duration | ||
|  |               ..forward(); // Start the animation
 | ||
|  |           }, repeat: false, reverse: false, frameRate: FrameRate(60), fit: BoxFit.fill) | ||
|  |               .center, | ||
|  |           // Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.fill).center,
 | ||
|  |           //
 | ||
|  |           // AnimatedContainer(
 | ||
|  |           //   duration: Duration(milliseconds: 500),
 | ||
|  |           //   width: screenSize.width,
 | ||
|  |           //   height: screenSize.height,
 | ||
|  |           //   color: isRipple ? AppColors.primaryRedColor : AppColors.whiteColor,
 | ||
|  |           // ),
 | ||
|  |           //
 | ||
|  |           // AnimatedBuilder(
 | ||
|  |           //   animation: _moveController,
 | ||
|  |           //   builder: (context, child) {
 | ||
|  |           //     final pos = _positionAnimation.value;
 | ||
|  |           //     return Positioned(
 | ||
|  |           //       left: (screenSize.width * .75) * (pos.dx),
 | ||
|  |           //       top: (screenSize.height * 0.75) * (pos.dy),
 | ||
|  |           //       child: Transform.rotate(
 | ||
|  |           //         angle: -120 * 3.1415927 / 150, // convert degrees to radians
 | ||
|  |           //         child: Container(
 | ||
|  |           //           width: 400,
 | ||
|  |           //           height: 653,
 | ||
|  |           //           decoration: BoxDecoration(
 | ||
|  |           //             color: Color(0xffED1C2B),
 | ||
|  |           //             borderRadius: BorderRadius.circular(330),
 | ||
|  |           //           ),
 | ||
|  |           //         ),
 | ||
|  |           //       ),
 | ||
|  |           //     );
 | ||
|  |           //   },
 | ||
|  |           // ),
 | ||
|  |           // // Expanding white circle
 | ||
|  |           // AnimatedBuilder(
 | ||
|  |           //   animation: _expandController,
 | ||
|  |           //   builder: (context, child) {
 | ||
|  |           //     return Center(
 | ||
|  |           //       child: Transform.scale(
 | ||
|  |           //         scale: _expandAnimation.value,
 | ||
|  |           //         child: Opacity(
 | ||
|  |           //           opacity: 1.0, //- _expandAnimation.value.clamp(0.0, 1.0),
 | ||
|  |           //           child: Container(
 | ||
|  |           //               decoration: const BoxDecoration(
 | ||
|  |           //             color: Colors.white,
 | ||
|  |           //             shape: BoxShape.circle,
 | ||
|  |           //             // border: Border.fromBorderSide(BorderSide(
 | ||
|  |           //             //   width: 0,
 | ||
|  |           //             //   color: Color(0xffED1C2B),
 | ||
|  |           //             // )
 | ||
|  |           //           )),
 | ||
|  |           //         ),
 | ||
|  |           //         // ),
 | ||
|  |           //       ),
 | ||
|  |           //     );
 | ||
|  |           //   },
 | ||
|  |           // ),
 | ||
|  |           // AnimatedBuilder(
 | ||
|  |           //   animation: _expandController,
 | ||
|  |           //   builder: (context, child) {
 | ||
|  |           //     final screenSize = MediaQuery.of(context).size;
 | ||
|  |           //     final maxDiameter =
 | ||
|  |           //         (screenSize.width > screenSize.height ? screenSize.width : screenSize.height) * 2;
 | ||
|  |           //
 | ||
|  |           //     return Center(
 | ||
|  |           //       child: Transform.scale(
 | ||
|  |           //         scale: _expandAnimation.value * maxDiameter / 100, // scale up to fill screen
 | ||
|  |           //         child: Opacity(
 | ||
|  |           //           opacity: (1.0 - _expandAnimation.value).clamp(0.0, 1.0),
 | ||
|  |           //           child: Container(
 | ||
|  |           //             decoration: const BoxDecoration(
 | ||
|  |           //               color: Colors.white,
 | ||
|  |           //               shape: BoxShape.circle,
 | ||
|  |           //             ),
 | ||
|  |           //           ),
 | ||
|  |           //         ),
 | ||
|  |           //       ),
 | ||
|  |           //     );
 | ||
|  |           //   },
 | ||
|  |           // ),
 | ||
|  |         ], | ||
|  |       ), | ||
|  |     ); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | class MoveObjectDemo extends StatefulWidget { | ||
|  |   const MoveObjectDemo({super.key}); | ||
|  | 
 | ||
|  |   @override | ||
|  |   State<MoveObjectDemo> createState() => _MoveObjectDemoState(); | ||
|  | } | ||
|  | 
 | ||
|  | class _MoveObjectDemoState extends State<MoveObjectDemo> with SingleTickerProviderStateMixin { | ||
|  |   late AnimationController _controller; | ||
|  |   late Animation<Alignment> _alignmentAnimation; | ||
|  | 
 | ||
|  |   @override | ||
|  |   void initState() { | ||
|  |     super.initState(); | ||
|  | 
 | ||
|  |     _controller = AnimationController( | ||
|  |       vsync: this, | ||
|  |       duration: const Duration(seconds: 1), | ||
|  |     ); | ||
|  | 
 | ||
|  |     _alignmentAnimation = AlignmentTween( | ||
|  |       begin: Alignment(-2.0, 2.5), | ||
|  |       end: Alignment(2.5, -2), | ||
|  |     ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); | ||
|  | 
 | ||
|  |     _controller.forward(); // start animation
 | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   void dispose() { | ||
|  |     _controller.dispose(); | ||
|  |     super.dispose(); | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   Widget build(BuildContext context) { | ||
|  |     return Scaffold( | ||
|  |       body: AnimatedBuilder( | ||
|  |         animation: _alignmentAnimation, | ||
|  |         builder: (context, child) { | ||
|  |           return Align( | ||
|  |             alignment: _alignmentAnimation.value, | ||
|  |             child: Transform.rotate( | ||
|  |               angle: -120 * 3.1415927 / 180, | ||
|  |               child: Container( | ||
|  |                 width: 200, | ||
|  |                 height: 375, | ||
|  |                 decoration: BoxDecoration( | ||
|  |                   color: const Color(0xffED1C2B), | ||
|  |                   borderRadius: BorderRadius.circular(330), | ||
|  |                 ), | ||
|  |               ), | ||
|  |             ), | ||
|  | 
 | ||
|  |             // Transform.rotate(
 | ||
|  |             //   angle: -120 * 3.1415927 / 180, // convert to radians
 | ||
|  |             //   child: Container(
 | ||
|  |             //     width: 400,
 | ||
|  |             //     height: 653,
 | ||
|  |             //     decoration: BoxDecoration(
 | ||
|  |             //       color: const Color(0xffED1C2B),
 | ||
|  |             //       borderRadius: BorderRadius.circular(330),
 | ||
|  |             //     ),
 | ||
|  |             //   ),
 | ||
|  |             // ),
 | ||
|  |           ); | ||
|  |         }, | ||
|  |       ), | ||
|  |     ); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | class MoveOnClickDemo extends StatefulWidget { | ||
|  |   const MoveOnClickDemo({super.key}); | ||
|  | 
 | ||
|  |   @override | ||
|  |   State<MoveOnClickDemo> createState() => _MoveOnClickDemoState(); | ||
|  | } | ||
|  | 
 | ||
|  | class _MoveOnClickDemoState extends State<MoveOnClickDemo> with TickerProviderStateMixin { | ||
|  |   late AnimationController _controller; | ||
|  |   late Animation<Alignment> _alignmentAnimation; | ||
|  | 
 | ||
|  |   @override | ||
|  |   void initState() { | ||
|  |     super.initState(); | ||
|  |     init(); | ||
|  |   } | ||
|  | 
 | ||
|  |   init() { | ||
|  |     _controller = AnimationController( | ||
|  |       vsync: this, | ||
|  |       duration: const Duration(milliseconds: 1000), // Figma duration
 | ||
|  |     ); | ||
|  | 
 | ||
|  |     _alignmentAnimation = AlignmentTween( | ||
|  |       // begin: Alignment(-10.0, 5),
 | ||
|  |       // end: Alignment(5, -2),
 | ||
|  |       begin: Alignment.bottomLeft, | ||
|  |       end: Alignment.topRight, | ||
|  |     ).animate(CurvedAnimation( | ||
|  |       parent: _controller, | ||
|  |       curve: const Cubic(0.82, -0.01, 0.58, 1), // Figma cubic-bezier
 | ||
|  |     )); | ||
|  |     _animate(); | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   void dispose() { | ||
|  |     _controller.dispose(); | ||
|  |     super.dispose(); | ||
|  |   } | ||
|  | 
 | ||
|  |   void _animate() { | ||
|  |     if (_controller.isCompleted) { | ||
|  |       _controller.reverse(); | ||
|  |     } else { | ||
|  |       _controller.forward(); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   @override | ||
|  |   Widget build(BuildContext context) { | ||
|  |     return Scaffold( | ||
|  |       body: GestureDetector( | ||
|  |         onTap: _animate, // trigger on click
 | ||
|  |         onDoubleTap: () { | ||
|  |           _controller.dispose(); | ||
|  |           init(); | ||
|  |         }, | ||
|  |         child: AnimatedBuilder( | ||
|  |           animation: _alignmentAnimation, | ||
|  |           builder: (context, child) { | ||
|  |             print(_alignmentAnimation.value); | ||
|  |             return Align( | ||
|  |               alignment: _alignmentAnimation.value, | ||
|  |               child: Transform.rotate( | ||
|  |                 angle: -120 * 3.1415927 / 145, // -120 deg
 | ||
|  |                 child: Container( | ||
|  |                   width: 100, | ||
|  |                   height: 150, | ||
|  |                   decoration: BoxDecoration( | ||
|  |                     color: const Color(0xffED1C2B), | ||
|  |                     borderRadius: BorderRadius.circular(330), | ||
|  |                   ), | ||
|  |                 ), | ||
|  |               ), | ||
|  |             ); | ||
|  |           }, | ||
|  |         ), | ||
|  |       ), | ||
|  |     ); | ||
|  |   } | ||
|  | } |