Skip to content

Commit

Permalink
Improved loading times and search for numbers support (closes #162, c…
Browse files Browse the repository at this point in the history
…loses #105)
  • Loading branch information
Bnyro committed May 25, 2023
1 parent 954c404 commit d50f257
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 49 deletions.
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 0 additions & 13 deletions app/src/main/java/com/bnyro/contacts/ext/WithIO.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,10 @@ fun ContactsPage(
scrollConnection?.let { modifier.nestedScroll(it) } ?: modifier
}
) {
val query = searchQuery.value.text.lowercase()
val contactGroups = contacts.asSequence().filter {
it.displayName.orEmpty().lowercase().contains(
searchQuery.value.text.lowercase()
)
it.displayName.orEmpty().lowercase().contains(query) ||
it.numbers.any { number -> number.value.contains(query) }
}.filter {
!filterOptions.hiddenAccountNames.contains(it.accountName)
}.filter {
Expand Down
60 changes: 43 additions & 17 deletions app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import android.Manifest
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bnyro.contacts.R
import com.bnyro.contacts.ext.toast
import com.bnyro.contacts.ext.withIO
import com.bnyro.contacts.obj.ContactData
import com.bnyro.contacts.util.ContactsHelper
import com.bnyro.contacts.util.DeviceContactsHelper
Expand All @@ -18,14 +19,22 @@ import com.bnyro.contacts.util.IntentHelper
import com.bnyro.contacts.util.LocalContactsHelper
import com.bnyro.contacts.util.PermissionHelper
import com.bnyro.contacts.util.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch

class ContactsModel : ViewModel() {
var contacts by mutableStateOf<List<ContactData>?>(null)
var contacts = mutableStateListOf<ContactData>()
var isLoading by mutableStateOf(false)
var contactsHelper by mutableStateOf<ContactsHelper?>(null)
private val permissions = arrayOf(
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_CONTACTS
)
private var cancelJob: () -> Unit = {}

fun init(context: Context) {
contactsHelper = when (Preferences.getInt(Preferences.homeTabKey, 0)) {
Expand All @@ -36,35 +45,52 @@ class ContactsModel : ViewModel() {

fun loadContacts(context: Context) {
if (!PermissionHelper.checkPermissions(context, permissions)) return
withIO {
contacts = contactsHelper?.getContactList()
viewModelScope.launch(Dispatchers.IO) {
cancelJob()
contacts.clear()
isLoading = true
contacts.addAll(contactsHelper?.getContactList().orEmpty())
isLoading = false
var isJobActive = true
cancelJob = { isJobActive = false }
CoroutineScope(Dispatchers.IO + Job()).launch {
(0 until contacts.size).map { i ->
async {
contacts.getOrNull(i)?.let {
val data = contactsHelper?.loadAdvancedData(it) ?: return@async
if (!isJobActive) return@let
contacts[i] = data
}
}
}.awaitAll()
}
}
}

private suspend fun deleteContactsSuspend(contactsToDelete: List<ContactData>) {
contactsHelper?.deleteContacts(contactsToDelete)
contacts = contacts?.filter { ct ->
contactsToDelete.none {
contacts.removeAll { ct ->
contactsToDelete.any {
it.contactId == ct.contactId
}
}
}

fun deleteContacts(contactsToDelete: List<ContactData>) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
deleteContactsSuspend(contactsToDelete)
}
}

fun createContact(context: Context, contact: ContactData) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
contactsHelper?.createContact(contact)
loadContacts(context)
}
}

fun updateContact(context: Context, contact: ContactData) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
contactsHelper?.updateContact(contact)
loadContacts(context)
}
Expand All @@ -82,13 +108,13 @@ class ContactsModel : ViewModel() {
}

fun copyContacts(context: Context, contacts: List<ContactData>) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
copyContactsSuspend(context, contacts)
}
}

fun moveContacts(context: Context, contacts: List<ContactData>) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
copyContactsSuspend(context, contacts)
deleteContactsSuspend(contacts)
}
Expand All @@ -99,7 +125,7 @@ class ContactsModel : ViewModel() {
}

fun importVcf(context: Context, uri: Uri) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
val exportHelper = ExportHelper(context, contactsHelper!!)
exportHelper.importContacts(uri)
context.toast(R.string.import_success)
Expand All @@ -109,25 +135,25 @@ class ContactsModel : ViewModel() {

fun exportVcf(context: Context, uri: Uri) {
val exportHelper = ExportHelper(context, contactsHelper!!)
exportHelper.exportContacts(uri, contacts.orEmpty())
exportHelper.exportContacts(uri, contacts)
context.toast(R.string.export_success)
}

fun exportSingleVcf(context: Context, contact: ContactData) {
withIO {
viewModelScope.launch(Dispatchers.IO) {
val exportHelper = ExportHelper(context, contactsHelper!!)
val tempFileUri = exportHelper.exportContact(contact)
IntentHelper.shareContactVcf(context, tempFileUri)
}
}

fun getAvailableAccountTypes() = contacts.orEmpty().mapNotNull {
fun getAvailableAccountTypes() = contacts.mapNotNull {
it.accountName
}.distinct()

fun getAvailableAccountTypesAndNames() = contacts.orEmpty().mapNotNull {
fun getAvailableAccountTypesAndNames() = contacts.mapNotNull {
it.accountType?.let { type -> type to it.accountName }
}.distinct().toMutableList()

fun getAvailableGroups() = contacts?.map { it.groups }?.flatten().orEmpty().distinct()
fun getAvailableGroups() = contacts.map { it.groups }.flatten().distinct()
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fun ContactsScreen(

LaunchedEffect(viewModel.contacts) {
initialContact ?: return@LaunchedEffect
viewModel.contacts?.firstOrNull {
viewModel.contacts.firstOrNull {
it.contactId == initialContact
}?.let {
scope.launch {
Expand All @@ -97,15 +97,13 @@ fun ContactsScreen(
stringResource(R.string.device),
Icons.Default.Home
) {
viewModel.contacts = null
viewModel.contactsHelper = DeviceContactsHelper(context)
viewModel.loadContacts(context)
},
NavBarItem(
stringResource(R.string.local),
Icons.Default.Storage
) {
viewModel.contacts = null
viewModel.contactsHelper = LocalContactsHelper(context)
viewModel.loadContacts(context)
}
Expand Down Expand Up @@ -156,7 +154,7 @@ fun ContactsScreen(
color = MaterialTheme.colorScheme.background
) {
ContactsPage(
viewModel.contacts,
viewModel.contacts.takeIf { !viewModel.isLoading },
contactToInsert,
nestedScrollConnection.takeIf { themeModel.collapsableBottomBar },
bottomBarOffsetHeight = with(LocalDensity.current) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private val LightColorScheme = lightColorScheme(
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
*/
)

@Composable
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/bnyro/contacts/ui/theme/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ val Typography = Typography(
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
*/
)
16 changes: 6 additions & 10 deletions app/src/main/java/com/bnyro/contacts/util/DeviceContactsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import com.bnyro.contacts.enums.BackupType
import com.bnyro.contacts.ext.intValue
import com.bnyro.contacts.ext.longValue
import com.bnyro.contacts.ext.notAName
import com.bnyro.contacts.ext.pmap
import com.bnyro.contacts.ext.stringValue
import com.bnyro.contacts.obj.ContactData
import com.bnyro.contacts.obj.ContactsGroup
Expand All @@ -57,12 +56,12 @@ class DeviceContactsHelper(private val context: Context) : ContactsHelper() {
RawContacts.ACCOUNT_NAME
)

private val storedGroups = getStoredGroups()

@RequiresPermission(Manifest.permission.READ_CONTACTS)
override suspend fun getContactList(): List<ContactData> {
val contactList = mutableListOf<ContactData>()

val storedGroups = getStoredGroups()

@Suppress("SameParameterValue")
val cursor = contentResolver.query(
Data.CONTENT_URI,
Expand Down Expand Up @@ -116,20 +115,17 @@ class DeviceContactsHelper(private val context: Context) : ContactsHelper() {
}
}

return contactList.pmap {
it.apply {
thumbnail = getContactPhotoThumbnail(contactId)
photo = getContactPhoto(contactId)
groups = getGroups(contactId, storedGroups)
}
}
return contactList
}

private fun getEntry(contactId: Long, type: String, column: String): String? {
return getExtras(contactId, column, null, type).firstOrNull()?.value
}

override suspend fun loadAdvancedData(contact: ContactData) = contact.apply {
thumbnail = getContactPhotoThumbnail(contactId)
photo = getContactPhoto(contactId)
groups = getGroups(contactId, storedGroups)
nickName = getEntry(contactId, Nickname.CONTENT_ITEM_TYPE, Nickname.NAME)
organization = getEntry(contactId, Organization.CONTENT_ITEM_TYPE, Organization.COMPANY)
events = getExtras(
Expand Down

0 comments on commit d50f257

Please sign in to comment.