// import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:nhance_app_pwa/pages/email_verify.dart'; import 'package:nhance_app_pwa/pages/service/SessionManager.dart'; import 'package:nhance_app_pwa/pages/service/TokenService.dart'; import 'package:nhance_app_pwa/pages/verify.dart'; import 'dart:convert'; import 'dart:io'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:google_fonts/google_fonts.dart'; import '../config/environment.dart'; import '../customAppBar/responsive.dart'; import '../customAppBar/toastHelper.dart'; import '../models/platform_helper_mobile.dart' if (dart.library.html) '../models/platform_helper_other.dart'; import 'package:pinput/pinput.dart'; class login extends StatefulWidget { const login({Key? key}); @override State createState() => _loginState(); } class _loginState extends State { final TextEditingController emailMobileController = TextEditingController(); final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); final TextEditingController resetPasswordController = TextEditingController(); final TextEditingController confirmPasswordController = TextEditingController(); final TextEditingController _otpController = TextEditingController(); TextEditingController countryController = TextEditingController(); TextEditingController mobileController = TextEditingController(); final _formKey = GlobalKey(); bool _isCheckingToken = false; dynamic empMobileNo; dynamic empEmailid; int switcherStatus = 1; int selectedIndex = 1; dynamic _preToken; dynamic _postToken; dynamic clientName; dynamic clientLogo; late String _verificationId; int? _resendToken; bool _isLoading = false; bool isEmailFieldVisible = false; bool _obscurePassword = true; bool _resetObscurePassword = true; bool _obscureConfirmPassword = true; late SessionManager session; bool clickedForgotPassword = false; bool resetPasswordEnable = false; bool otpFieldShow = false; bool otpValueStatus = false; final defaultPinTheme = PinTheme( width: 56, height: 56, textStyle: TextStyle( fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), fontWeight: FontWeight.w600, ), decoration: BoxDecoration( border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)), borderRadius: BorderRadius.circular(20), ), ); bool hasMinLength = false; bool hasUpperLower = false; bool hasNumber = false; bool hasSpecialChar = false; bool get isPasswordValid => hasMinLength && hasUpperLower && hasNumber && hasSpecialChar; @override void initState() { countryController.text = "+91"; super.initState(); // checkLoginPinOnMobile(); } void validatePassword(String password) { setState(() { hasMinLength = password.length >= 8; hasUpperLower = RegExp(r'(?=.*[A-Za-z])').hasMatch(password); hasNumber = RegExp(r'(?=.*\d)').hasMatch(password); hasSpecialChar = RegExp(r'(?=.*[@$!%*#?&])').hasMatch(password); }); } Future checkLoginPinOnMobile() async { if (isMobilePlatform()) { final SharedPreferences prefs = await SharedPreferences.getInstance(); // final skipStatus = prefs.getInt('skipStatus') ?? 1; // if(skipStatus == 0){ empMobileNo = prefs.getString('empMobileNo'); empEmailid = prefs.getString('empEmailid'); if ((empMobileNo != null && empMobileNo.isNotEmpty) || (empEmailid != null && empEmailid.isNotEmpty)) { checkLoginPin(context); } // } } else { if (kIsWeb) { final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); print('Local Storage Clear'); } } } Future checkLoginPin(BuildContext context) async { print('checkLoginPin'); try { final SharedPreferences prefs = await SharedPreferences.getInstance(); empMobileNo = prefs.getString('empMobileNo'); empEmailid = prefs.getString('empEmailid'); print('empMobileNo $empMobileNo'); print('empEmailid $empEmailid'); // _token = prefs.getString('token'); var params = {}; if (empMobileNo != null && empMobileNo.isNotEmpty) { params = {'mobile_number': empMobileNo}; } else if (empEmailid != null && empEmailid.isNotEmpty) { params = {'email_id': empEmailid}; } final response = await http.post( Uri.parse(Environment.apiUrlEnrollment + 'checkMpin'), body: json.encode(params), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); if (data['status'] == 'success') { print('data'); if (data['data'] != null) { prefs.setString('mpinText', data['data']); } if (data['Mpin'] != null) { prefs.setString('mpin', data['Mpin']); } if (data['is_biometric_enabled'] != null) { prefs.setString( 'is_biometric_enabled', data['is_biometric_enabled']); } if (data['is_mpin_skipped'] != null) { prefs.setString('is_mpin_skipped', data['is_mpin_skipped']); } if (data['is_mpin_skipped'] != null && data['is_mpin_skipped'] == '0') { context.go('/pinPage'); // Navigator.pushReplacementNamed(context, 'pinPage'); } } else { final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); } } else { ToastHelper.showErrorToast(context, 'Something went wrong'); throw Exception('Failed to verify pin number'); } } catch (e) { // ToastHelper.showErrorToast(context, 'Something went wrong'); print('Error: $e'); } } void toggleField() async { // final SharedPreferences prefs = await SharedPreferences.getInstance(); if (isEmailFieldVisible) { mobileController.text = ''; } else { emailController.text = ''; } setState(() { isEmailFieldVisible = !isEmailFieldVisible; }); } Future verifyMobileAndEmailNumber() async { try { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); String input = emailMobileController.text.trim(); bool isEmail = RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(input); bool isMobile = RegExp(r'^[0-9]{10}$').hasMatch(input); if (isEmail) { setState(() { isEmailFieldVisible = true; }); print("User entered Email: $input"); } else if (isMobile) { isEmailFieldVisible = false; print("User entered Mobile: $input"); } // Determine the API and the payload based on the visible field String apiEndpoint = isEmailFieldVisible ? Environment.apiUrlEnrollment + 'verifyEmployeeEmailId' : Environment.apiUrlEnrollment + 'verifyEmployeeNumber'; Map payload = isEmailFieldVisible ? {'email': emailMobileController.text} : {'mobile_number': emailMobileController.text}; // var enteredMobileNumber = mobileController.text; final response = await http.post( Uri.parse(apiEndpoint), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { final SharedPreferences prefs = await SharedPreferences.getInstance(); // var enteredMobileNumber = mobileController.text; // prefs.setString('empMobileNo', enteredMobileNumber); ToastHelper.showSuccessToast( context, 'Verification code sent to ${emailMobileController.text}'); if (isEmailFieldVisible) { print('isEmailFieldVisible $isEmailFieldVisible'); // prefs.setString('empEmail', emailController.text); print('${emailMobileController.text}'); context.push( '/mailVerify', extra: { 'type': 'email', 'value': emailMobileController.text.trim(), }, ); } else { // _verifyPhoneNumber(); context.push( '/mailVerify', extra: { 'type': 'mobile', 'value': emailMobileController.text.trim(), }, ); } setState(() { _isLoading = false; }); // ToastHelper.showSuccessToast(context, message); } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, message); print('Invalid mobile number'); } } else if (response.statusCode == 429) { setState(() { _isLoading = false; }); Map data = json.decode(response.body); final message = data['message']; ToastHelper.showErrorToast(context, message); } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); throw Exception('Failed to verify mobile number'); } } } catch (e) { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); print('Error: $e'); } } // Future _verifyPhoneNumber() async { // var enteredMobileNumber = mobileController.text; // var countryCode = countryController.text; // print('${countryCode + enteredMobileNumber}'); // await _auth.verifyPhoneNumber( // phoneNumber: '${countryCode + enteredMobileNumber}', // timeout: const Duration(seconds: 60), // verificationCompleted: (PhoneAuthCredential credential) async { // await _auth.signInWithCredential(credential); // // ToastHelper.showSuccessToast(context, 'Verified Successfully!'); // // setState(() { // // _isLoading = false; // // }); // }, // verificationFailed: (FirebaseAuthException e) { // print('Verification Failed: ${e.code} - ${e.message}'); // String errorMessage; // if (e.code == 'invalid-app-credential') { // errorMessage = 'Invalid Credential. Please try again.'; // } else if (e.code == 'invalid-phone-number') { // errorMessage = 'The provided phone number is not valid.'; // } else if (e.code == 'too-many-requests') { // errorMessage = 'Too many requests. Try again later.'; // } else { // errorMessage = 'Verification Failed: ${e.message}'; // } // ScaffoldMessenger.of(context).showSnackBar( // SnackBar( // content: Text(errorMessage), // duration: Duration(seconds: 2), // ), // ); // // ToastHelper.showSuccessToast(context, errorMessage); // setState(() { // _isLoading = false; // }); // }, // codeSent: (String verificationId, int? resendToken) async { // setState(() { // _verificationId = verificationId; // _resendToken = resendToken; // }); // // // When getting the verificationId // SharedPreferences prefs = await SharedPreferences.getInstance(); // prefs.setString('verificationId', _verificationId); // // // ToastHelper.showSuccessToast( // context, 'Verification code sent to ${enteredMobileNumber}'); // // context.go( // '/verify', // extra: { // 'verificationId': _verificationId, // 'mobileNumber': enteredMobileNumber, // 'resendToken': _resendToken, // 'onResendCode': _resendCode, // }, // ); // // // Navigator.push( // // context, // // MaterialPageRoute( // // builder: (context) => MyVerify( // // verificationId: _verificationId, // // mobileNumber: enteredMobileNumber, // // resendToken: _resendToken, // // onResendCode: _resendCode, // Pass the phone number // // ), // // ), // // ); // setState(() { // _isLoading = false; // }); // }, // codeAutoRetrievalTimeout: (String verificationId) async{ // setState(() { // _verificationId = verificationId; // }); // final prefs = await SharedPreferences.getInstance(); // await prefs.setString('verificationId', verificationId); // ToastHelper.showSuccessToast(context, 'Code auto-retrieval timed out.'); // setState(() { // _isLoading = false; // }); // }, // ); // } // // void _resendCode(String mobileNumber, int? resendToken) async { // await _auth.verifyPhoneNumber( // phoneNumber: '${countryController.text + mobileNumber}', // timeout: const Duration(seconds: 60), // forceResendingToken: resendToken, // verificationCompleted: (PhoneAuthCredential credential) async { // await _auth.signInWithCredential(credential); // }, // verificationFailed: (FirebaseAuthException e) { // if (e.code == 'invalid-phone-number') { // print('The provided phone number is not valid.'); // } // }, // codeSent: (String verificationId, int? resendToken) async{ // final prefs = await SharedPreferences.getInstance(); // await prefs.setString('verificationId', verificationId); // setState(() { // _verificationId = verificationId; // _resendToken = resendToken; // }); // ToastHelper.showSuccessToast( // context, 'Verification code resent to ${mobileNumber}'); // context.push( // '/verify', // extra: { // 'verificationId': _verificationId, // 'mobileNumber': mobileNumber, // 'resendToken': _resendToken, // 'onResendCode': _resendCode, // }, // ); // // // Navigator.push( // // context, // // MaterialPageRoute( // // builder: (context) => MyVerify( // // verificationId: _verificationId, // // mobileNumber: mobileNumber, // // resendToken: _resendToken, // // onResendCode: _resendCode, // // ), // // ), // // ); // }, // codeAutoRetrievalTimeout: (String verificationId) async{ // final prefs = await SharedPreferences.getInstance(); // await prefs.setString('verificationId', verificationId); // setState(() { // _verificationId = verificationId; // }); // }, // ); // } //starts Login with UserName and Pasword void loginWithUsernameAndPw() async { try { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); // print('EMAIL PARAMS : ${widget.email} - OTP : $otp'); final Map payload = { 'email_id': emailController.text, 'password': passwordController.text }; final response = await http.post( Uri.parse(Environment.apiUrlEnrollment + 'verifyPassword'), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); print('response : ${response.statusCode}'); if (response.statusCode == 200) { setState(() { _isLoading = false; }); Map data = json.decode(response.body); print('data: $data'); /// Check API response status first if (data['status'] == 'Invalid Password') { final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); ToastHelper.showErrorToast( context, data['message'] ?? 'Invalid Password'); print('API error → ${data['message']}'); return; // stop execution } // --- Extract tokens safely --- String? preToken; if (data['data'] is String) { preToken = data['data']; } String? postToken; if (data['post_enrollment'] != null && data['post_enrollment']['data'] is String) { postToken = data['post_enrollment']['data']; } // Save tokens await TokenService.saveTokens( preToken: preToken, postToken: postToken); print('Tokens saved → preToken: $preToken, postToken: $postToken'); _preToken = preToken; String status = data['status']; // Directly access the post_enrollment data Map post = data['post_enrollment']; print('post: $post'); _postToken = postToken; String postStatus = post['status']; if (postStatus == 'success') { setState(() { _isLoading = false; }); postSuccessData(post, data); } else if (status == 'success') { setState(() { _isLoading = false; }); enrollmentSuccessData(data); } else { setState(() { _isLoading = false; }); final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); ToastHelper.showErrorToast( context, 'Invalid Passsword. Please try again'); // Show a Snackbar if the OTP is invalid print('Invalid Password. Please try again'); } } else { setState(() { _isLoading = false; }); final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); ToastHelper.showWarningToast(context, 'Something went wrong'); throw Exception('Failed to verify OTP'); } } } catch (e) { setState(() { _isLoading = false; }); final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); print('Error: $e'); ToastHelper.showWarningToast(context, 'Something went wrong'); // Show a Snackbar if there's an error while verifying OTP print('Failed to verify OTP. Please try again.'); } } void postSuccessData(post, data) async { String status = data['status']; String postStatus = post['status']; if (postStatus == 'success') { final SharedPreferences prefs = await SharedPreferences.getInstance(); await SessionManager().initializeFromPostToken(post['data']); session = await SessionManager(); print('Successfully Login'); } if (status == 'success') { final SharedPreferences prefs = await SharedPreferences.getInstance(); await SessionManager().initializeFromPreToken(data['data']); session = await SessionManager(); getClientLogoAndDetails(); } if (_postToken != null && _postToken.isNotEmpty) { ToastHelper.showSuccessToast(context, 'Successfully Logged In'); context.go('/home'); // Navigator.pushReplacementNamed(context, 'home'); } else { context.go('/empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails'); } } void enrollmentSuccessData(data) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await SessionManager().initializeFromPreToken(data['data']); session = await SessionManager(); getClientLogoAndDetails(); print('Successfully Login'); print(isMobilePlatform()); if (_preToken != null && _preToken.isNotEmpty) { ToastHelper.showSuccessToast(context, 'Successfully Logged In'); context.go('/home'); // Navigator.pushReplacementNamed(context, 'home'); } else { context.go('/empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails'); } } Future getClientLogoAndDetails() async { var url = Uri.parse(Environment.apiUrlEnrollment + 'getClientDetails?post_client_id=${session.client_id}&post_branch_id=${session.empClientBranchId}&pre_client_id=${session.enrollmentClient_id}&pre_branch_id=${session.enrollmentEmpClientBranchId}'); try { var response = await http.get( url, headers: { 'Authorization': 'Bearer $_preToken', // Add token to the Authorization header 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { // print('response.statusCode == 200'); Map data = json.decode(response.body); // print(data); if (data.containsKey('data')) { dynamic clientDetails = data['data']; final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString( 'clientLogo', clientDetails['client']['client_logo']); await prefs.setString( 'clientName', clientDetails['client']['client_name']); await prefs.setString( 'addon_subheading', clientDetails['client']['addon_subheading']); setState(() { // dynamic clientDetails = data['data']; // print(clientDetails); clientName = clientDetails['client']['client_name']; print(clientName); clientLogo = clientDetails['client']['client_logo']; print(clientLogo); }); } else { // Handle other status messages if needed // ToastHelper.showErrorToast( // context, 'API request failed with status: ${data['status']}'); print('API request failed with status: ${data['status']}'); } } else { // Handle other status codes // ToastHelper.showErrorToast( // context, 'Request failed with status: ${response.statusCode}'); print('Request failed with status: ${response.statusCode}'); } } catch (e) { // Handle exceptions print('Exception occurred: $e'); } } Future forgotPassword() async { setState(() { clickedForgotPassword = true; passwordController.text = ''; passwordController.clear(); }); } Future mailVerify() async { try { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); // Determine the API and the payload based on the visible field String apiEndpoint = Environment.apiUrlEnrollment + 'verifyEmployeeEmailId'; Map payload = {'email': emailController.text}; // var enteredMobileNumber = mobileController.text; final response = await http.post( Uri.parse(apiEndpoint), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { final SharedPreferences prefs = await SharedPreferences.getInstance(); // var enteredMobileNumber = mobileController.text; // prefs.setString('empMobileNo', enteredMobileNumber); ToastHelper.showSuccessToast( context, 'OTP send to registered email'); setState(() { otpFieldShow = true; _isLoading = false; }); // ToastHelper.showSuccessToast(context, message); } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, message); print('Invalid mobile number'); } } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); throw Exception('Failed to verify mobile number'); } } } catch (e) { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); print('Error: $e'); } } Future otpVerify() async { try { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); // Determine the API and the payload based on the visible field String apiEndpoint = Environment.apiUrlEnrollment + 'verifyOtp'; Map payload = { 'email_id': emailController.text, 'otp': _otpController.text }; // var enteredMobileNumber = mobileController.text; final response = await http.post( Uri.parse(apiEndpoint), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); print(data); String? verificationStatus = data['status']; print(verificationStatus); String? message = data['message']; print(message); if (verificationStatus == 'success') { final SharedPreferences prefs = await SharedPreferences.getInstance(); // var enteredMobileNumber = mobileController.text; // prefs.setString('empMobileNo', enteredMobileNumber); ToastHelper.showSuccessToast(context, message!); setState(() { otpValueStatus = false; otpFieldShow = false; clickedForgotPassword = false; resetPasswordEnable = true; _isLoading = false; }); // ToastHelper.showSuccessToast(context, message); } else { setState(() { otpValueStatus = true; _isLoading = false; }); ToastHelper.showErrorToast(context, message!); print('Invalid mobile number'); } } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); throw Exception('Failed to verify mobile number'); } } } catch (e) { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); print('Error: $e'); } } Future resetYourPassword() async { final password = resetPasswordController.text.trim(); final confirmPassword = confirmPasswordController.text.trim(); try { if (confirmPassword != password) { ToastHelper.showErrorToast(context, 'Passwords do not match'); return; } setState(() { _isLoading = true; }); // Determine the API and the payload based on the visible field String apiEndpoint = Environment.apiUrlEnrollment + 'savePassword'; Map payload = { 'email_id': emailController.text, 'password': password, 'confirm_password': confirmPassword }; // var enteredMobileNumber = mobileController.text; final response = await http.post( Uri.parse(apiEndpoint), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { ToastHelper.showSuccessToast(context, message!); setState(() { _isLoading = false; resetPasswordEnable = false; clickedForgotPassword = false; otpFieldShow = false; }); // ToastHelper.showSuccessToast(context, message); } else { setState(() { resetPasswordEnable = true; _isLoading = false; }); ToastHelper.showErrorToast(context, message!); print('Invalid mobile number'); } } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); throw Exception('Failed to verify mobile number'); } } catch (e) { setState(() { _isLoading = false; }); // ToastHelper.showErrorToast(context, 'Something went wrong'); print('Error: $e'); } // 🔹 Password validation // if (password.isEmpty) { // ToastHelper.showErrorToast(context, 'Please enter your password'); // return; // } // if (password.length < 6) { // ToastHelper.showErrorToast(context, 'Password must be at least 6 characters'); // return; // } // if (!RegExp(r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$').hasMatch(password)) { // ToastHelper.showErrorToast(context, 'Include at least 1 uppercase letter and 1 number'); // return; // } // // // 🔹 Confirm password validation // if (confirmPassword.isEmpty) { // ToastHelper.showErrorToast(context, 'Please confirm your password'); // return; // } } Future resendOTP() async { try { if (emailController.text.isNotEmpty) { setState(() { otpFieldShow = true; _isLoading = true; }); // Determine the API and the payload based on the visible field String apiEndpoint = Environment.apiUrlEnrollment + 'verifyEmployeeEmailId'; Map payload = {'email': emailController.text}; // var enteredMobileNumber = mobileController.text; final response = await http.post( Uri.parse(apiEndpoint), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { final SharedPreferences prefs = await SharedPreferences.getInstance(); // var enteredMobileNumber = mobileController.text; // prefs.setString('empMobileNo', enteredMobileNumber); ToastHelper.showSuccessToast( context, 'OTP send to registered email'); setState(() { otpFieldShow = true; _isLoading = false; }); // ToastHelper.showSuccessToast(context, message); } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, message); print('Invalid mobile number'); } } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); throw Exception('Failed to verify mobile number'); } } } catch (e) { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, 'Something went wrong'); print('Error: $e'); } } String getLoginDescription() { if (switcherStatus == 1) { return "Login with your email / mobile number and OTP to review and enroll for exciting health benefits for you and your family"; } if (clickedForgotPassword) { return "Enter your registered email to receive a OTP"; } if (switcherStatus == 0 && resetPasswordEnable) { return "Set your new password to continue accessing your Nhance account securely"; } return "Login with your Username and Password to review and enroll for exciting health benefits for you and your family"; } String getLoginHeading() { if (switcherStatus == 1) { return "Welcome to Nhance"; } if (clickedForgotPassword) { return "Forgot Password"; } if (switcherStatus == 0 && resetPasswordEnable) { return "Reset Password"; } return "Welcome to Nhance"; } //Ends Login with UserName and Password @override Widget build(BuildContext context) { Size _size = MediaQuery.of(context).size; EdgeInsets marginInsets = EdgeInsets.zero; // const focusedBorderColor = Colors.white; // const fillColor = Color.fromRGBO(243, 246, 249, 0); // const borderColor = Color.fromRGBO(23, 171, 144, 0.4); if (Responsive.isDesktop(context)) { marginInsets = const EdgeInsets.only( left: 0, right: 0, bottom: 0, top: 0, ); } else if (Responsive.isMobile(context)) { marginInsets = const EdgeInsets.only( left: 25, // Example value for mobile right: 25, // Example value for mobile bottom: 0, // Example value for mobile top: 0, // Example value for mobile ); } else if (Responsive.isTablet(context)) { marginInsets = const EdgeInsets.only( left: 25, //// Example value for mobile right: 25, // Example value for mobile bottom: 0, // Example value for mobile top: 0, // Example value for mobile ); } return WillPopScope( onWillPop: () async { // Close the app on mobile back button press exit(0); // This will exit the app return false; // Return false to prevent any other actions }, child: Scaffold( body: SingleChildScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, child: Container( height: _size.height, color: Colors.white, child: Stack( children: [ Visibility( visible: _size.width <= 1100, child: ClipRRect( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30), ), child: Container( height: _size.height / 3, width: double.infinity, color: Color(0xFFFFFCE5), child: Stack( children: [ Column( children: [ SizedBox(height: _size.height / 6.4), Row( mainAxisAlignment: MainAxisAlignment .center, // Align to the center children: [ Expanded( flex: Responsive.isDesktop(context) ? 10 : 12, child: Align( alignment: Responsive.isDesktop(context) ? Alignment.centerLeft : Alignment.bottomCenter, child: Image.asset( 'assets/nhance_app_logo.png', width: 150, height: 100, )), ), if (!Responsive.isMobile(context) && !Responsive.isTablet(context)) Expanded( flex: 2, child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { // Add your navigation logic here // For example, you can use Navigator.push to navigate to another page Navigator.pushNamed( context, 'hrLogin'); }, child: Row( mainAxisAlignment: MainAxisAlignment .end, // Align to the end (right) children: [ Text( 'HR Login', style: TextStyle( color: Color( 0xFF000000), // Text color // Add other text styles as needed ), ), SizedBox(width: 5), Icon( Icons .east, // Icon for customer login color: Colors .black, // Adjust color as needed ), ], ), ), ), ), ], ), ], ), ], ), ), ), ), Container( margin: marginInsets, alignment: Alignment.bottomCenter, child: SingleChildScrollView( child: Form( key: _formKey, child: Column( children: [ Row( children: [ Expanded( flex: _size.width < 1100 ? 6 : 12, child: Container( margin: _size.width > 1100 ? EdgeInsets.only(left: 20, right: 20) : EdgeInsets.only(left: 0, right: 0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (!Responsive.isMobile(context) && !Responsive.isTablet(context)) Row( children: [ Expanded( flex: 12, child: Align( alignment: Alignment.center, child: _size.width <= 1100 ? Image.asset( 'assets/nhance_app_logo.png', width: 150, height: 150, ) : _size.width > 1100 ? Image.asset( 'assets/nhance_app_logo.png', width: 150, height: 150, ) : Image.asset( 'assets/nhance_app_logo.png', width: 150, height: 150, ), ), ), ], ), SizedBox( height: Responsive.isDesktop(context) ? _size.height * 0.0 : 10, ), SizedBox(height: 10), if (Responsive.isDesktop(context)) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(50), ), child: Row( children: [ buildTab( "Login with OTP", 0), const SizedBox(width: 10), buildTab( "Login with Password", 1), ], ), ), ], ), SizedBox(height: 10), Container( margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( getLoginHeading(), style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), SizedBox( height: 10, ), Container( margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Text( getLoginDescription(), style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF000000)), textAlign: TextAlign.center, ), ) ], ), ), SizedBox( height: 20, ), if (switcherStatus == 1) ...[ Column( children: [ Container( height: 55, margin: Responsive.isDesktop(context) ? const EdgeInsets.symmetric(horizontal: 150) : const EdgeInsets.symmetric(horizontal: 0), decoration: BoxDecoration( border: Border.all(width: 1, color: Colors.grey), borderRadius: BorderRadius.circular(10), ), child: TextFormField( controller: emailMobileController, keyboardType: TextInputType.text, decoration: const InputDecoration( border: InputBorder.none, hintText: "Email / Mobile Number ", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), validator: (value) { if (value == null || value.trim().isEmpty) { return "Please enter email or mobile number"; } String input = value.trim(); // ❌ Reject all spaces if (input.contains(' ')) { return "No spaces allowed"; } final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$'); final mobileRegex = RegExp(r'^[0-9]{10}$'); bool isEmailFormat = emailRegex.hasMatch(input); bool isMobileFormat = mobileRegex.hasMatch(input); // --------------------------- // 🛑 MOBILE VALIDATION // --------------------------- if (RegExp(r'^[0-9]+$').hasMatch(input)) { if (input.length != 10) { return "Mobile number must be exactly 10 digits"; } } // --------------------------- // 🛑 EMAIL VALIDATION // --------------------------- // Reject anything that has '@' but is NOT a valid email format if (input.contains('@') && !isEmailFormat) { return "Enter a valid email address"; } // Reject email with extra digits at the end if (input.contains('@') && RegExp(r'\d+$').hasMatch(input)) { return "Email cannot contain extra numbers"; } // Reject email+mobile combination if (input.contains('@') && RegExp(r'\d{10}$').hasMatch(input)) { return "Enter only email OR mobile number"; } // --------------------------- // 🛑 MIXED CONTENT (letters + digits but NOT email) // --------------------------- bool hasLetters = RegExp(r'[A-Za-z]').hasMatch(input); bool hasDigits = RegExp(r'[0-9]').hasMatch(input); if ((hasLetters && hasDigits) && !input.contains('@')) { return "Enter only email OR 10-digit mobile number"; } // --------------------------- // 🟢 FINAL CHECK // --------------------------- if (!isEmailFormat && !isMobileFormat) { return "Enter a valid email or 10-digit mobile number"; } return null; } ), ), SizedBox(height: 15), Container( margin: Responsive.isDesktop( context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: SizedBox( width: double.infinity, height: 45, child: ElevatedButton( style: ElevatedButton .styleFrom( backgroundColor: Color(0xFF00989E), shape: RoundedRectangleBorder( borderRadius: BorderRadius .circular(10), ), ), onPressed: _isLoading ? null : verifyMobileAndEmailNumber, child: _isLoading ? CircularProgressIndicator( valueColor: AlwaysStoppedAnimation< Color>( Color(0xFF00989E), ), ) : Text( "Login with Email / Mobile OTP", style: GoogleFonts .poppins( color: Color( 0xFFFFFFFF), ), ), ), ), ), SizedBox(height: 15), // MouseRegion( // cursor: // SystemMouseCursors.click, // child: GestureDetector( // onTap: toggleField, // child: Text( // isEmailFieldVisible // ? "Login with Mobile No" // : "Login with Email", // style: TextStyle( // color: Color(0xFF00989E), // ), // ), // ), // ), ], ), ] else if (switcherStatus == 0) ...[ if (!resetPasswordEnable) Column( children: [ if (!clickedForgotPassword) ...[ Container( height: 55, margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), decoration: BoxDecoration( border: Border.all( width: 1, color: Colors.grey), borderRadius: BorderRadius.circular( 10), ), child: TextFormField( controller: emailController, keyboardType: TextInputType .emailAddress, decoration: InputDecoration( border: InputBorder.none, hintText: "Enter your email", contentPadding: EdgeInsets .symmetric( horizontal: 10), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email'; } if (!RegExp( r'^[^@]+@[^@]+\.[^@]+') .hasMatch(value)) { return 'Please enter a valid email'; } return null; }, ), ), SizedBox(height: 10), Container( height: 55, margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), decoration: BoxDecoration( border: Border.all( width: 1, color: Colors.grey), borderRadius: BorderRadius.circular( 10), ), child: TextFormField( controller: passwordController, obscureText: _obscurePassword, textAlignVertical: TextAlignVertical .center, // ✅ THE FIX decoration: InputDecoration( border: InputBorder.none, hintText: "Enter your password", contentPadding: EdgeInsets .symmetric( horizontal: 10, ), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons .visibility_off : Icons .visibility, color: Colors.grey, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your password'; } // if (value.length < 6) { // return 'Password must be at least 6 characters'; // } // // Optional strong password rule // if (!RegExp( // r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$') // .hasMatch(value)) { // return 'Include at least 1 uppercase letter and 1 number'; // } return null; }, ), ), SizedBox(height: 15), Container( margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Row( mainAxisAlignment: MainAxisAlignment .end, // Align text to the right children: [ InkWell( onTap: () { forgotPassword(); }, child: Text( "Forgot Password?", style: GoogleFonts .poppins( color: Colors .blue), ), ), ], ), ), SizedBox(height: 15), Container( margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: SizedBox( width: double.infinity, height: 40, child: ElevatedButton( style: ElevatedButton .styleFrom( backgroundColor: Color(0xFF00989E), shape: RoundedRectangleBorder( borderRadius: BorderRadius .circular( 10), ), ), onPressed: _isLoading ? null : loginWithUsernameAndPw, child: _isLoading ? CircularProgressIndicator( valueColor: AlwaysStoppedAnimation< Color>( Color( 0xFF00989E), ), ) : Text( "Login", style: GoogleFonts .poppins( color: Color( 0xFFFFFFFF), ), ), ), ), ), ], if (clickedForgotPassword) ...[ Container( height: 55, margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), decoration: BoxDecoration( border: Border.all( width: 1, color: Colors.grey), borderRadius: BorderRadius.circular( 10), ), child: TextFormField( controller: emailController, keyboardType: TextInputType .emailAddress, decoration: InputDecoration( border: InputBorder.none, hintText: "Enter your email", contentPadding: EdgeInsets .symmetric( horizontal: 10), ), readOnly: otpFieldShow, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email'; } if (!RegExp( r'^[^@]+@[^@]+\.[^@]+') .hasMatch(value)) { return 'Please enter a valid email'; } return null; }, ), ), if (otpFieldShow) ...[ SizedBox(height: 10), AnimatedSwitcher( duration: const Duration( milliseconds: 500, ), // animation speed switchInCurve: Curves.easeInOutCirc, switchOutCurve: Curves.easeOutCirc, child: clickedForgotPassword ? Column( key: const ValueKey( 'otp_block'), crossAxisAlignment: CrossAxisAlignment .start, children: [ Container( child: Center( child: Text( 'OTP', style: TextStyle( fontSize: Responsive.isMobile(context) ? 14 : 18, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), ), const SizedBox( height: 10), Container( alignment: Alignment.center, margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Pinput( length: 6, // defaultPinTheme: defaultPinTheme, // focusedPinTheme: focusedPinTheme, // submittedPinTheme: submittedPinTheme, inputFormatters: [ FilteringTextInputFormatter .digitsOnly, // ✅ allows only 0–9 ], keyboardType: TextInputType.number, showCursor: true, controller: _otpController, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter OTP'; } if (value.length < 6) { return 'OTP must be 6 digits'; } // if (otpValueStatus) { // // example // return 'Invalid OTP'; // } return null; }, ), ), const SizedBox( height: 10), Container( margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), alignment: Alignment .centerRight, // center the text child:InkWell( onTap: (){ setState(() { otpFieldShow = false; }); print('ABCDEF'); resendOTP(); }, mouseCursor: SystemMouseCursors.click, child: Text( 'Didn’t Receive Code?', style: GoogleFonts.poppins( color: Colors.blue, fontSize: 14, ), ), ), // RichText( // textAlign: // TextAlign.right, // text: // TextSpan( // text: // 'Didn’t Receive Code? ', // normal text // style: // TextStyle( // fontSize: Responsive.isMobile(context) // ? 12 // : 14, // fontWeight: // FontWeight.normal, // color: // Colors.black, // ), // children: [ // TextSpan( // text: 'Resend', // bold clickable part // style: TextStyle( // fontWeight: FontWeight.bold, // color: Colors.white, // decoration: TextDecoration.underline, // optional // ), // // recognizer: TapGestureRecognizer() // // ..onTap = () { // // // // }, // ), // ], // ), // ), ), ], ) : const SizedBox .shrink(), // Empty widget when false ), ], SizedBox(height: 20), Container( margin: Responsive .isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: SizedBox( width: double.infinity, height: 40, child: ElevatedButton( style: ElevatedButton .styleFrom( backgroundColor: Color(0xFF00989E), shape: RoundedRectangleBorder( borderRadius: BorderRadius .circular( 10), ), ), onPressed: _isLoading ? null : () => { otpFieldShow ? otpVerify() : mailVerify() }, child: _isLoading ? CircularProgressIndicator( valueColor: AlwaysStoppedAnimation< Color>( Color( 0xFF00989E), ), ) : Text( otpFieldShow ? "Verify OTP " : "Verify Mail", style: GoogleFonts .poppins( color: Color( 0xFFFFFFFF), ), ), ), ), ), ], ], ), if (resetPasswordEnable) ...[ Column( children: [ Container( height: 55, margin: Responsive.isDesktop(context) ? const EdgeInsets.symmetric(horizontal: 150) : const EdgeInsets.symmetric(horizontal: 0), decoration: BoxDecoration( border: Border.all(width: 1, color: Colors.grey), borderRadius: BorderRadius.circular(10), ), child: TextFormField( controller: resetPasswordController, obscureText: _resetObscurePassword, onChanged: validatePassword, textAlignVertical: TextAlignVertical.center, decoration: InputDecoration( border: InputBorder.none, hintText: "New Password", contentPadding: const EdgeInsets.symmetric(horizontal: 10), suffixIcon: IconButton( icon: Icon( _resetObscurePassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), onPressed: () { setState(() { _resetObscurePassword = !_resetObscurePassword; }); }, ), ), ), ), const SizedBox(height: 8), // 🔹 VALIDATION LIST Padding( padding: Responsive.isDesktop(context) ? const EdgeInsets.symmetric(horizontal: 150) : const EdgeInsets.symmetric(horizontal: 5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded(child: _buildCheckItem(hasMinLength, "Minimum 8 characters")), Expanded(child: _buildCheckItem(hasSpecialChar, "1 special character")), ], ), Row( children: [ Expanded(child: _buildCheckItem(hasUpperLower, "1 UPPER or lower case")), Expanded(child: _buildCheckItem(hasNumber, "1 numerical")), ], ), ], ), ), const SizedBox(height: 10), Container( height: 55, margin: Responsive.isDesktop(context) ? const EdgeInsets.symmetric(horizontal: 150) : const EdgeInsets.symmetric(horizontal: 0), decoration: BoxDecoration( border: Border.all(width: 1, color: Colors.grey), borderRadius: BorderRadius.circular(10), ), child: TextFormField( controller: confirmPasswordController, obscureText: _obscureConfirmPassword, textAlignVertical: TextAlignVertical.center, decoration: InputDecoration( border: InputBorder.none, hintText: "Confirm Password", contentPadding: const EdgeInsets.symmetric(horizontal: 10), suffixIcon: IconButton( icon: Icon( _obscureConfirmPassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), onPressed: () { setState(() { _obscureConfirmPassword = !_obscureConfirmPassword; }); }, ), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please confirm your password'; } if (value != resetPasswordController.text) { return 'Passwords do not match'; } return null; }, ), ), ], ), SizedBox(height: 15), Container( margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: SizedBox( width: double.infinity, height: 40, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isPasswordValid ? Color(0xFF00989E) : Colors.grey.shade400, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: _isLoading || !isPasswordValid ? null : () { resetYourPassword(); }, child: _isLoading ? CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.white), ) : Text( "Reset Password", style: GoogleFonts.poppins(color: Colors.white), ), ), ), ), ] ], SizedBox( height: _size.width <= 1100 ? 0 : 0, ), SizedBox( height: Responsive.isDesktop(context) ? _size.height * 0.1 : _size.height * 0.2, ), // SizedBox( // height: _size.height * 0.1, // ), Container( alignment: Alignment.bottomCenter, padding: EdgeInsets.symmetric(vertical: 8), child: RichText( textAlign: TextAlign.center, text: TextSpan( text: 'By continuing, you agree with our ', style: GoogleFonts.poppins( color: Colors.black, fontSize: 9, ), children: [ WidgetSpan( child: MouseRegion( cursor: SystemMouseCursors .click, child: GestureDetector( onTap: () { context.go( '/privacypolicy'); // Navigator.pushNamed( // context, // 'privacypolicy'); }, child: Text( 'privacy policy ', style: GoogleFonts.poppins( color: Color(0xFF00989E), fontSize: 9, decoration: TextDecoration .underline, ), ), ), ), ), TextSpan( text: 'and ', style: GoogleFonts.poppins( color: Colors.black, fontSize: 9, ), ), WidgetSpan( child: MouseRegion( cursor: SystemMouseCursors .click, child: GestureDetector( onTap: () { context .go('/termsofuse'); // Navigator.pushNamed( // context, // 'termsofuse'); }, child: Text( 'terms of use', style: GoogleFonts.poppins( color: Color(0xFF00989E), fontSize: 9, decoration: TextDecoration .underline, ), ), ), ), ), ], ), ), ), ], ), ), ), if (_size.width > 1100) Expanded( flex: _size.width < 1100 ? 6 : 12, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { if (constraints.maxWidth > 600) { return Image.asset( 'assets/login_web.jpg', height: _size.height, fit: BoxFit.cover, ); } else { return SizedBox(); } }, ), ), ], ), ], ), ), ), ), ], )), ))); } Widget _buildCheckItem(bool status, String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ Icon( status ? Icons.check : Icons.close, color: status ? Colors.green : Colors.red, size: 18, ), const SizedBox(width: 6), Text( text, style: TextStyle( color: status ? Colors.green : Colors.red, fontSize: 14, ), ), ], ), ); } Widget buildTab(String title, int index) { final isSelected = selectedIndex == index + 1; return GestureDetector( onTap: () { setState(() { selectedIndex = index + 1; if (selectedIndex == 2) { switcherStatus = 0; clickedForgotPassword = false; passwordController.text = ''; passwordController.clear(); resetPasswordController.text = ''; resetPasswordController.clear(); confirmPasswordController.text = ''; confirmPasswordController.clear(); _otpController.text = ''; _otpController.clear(); otpFieldShow = false; otpValueStatus = false; resetPasswordEnable = false; emailController.text = ''; emailController.clear(); _formKey.currentState?.reset(); } else { switcherStatus = 1; emailMobileController.text = ''; emailMobileController.clear(); _formKey.currentState?.reset(); } }); _formKey.currentState?.reset(); }, child: Container( padding: EdgeInsets.only( top: 6.5, bottom: 6.0, left: 40.0, right: 40.0, ), decoration: BoxDecoration( color: isSelected ? const Color(0xFF00989E) : Colors.transparent, boxShadow: isSelected ? [ BoxShadow( color: isSelected ? const Color(0xFF00989E) : Colors.transparent, // Grey shadow spreadRadius: 0.2, blurRadius: 1, offset: const Offset(0, 1), // Horizontal, Vertical ), ] : [], borderRadius: BorderRadius.circular(16), ), child: Text( title, style: GoogleFonts.poppins( fontSize: 12, color: isSelected ? Colors.white : Colors.black, fontWeight: FontWeight.w500, ), ), ), ); } }