Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .yarnrc.yml

This file was deleted.

81 changes: 41 additions & 40 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {Colors, Header} from 'react-native/Libraries/NewAppScreen';

import {NitroContact} from './modules/contact/src';

import Contacts from '@s77rt/react-native-contacts';
// import Contacts from '@s77rt/react-native-contacts';

function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
Expand All @@ -27,52 +27,53 @@ function App(): React.JSX.Element {

function fetchContacts() {
const startTime = performance.now();
NitroContact.getAll(['FIRST_NAME', 'LAST_NAME', 'PHONE_NUMBERS', 'EMAIL_ADDRESSES'])
.then(contacts => {
const endTime = performance.now();
const duration = endTime - startTime;
// NitroContact.getAll(['FIRST_NAME', 'LAST_NAME', 'PHONE_NUMBERS', 'EMAIL_ADDRESSES'])
NitroContact.getAll()
// .then(contacts => {
// const endTime = performance.now();
// const duration = endTime - startTime;

console.log(
`Fetched ${contacts.length} contacts. Time taken: ${duration.toFixed(
2,
)} milliseconds`,
);
setData(duration);
setCount(contacts.length);
})
.catch(error => {
const endTime = performance.now();
const duration = endTime - startTime;
// console.log(
// `Fetched ${contacts.length} contacts. Time taken: ${duration.toFixed(
// 2,
// )} milliseconds`,
// );
// setData(duration);
// setCount(contacts.length);
// })
// .catch(error => {
// const endTime = performance.now();
// const duration = endTime - startTime;

console.error(
`Error occurred after ${duration.toFixed(2)} milliseconds:`,
error,
);
});
// console.error(
// `Error occurred after ${duration.toFixed(2)} milliseconds:`,
// error,
// );
// });
}
function fetchContactsCompare() {
const startTime = performance.now();
Contacts.getAll(['firstName', 'lastName', 'phoneNumbers', 'emailAddresses'])
.then(contacts => {
const endTime = performance.now();
const duration = endTime - startTime;
// Contacts.getAll(['firstName', 'lastName', 'phoneNumbers', 'emailAddresses'])
// .then(contacts => {
// const endTime = performance.now();
// const duration = endTime - startTime;

console.log(
`Fetched ${contacts.length} contacts. Time taken: ${duration.toFixed(
2,
)} milliseconds`,
);
setData(duration);
})
.catch(error => {
const endTime = performance.now();
const duration = endTime - startTime;
// console.log(
// `Fetched ${contacts.length} contacts. Time taken: ${duration.toFixed(
// 2,
// )} milliseconds`,
// );
// setData(duration);
// })
// .catch(error => {
// const endTime = performance.now();
// const duration = endTime - startTime;

console.error(
`Error occurred after ${duration.toFixed(2)} milliseconds:`,
error,
);
});
// console.error(
// `Error occurred after ${duration.toFixed(2)} milliseconds:`,
// error,
// );
// });
}

return (
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ react {

/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
nodeExecutableAndArgs = ["/Users/terryperun/.nvm/versions/node/v20.15.1/bin/node"]
// nodeExecutableAndArgs = ["/Users/terryperun/.nvm/versions/node/v20.15.1/bin/node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,181 +12,11 @@ import com.margelo.nitro.core.Promise

class HybridContact : HybridContactSpec() {
override val memorySize: Long
get() = estimateMemorySize()
get() = 0

private val context: ReactApplicationContext? = NitroModules.applicationContext

fun requestContactPermission(): Boolean {
val currentActivity = context?.currentActivity
if (currentActivity != null) {
ActivityCompat.requestPermissions(
currentActivity,
arrayOf(REQUIRED_PERMISSION),
PERMISSION_REQUEST_CODE
)
return true
}
return false
}

private fun hasPhoneContactsPermission(): Boolean {
if (context == null) {
return false
}

val permissionStatus = ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CONTACTS
)

return permissionStatus == PackageManager.PERMISSION_GRANTED
}

override fun getAll(keys: Array<ContactFields>): Promise<Array<ContactData>> {
return Promise.async {
if (!hasPhoneContactsPermission()) {
requestContactPermission()
throw Exception("Contact permission not granted")
}

val startTime = System.currentTimeMillis()
val contacts = mutableListOf<ContactData>()

context?.contentResolver?.let { resolver ->
val projection = arrayOf(
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_URI,
ContactsContract.Contacts.PHOTO_THUMBNAIL_URI,
ContactsContract.Data.DATA1,
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
)

val selection = StringBuilder()
val selectionArgs = mutableListOf<String>()

selectionArgs.addAll(listOf(
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
))

selection.append("${ContactsContract.Data.MIMETYPE} IN (?, ?, ?)")

val sortOrder = "${ContactsContract.Data.CONTACT_ID} ASC"

val cursor = resolver.query(
ContactsContract.Data.CONTENT_URI,
projection,
selection.toString(),
selectionArgs.toTypedArray(),
sortOrder
)

cursor?.use {
val mimeTypeIndex = it.getColumnIndex(ContactsContract.Data.MIMETYPE)
val contactIdIndex = it.getColumnIndex(ContactsContract.Data.CONTACT_ID)
val photoUriIndex = it.getColumnIndex(ContactsContract.Contacts.PHOTO_URI)
val thumbnailUriIndex = it.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)
val data1Index = it.getColumnIndex(ContactsContract.Data.DATA1)
val givenNameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
val familyNameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
val middleNameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)

var currentContact: ContactData? = null
var currentContactId: String? = null
val currentPhoneNumbers = mutableListOf<StringHolder>()
val currentEmailAddresses = mutableListOf<StringHolder>()

while (it.moveToNext()) {
val contactId = it.getString(contactIdIndex)
val mimeType = it.getString(mimeTypeIndex)

if (contactId != currentContactId) {
currentContact?.let { contact ->
contacts.add(contact.copy(
phoneNumbers = currentPhoneNumbers.toTypedArray(),
emailAddresses = currentEmailAddresses.toTypedArray()
))
}
currentPhoneNumbers.clear()
currentEmailAddresses.clear()
currentContact = ContactData(
firstName = "",
lastName = "",
middleName = null,
phoneNumbers = emptyArray(),
emailAddresses = emptyArray(),
imageData = it.getString(photoUriIndex) ?: "",
thumbnailImageData = it.getString(thumbnailUriIndex) ?: ""
)
currentContactId = contactId
}

when (mimeType) {
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
currentContact = currentContact?.copy(
firstName = it.getString(givenNameIndex) ?: "",
lastName = it.getString(familyNameIndex) ?: "",
middleName = it.getString(middleNameIndex)
)
}
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
it.getString(data1Index)?.let { phone ->
currentPhoneNumbers.add(StringHolder(phone))
}
}
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> {
it.getString(data1Index)?.let { email ->
currentEmailAddresses.add(StringHolder(email))
}
}
}
}
currentContact?.let { contact ->
contacts.add(contact.copy(
phoneNumbers = currentPhoneNumbers.toTypedArray(),
emailAddresses = currentEmailAddresses.toTypedArray()
))
}
}
}

val endTime = System.currentTimeMillis()
val executionTime = endTime - startTime
Log.d("HybridContact", "getAll execution time: $executionTime ms")
Log.d("HybridContact", "Total contacts retrieved: ${contacts.size}")

contacts.toTypedArray()
}
}

private fun estimateMemorySize(): Long {
// This is a rough estimate. You might want to implement a more accurate calculation based on actual data.
return 1024 * 1024 // 1MB as a placeholder
}

companion object {
const val PERMISSION_REQUEST_CODE = 1
const val REQUIRED_PERMISSION = Manifest.permission.READ_CONTACTS

fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
onGranted: () -> Unit,
onDenied: () -> Unit
) {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
onGranted()
} else {
onDenied()
}
}
}
override fun getAll(): Boolean {
Log.d("Module", "Hello nitro")
return true
}
}
Loading