diff --git a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt index 104dbcba..4446b2ce 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt @@ -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, "")) diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt index c2697c8b..3de1b8c4 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactScreen.kt @@ -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 diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt index 625611ba..bba6df23 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt @@ -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,17 +633,29 @@ 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) + } } - .map { contact -> - Pair(contact, Utils.buildAnnotatedStringWithHighlight(contact.name(), searchQuery)) + if (nameMatch || matchingUri != null) { + 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), ) { 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,72 +705,73 @@ 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 -> { + val textModifier = Modifier + .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 = annotatedName, - fontSize = 20.sp, - fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal, - modifier = Modifier - .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 = { - 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 - } - ) + text = buildAnnotatedString { + if (matchingUri.label.isNotEmpty()) + append("${matchingUri.label} ") + append(annotatedTel) + }, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } }