NTP-17778 | Add batch fetching of contacts (#13960)

This commit is contained in:
Kishan Kumar
2024-12-04 21:23:19 +05:30
committed by GitHub
parent b210794863
commit 424e3017cf
3 changed files with 252 additions and 25 deletions

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -26,6 +27,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -41,6 +43,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color.Companion.White
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
@@ -54,6 +57,7 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import co.hyperverge.hypersnapsdk.R as HyperSnapR
import com.navi.common.R as CommonR
import com.navi.common.commoncomposables.utils.loadingShimmerEffect
import com.navi.common.utils.shareutil.ContentSharingManager
import com.navi.design.R as DesignR
import com.navi.design.font.FontWeightEnum
@@ -64,6 +68,7 @@ import com.navi.naviwidgets.R as WidgetsR
import com.navi.naviwidgets.models.ShareData
import com.navi.rr.R
import com.navi.rr.referral.models.ReferralContact
import com.navi.rr.referral.vm.ContactState
import com.navi.rr.referral.vm.ReferralContactsBottomSheetVM
import com.navi.rr.utils.NaviRRAnalytics
import com.navi.rr.utils.constants.Constants
@@ -87,10 +92,168 @@ fun ReferralContactBottomSheetRenderer(
) {
val searchText by viewModel.searchText.collectAsState()
val contacts by viewModel.listOfContacts.collectAsState()
val contactState by viewModel.contactState.collectAsState()
var isValid by remember { mutableStateOf(true) }
var isClickable by remember { mutableStateOf(false) }
val context = LocalContext.current
val screenHeight = (LocalConfiguration.current.screenHeightDp / 1.8).dp
fun LazyListScope.NoContactFoundRenderer(contacts: List<ReferralContact>, searchText: String) {
if (contacts.isEmpty()) {
if (searchText.matches(Regex(NUMBER_REGEX_WITHOUT_COUNTRY_CODE))) {
item {
Contacts(
name = searchText,
phoneNum = "",
clickable = isClickable,
isValid = isValid,
isEmptyList = true,
onClick = {
if (isClickable) {
handleContactClickOnManualSearch(
searchText,
context,
message,
imageUrl,
shareableLink
)
}
}
)
}
isClickable = false
isValid = true
if (searchText.length == CONTACT_NUMBER_LENGTH) {
isClickable = true
isValid = true
} else if (searchText.length > CONTACT_NUMBER_LENGTH) {
isClickable = false
isValid = false
}
} else if (searchText.matches(Regex(NUMBER_REGEX_WITH_ZERO))) {
item {
Contacts(
name = searchText,
phoneNum = "",
clickable = isClickable,
isValid = isValid,
isEmptyList = true,
onClick = {
if (isClickable) {
handleContactClickOnManualSearch(
searchText,
context,
message,
imageUrl,
shareableLink
)
}
}
)
}
isClickable = false
isValid = true
if (searchText.length == CONTACT_NUMBER_LENGTH_WITH_ZERO) {
isClickable = true
isValid = true
} else if (searchText.length > CONTACT_NUMBER_LENGTH_WITH_ZERO) {
isClickable = false
isValid = false
}
} else if (searchText.matches(Regex(NUMBER_REGEX_WITH_COUNTRY_CODE))) {
item {
Contacts(
name = searchText,
phoneNum = "",
clickable = isClickable,
isValid = isValid,
isEmptyList = true,
onClick = {
if (isClickable) {
handleContactClickOnManualSearch(
searchText,
context,
message,
imageUrl,
shareableLink
)
}
}
)
}
isClickable = false
isValid = true
if (searchText.length == CONTACT_NUMBER_LENGTH_WITH_COUNTRY_CODE) {
isClickable = true
isValid = true
} else if (searchText.length > CONTACT_NUMBER_LENGTH_WITH_COUNTRY_CODE) {
isClickable = false
isValid = false
}
} else {
item {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.no_contact_found_image),
contentDescription = "",
modifier = Modifier.size(168.dp)
)
Text(
text = stringResource(id = R.string.contacts_book_empty_state_title),
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp, start = 54.dp, end = 54.dp),
fontSize = 12.sp,
color = colorResource(id = DesignR.color.color_6B6B6B),
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
letterSpacing = 0.01.sp,
lineHeight = 16.sp
)
}
}
}
}
}
@Composable
fun ContactShimmerItem() {
Row(
modifier = Modifier.padding(vertical = 12.dp).fillMaxWidth().height(40.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier =
Modifier.padding(start = 16.dp)
.size(32.dp)
.clip(shape = CircleShape)
.loadingShimmerEffect()
)
Column(
modifier = Modifier.wrapContentHeight().padding(start = 8.dp),
verticalArrangement = Arrangement.Center
) {
Box(
modifier =
Modifier.height(20.dp)
.width(248.dp)
.clip(RoundedCornerShape(2.dp))
.loadingShimmerEffect()
)
Spacer(modifier = Modifier.height(2.dp))
Box(
modifier =
Modifier.height(20.dp)
.width(66.dp)
.clip(RoundedCornerShape(2.dp))
.loadingShimmerEffect()
)
}
}
}
Column(Modifier.fillMaxWidth().background(White).height(screenHeight)) {
Text(
text = stringResource(id = R.string.contacts_book_bottom_sheet_title),
@@ -124,29 +287,38 @@ fun ReferralContactBottomSheetRenderer(
)
}
LazyColumn(modifier = Modifier.fillMaxWidth().weight(0.5f).padding(top = 20.dp)) {
if (contacts.isEmpty().not()) {
items(contacts) { contact ->
Contacts(
name = contact.name.orEmpty(),
phoneNum = contact.number.toString(),
initials = getInitialsFromName(contact.name.orEmpty()),
clickable = true,
onClick = {
contact.number?.let {
handleContactClick(
contact.isWhatsappContact ?: false,
contact = contact,
context,
message = message,
imageUrl = imageUrl,
shareLink = shareableLink
)
when (contactState) {
ContactState.Loading -> {
items(10) { ContactShimmerItem() }
return@LazyColumn
}
ContactState.NotFound -> {
NoContactFoundRenderer(contacts, searchText)
return@LazyColumn
}
ContactState.Success -> {
items(contacts) { contact ->
Contacts(
name = contact.name.orEmpty(),
phoneNum = contact.number.toString(),
initials = getInitialsFromName(contact.name.orEmpty()),
clickable = true,
onClick = {
contact.number?.let {
handleContactClick(
contact.isWhatsappContact ?: false,
contact = contact,
context,
message = message,
imageUrl = imageUrl,
shareLink = shareableLink
)
}
}
}
)
)
}
}
}
if (contacts.isEmpty()) {
if (searchText.matches(Regex(NUMBER_REGEX_WITHOUT_COUNTRY_CODE))) {
item {

View File

@@ -20,6 +20,8 @@ import com.navi.rr.utils.ext.removeSpaces
import com.navi.rr.utils.removeCodeFromPhoneNumber
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
class ReferralContactsUtil @Inject constructor() {
@@ -47,6 +49,42 @@ class ReferralContactsUtil @Inject constructor() {
}
}
fun getContactListAsFlow(batchSize: Int = 200): Flow<List<ReferralContact>> = flow {
var allContactsCursor: Cursor? = null
try {
val batchContactsList = mutableListOf<ReferralContact>()
allContactsCursor =
executeQueryToReadAllContactsWithThumbnails(
AppServiceManager.application.applicationContext
)
if (allContactsCursor == null) {
return@flow
}
allContactsCursor.run {
if (!moveToFirst()) {
return@flow
}
do {
readContact(batchContactsList)
if (batchContactsList.size == batchSize) {
emit(batchContactsList.distinctBy { it.number })
batchContactsList.clear()
}
} while (moveToNext())
}
if (batchContactsList.isNotEmpty()) {
emit(batchContactsList.distinctBy { it.number })
}
} catch (e: Exception) {
e.log()
return@flow
} finally {
allContactsCursor?.close()
}
}
private fun executeQueryToReadAllContactsWithThumbnails(applicationContext: Context): Cursor? {
return applicationContext.contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

View File

@@ -14,7 +14,6 @@ import com.navi.rr.referral.utils.ReferralContactsUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
@@ -29,6 +28,9 @@ class ReferralContactsBottomSheetVM
constructor(private val referralContactsUtil: ReferralContactsUtil) :
BaseVM(isExceptionNeedToShow = false) {
private val _contactState = MutableStateFlow<ContactState>(ContactState.Loading)
val contactState = _contactState.asStateFlow()
private val _searchText = MutableStateFlow("")
val searchText = _searchText.asStateFlow()
@@ -54,10 +56,25 @@ constructor(private val referralContactsUtil: ReferralContactsUtil) :
private fun fetchContacts() {
viewModelScope.launch(Dispatchers.IO) {
var contactList = mutableListOf<ReferralContact>()
val contactTask = async { contactList = referralContactsUtil.getContactList() }
contactTask.await()
_listOfContacts.update { contactList }
referralContactsUtil.getContactListAsFlow().collect { newContacts ->
_listOfContacts.update { currentList ->
(currentList + newContacts).distinctBy { it.number }
}
_contactState.value =
if (_listOfContacts.value.isEmpty()) {
ContactState.NotFound
} else {
ContactState.Success
}
}
}
}
}
sealed interface ContactState {
data object Success : ContactState
data object NotFound : ContactState
data object Loading : ContactState
}