diff --git a/lib/data/datasource_impl/contact/phonebook_contact_datasource_impl.dart b/lib/data/datasource_impl/contact/phonebook_contact_datasource_impl.dart index 976cc3273b..fa6a184b67 100644 --- a/lib/data/datasource_impl/contact/phonebook_contact_datasource_impl.dart +++ b/lib/data/datasource_impl/contact/phonebook_contact_datasource_impl.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/data/datasource/phonebook_datasouce.dart'; import 'package:fluffychat/domain/model/contact/contact.dart'; +import 'package:fluffychat/utils/string_extension.dart'; import 'package:flutter_contacts/flutter_contacts.dart' hide Contact; class PhonebookContactDatasourceImpl implements PhonebookContactDatasource { @@ -8,63 +9,75 @@ class PhonebookContactDatasourceImpl implements PhonebookContactDatasource { final phonebookContacts = await FlutterContacts.getContacts(withProperties: true); - final listAllContacts = phonebookContacts + final listPhoneContacts = phonebookContacts .expand( - (contact) => [ - ...contact.emails.map( - (email) => Contact( - email: email.address, - displayName: contact.displayName, - ), + (contact) => contact.phones.map( + (phone) => Contact( + phoneNumber: phone.number, + displayName: contact.displayName, ), - ...contact.phones.map( - (phone) => Contact( - phoneNumber: phone.number, - displayName: contact.displayName, - ), + ), + ) + .toList(); + + final listEmailContacts = phonebookContacts + .expand( + (contact) => contact.emails.map( + (email) => Contact( + email: email.address, + displayName: contact.displayName, ), - ], + ), ) .toList(); - return removeDuplicatedPhoneNumbers(listAllContacts); + final listAllContacts = [ + ..._removeDuplicatedPhoneNumbers(listPhoneContacts), + ...listEmailContacts, + ]; + + return listAllContacts; } - List removeDuplicatedPhoneNumbers(List listContacts) { + List _removeDuplicatedPhoneNumbers(List listContacts) { final listVisitedPhoneNumbers = []; final listFilteredContacts = []; - for (final contact in listContacts) { + final listContactHasPhoneNumber = + listContacts.where((contact) => contact.phoneNumber != null).toList(); + + for (final contact in listContactHasPhoneNumber) { final phoneNumber = contact.phoneNumber; - if (phoneNumber != null) { - final normalizedPhoneNumber = normalizePhoneNumber(phoneNumber); - if (listVisitedPhoneNumbers.contains(normalizedPhoneNumber)) { - final contactsWithSamePhoneNumber = - listFilteredContacts.where((filteredContact) { - final filteredPhoneNumber = filteredContact.phoneNumber; - if (filteredPhoneNumber != null) { - return filteredContact.displayName == contact.displayName && - normalizePhoneNumber(filteredPhoneNumber) == - normalizedPhoneNumber; - } - return false; - }); - if (contactsWithSamePhoneNumber.isEmpty) { - listFilteredContacts.add(contact); - } else { - continue; - } - } else { - listVisitedPhoneNumbers.add(normalizedPhoneNumber); + final normalizedPhoneNumber = phoneNumber!.normalizePhoneNumber(); + + if (listVisitedPhoneNumbers.contains(normalizedPhoneNumber)) { + final hasSameFilteredContact = _hasSameFilteredContact( + listFilteredContacts, + contact.displayName ?? '', + normalizedPhoneNumber, + ); + if (!hasSameFilteredContact) { listFilteredContacts.add(contact); } + } else { + listVisitedPhoneNumbers.add(normalizedPhoneNumber); + listFilteredContacts.add(contact); } } return listFilteredContacts; } - String normalizePhoneNumber(String phoneNumber) { - return phoneNumber.replaceAll(RegExp(r'\D'), ''); + bool _hasSameFilteredContact( + List listFilteredContacts, + String contactName, + String phoneNumberNormalized, + ) { + return listFilteredContacts.any( + (filteredContact) => + filteredContact.displayName == contactName && + filteredContact.phoneNumber?.normalizePhoneNumber() == + phoneNumberNormalized, + ); } } diff --git a/lib/utils/string_extension.dart b/lib/utils/string_extension.dart index 848a25f04c..d93fd8ae7c 100644 --- a/lib/utils/string_extension.dart +++ b/lib/utils/string_extension.dart @@ -337,4 +337,8 @@ extension StringCasingExtension on String { String get sha256Hash { return sha256.convert(utf8.encode(this)).toString(); } + + String normalizePhoneNumber() { + return replaceAll(RegExp(r'\D'), ''); + } } diff --git a/test/data/datasource_impl/contact/phonebook_contact_datasource_impl_test.dart b/test/data/datasource_impl/contact/phonebook_contact_datasource_impl_test.dart new file mode 100644 index 0000000000..18f7809176 --- /dev/null +++ b/test/data/datasource_impl/contact/phonebook_contact_datasource_impl_test.dart @@ -0,0 +1,190 @@ +import 'package:fluffychat/data/datasource_impl/contact/phonebook_contact_datasource_impl.dart'; +import 'package:fluffychat/domain/model/contact/contact.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late PhonebookContactDatasourceImpl dataSource; + const MethodChannel channel = + MethodChannel('github.com/QuisApp/flutter_contacts'); + + group('[PhonebookContactDatasourceImpl]', () { + final listAllContacts = [ + { + 'displayName': 'Alice', + 'phones': [ + {'number': '(212)555-6789'}, + {'number': '2125556789'}, + ], + }, + { + 'displayName': 'Bob', + 'phones': [ + {'number': '2124678190'}, + {'number': '(212)467-8190'}, + ], + }, + { + 'displayName': 'Charlie', + 'phones': [ + {'number': '212 555-6789'}, + {'number': '2125556789'}, + ], + }, + { + 'displayName': 'David', + 'phones': [ + {'number': '2124678190'}, + {'number': '212 467-8190'}, + ], + }, + { + 'displayName': 'Eve', + 'phones': [ + {'number': '+1.123.456.7890'}, + {'number': '11234567890'}, + ], + }, + { + 'displayName': 'Frank', + 'phones': [ + {'number': '81234977890'}, + {'number': '+8.123.497.7890'}, + ], + }, + { + 'displayName': 'Grace', + 'phones': [ + {'number': '+1 (800)-555-1234 ext. 123'}, + {'number': '18005551234123'}, + ], + }, + { + 'displayName': 'Hank', + 'phones': [ + {'number': '18005879106234'}, + {'number': '+1 (800)-587-9106 ext. 234'}, + ], + }, + { + 'displayName': 'Ivy', + 'phones': [ + {'number': '+1 (800)-555.1234'}, + {'number': '18005551234'}, + ], + }, + { + 'displayName': 'Karl', + 'phones': [ + {'number': '18005873456'}, + {'number': '+1 (800)-587.3456'}, + ], + }, + { + 'displayName': 'Liam', + 'phones': [ + {'number': '(212) 555-6789'}, + {'number': '2125556789'}, + ], + }, + { + 'displayName': 'Mia', + 'phones': [ + {'number': '2125556789'}, + {'number': '(212) 555-6789'}, + ], + }, + { + 'displayName': 'Nina', + 'emails': [ + { + 'address': 'nina@domain.com', + } + ], + } + ]; + + setUp(() { + final getIt = GetIt.instance; + getIt.registerFactory( + () => PhonebookContactDatasourceImpl(), + ); + dataSource = getIt.get(); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + if (methodCall.method == 'select') { + return listAllContacts; + } + return null; + }); + }); + + test( + 'fetchContacts should return a list of contacts without duplicated phone number', + () async { + final List expectedListContact = [ + const Contact( + displayName: 'Alice', + phoneNumber: '(212)555-6789', + ), + const Contact( + displayName: 'Bob', + phoneNumber: '2124678190', + ), + const Contact( + displayName: 'Charlie', + phoneNumber: '212 555-6789', + ), + const Contact( + displayName: 'David', + phoneNumber: '2124678190', + ), + const Contact( + displayName: 'Eve', + phoneNumber: '+1.123.456.7890', + ), + const Contact( + displayName: 'Frank', + phoneNumber: '81234977890', + ), + const Contact( + displayName: 'Grace', + phoneNumber: '+1 (800)-555-1234 ext. 123', + ), + const Contact( + displayName: 'Hank', + phoneNumber: '18005879106234', + ), + const Contact( + displayName: 'Ivy', + phoneNumber: '+1 (800)-555.1234', + ), + const Contact( + displayName: 'Karl', + phoneNumber: '18005873456', + ), + const Contact( + displayName: 'Liam', + phoneNumber: '(212) 555-6789', + ), + const Contact( + displayName: 'Mia', + phoneNumber: '2125556789', + ), + const Contact( + displayName: 'Nina', + email: 'nina@domain.com', + ), + ]; + + final result = await dataSource.fetchContacts(); + + expect(result, isA>()); + expect(result, equals(expectedListContact)); + }); + }); +} diff --git a/test/utils/string_extension_test.dart b/test/utils/string_extension_test.dart new file mode 100644 index 0000000000..76453a8b4d --- /dev/null +++ b/test/utils/string_extension_test.dart @@ -0,0 +1,52 @@ +import 'package:fluffychat/utils/string_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('[normalizePhoneNumber]', () { + test('should return phone number without any Hyphens or Parentheses', () { + const phoneNumber = '(212)555-6789'; + const expectedPhoneNumber = '2125556789'; + + final result = phoneNumber.normalizePhoneNumber(); + + expect(result, equals(expectedPhoneNumber)); + }); + + test('should return phone number without any Spaces or Dashes', () { + const phoneNumber = '212 555-6789'; + const expectedPhoneNumber = '2125556789'; + + final result = phoneNumber.normalizePhoneNumber(); + + expect(result, equals(expectedPhoneNumber)); + }); + + test('should return phone number without any Plus Sign or Dots', () { + const phoneNumber = '+1.123.456.7890'; + const expectedPhoneNumber = '11234567890'; + + final result = phoneNumber.normalizePhoneNumber(); + + expect(result, equals(expectedPhoneNumber)); + }); + + test('should return phone number without any Country Code or Extension', + () { + const phoneNumber = '+1 (800)-555-1234 ext. 123'; + const expectedPhoneNumber = '18005551234123'; + + final result = phoneNumber.normalizePhoneNumber(); + + expect(result, equals(expectedPhoneNumber)); + }); + + test('should return phone number without any special characters', () { + const phoneNumber = '+1 (800)-555.1234 ext. 325'; + const expectedPhoneNumber = '18005551234325'; + + final result = phoneNumber.normalizePhoneNumber(); + + expect(result, equals(expectedPhoneNumber)); + }); + }); +}