diff --git a/whatsapp_app/ios/Runner/Info.plist b/whatsapp_app/ios/Runner/Info.plist
index bb91378..ad4f66e 100644
--- a/whatsapp_app/ios/Runner/Info.plist
+++ b/whatsapp_app/ios/Runner/Info.plist
@@ -66,5 +66,7 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ NSContactsUsageDescription
+ This app requires contacts access to match phone numbers with your local address book names.
diff --git a/whatsapp_app/lib/controllers/conversations_controller.dart b/whatsapp_app/lib/controllers/conversations_controller.dart
index 7a6c3a6..5856a2f 100644
--- a/whatsapp_app/lib/controllers/conversations_controller.dart
+++ b/whatsapp_app/lib/controllers/conversations_controller.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../services/whatsapp_service.dart';
+import '../services/contacts_service.dart';
import '../models/conversation_model.dart';
class ConversationsController extends GetxController {
@@ -49,6 +50,17 @@ class ConversationsController extends GetxController {
super.onClose();
}
+ // Helper to resolve contact names from the local address book
+ ConversationModel _resolveContactNames(ConversationModel c) {
+ if (c.isGroup) return c; // Skip group chats
+
+ final parts = c.id.split('@');
+ final phoneNumber = parts[0];
+
+ final matchedName = Get.find().getContactName(phoneNumber, c.name);
+ return c.copyWith(name: matchedName);
+ }
+
// ── Local Caching ────────────────────────────────────────────────────────
Future _loadCachedConversations() async {
try {
@@ -56,7 +68,7 @@ class ConversationsController extends GetxController {
final cached = prefs.getString('cached_conversations');
if (cached != null) {
final List decoded = jsonDecode(cached);
- conversations.assignAll(decoded.map((c) => ConversationModel.fromJson(c as Map)));
+ conversations.assignAll(decoded.map((c) => _resolveContactNames(ConversationModel.fromJson(c as Map))));
print('[CACHE] Loaded ${conversations.length} conversations instantly.');
}
} catch (e) {
@@ -85,7 +97,7 @@ class ConversationsController extends GetxController {
final res = await _svc.getConversations();
if (res['type'] == 'conversations') {
final List data = res['data'] ?? [];
- conversations.assignAll(data.map((c) => ConversationModel.fromJson(c as Map)));
+ conversations.assignAll(data.map((c) => _resolveContactNames(ConversationModel.fromJson(c as Map))));
_saveConversationsToCache(data);
} else {
errorMessage.value = res['message'] ?? 'Failed to load conversations';
@@ -189,7 +201,7 @@ class ConversationsController extends GetxController {
final res = await _svc.getConversations();
if (res['type'] == 'conversations') {
final List data = res['data'] ?? [];
- conversations.assignAll(data.map((c) => ConversationModel.fromJson(c as Map)));
+ conversations.assignAll(data.map((c) => _resolveContactNames(ConversationModel.fromJson(c as Map))));
_saveConversationsToCache(data);
}
} catch (_) {}
diff --git a/whatsapp_app/lib/main.dart b/whatsapp_app/lib/main.dart
index bc18c9b..8ee755e 100644
--- a/whatsapp_app/lib/main.dart
+++ b/whatsapp_app/lib/main.dart
@@ -4,6 +4,7 @@ import 'package:get/get.dart';
import 'package:firebase_core/firebase_core.dart';
import 'services/whatsapp_service.dart';
import 'services/firebase_service.dart';
+import 'services/contacts_service.dart';
import 'screens/conversations_screen.dart';
import 'theme/app_theme.dart';
@@ -23,8 +24,12 @@ void main() async {
));
// Register services before app starts
+ Get.put(ContactsService(), permanent: true);
Get.put(WhatsAppService(), permanent: true);
Get.put(FirebaseService(), permanent: true);
+
+ // Initialize Contacts Service
+ await Get.find().init();
Get.find().init();
runApp(const WhatsAppApp());
diff --git a/whatsapp_app/lib/services/contacts_service.dart b/whatsapp_app/lib/services/contacts_service.dart
new file mode 100644
index 0000000..197c641
--- /dev/null
+++ b/whatsapp_app/lib/services/contacts_service.dart
@@ -0,0 +1,85 @@
+import 'package:flutter_contacts/flutter_contacts.dart';
+import 'package:get/get.dart';
+
+class ContactInfo {
+ final String name;
+ final String? avatarPath;
+ ContactInfo({required this.name, this.avatarPath});
+}
+
+class ContactsService extends GetxService {
+ final RxMap _contactsMap = {}.obs;
+ final RxBool permissionGranted = false.obs;
+
+ Future init() async {
+ await fetchContacts();
+ return this;
+ }
+
+ Future fetchContacts() async {
+ try {
+ // Check and request permission
+ bool permission = await FlutterContacts.requestPermission(readonly: true);
+ permissionGranted.value = permission;
+
+ if (permission) {
+ // Fetch contacts with photos and phone numbers
+ final contacts = await FlutterContacts.getContacts(withProperties: true, withPhoto: true);
+
+ final Map tempMap = {};
+ for (var contact in contacts) {
+ final fullName = contact.displayName;
+ if (fullName.isEmpty) continue;
+
+ for (var phone in contact.phones) {
+ final normalized = normalizePhoneNumber(phone.number);
+ if (normalized.isNotEmpty) {
+ tempMap[normalized] = ContactInfo(
+ name: fullName,
+ avatarPath: null, // Custom local avatar path can be handled if needed
+ );
+ }
+ }
+ }
+ _contactsMap.assignAll(tempMap);
+ print('[CONTACTS] Successfully loaded ${_contactsMap.length} normalized phone contacts');
+ }
+ } catch (e) {
+ print('[CONTACTS ERROR] Failed to fetch system contacts: $e');
+ }
+ }
+
+ // Normalizes numbers to match them easily (e.g., removes spaces, dashes, brackets, and leading zeros)
+ String normalizePhoneNumber(String number) {
+ String clean = number.replaceAll(RegExp(r'[\s\-\(\)\+]'), '');
+ // If it starts with local country prefix or leading 0, we can do substring matches
+ if (clean.startsWith('00')) {
+ clean = clean.substring(2);
+ }
+ return clean;
+ }
+
+ String getContactName(String rawNumber, String fallback) {
+ if (!permissionGranted.value || _contactsMap.isEmpty) return fallback;
+
+ final clean = normalizePhoneNumber(rawNumber);
+ if (clean.isEmpty) return fallback;
+
+ // Direct match
+ if (_contactsMap.containsKey(clean)) {
+ return _contactsMap[clean]!.name;
+ }
+
+ // Partial match for varying country codes (match last 9 digits of the phone number)
+ if (clean.length >= 9) {
+ final suffix = clean.substring(clean.length - 9);
+ for (var key in _contactsMap.keys) {
+ if (key.endsWith(suffix)) {
+ return _contactsMap[key]!.name;
+ }
+ }
+ }
+
+ return fallback;
+ }
+}
diff --git a/whatsapp_app/lib/services/whatsapp_service.dart b/whatsapp_app/lib/services/whatsapp_service.dart
index f9091ed..a2e353a 100644
--- a/whatsapp_app/lib/services/whatsapp_service.dart
+++ b/whatsapp_app/lib/services/whatsapp_service.dart
@@ -215,6 +215,28 @@ class WhatsAppService extends GetxService {
Future