import 'package:english_learning/core/widgets/custom_snackbar.dart'; import 'package:english_learning/features/auth/provider/user_provider.dart'; import 'package:english_learning/features/auth/provider/validator_provider.dart'; import 'package:english_learning/features/auth/screens/forgot_password/forgot_password_screen.dart'; import 'package:english_learning/features/auth/screens/signup/signup_screen.dart'; import 'package:english_learning/features/home/screens/home_screen.dart'; import 'package:english_learning/core/widgets/form_field/custom_field_widget.dart'; import 'package:english_learning/core/widgets/global_button.dart'; import 'package:english_learning/core/utils/styles/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; class SigninScreen extends StatefulWidget { const SigninScreen({super.key}); @override State createState() => _SigninScreenState(); } class _SigninScreenState extends State { final TextEditingController _loginController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final FocusNode _loginFocus = FocusNode(); final FocusNode _passwordFocus = FocusNode(); final _formKey = GlobalKey(); @override void initState() { super.initState(); context.read().setController('login', _loginController); context .read() .setController('password', _passwordController); } @override void dispose() { context.read().removeController('login'); context.read().removeController('password'); _loginController.dispose(); _passwordController.dispose(); _loginFocus.dispose(); _passwordFocus.dispose(); super.dispose(); } Future _handleLogin(BuildContext context) async { if (!_formKey.currentState!.validate()) return; final userProvider = context.read(); final validatorProvider = context.read(); try { final loginCredential = _loginController.text.trim(); final password = _passwordController.text; final isSuccess = await userProvider.login( credential: loginCredential, password: password, ); if (!mounted) return; if (isSuccess) { // Hide keyboard before navigation FocusScope.of(context).unfocus(); await Navigator.pushAndRemoveUntil( context, PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => const HomeScreen(), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition(opacity: animation, child: child); }, transitionDuration: const Duration(milliseconds: 300), ), (route) => false, ); // Reset form validatorProvider.resetFields(); } else { _handleLoginError(context, userProvider.errorCode); } } catch (e) { if (mounted) { CustomSnackBar.show( context, message: 'An unexpected error occurred. Please try again.', isError: true, ); } } } void _handleLoginError(BuildContext context, int? errorCode) { String message = 'Login failed, please check your credentials'; switch (errorCode) { case 403: message = 'Please verify your account first to continue.'; break; case 401: message = 'Invalid credentials. Please try again.'; break; case 429: message = 'Too many attempts. Please try again later.'; break; } CustomSnackBar.show(context, message: message, isError: true); } @override Widget build(BuildContext context) { final mediaQuery = MediaQuery.of(context); final screenHeight = mediaQuery.size.height; return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Scaffold( backgroundColor: AppColors.whiteColor, body: SafeArea( child: Center( child: Container( margin: const EdgeInsets.symmetric(horizontal: 16), child: SingleChildScrollView( child: Form( key: _formKey, child: Consumer( builder: (context, validatorProvider, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: screenHeight * 0.02), Hero( tag: 'welcome_text', child: Material( color: Colors.transparent, child: Text( 'Welcome Back!', style: AppTextStyles.blueTextStyle.copyWith( fontSize: 30, fontWeight: FontWeight.w900, ), ), ), ), const SizedBox(height: 4), Text( 'Login to continue your personalized learning journey.', style: AppTextStyles.greyTextStyle.copyWith( fontSize: 14, ), ), const SizedBox(height: 26), // SizedBox(height: screenHeight * 0.05), Center( child: Hero( tag: 'login_illustration', child: SvgPicture.asset( 'lib/features/auth/assets/images/login_illustration.svg', width: 200, ), ), ), const SizedBox(height: 30), // SizedBox(height: screenHeight * 0.05), CustomFieldWidget( fieldName: 'login', controller: _loginController, focusNode: _loginFocus, isRequired: true, textInputAction: TextInputAction.next, labelText: 'Email or NISN', hintText: 'Enter Email or NISN Number', keyboardType: TextInputType.emailAddress, validator: (value) { validatorProvider.validateField( 'login', value?.trim(), validator: (val) { if (val == null || val.isEmpty) { return 'Login credential cannot be empty'; } val = val.trim(); if (val.contains('@')) { return validatorProvider.emailValidator(val); } else if (RegExp(r'^\d{10}$').hasMatch(val)) { return null; // Valid NISN } else { return 'Please enter a valid email or NISN (10 digits)'; } }); return validatorProvider.getError('login'); }, onChanged: (value) => validatorProvider.validateField( 'login', value?.trim(), validator: (val) { if (val == null || val.isEmpty) { return 'Login credential cannot be empty'; } val = val.trim(); if (val.contains('@')) { return validatorProvider.emailValidator(val); } else if (RegExp(r'^\d{10}$').hasMatch(val)) { return null; // Valid NISN } else { return 'Please enter a valid email or NISN (10 digits)'; } }, ), onFieldSubmitted: (_) { FocusScope.of(context) .requestFocus(_passwordFocus); }, errorText: validatorProvider.getError('login'), ), const SizedBox(height: 14), CustomFieldWidget( fieldName: 'password', controller: _passwordController, focusNode: _passwordFocus, isRequired: true, textInputAction: TextInputAction.done, labelText: 'Password', hintText: 'Enter Your Password', obscureText: validatorProvider.isObscure('password'), keyboardType: TextInputType.visiblePassword, validator: (value) { validatorProvider.validateField('password', value, validator: validatorProvider.passwordValidator); return validatorProvider.getError('password'); }, onChanged: (value) => validatorProvider.validateField( 'password', value, validator: validatorProvider.passwordValidator, ), onFieldSubmitted: (_) => _handleLogin(context), onSuffixIconTap: () => validatorProvider.toggleVisibility('password'), errorText: validatorProvider.getError('password'), ), const SizedBox(height: 16), Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const ForgotPasswordScreen(), ), ); }, style: TextButton.styleFrom( padding: EdgeInsets.zero, minimumSize: const Size(0, 0), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: ShaderMask( shaderCallback: (bounds) => AppColors.gradientTheme.createShader( Rect.fromLTWH( 0, 0, bounds.width, bounds.height), ), child: const Text( 'Forgot Password?', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ), const SizedBox(height: 24), Consumer( builder: (context, userProvider, _) { return GlobalButton( text: 'Login', isLoading: userProvider.isLoading, onPressed: userProvider.isLoading ? null : () => _handleLogin(context), ); }, ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Haven\'t joined us yet? ', style: AppTextStyles.blackTextStyle .copyWith(fontSize: 14), ), TextButton( onPressed: () { context .read() .resetFields(); Navigator.push( context, PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => const SignupScreen(), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: child, ); }, ), ); }, style: TextButton.styleFrom( padding: EdgeInsets.zero, minimumSize: const Size(0, 0), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Text( 'Sign Up Here', style: AppTextStyles.blueTextStyle.copyWith( fontSize: 14, fontWeight: FontWeight.bold, ), ), ), ], ), SizedBox(height: screenHeight * 0.02), ], ); }, ), ), ), ), ), ), ), ); } }