From ffba37b767b6a54cc5709a542e096f21cbe36097 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 9 Jul 2025 13:02:35 +0530 Subject: [PATCH] feat(forgot-password): enhance ForgotPasswordScreen with logo animation and improved layout --- lib/view/auth/forgot_password_screen.dart | 388 +++++++++++++--------- 1 file changed, 226 insertions(+), 162 deletions(-) diff --git a/lib/view/auth/forgot_password_screen.dart b/lib/view/auth/forgot_password_screen.dart index e6d0c64..76a9330 100644 --- a/lib/view/auth/forgot_password_screen.dart +++ b/lib/view/auth/forgot_password_screen.dart @@ -1,15 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:marco/controller/auth/forgot_password_controller.dart'; import 'package:marco/helpers/widgets/my_button.dart'; -import 'package:marco/helpers/widgets/my_text_style.dart'; -import 'package:marco/images.dart'; +import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; -import 'package:marco/helpers/widgets/my_text.dart'; -import 'package:marco/helpers/widgets/my_spacing.dart'; +import 'package:marco/images.dart'; class ForgotPasswordScreen extends StatefulWidget { const ForgotPasswordScreen({super.key}); @@ -19,208 +18,273 @@ class ForgotPasswordScreen extends StatefulWidget { } class _ForgotPasswordScreenState extends State - with UIMixin { + with UIMixin, SingleTickerProviderStateMixin { final ForgotPasswordController controller = Get.put(ForgotPasswordController()); + late AnimationController _controller; + late Animation _logoAnimation; + bool get _isBetaEnvironment => ApiEndpoints.baseUrl.contains("stage"); bool _isLoading = false; - void _handleForgotPassword() async { - setState(() { - _isLoading = true; - }); + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + ); + _logoAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeOutBack, + ); + _controller.forward(); + } + @override + void dispose() { + _controller.dispose(); + Get.delete(); + super.dispose(); + } + + Future _handleForgotPassword() async { + setState(() => _isLoading = true); await controller.onForgotPassword(); - - setState(() { - _isLoading = false; - }); + setState(() => _isLoading = false); } @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: contentTheme.brandRed, - body: SafeArea( - child: LayoutBuilder(builder: (context, constraints) { - return Column( - children: [ - const SizedBox(height: 24), - _buildHeader(), - const SizedBox(height: 16), - _buildWelcomeTextsAndChips(), - const SizedBox(height: 16), - Expanded( - child: Container( - width: double.infinity, - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.vertical(top: Radius.circular(32)), - ), - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 32), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 120, + body: Stack( + children: [ + const _RedWaveBackground(), + SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 24), + ScaleTransition( + scale: _logoAnimation, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 10, + offset: Offset(0, 4), + ), + ], ), - child: Form( - key: controller.basicValidator.formKey, + padding: const EdgeInsets.all(20), + child: Image.asset(Images.logoDark), + ), + ), + const SizedBox(height: 8), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, children: [ - MyText.titleLarge( - 'Forgot Password', - fontWeight: 700, + const SizedBox(height: 12), + MyText( + "Welcome to Marco", + fontSize: 24, + fontWeight: 800, color: Colors.black87, + textAlign: TextAlign.center, ), - const SizedBox(height: 8), - MyText.bodyMedium( - "Enter your email and we'll send you instructions to reset your password.", + const SizedBox(height: 10), + MyText( + "Streamline Project Management\nBoost Productivity with Automation.", + fontSize: 14, color: Colors.black54, textAlign: TextAlign.center, ), - const SizedBox(height: 32), - TextFormField( - validator: controller.basicValidator - .getValidation('email'), - controller: controller.basicValidator - .getController('email'), - keyboardType: TextInputType.emailAddress, - style: MyTextStyle.labelMedium(), - decoration: InputDecoration( - labelText: "Email Address", - labelStyle: MyTextStyle.bodySmall(xMuted: true), - filled: true, - fillColor: Colors.grey.shade100, - prefixIcon: - const Icon(LucideIcons.mail, size: 20), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, + if (_isBetaEnvironment) ...[ + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Colors.orangeAccent, + borderRadius: BorderRadius.circular(6), + ), + child: MyText( + 'BETA', + color: Colors.white, + fontWeight: 600, + fontSize: 12, ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 16), - floatingLabelBehavior: - FloatingLabelBehavior.auto, ), - ), - const SizedBox(height: 40), - MyButton.rounded( - onPressed: - _isLoading ? null : _handleForgotPassword, - elevation: 2, - padding: MySpacing.xy(80, 16), - borderRadiusAll: 10, - backgroundColor: _isLoading ? Colors.red.withOpacity(0.6) : contentTheme.brandRed, - child: _isLoading - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2, - ), - ) - : MyText.labelLarge( - 'Send Reset Link', - fontWeight: 700, - color: Colors.white, - ), - ), - const SizedBox(height: 24), - TextButton( - onPressed: () async { - await LocalStorage.logout(); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.arrow_back, - size: 16, color: Colors.red), - const SizedBox(width: 4), - MyText.bodySmall( - 'Back to log in', - fontWeight: 600, - fontSize: 14, - color: contentTheme.brandRed, - ), - ], - ), - ), + ], + const SizedBox(height: 36), + _buildForgotCard(), ], ), ), ), ), - ), + ], ), - ], - ); - }), - ), - ); - } - - Widget _buildWelcomeTextsAndChips() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - MyText.titleMedium( - "Welcome to Marco", - fontWeight: 600, - color: Colors.white, - textAlign: TextAlign.center, + ), ), - const SizedBox(height: 4), - MyText.bodySmall( - "Streamline Project Management and Boost Productivity with Automation.", - color: Colors.white70, - textAlign: TextAlign.center, - ), - if (_isBetaEnvironment) ...[ - const SizedBox(height: 8), - _buildBetaLabel(), - ], ], ), ); } - Widget _buildBetaLabel() { + Widget _buildForgotCard() { return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: Colors.orangeAccent, - borderRadius: BorderRadius.circular(4), - ), - child: MyText.bodySmall( - 'BETA', - fontWeight: 600, - color: Colors.white, - ), - ); - } - - Widget _buildHeader() { - return Container( - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow( color: Colors.black12, - blurRadius: 6, - offset: Offset(0, 3), + blurRadius: 10, + offset: Offset(0, 4), ), ], ), - child: Image.asset(Images.logoDark, height: 70), + child: Form( + key: controller.basicValidator.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + MyText( + 'Forgot Password', + fontSize: 20, + fontWeight: 700, + color: Colors.black87, + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + MyText( + "Enter your email and we'll send you instructions to reset your password.", + fontSize: 14, + color: Colors.black54, + textAlign: TextAlign.center, + ), + const SizedBox(height: 30), + TextFormField( + validator: controller.basicValidator.getValidation('email'), + controller: controller.basicValidator.getController('email'), + keyboardType: TextInputType.emailAddress, + style: const TextStyle(fontSize: 14), + decoration: InputDecoration( + labelText: "Email Address", + labelStyle: const TextStyle(color: Colors.black54), + filled: true, + fillColor: Colors.grey.shade100, + prefixIcon: const Icon(LucideIcons.mail, size: 20), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + ), + ), + const SizedBox(height: 32), + MyButton.rounded( + onPressed: _isLoading ? null : _handleForgotPassword, + elevation: 2, + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + borderRadiusAll: 10, + backgroundColor: _isLoading + ? Colors.red.withOpacity(0.6) + : contentTheme.brandRed, + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, strokeWidth: 2), + ) + : MyText.bodyMedium( + 'Send Reset Link', + color: Colors.white, + fontWeight: 700, + fontSize: 16, + ), + ), + const SizedBox(height: 20), + TextButton.icon( + onPressed: () async => await LocalStorage.logout(), + icon: const Icon(Icons.arrow_back, + size: 18, color: Colors.redAccent), + label: MyText.bodyMedium( + 'Back to Login', + color: contentTheme.brandRed, + fontWeight: 600, + fontSize: 14, + ), + ), + ], + ), + ), ); } } + +// Same red wave background as MPINAuthScreen +class _RedWaveBackground extends StatelessWidget { + const _RedWaveBackground(); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _WavePainter(), + size: Size.infinite, + ); + } +} + +class _WavePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint1 = Paint() + ..shader = const LinearGradient( + colors: [Color(0xFFB71C1C), Color(0xFFD32F2F)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ).createShader(Rect.fromLTWH(0, 0, size.width, size.height)); + + final path1 = Path() + ..moveTo(0, size.height * 0.2) + ..quadraticBezierTo(size.width * 0.25, size.height * 0.05, + size.width * 0.5, size.height * 0.15) + ..quadraticBezierTo( + size.width * 0.75, size.height * 0.25, size.width, size.height * 0.1) + ..lineTo(size.width, 0) + ..lineTo(0, 0) + ..close(); + + canvas.drawPath(path1, paint1); + + final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15); + final path2 = Path() + ..moveTo(0, size.height * 0.25) + ..quadraticBezierTo( + size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2) + ..lineTo(size.width, 0) + ..lineTo(0, 0) + ..close(); + + canvas.drawPath(path2, paint2); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +}