diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c45876e..ec90481 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ + + + + files = []; - MyFormValidator basicValidator = MyFormValidator(); + final MyFormValidator basicValidator = MyFormValidator(); Gender? selectedGender; List> roles = []; String? selectedRoleId; @@ -59,11 +61,18 @@ class AddEmployeeController extends MyController { }; String selectedCountryCode = "+91"; + bool showOnline = true; + final List categories = []; + @override void onInit() { super.onInit(); logger.i("Initializing AddEmployeeController..."); + _initializeFields(); fetchRoles(); + } + + void _initializeFields() { basicValidator.addField( 'first_name', label: "First Name", @@ -85,10 +94,6 @@ class AddEmployeeController extends MyController { logger.i("Fields initialized for first_name, phone_number, last_name."); } - bool showOnline = true; - - final List categories = []; - void onGenderSelected(Gender? gender) { selectedGender = gender; logger.i("Gender selected: ${gender?.name}"); @@ -97,13 +102,17 @@ class AddEmployeeController extends MyController { Future fetchRoles() async { logger.i("Fetching roles..."); - final result = await ApiService.getRoles(); - if (result != null) { - roles = List>.from(result); - logger.i("Roles fetched successfully."); - update(); - } else { - logger.e("Failed to fetch roles."); + try { + final result = await ApiService.getRoles(); + if (result != null) { + roles = List>.from(result); + logger.i("Roles fetched successfully."); + update(); + } else { + logger.e("Failed to fetch roles: null result"); + } + } catch (e, st) { + logger.e("Error fetching roles: $e", error: e, stackTrace: st); } } @@ -133,30 +142,149 @@ class AddEmployeeController extends MyController { logger.i( "Creating employee with Name: $firstName $lastName, Phone: $phoneNumber, Gender: ${selectedGender!.name}"); - final response = await ApiService.createEmployee( - firstName: firstName!, - lastName: lastName!, - phoneNumber: phoneNumber!, - gender: selectedGender!.name, - jobRoleId: selectedRoleId!, - ); - - if (response == true) { - logger.i("Employee created successfully."); - showAppSnackbar( - title: "Success", - message: "Employee created successfully!", - type: SnackbarType.success, + try { + final response = await ApiService.createEmployee( + firstName: firstName!, + lastName: lastName!, + phoneNumber: phoneNumber!, + gender: selectedGender!.name, + jobRoleId: selectedRoleId!, ); + + if (response == true) { + logger.i("Employee created successfully."); + showAppSnackbar( + title: "Success", + message: "Employee created successfully!", + type: SnackbarType.success, + ); + return true; + } else { + logger.e("Failed to create employee (response false)."); + } + } catch (e, st) { + logger.e("Error creating employee: $e", error: e, stackTrace: st); + } + + showAppSnackbar( + title: "Error", + message: "Failed to create employee.", + type: SnackbarType.error, + ); + return false; + } + + Future _checkAndRequestContactsPermission() async { + final status = await Permission.contacts.request(); + + if (status.isGranted) { return true; - } else { - logger.e("Failed to create employee."); + } + + if (status.isPermanentlyDenied) { + await openAppSettings(); + } + + showAppSnackbar( + title: "Permission Required", + message: + "Please allow Contacts permission from settings to pick a contact.", + type: SnackbarType.warning, + ); + return false; + } + + Future pickContact(BuildContext context) async { + final permissionGranted = await _checkAndRequestContactsPermission(); + + if (!permissionGranted) return; + + try { + final picked = await FlutterContacts.openExternalPick(); + + if (picked == null) return; // User canceled contact picking + + final contact = + await FlutterContacts.getContact(picked.id, withProperties: true); + + if (contact == null) { + showAppSnackbar( + title: "Error", + message: "Failed to load contact details.", + type: SnackbarType.error, + ); + return; + } + + if (contact.phones.isEmpty) { + showAppSnackbar( + title: "No Phone Number", + message: "Selected contact has no phone number.", + type: SnackbarType.warning, + ); + return; + } + final indiaPhones = contact.phones.where((p) { + final normalized = p.number.replaceAll(RegExp(r'[^0-9+]'), ''); + return normalized.startsWith('+91') || + RegExp(r'^\d{10}$').hasMatch(normalized); + }).toList(); + + if (indiaPhones.isEmpty) { + showAppSnackbar( + title: "No Indian Number", + message: "Selected contact has no Indian (+91) phone number.", + type: SnackbarType.warning, + ); + return; + } + + String? selectedPhone; + + if (indiaPhones.length == 1) { + selectedPhone = indiaPhones.first.number; + } else { + selectedPhone = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text("Choose an Indian number"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: indiaPhones + .map((p) => ListTile( + title: Text(p.number), + onTap: () => Navigator.of(ctx).pop(p.number), + )) + .toList(), + ), + ), + ); + + if (selectedPhone == null) return; + } + final normalizedPhone = selectedPhone.replaceAll(RegExp(r'[^0-9]'), ''); + +// Remove country code prefix if present, keep only 10 digits + String phoneWithoutCountryCode; + + if (normalizedPhone.length > 10) { + phoneWithoutCountryCode = + normalizedPhone.substring(normalizedPhone.length - 10); + } else { + phoneWithoutCountryCode = normalizedPhone; + } + + basicValidator.getController('phone_number')?.text = + phoneWithoutCountryCode; + + update(); + } catch (e, st) { + logger.e("Error fetching contacts: $e", error: e, stackTrace: st); showAppSnackbar( title: "Error", - message: "Failed to create employee.", + message: "Failed to fetch contacts.", type: SnackbarType.error, ); - return false; } } } diff --git a/lib/model/employees/add_employee_bottom_sheet.dart b/lib/model/employees/add_employee_bottom_sheet.dart index 4302d7a..c690514 100644 --- a/lib/model/employees/add_employee_bottom_sheet.dart +++ b/lib/model/employees/add_employee_bottom_sheet.dart @@ -214,7 +214,17 @@ class _AddEmployeeBottomSheetState extends State }, keyboardType: TextInputType.phone, decoration: - _inputDecoration("eg: 9876543210"), + _inputDecoration("eg: 9876543210") + .copyWith( + suffixIcon: IconButton( + icon: Icon(Icons.contacts), + tooltip: "Pick from contacts", + onPressed: () async { + await controller + .pickContact(context); + }, + ), + ), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 91df0dd..0c536d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: path_provider: ^2.1.2 path: ^1.9.0 percent_indicator: ^4.2.2 + flutter_contacts: ^1.1.9+2 dev_dependencies: flutter_test: sdk: flutter