NTP-17778 | Add batch fetching of contacts (#13960)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user