From 91e2bb7bc855106318f16546aef548046d77dec4 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 9 Jul 2025 12:27:58 +0530 Subject: [PATCH] feat(login): enhance WelcomeScreen with animation and improved dialog layout --- lib/view/auth/login_option_screen.dart | 359 +++++++++++++++++-------- 1 file changed, 240 insertions(+), 119 deletions(-) diff --git a/lib/view/auth/login_option_screen.dart b/lib/view/auth/login_option_screen.dart index 9b25cc3..572a402 100644 --- a/lib/view/auth/login_option_screen.dart +++ b/lib/view/auth/login_option_screen.dart @@ -16,143 +16,199 @@ class LoginOptionScreen extends StatelessWidget { Widget build(BuildContext context) => const WelcomeScreen(); } -class WelcomeScreen extends StatelessWidget { +class WelcomeScreen extends StatefulWidget { const WelcomeScreen({super.key}); + @override + State createState() => _WelcomeScreenState(); +} + +class _WelcomeScreenState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _logoAnimation; + bool get _isBetaEnvironment => ApiEndpoints.baseUrl.contains("stage"); + @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(); + super.dispose(); + } + void _showLoginDialog(BuildContext context, LoginOption option) { - showDialog( - context: context, - builder: (_) => Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - insetPadding: const EdgeInsets.all(24), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(24), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 420), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - MyText( - option == LoginOption.email ? "Login with Email" : "Login with OTP", - fontSize: 20, - fontWeight: 700, - ), - const SizedBox(height: 20), - option == LoginOption.email - ? EmailLoginForm() - : const OTPLoginScreen(), - ], - ), + showDialog( + context: context, + barrierDismissible: false, // Prevent dismiss on outside tap + builder: (_) => Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + insetPadding: const EdgeInsets.all(24), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(24), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Row with title and close button + Row( + children: [ + Expanded( + child: MyText( + option == LoginOption.email + ? "Login with Email" + : "Login with OTP", + fontSize: 20, + fontWeight: 700, + ), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + const SizedBox(height: 20), + option == LoginOption.email + ? EmailLoginForm() + : const OTPLoginScreen(), + ], ), ), ), ), - ); - } + ), + ); +} @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; return Scaffold( - backgroundColor: const Color(0xFFB71C1C), - body: SafeArea( - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: screenWidth < 500 ? double.infinity : 420, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Logo - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow(color: Colors.black26, blurRadius: 8), - ], - ), - child: Image.asset(Images.logoDark, height: 60), + body: Stack( + children: [ + const _RedWaveBackground(), + SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: screenWidth < 500 ? double.infinity : 420, ), - - const SizedBox(height: 24), - - // Welcome Text - MyText( - "Welcome to Marco", - fontSize: 24, - fontWeight: 800, - color: Colors.white, - textAlign: TextAlign.center, - ), - const SizedBox(height: 10), - MyText( - "Streamline Project Management\nBoost Productivity with Automation.", - fontSize: 14, - color: Colors.white70, - textAlign: TextAlign.center, - ), - - 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: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Logo with circular background + ScaleTransition( + scale: _logoAnimation, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + padding: const EdgeInsets.all(20), + child: Image.asset(Images.logoDark), + ), ), - child: MyText( - 'BETA', - color: Colors.white, - fontWeight: 600, + + const SizedBox(height: 24), + + // Welcome Text + MyText( + "Welcome to Marco", + fontSize: 26, + fontWeight: 800, + color: Colors.black87, + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + MyText( + "Streamline Project Management\nBoost Productivity with Automation.", + fontSize: 14, + color: Colors.black54, + textAlign: TextAlign.center, + ), + + 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, + ), + ), + ], + + const SizedBox(height: 36), + + _buildActionButton( + context, + label: "Login with Username", + icon: LucideIcons.mail, + option: LoginOption.email, + ), + const SizedBox(height: 16), + _buildActionButton( + context, + label: "Login with OTP", + icon: LucideIcons.message_square, + option: LoginOption.otp, + ), + const SizedBox(height: 16), + _buildActionButton( + context, + label: "Request a Demo", + icon: LucideIcons.phone_call, + option: null, + ), + + const SizedBox(height: 36), + MyText( + 'App version 1.0.0', + color: Colors.grey, fontSize: 12, ), - ), - ], - - const SizedBox(height: 36), - - // Login Buttons - _buildActionButton( - context, - label: "Login with Username", - icon: LucideIcons.mail, - option: LoginOption.email, + ], ), - const SizedBox(height: 16), - _buildActionButton( - context, - label: "Login with OTP", - icon: LucideIcons.message_square, - option: LoginOption.otp, - ), - const SizedBox(height: 16), - _buildActionButton( - context, - label: "Request a Demo", - icon: LucideIcons.phone_call, - option: null, - ), - const SizedBox(height: 36), - // Version Info - MyText( - 'App version 1.0.0', - color: Colors.white60, - fontSize: 12, - ), - ], + ), ), ), ), - ), + ], ), ); } @@ -161,14 +217,14 @@ class WelcomeScreen extends StatelessWidget { BuildContext context, { required String label, required IconData icon, - LoginOption? option, // nullable for "Request a Demo" + LoginOption? option, }) { return SizedBox( width: double.infinity, child: ElevatedButton.icon( icon: Icon(icon, size: 20, color: Colors.white), label: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 14), child: MyText( label, fontSize: 16, @@ -177,12 +233,13 @@ class WelcomeScreen extends StatelessWidget { ), ), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFB71C1C), - side: const BorderSide(color: Colors.white70), + backgroundColor: const Color(0xFFB71C1C), // Red background + foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(14), ), elevation: 4, + shadowColor: Colors.black26, ), onPressed: () { if (option == null) { @@ -195,3 +252,67 @@ class WelcomeScreen extends StatelessWidget { ); } } + +/// Custom red wave background shifted lower to reduce red area at top +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; +}