2215 lines
114 KiB
Dart
Executable File
2215 lines
114 KiB
Dart
Executable File
// 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<login> createState() => _loginState();
|
||
}
|
||
|
||
class _loginState extends State<login> {
|
||
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<FormState>();
|
||
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<void> 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<void> 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<String, dynamic> 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<void> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<void> _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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<void> 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<String, dynamic> 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<void> forgotPassword() async {
|
||
setState(() {
|
||
clickedForgotPassword = true;
|
||
passwordController.text = '';
|
||
passwordController.clear();
|
||
});
|
||
}
|
||
|
||
Future<void> 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<String, dynamic> 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<String, dynamic> 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<void> 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<String, dynamic> 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<String, dynamic> 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<void> 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<String, dynamic> 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<String, dynamic> 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<void> 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<String, dynamic> 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<String, dynamic> 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<Color>(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: <InlineSpan>[
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} |