Merge pull request 'feat: Add contact picking functionality in employee form and update dependencies' (#45) from Vaibhav_Enhancement-#454 into main

Reviewed-on: #45
This commit is contained in:
vaibhav.surve 2025-06-04 10:44:11 +00:00
commit 3926e762e5
4 changed files with 174 additions and 31 deletions

View File

@ -1,6 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<application
android:label="marco"

View File

@ -5,6 +5,8 @@ import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/services/api_service.dart';
import 'package:logger/logger.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:permission_handler/permission_handler.dart';
enum Gender {
male,
@ -18,7 +20,7 @@ final Logger logger = Logger();
class AddEmployeeController extends MyController {
List<PlatformFile> files = [];
MyFormValidator basicValidator = MyFormValidator();
final MyFormValidator basicValidator = MyFormValidator();
Gender? selectedGender;
List<Map<String, dynamic>> roles = [];
String? selectedRoleId;
@ -59,11 +61,18 @@ class AddEmployeeController extends MyController {
};
String selectedCountryCode = "+91";
bool showOnline = true;
final List<String> 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<String> categories = [];
void onGenderSelected(Gender? gender) {
selectedGender = gender;
logger.i("Gender selected: ${gender?.name}");
@ -97,13 +102,17 @@ class AddEmployeeController extends MyController {
Future<void> fetchRoles() async {
logger.i("Fetching roles...");
try {
final result = await ApiService.getRoles();
if (result != null) {
roles = List<Map<String, dynamic>>.from(result);
logger.i("Roles fetched successfully.");
update();
} else {
logger.e("Failed to fetch roles.");
logger.e("Failed to fetch roles: null result");
}
} catch (e, st) {
logger.e("Error fetching roles: $e", error: e, stackTrace: st);
}
}
@ -133,6 +142,7 @@ class AddEmployeeController extends MyController {
logger.i(
"Creating employee with Name: $firstName $lastName, Phone: $phoneNumber, Gender: ${selectedGender!.name}");
try {
final response = await ApiService.createEmployee(
firstName: firstName!,
lastName: lastName!,
@ -150,7 +160,12 @@ class AddEmployeeController extends MyController {
);
return true;
} else {
logger.e("Failed to create employee.");
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.",
@ -158,5 +173,118 @@ class AddEmployeeController extends MyController {
);
return false;
}
Future<bool> _checkAndRequestContactsPermission() async {
final status = await Permission.contacts.request();
if (status.isGranted) {
return true;
}
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<void> 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<String>(
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 fetch contacts.",
type: SnackbarType.error,
);
}
}
}

View File

@ -214,7 +214,17 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
},
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);
},
),
),
),
],
),

View File

@ -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