// lib/screens/desktop_login_screen.dart import 'package:flutter/material.dart'; import '../core/utils/error_utils.dart'; import '../features/auth/services/auth_service.dart'; import '../core/auth/auth_guard.dart'; import 'home_desktop_screen.dart'; import '../core/app_decoration.dart'; class DesktopLoginScreen extends StatefulWidget { const DesktopLoginScreen({Key? key}) : super(key: key); @override State createState() => _DesktopLoginScreenState(); } class _DesktopLoginScreenState extends State with SingleTickerProviderStateMixin { // Login controllers final TextEditingController _emailCtrl = TextEditingController(); final TextEditingController _passCtrl = TextEditingController(); // Signup controllers final TextEditingController _signupEmailCtrl = TextEditingController(); final TextEditingController _signupPhoneCtrl = TextEditingController(); final TextEditingController _signupPassCtrl = TextEditingController(); final TextEditingController _signupConfirmCtrl = TextEditingController(); String? _signupDistrict; static const _districts = [ 'Thiruvananthapuram', 'Kollam', 'Pathanamthitta', 'Alappuzha', 'Kottayam', 'Idukki', 'Ernakulam', 'Thrissur', 'Palakkad', 'Malappuram', 'Kozhikode', 'Wayanad', 'Kannur', 'Kasaragod', ]; final AuthService _auth = AuthService(); AnimationController? _controller; Animation? _leftWidthAnim; Animation? _leftTextOpacityAnim; Animation? _formOpacityAnim; Animation? _formOffsetAnim; final double _collapsedWidth = 220; final Duration _duration = const Duration(milliseconds: 700); final Curve _curve = Curves.easeInOutCubic; bool _isAnimating = false; bool _loading = false; bool _isSignupMode = false; @override void dispose() { _controller?.dispose(); _emailCtrl.dispose(); _passCtrl.dispose(); _signupEmailCtrl.dispose(); _signupPhoneCtrl.dispose(); _signupPassCtrl.dispose(); _signupConfirmCtrl.dispose(); super.dispose(); } Future _startCollapseAnimation(double initialLeftWidth) async { _controller?.dispose(); _controller = AnimationController(vsync: this, duration: _duration); _leftWidthAnim = Tween(begin: initialLeftWidth, end: _collapsedWidth).animate( CurvedAnimation(parent: _controller!, curve: _curve), ); _leftTextOpacityAnim = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _controller!, curve: const Interval(0.0, 0.35, curve: Curves.easeOut)), ); _formOpacityAnim = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _controller!, curve: const Interval(0.0, 0.55, curve: Curves.easeOut)), ); _formOffsetAnim = Tween(begin: 0.0, end: -60.0).animate( CurvedAnimation(parent: _controller!, curve: const Interval(0.0, 0.55, curve: Curves.easeOut)), ); setState(() => _isAnimating = true); await _controller!.forward(); await Future.delayed(const Duration(milliseconds: 120)); } Future _performLoginFlow(double initialLeftWidth) async { if (_isAnimating || _loading) return; setState(() => _loading = true); final email = _emailCtrl.text.trim(); final password = _passCtrl.text; if (email.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Enter email'))); setState(() => _loading = false); return; } if (password.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Enter password'))); setState(() => _loading = false); return; } try { await _auth.login(email, password); await _startCollapseAnimation(initialLeftWidth); if (!mounted) return; Navigator.of(context).pushReplacement(PageRouteBuilder( pageBuilder: (context, a1, a2) => const HomeDesktopScreen(skipSidebarEntranceAnimation: true), transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, )); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)))); setState(() => _isAnimating = false); } finally { if (mounted) setState(() => _loading = false); } } Future _performSignupFlow(double initialLeftWidth) async { if (_isAnimating || _loading) return; final email = _signupEmailCtrl.text.trim(); final phone = _signupPhoneCtrl.text.trim(); final pass = _signupPassCtrl.text; final confirm = _signupConfirmCtrl.text; if (email.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Enter email'))); return; } if (phone.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Enter phone number'))); return; } if (pass.length < 6) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Password must be at least 6 characters'))); return; } if (pass != confirm) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Passwords do not match'))); return; } setState(() => _loading = true); try { await _auth.register( email: email, phoneNumber: phone, password: pass, district: _signupDistrict, ); await _startCollapseAnimation(initialLeftWidth); if (!mounted) return; Navigator.of(context).pushReplacement(PageRouteBuilder( pageBuilder: (context, a1, a2) => const HomeDesktopScreen(skipSidebarEntranceAnimation: true), transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, )); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)))); setState(() => _isAnimating = false); } finally { if (mounted) setState(() => _loading = false); } } Future _openForgotPasswordDialog() async { final emailCtrl = TextEditingController(text: _emailCtrl.text.trim()); bool submitting = false; await showDialog( context: context, builder: (ctx) { return StatefulBuilder( builder: (ctx, setDialog) { return AlertDialog( title: const Text('Forgot Password'), content: SizedBox( width: 360, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("Enter your email and we'll send reset instructions."), const SizedBox(height: 12), TextField( controller: emailCtrl, decoration: InputDecoration( prefixIcon: const Icon(Icons.email), labelText: 'Email', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), keyboardType: TextInputType.emailAddress, ), ], ), ), actions: [ TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Cancel')), ElevatedButton( onPressed: submitting ? null : () async { final email = emailCtrl.text.trim(); if (email.isEmpty) return; setDialog(() => submitting = true); try { await _auth.forgotPassword(email); } catch (_) { // safe-degrade } if (!ctx.mounted) return; Navigator.of(ctx).pop(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("If that email is registered, we've sent reset instructions."), duration: Duration(seconds: 4), ), ); }, child: submitting ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) : const Text('Send reset link'), ), ], ); }, ); }, ); emailCtrl.dispose(); } Widget _buildLoginFields(double safeInitialWidth) { return Column( key: const ValueKey('login'), mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text('Sign In', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), textAlign: TextAlign.center), const SizedBox(height: 6), const Text('Please enter your details to continue', textAlign: TextAlign.center, style: TextStyle(color: Colors.black54)), const SizedBox(height: 22), TextField( controller: _emailCtrl, decoration: InputDecoration( prefixIcon: const Icon(Icons.email), labelText: 'Email Address', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(height: 12), TextField( controller: _passCtrl, obscureText: true, decoration: InputDecoration( prefixIcon: const Icon(Icons.lock), labelText: 'Password', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [ Checkbox(value: true, onChanged: (_) {}), const Text('Remember me'), ]), TextButton(onPressed: _openForgotPasswordDialog, child: const Text('Forgot Password?')), ], ), const SizedBox(height: 8), SizedBox( height: 50, child: ElevatedButton( onPressed: (_isAnimating || _loading) ? null : () => _performLoginFlow(safeInitialWidth), style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), child: (_isAnimating || _loading) ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) : const Text('Sign In', style: TextStyle(fontSize: 16)), ), ), const SizedBox(height: 12), Wrap( alignment: WrapAlignment.spaceBetween, children: [ TextButton( onPressed: () => setState(() => _isSignupMode = true), child: const Text("Don't have an account? Register"), ), TextButton(onPressed: () {}, child: const Text('Contact support')), TextButton( onPressed: () { AuthGuard.setGuest(true); Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (_) => const HomeDesktopScreen(skipSidebarEntranceAnimation: true)), (route) => false, ); }, child: const Text('Continue as Guest'), ), ], ), ], ); } Widget _buildSignupFields(double safeInitialWidth) { return Column( key: const ValueKey('signup'), mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text('Create Account', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), textAlign: TextAlign.center), const SizedBox(height: 6), const Text('Fill in your details to get started', textAlign: TextAlign.center, style: TextStyle(color: Colors.black54)), const SizedBox(height: 22), TextField( controller: _signupEmailCtrl, keyboardType: TextInputType.emailAddress, decoration: InputDecoration( prefixIcon: const Icon(Icons.email), labelText: 'Email Address', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(height: 12), TextField( controller: _signupPhoneCtrl, keyboardType: TextInputType.phone, decoration: InputDecoration( prefixIcon: const Icon(Icons.phone), labelText: 'Phone Number', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(height: 12), DropdownButtonFormField( value: _signupDistrict, decoration: InputDecoration( prefixIcon: const Icon(Icons.location_on), labelText: 'District (optional)', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), items: _districts.map((d) => DropdownMenuItem(value: d, child: Text(d))).toList(), onChanged: (v) => setState(() => _signupDistrict = v), ), const SizedBox(height: 12), TextField( controller: _signupPassCtrl, obscureText: true, decoration: InputDecoration( prefixIcon: const Icon(Icons.lock), labelText: 'Password', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(height: 12), TextField( controller: _signupConfirmCtrl, obscureText: true, decoration: InputDecoration( prefixIcon: const Icon(Icons.lock_outline), labelText: 'Confirm Password', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(height: 16), SizedBox( height: 50, child: ElevatedButton( onPressed: (_isAnimating || _loading) ? null : () => _performSignupFlow(safeInitialWidth), style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), child: (_isAnimating || _loading) ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) : const Text('Create Account', style: TextStyle(fontSize: 16)), ), ), const SizedBox(height: 12), Center( child: TextButton( onPressed: () => setState(() => _isSignupMode = false), child: const Text('Already have an account? Sign in'), ), ), ], ); } @override Widget build(BuildContext context) { final screenW = MediaQuery.of(context).size.width; final double safeInitialWidth = (screenW * 0.45).clamp(360.0, screenW * 0.65); final bool animAvailable = _controller != null && _leftWidthAnim != null; final Listenable animation = _controller ?? AlwaysStoppedAnimation(0.0); return Scaffold( body: SafeArea( child: AnimatedBuilder( animation: animation, builder: (context, child) { final leftWidth = animAvailable ? _leftWidthAnim!.value : safeInitialWidth; final leftTextOpacity = animAvailable && _leftTextOpacityAnim != null ? _leftTextOpacityAnim!.value : 1.0; final formOpacity = animAvailable && _formOpacityAnim != null ? _formOpacityAnim!.value : 1.0; final formOffset = animAvailable && _formOffsetAnim != null ? _formOffsetAnim!.value : 0.0; return Row( children: [ Container( width: leftWidth, height: double.infinity, decoration: AppDecoration.blueGradient, padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 28), child: Opacity( opacity: leftTextOpacity, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), const Text('EVENTIFY', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 22)), const Spacer(), Text( _isSignupMode ? 'Join Eventify!' : 'Welcome Back!', style: const TextStyle(color: Colors.white, fontSize: 34, fontWeight: FontWeight.bold), ), const SizedBox(height: 12), Text( _isSignupMode ? 'Create your account to discover events, book tickets, and connect with your community.' : 'Sign in to access your dashboard, manage events, and stay connected.', style: const TextStyle(color: Colors.white70, fontSize: 14), ), const Spacer(flex: 2), Opacity( opacity: leftWidth > (_collapsedWidth + 8) ? 1.0 : 0.0, child: const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('© Eventify', style: TextStyle(color: Colors.white54)), ), ), ], ), ), ), Expanded( child: Transform.translate( offset: Offset(formOffset, 0), child: Opacity( opacity: formOpacity, child: Container( color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 36), child: Center( child: SingleChildScrollView( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 520), child: Card( elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28.0, vertical: 28.0), child: AnimatedSwitcher( duration: const Duration(milliseconds: 260), child: _isSignupMode ? _buildSignupFields(safeInitialWidth) : _buildLoginFields(safeInitialWidth), ), ), ), ), ), ), ), ), ), ), ], ); }, ), ), ); } }