On contacts screen seach, show also contacts where search string matches contacts TEL URIs
Improved contact labeling
This commit is contained in:
@ -211,8 +211,10 @@ sealed class Contact {
|
|||||||
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
|
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
|
||||||
ContactsContract.Data.DATA2, ContactsContract.Data.PHOTO_THUMBNAIL_URI,
|
ContactsContract.Data.DATA2, ContactsContract.Data.PHOTO_THUMBNAIL_URI,
|
||||||
ContactsContract.Contacts.STARRED)
|
ContactsContract.Contacts.STARRED)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val sipMime = ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
||||||
val selection =
|
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.Phone.CONTENT_ITEM_TYPE + "' OR " +
|
||||||
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'"
|
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'"
|
||||||
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
|
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 })
|
if (contact.uris.none { it.uri == uri })
|
||||||
contact.uris.add(ContactUri(uri, label))
|
contact.uris.add(ContactUri(uri, label))
|
||||||
}
|
}
|
||||||
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
|
sipMime -> {
|
||||||
val uri = "sip:$data"
|
val uri = "sip:$data"
|
||||||
if (contact.uris.none { it.uri == uri })
|
if (contact.uris.none { it.uri == uri })
|
||||||
contact.uris.add(ContactUri(uri, ""))
|
contact.uris.add(ContactUri(uri, ""))
|
||||||
|
|||||||
@ -626,7 +626,14 @@ private fun UrisSection(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
|
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)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@ -683,15 +690,10 @@ private fun UrisSection(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
uris.forEachIndexed { index, contactUri ->
|
uris.forEach { contactUri ->
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label.ifEmpty {
|
val label = contactUri.label
|
||||||
if (uri.startsWith("tel:"))
|
|
||||||
"${stringResource(R.string.tel_uri)} ${index + 1}"
|
|
||||||
else
|
|
||||||
"${stringResource(R.string.sip_uri)} ${index + 1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uri.substringAfter(":"),
|
value = uri.substringAfter(":"),
|
||||||
@ -699,7 +701,15 @@ private fun UrisSection(
|
|||||||
readOnly = true,
|
readOnly = true,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
|
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(
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
focusedBorderColor = MaterialTheme.colorScheme.outline,
|
focusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||||
unfocusedBorderColor = 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) {
|
for (contactUri in contact.uris) {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label
|
val label = contactUri.label
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val mimeType = if (uri.startsWith("sip:"))
|
val mimeType = if (uri.startsWith("sip:"))
|
||||||
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
||||||
else
|
else
|
||||||
@ -1092,6 +1103,7 @@ private fun updateAndroidContact(ctx: Context, rawContactId: Long, contact: Cont
|
|||||||
private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri) {
|
private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri) {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label
|
val label = contactUri.label
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val mimeType = if (uri.startsWith("sip:"))
|
val mimeType = if (uri.startsWith("sip:"))
|
||||||
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
||||||
else
|
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 {
|
private fun updateAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri): Int {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label
|
val label = contactUri.label
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val mimeType = if (uri.startsWith("sip:"))
|
val mimeType = if (uri.startsWith("sip:"))
|
||||||
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
|
||||||
else
|
else
|
||||||
|
|||||||
@ -66,6 +66,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
@ -561,9 +562,8 @@ private fun BottomBar(
|
|||||||
value = searchContactName,
|
value = searchContactName,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
onSearchContactNameChange(it)
|
onSearchContactNameChange(it)
|
||||||
if (it.isBlank()) {
|
if (it.isBlank())
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
}
|
|
||||||
},
|
},
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
@ -633,17 +633,29 @@ private fun ContactsContent(
|
|||||||
val filteredContacts = remember(BaresipService.contacts, searchQuery) {
|
val filteredContacts = remember(BaresipService.contacts, searchQuery) {
|
||||||
if (searchQuery.isBlank())
|
if (searchQuery.isBlank())
|
||||||
BaresipService.contacts.map { contact ->
|
BaresipService.contacts.map { contact ->
|
||||||
Pair(contact, buildAnnotatedString { append(contact.name()) })
|
Triple(contact, buildAnnotatedString { append(contact.name()) }, null)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val normalizedQuery = Utils.unaccent(searchQuery)
|
val normalizedQuery = Utils.unaccent(searchQuery)
|
||||||
BaresipService.contacts
|
val numericQuery = searchQuery.filter { it.isDigit() || it == '+' }
|
||||||
.filter { contact ->
|
BaresipService.contacts.mapNotNull { contact ->
|
||||||
Utils.unaccent(contact.name()).contains(normalizedQuery, ignoreCase = true)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map { contact ->
|
if (nameMatch || matchingUri != null) {
|
||||||
Pair(contact, Utils.buildAnnotatedStringWithHighlight(contact.name(), searchQuery))
|
val annotatedName = if (nameMatch)
|
||||||
|
Utils.buildAnnotatedStringWithHighlight(contact.name(), searchQuery)
|
||||||
|
else
|
||||||
|
AnnotatedString(contact.name())
|
||||||
|
Triple(contact, annotatedName, matchingUri)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,8 +679,8 @@ private fun ContactsContent(
|
|||||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
filteredContacts, key = { index, (contact, _) -> "${contact.id()}_$index" }
|
filteredContacts, key = { index, (contact, _, _) -> "${contact.id()}_$index" }
|
||||||
) { _, (contact, annotatedName) ->
|
) { _, (contact, annotatedName, matchingUri) ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
@ -681,9 +693,7 @@ private fun ContactsContent(
|
|||||||
bitmap = avatarImage.asImageBitmap(),
|
bitmap = avatarImage.asImageBitmap(),
|
||||||
contentDescription = "Avatar",
|
contentDescription = "Avatar",
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier.size(36.dp).clip(CircleShape)
|
||||||
.size(36.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
TextAvatar(contact.name(), contact.color)
|
TextAvatar(contact.name(), contact.color)
|
||||||
@ -695,72 +705,73 @@ private fun ContactsContent(
|
|||||||
model = thumbNailUri,
|
model = thumbNailUri,
|
||||||
contentDescription = "Avatar",
|
contentDescription = "Avatar",
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier.size(36.dp).clip(CircleShape),
|
||||||
.size(36.dp)
|
|
||||||
.clip(CircleShape),
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
TextAvatar(contact.name(), contact.color)
|
TextAvatar(contact.name(), contact.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (contact) {
|
val textModifier = Modifier
|
||||||
is Contact.BaresipContact -> {
|
.weight(1f)
|
||||||
|
.padding(start = 10.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 = {
|
||||||
|
when (contact) {
|
||||||
|
is Contact.BaresipContact -> {
|
||||||
|
val id = contact.id
|
||||||
|
val avatarFile = File(BaresipService.filesPath, "$id.png")
|
||||||
|
if (avatarFile.exists())
|
||||||
|
try {
|
||||||
|
avatarFile.delete()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Could not delete file $id.png: ${e.message}")
|
||||||
|
}
|
||||||
|
Contact.removeBaresipContact(contact)
|
||||||
|
}
|
||||||
|
is Contact.AndroidContact -> {
|
||||||
|
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(
|
||||||
text = annotatedName,
|
text = buildAnnotatedString {
|
||||||
fontSize = 20.sp,
|
if (matchingUri.label.isNotEmpty())
|
||||||
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,
|
append("${matchingUri.label} ")
|
||||||
modifier = Modifier
|
append(annotatedTel)
|
||||||
.weight(1f)
|
},
|
||||||
.padding(start = 10.dp)
|
fontSize = 16.sp,
|
||||||
.combinedClickable(
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
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 = {
|
|
||||||
val id = contact.id
|
|
||||||
val avatarFile = File(BaresipService.filesPath, "$id.png")
|
|
||||||
if (avatarFile.exists())
|
|
||||||
try {
|
|
||||||
avatarFile.delete()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e(TAG, "Could not delete file $id.png: ${e.message}")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user