From 08991f20956f2f021c4cae5c57cfc4001f131ffd Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Sat, 31 May 2025 10:43:39 +0530 Subject: [PATCH] refactor: Improve LoginController and LoginScreen structure and readability --- lib/controller/auth/login_controller.dart | 64 ++-- lib/view/auth/login_screen.dart | 381 +++++++++++----------- 2 files changed, 228 insertions(+), 217 deletions(-) diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index 9a18200..7478bd4 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -6,51 +6,65 @@ import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/widgets/my_validators.dart'; class LoginController extends MyController { - MyFormValidator basicValidator = MyFormValidator(); + // Form validator + final MyFormValidator basicValidator = MyFormValidator(); - bool showPassword = false, isChecked = false; - RxBool isLoading = false.obs; // Add reactive loading state + // UI states + final RxBool isLoading = false.obs; + final RxBool showPassword = false.obs; + final RxBool isChecked = false.obs; + // Dummy credentials final String _dummyEmail = "admin@marcoaiot.com"; final String _dummyPassword = "User@123"; @override void onInit() { - basicValidator.addField('username', required: true, label: "User_Name", validators: [MyEmailValidator()], controller: TextEditingController(text: _dummyEmail)); - basicValidator.addField('password', required: true, label: "Password", validators: [MyLengthValidator(min: 6, max: 10)], controller: TextEditingController(text: _dummyPassword)); super.onInit(); + + basicValidator.addField( + 'username', + required: true, + label: "User_Name", + validators: [MyEmailValidator()], + controller: TextEditingController(text: _dummyEmail), + ); + + basicValidator.addField( + 'password', + required: true, + label: "Password", + validators: [MyLengthValidator(min: 6, max: 10)], + controller: TextEditingController(text: _dummyPassword), + ); } void onChangeCheckBox(bool? value) { - isChecked = value ?? isChecked; - update(); + isChecked.value = value ?? isChecked.value; } void onChangeShowPassword() { - showPassword = !showPassword; - update(); + showPassword.toggle(); } Future onLogin() async { - if (basicValidator.validateForm()) { - // Set loading to true - isLoading.value = true; - update(); + if (!basicValidator.validateForm()) return; - var errors = await AuthService.loginUser(basicValidator.getData()); - if (errors != null) { - basicValidator.addErrors(errors); - basicValidator.validateForm(); - basicValidator.clearErrors(); - } else { - String nextUrl = Uri.parse(ModalRoute.of(Get.context!)?.settings.name ?? "").queryParameters['next'] ?? "/home"; - Get.toNamed(nextUrl); - } + isLoading.value = true; - // Set loading to false after the API call is complete - isLoading.value = false; - update(); + final errors = await AuthService.loginUser(basicValidator.getData()); + + if (errors != null) { + basicValidator.addErrors(errors); + basicValidator.validateForm(); + basicValidator.clearErrors(); + } else { + final currentRoute = ModalRoute.of(Get.context!)?.settings.name ?? ""; + final nextUrl = Uri.parse(currentRoute).queryParameters['next'] ?? "/home"; + Get.toNamed(nextUrl); } + + isLoading.value = false; } void goToForgotPassword() { diff --git a/lib/view/auth/login_screen.dart b/lib/view/auth/login_screen.dart index 2ad8a6b..c3c6bb5 100644 --- a/lib/view/auth/login_screen.dart +++ b/lib/view/auth/login_screen.dart @@ -20,11 +20,11 @@ class LoginScreen extends StatefulWidget { } class _LoginScreenState extends State with UIMixin { - late LoginController controller; + late final LoginController controller; @override void initState() { - controller = Get.put(LoginController()); + controller = Get.put(LoginController(), tag: 'login_controller'); super.initState(); } @@ -32,204 +32,201 @@ class _LoginScreenState extends State with UIMixin { Widget build(BuildContext context) { return AuthLayout( child: GetBuilder( - init: controller, tag: 'login_controller', - builder: (controller) { + builder: (_) { return Obx(() { - return controller.isLoading.value - ? Center(child: CircularProgressIndicator()) // Show loading spinner when isLoading is true - : Form( - key: controller.basicValidator.formKey, - child: SingleChildScrollView( - padding: MySpacing.xy(2, 40), - child: Container( - width: double.infinity, - padding: MySpacing.all(24), - decoration: BoxDecoration( - color: theme.colorScheme.primary.withOpacity(0.02), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: contentTheme.primary.withOpacity(0.5), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - /// Logo - Center( - child: Image.asset( - Images.logoDark, - height: 120, - fit: BoxFit.contain, - ), - ), - MySpacing.height(20), + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } - /// Welcome Text - Center( - child: MyText.bodyLarge("Welcome Back!", fontWeight: 600), - ), - MySpacing.height(4), - Center( - child: MyText.bodySmall("Please sign in to continue."), - ), - MySpacing.height(20), - - /// Email Field - MyText.bodySmall("Email Address", fontWeight: 600), - MySpacing.height(8), - Material( - elevation: 2, - shadowColor: contentTheme.secondary.withAlpha(30), - borderRadius: BorderRadius.circular(12), - child: TextFormField( - validator: - controller.basicValidator.getValidation('username'), - controller: - controller.basicValidator.getController('username'), - keyboardType: TextInputType.emailAddress, - style: MyTextStyle.labelMedium(), - decoration: InputDecoration( - hintText: "Enter your email", - hintStyle: MyTextStyle.bodySmall(xMuted: true), - filled: true, - fillColor: theme.cardColor, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(2), - borderSide: BorderSide.none, - ), - prefixIcon: const Icon(LucideIcons.mail, size: 18), - contentPadding: MySpacing.xy(12, 16), - ), - ), - ), - MySpacing.height(16), - - /// Password Field Label - MyText.bodySmall("Password", fontWeight: 600), - MySpacing.height(8), - Material( - elevation: 2, - shadowColor: contentTheme.secondary.withAlpha(25), - borderRadius: BorderRadius.circular(12), - child: TextFormField( - validator: - controller.basicValidator.getValidation('password'), - controller: - controller.basicValidator.getController('password'), - keyboardType: TextInputType.visiblePassword, - obscureText: !controller.showPassword, - style: MyTextStyle.labelMedium(), - decoration: InputDecoration( - hintText: "Enter your password", - hintStyle: MyTextStyle.bodySmall(xMuted: true), - filled: true, - fillColor: theme.cardColor, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(2), - borderSide: BorderSide.none, - ), - prefixIcon: const Icon(LucideIcons.lock, size: 18), - suffixIcon: InkWell( - onTap: controller.onChangeShowPassword, - child: Icon( - controller.showPassword - ? LucideIcons.eye - : LucideIcons.eye_off, - size: 18, - ), - ), - contentPadding: MySpacing.all(3), - ), - ), - ), - - MySpacing.height(16), - - /// Remember Me + Forgot Password - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: () => controller - .onChangeCheckBox(!controller.isChecked), - child: Row( - children: [ - Checkbox( - onChanged: controller.onChangeCheckBox, - value: controller.isChecked, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - fillColor: WidgetStatePropertyAll( - contentTheme.secondary), - checkColor: contentTheme.onPrimary, - visualDensity: getCompactDensity, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - ), - MySpacing.width(8), - MyText.bodySmall("Remember Me"), - ], - ), - ), - MyButton.text( - onPressed: controller.goToForgotPassword, - elevation: 0, - padding: MySpacing.xy(8, 0), - splashColor: contentTheme.secondary.withAlpha(36), - child: MyText.bodySmall( - 'Forgot password?', - fontWeight: 600, - color: contentTheme.secondary, - ), - ), - ], - ), - MySpacing.height(28), - - /// Login Button - Center( - child: MyButton.rounded( - onPressed: controller.onLogin, - elevation: 2, - padding: MySpacing.xy(24, 16), - borderRadiusAll: 16, - backgroundColor: Colors.blueAccent, - child: MyText.labelMedium( - 'Login', - fontWeight: 600, - color: contentTheme.onPrimary, - ), - ), - ), - MySpacing.height(16), - - /// Register Link - Center( - child: MyButton.text( - onPressed: () { - OrganizationFormBottomSheet.show(context); - }, - elevation: 0, - padding: MySpacing.xy(12, 8), - splashColor: - contentTheme.secondary.withAlpha(30), - child: MyText.bodySmall( - "Request a Demo", - color: contentTheme.secondary, - fontWeight: 600, - ), - ), - ), - ], + return Form( + key: controller.basicValidator.formKey, + child: SingleChildScrollView( + padding: MySpacing.xy(2, 40), + child: Container( + width: double.infinity, + padding: MySpacing.all(24), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withOpacity(0.02), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: contentTheme.primary.withOpacity(0.5), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Logo + Center( + child: Image.asset( + Images.logoDark, + height: 120, + fit: BoxFit.contain, ), ), - ), - ); + MySpacing.height(20), + + /// Welcome + Center(child: MyText.bodyLarge("Welcome Back!", fontWeight: 600)), + MySpacing.height(4), + Center(child: MyText.bodySmall("Please sign in to continue.")), + MySpacing.height(20), + + /// Email + MyText.bodySmall("Email Address", fontWeight: 600), + MySpacing.height(8), + _buildInputField( + controller.basicValidator.getController('username')!, + controller.basicValidator.getValidation('username'), + hintText: "Enter your email", + icon: LucideIcons.mail, + keyboardType: TextInputType.emailAddress, + ), + MySpacing.height(16), + + /// Password + MyText.bodySmall("Password", fontWeight: 600), + MySpacing.height(8), + Obx(() { + return _buildInputField( + controller.basicValidator.getController('password')!, + controller.basicValidator.getValidation('password'), + hintText: "Enter your password", + icon: LucideIcons.lock, + obscureText: !controller.showPassword.value, + suffix: IconButton( + icon: Icon( + controller.showPassword.value + ? LucideIcons.eye + : LucideIcons.eye_off, + size: 18, + ), + onPressed: controller.onChangeShowPassword, + ), + ); + }), + MySpacing.height(16), + + /// Remember me + Forgot password + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Obx(() { + return InkWell( + onTap: () => + controller.onChangeCheckBox(!controller.isChecked.value), + child: Row( + children: [ + Checkbox( + value: controller.isChecked.value, + onChanged: controller.onChangeCheckBox, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + fillColor: WidgetStatePropertyAll(contentTheme.secondary), + checkColor: contentTheme.onPrimary, + visualDensity: getCompactDensity, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + ), + MySpacing.width(8), + MyText.bodySmall("Remember Me"), + ], + ), + ); + }), + MyButton.text( + onPressed: controller.goToForgotPassword, + elevation: 0, + padding: MySpacing.xy(8, 0), + splashColor: contentTheme.secondary.withAlpha(36), + child: MyText.bodySmall( + 'Forgot password?', + fontWeight: 600, + color: contentTheme.secondary, + ), + ), + ], + ), + MySpacing.height(28), + + /// Login button + Center( + child: MyButton.rounded( + onPressed: controller.onLogin, + elevation: 2, + padding: MySpacing.xy(24, 16), + borderRadiusAll: 16, + backgroundColor: Colors.blueAccent, + child: MyText.labelMedium( + 'Login', + fontWeight: 600, + color: contentTheme.onPrimary, + ), + ), + ), + MySpacing.height(16), + + /// Request demo + Center( + child: MyButton.text( + onPressed: () { + OrganizationFormBottomSheet.show(context); + }, + elevation: 0, + padding: MySpacing.xy(12, 8), + splashColor: contentTheme.secondary.withAlpha(30), + child: MyText.bodySmall( + "Request a Demo", + color: contentTheme.secondary, + fontWeight: 600, + ), + ), + ), + ], + ), + ), + ), + ); }); }, ), ); } + + Widget _buildInputField( + TextEditingController controller, + FormFieldValidator? validator, { + required String hintText, + required IconData icon, + TextInputType keyboardType = TextInputType.text, + bool obscureText = false, + Widget? suffix, + }) { + return Material( + elevation: 2, + shadowColor: contentTheme.secondary.withAlpha(30), + borderRadius: BorderRadius.circular(12), + child: TextFormField( + controller: controller, + validator: validator, + obscureText: obscureText, + keyboardType: keyboardType, + style: MyTextStyle.labelMedium(), + decoration: InputDecoration( + hintText: hintText, + hintStyle: MyTextStyle.bodySmall(xMuted: true), + filled: true, + fillColor: theme.cardColor, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(2), + borderSide: BorderSide.none, + ), + prefixIcon: Icon(icon, size: 18), + suffixIcon: suffix, + contentPadding: MySpacing.xy(12, 16), + ), + ), + ); + } }