import 'package:cb_prestige_qr/app/navigation/main_shell.dart'; import 'package:cb_prestige_qr/features/auth/presentation/viewmodels/login_view_model.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class LoginView extends ConsumerStatefulWidget { const LoginView({super.key}); @override ConsumerState createState() => _LoginViewState(); } class _LoginViewState extends ConsumerState { final _formKey = GlobalKey(); Future _submit() async { final form = _formKey.currentState; if (form == null || !form.validate()) return; final didLogin = await ref.read(loginViewModelProvider.notifier).submit(); if (!didLogin || !mounted) return; Navigator.of( context, ).pushReplacement(MaterialPageRoute(builder: (_) => const MainShell())); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final state = ref.watch(loginViewModelProvider); final viewModel = ref.read(loginViewModelProvider.notifier); return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xff17181c), Color(0xff25262b), Color(0xff2c2d33)], ), ), child: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xff1d1e23).withValues(alpha: 0.92), borderRadius: BorderRadius.circular(28), border: Border.all( color: Colors.white.withValues(alpha: 0.08), ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.28), blurRadius: 28, offset: const Offset(0, 18), ), ], ), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Container( height: 56, width: 56, decoration: BoxDecoration( color: colorScheme.primary.withValues( alpha: 0.16, ), borderRadius: BorderRadius.circular(18), ), child: const Icon( Icons.lock_person_rounded, color: Colors.white, size: 30, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Secure Login', style: theme.textTheme.headlineSmall ?.copyWith( fontWeight: FontWeight.w700, color: Colors.white, ), ), const SizedBox(height: 4), Text( 'Use your username and password to continue.', style: theme.textTheme.bodyMedium?.copyWith( color: Colors.white70, height: 1.35, ), ), ], ), ), ], ), const SizedBox(height: 28), Container( width: double.infinity, padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.04), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withValues(alpha: 0.06), ), ), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(14), child: Image.asset( 'assets/images/logo_white.png', height: 48, width: 48, fit: BoxFit.cover, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'CB Prestige Banking', style: theme.textTheme.titleMedium ?.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 2), Text( 'Sign in to access your QR dashboard.', style: theme.textTheme.bodySmall ?.copyWith(color: Colors.white60), ), ], ), ), ], ), ), const SizedBox(height: 28), _AuthTextField( initialValue: state.username, label: 'Username', hintText: 'Enter your username', keyboardType: TextInputType.text, prefixIcon: Icons.person_rounded, onChanged: viewModel.updateUsername, validator: (value) { final username = value?.trim() ?? ''; if (username.isEmpty) { return 'Username is required'; } if (username.length < 3) { return 'Username must be at least 3 characters'; } return null; }, ), const SizedBox(height: 16), _AuthTextField( initialValue: state.password, label: 'Password', hintText: 'Enter your password', prefixIcon: Icons.lock_rounded, obscureText: state.obscurePassword, onChanged: viewModel.updatePassword, validator: (value) { final password = value ?? ''; if (password.isEmpty) { return 'Password is required'; } if (password.length < 6) { return 'Password must be at least 6 characters'; } return null; }, suffixIcon: IconButton( onPressed: viewModel.togglePasswordVisibility, icon: Icon( state.obscurePassword ? Icons.visibility_off_rounded : Icons.visibility_rounded, color: Colors.white70, ), ), ), const SizedBox(height: 12), Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () {}, child: const Text('Forgot password?'), ), ), if (state.errorMessage != null) ...[ Text( state.errorMessage!, style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.error, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 14), ] else const SizedBox(height: 6), SizedBox( width: double.infinity, child: FilledButton( onPressed: state.isSubmitting ? null : _submit, style: FilledButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(18), ), ), child: state.isSubmitting ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2.4, valueColor: AlwaysStoppedAnimation( Colors.white, ), ), ) : const Text( 'Sign In', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ), ), ), ), ), ); } } class _AuthTextField extends StatelessWidget { const _AuthTextField({ required this.initialValue, required this.label, required this.hintText, required this.prefixIcon, required this.validator, required this.onChanged, this.keyboardType, this.obscureText = false, this.suffixIcon, }); final String initialValue; final String label; final String hintText; final IconData prefixIcon; final String? Function(String?) validator; final ValueChanged onChanged; final TextInputType? keyboardType; final bool obscureText; final Widget? suffixIcon; @override Widget build(BuildContext context) { return TextFormField( initialValue: initialValue, keyboardType: keyboardType, obscureText: obscureText, validator: validator, onChanged: onChanged, style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: label, hintText: hintText, prefixIcon: Icon(prefixIcon), suffixIcon: suffixIcon, ), ); } }