On contacts screen seach, show also contacts where search string matches contacts TEL URIs

Improved contact labeling
This commit is contained in:
Juha Heinanen
2026-07-21 09:42:55 +03:00
parent c8f54de980
commit 0ed2659330
3 changed files with 110 additions and 84 deletions

View File

@ -211,8 +211,10 @@ sealed class Contact {
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
ContactsContract.Data.DATA2, ContactsContract.Data.PHOTO_THUMBNAIL_URI,
ContactsContract.Contacts.STARRED)
@Suppress("DEPRECATION")
val sipMime = ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
val selection =
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + sipMime + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'"
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
@ -242,7 +244,7 @@ sealed class Contact {
if (contact.uris.none { it.uri == uri })
contact.uris.add(ContactUri(uri, label))
}
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
sipMime -> {
val uri = "sip:$data"
if (contact.uris.none { it.uri == uri })
contact.uris.add(ContactUri(uri, ""))

View File

@ -626,7 +626,14 @@ private fun UrisSection(
},
modifier = Modifier.fillMaxWidth(),
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
label = { Text("${stringResource(R.string.sip_or_tel_uri)} ${index + 1}") },
label = {
val labelText = when {
contactUri.uri.startsWith("sip:") -> stringResource(R.string.sip_uri)
contactUri.uri.startsWith("tel:") -> stringResource(R.string.tel_uri)
else -> stringResource(R.string.sip_or_tel_uri)
}
Text(labelText)
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
OutlinedTextField(
@ -683,15 +690,10 @@ private fun UrisSection(
}
else
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
uris.forEachIndexed { index, contactUri ->
uris.forEach { contactUri ->
Row(verticalAlignment = Alignment.CenterVertically) {
val uri = contactUri.uri
val label = contactUri.label.ifEmpty {
if (uri.startsWith("tel:"))
"${stringResource(R.string.tel_uri)} ${index + 1}"
else
"${stringResource(R.string.sip_uri)} ${index + 1}"
}
val label = contactUri.label
OutlinedTextField(
value = uri.substringAfter(":"),
@ -699,7 +701,15 @@ private fun UrisSection(
readOnly = true,
modifier = Modifier.weight(1f),
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
label = { Text(text = label, fontWeight = FontWeight.Bold) },
label = {
val labelText = label.ifEmpty {
if (uri.startsWith("tel:"))
stringResource(R.string.tel_uri)
else
stringResource(R.string.sip_uri)
}
Text(text = labelText, fontWeight = FontWeight.Bold)
},
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.outline,
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
@ -1044,6 +1054,7 @@ private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Bo
for (contactUri in contact.uris) {
val uri = contactUri.uri
val label = contactUri.label
@Suppress("DEPRECATION")
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
@ -1092,6 +1103,7 @@ private fun updateAndroidContact(ctx: Context, rawContactId: Long, contact: Cont
private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri) {
val uri = contactUri.uri
val label = contactUri.label
@Suppress("DEPRECATION")
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
@ -1118,6 +1130,7 @@ private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.
private fun updateAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri): Int {
val uri = contactUri.uri
val label = contactUri.label
@Suppress("DEPRECATION")
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else

View File

@ -66,6 +66,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
@ -561,9 +562,8 @@ private fun BottomBar(
value = searchContactName,
onValueChange = {
onSearchContactNameChange(it)
if (it.isBlank()) {
if (it.isBlank())
keyboardController?.hide()
}
},
modifier = Modifier.weight(1f),
singleLine = true,
@ -633,16 +633,28 @@ private fun ContactsContent(
val filteredContacts = remember(BaresipService.contacts, searchQuery) {
if (searchQuery.isBlank())
BaresipService.contacts.map { contact ->
Pair(contact, buildAnnotatedString { append(contact.name()) })
Triple(contact, buildAnnotatedString { append(contact.name()) }, null)
}
else {
val normalizedQuery = Utils.unaccent(searchQuery)
BaresipService.contacts
.filter { contact ->
Utils.unaccent(contact.name()).contains(normalizedQuery, ignoreCase = true)
val numericQuery = searchQuery.filter { it.isDigit() || it == '+' }
BaresipService.contacts.mapNotNull { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedQuery, ignoreCase = true)
var matchingUri: Contact.ContactUri? = null
if (numericQuery.isNotEmpty()) {
matchingUri = contact.uris().find {
it.uri.startsWith("tel:") && it.uri.substring(4).contains(numericQuery)
}
}
if (nameMatch || matchingUri != null) {
val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), searchQuery)
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
}
.map { contact ->
Pair(contact, Utils.buildAnnotatedStringWithHighlight(contact.name(), searchQuery))
}
}
}
@ -667,8 +679,8 @@ private fun ContactsContent(
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
itemsIndexed(
filteredContacts, key = { index, (contact, _) -> "${contact.id()}_$index" }
) { _, (contact, annotatedName) ->
filteredContacts, key = { index, (contact, _, _) -> "${contact.id()}_$index" }
) { _, (contact, annotatedName, matchingUri) ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
@ -681,9 +693,7 @@ private fun ContactsContent(
bitmap = avatarImage.asImageBitmap(),
contentDescription = "Avatar",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
modifier = Modifier.size(36.dp).clip(CircleShape)
)
else
TextAvatar(contact.name(), contact.color)
@ -695,21 +705,13 @@ private fun ContactsContent(
model = thumbNailUri,
contentDescription = "Avatar",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(36.dp)
.clip(CircleShape),
modifier = Modifier.size(36.dp).clip(CircleShape),
)
else
TextAvatar(contact.name(), contact.color)
}
}
when (contact) {
is Contact.BaresipContact -> {
Text(
text = annotatedName,
fontSize = 20.sp,
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,
modifier = Modifier
val textModifier = Modifier
.weight(1f)
.padding(start = 10.dp)
.combinedClickable(
@ -721,6 +723,8 @@ private fun ContactsContent(
onFirstClicked.value = { }
lastButtonText.value = deleteText
onLastClicked.value = {
when (contact) {
is Contact.BaresipContact -> {
val id = contact.id
val avatarFile = File(BaresipService.filesPath, "$id.png")
if (avatarFile.exists())
@ -731,36 +735,43 @@ private fun ContactsContent(
}
Contact.removeBaresipContact(contact)
}
showDialog.value = true
}
)
)
}
is Contact.AndroidContact -> {
Text(text = annotatedName,
fontSize = 20.sp,
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,
modifier = Modifier
.weight(1f)
.padding(start = 10.dp, top = 4.dp, bottom = 4.dp)
.combinedClickable(
onClick = { navController.navigate("contact/${contact.name()}/old") },
onLongClick = {
title.value = confirmationText
message.value = String.format(contactDeleteQuestion, contact.name())
firstButtonText.value = cancelText
onFirstClicked.value = { }
lastButtonText.value = deleteText
onLastClicked.value = {
ctx.contentResolver.delete(
ContactsContract.RawContacts.CONTENT_URI,
ContactsContract.Contacts.DISPLAY_NAME + "='" + contact.name() + "'",
null
)
}
}
}
showDialog.value = true
}
)
Column(
modifier = if (contact is Contact.AndroidContact)
textModifier.padding(top = 4.dp, bottom = 4.dp)
else
textModifier
) {
Text(
text = annotatedName,
fontSize = 20.sp,
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal
)
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
searchQuery.filter { it.isDigit() || it == '+' })
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
},
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}