Match search string to contacts' TEL URIs also on main and chats screens

This commit is contained in:
Juha Heinanen
2026-07-22 11:45:16 +03:00
parent 28e69b2feb
commit c2ea745aa3
3 changed files with 180 additions and 76 deletions

View File

@ -14,9 +14,11 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -67,6 +69,7 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString 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.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
@ -83,7 +86,6 @@ import androidx.navigation.NavType
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument import androidx.navigation.navArgument
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.tutpro.baresip.BaresipService.Companion.contactNames
import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.AlertDialog
import com.tutpro.baresip.CustomElements.DropdownMenu import com.tutpro.baresip.CustomElements.DropdownMenu
import com.tutpro.baresip.CustomElements.SelectableAlertDialog import com.tutpro.baresip.CustomElements.SelectableAlertDialog
@ -137,6 +139,7 @@ private fun ChatsScreen(navController: NavController, aor: String) {
} }
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
topBar = { topBar = {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
@ -260,7 +263,6 @@ private fun Chats(
account: Account, account: Account,
uaMessages: MutableState<List<Message>> uaMessages: MutableState<List<Message>>
) { ) {
val ctx = LocalContext.current
val aor = account.aor val aor = account.aor
val showDialog = remember { mutableStateOf(false) } val showDialog = remember { mutableStateOf(false) }
@ -272,6 +274,14 @@ private fun Chats(
val lastButtonText = remember { mutableStateOf("") } val lastButtonText = remember { mutableStateOf("") }
val lastAction = remember { mutableStateOf({}) } val lastAction = remember { mutableStateOf({}) }
val shortChatQuestion = stringResource(R.string.short_chat_question)
val longChatQuestion = stringResource(R.string.long_chat_question)
val addContactText = stringResource(R.string.add_contact)
val blockText = stringResource(R.string.block)
val deleteText = stringResource(R.string.delete)
val anonymousText = stringResource(R.string.anonymous)
val unknownText = stringResource(R.string.unknown)
if (showDialog.value) if (showDialog.value)
AlertDialog( AlertDialog(
showDialog = showDialog, showDialog = showDialog,
@ -341,17 +351,17 @@ private fun Chats(
CustomElements.Button( CustomElements.Button(
onClick = { navController.navigate("chat/${aor}/${message.peerUri}") }, onClick = { navController.navigate("chat/${aor}/${message.peerUri}") },
onLongClick = { onLongClick = {
val peerName = Utils.friendlyUri(ctx, message.peerUri, account, includeLabel = false) val peerName = Utils.friendlyUri(message.peerUri, account, includeLabel = false, anonymous = anonymousText, unknown = unknownText)
val peerNameWithLabel = Utils.friendlyUri(ctx, message.peerUri, account) val peerNameWithLabel = Utils.friendlyUri(message.peerUri, account, anonymous = anonymousText, unknown = unknownText)
val contactExists = val contactExists =
Contact.nameExists(peerName, BaresipService.contacts, false) Contact.nameExists(peerName, BaresipService.contacts, false)
if (contactExists) { if (contactExists) {
dialogMessage.value = String.format( dialogMessage.value = String.format(
ctx.getString(R.string.short_chat_question), shortChatQuestion,
peerNameWithLabel peerNameWithLabel
) )
secondButtonText.value = "" secondButtonText.value = ""
lastButtonText.value = ctx.getString(R.string.delete) lastButtonText.value = deleteText
lastAction.value = { lastAction.value = {
deleteMessages(uaMessages, account, message.peerUri) deleteMessages(uaMessages, account, message.peerUri)
} }
@ -359,19 +369,19 @@ private fun Chats(
else { else {
dialogMessage.value = dialogMessage.value =
String.format( String.format(
ctx.getString(R.string.long_chat_question), longChatQuestion,
peerName peerName
) )
secondButtonText.value = ctx.getString(R.string.add_contact) secondButtonText.value = addContactText
secondAction.value = { navController.navigate("contact/${message.peerUri}/new") } secondAction.value = { navController.navigate("contact/${message.peerUri}/new") }
thirdButtonText.value = ctx.getString(R.string.block) thirdButtonText.value = blockText
thirdAction.value = { thirdAction.value = {
if (!BlockRule.exists(message.peerUri)) { if (!BlockRule.exists(message.peerUri)) {
BaresipService.blockRules.add(BlockRule(message.peerUri)) BaresipService.blockRules.add(BlockRule(message.peerUri))
BlockRule.save() BlockRule.save()
} }
} }
lastButtonText.value = ctx.getString(R.string.delete) lastButtonText.value = deleteText
lastAction.value = { deleteMessages(uaMessages, account, message.peerUri) } lastAction.value = { deleteMessages(uaMessages, account, message.peerUri) }
} }
showDialog.value = true showDialog.value = true
@ -384,7 +394,7 @@ private fun Chats(
else else
MaterialTheme.colorScheme.primaryContainer MaterialTheme.colorScheme.primaryContainer
) { ) {
val peerName = Utils.friendlyUri(ctx, message.peerUri, account) val peerName = Utils.friendlyUri(message.peerUri, account, anonymous = anonymousText, unknown = unknownText)
val cal = GregorianCalendar() val cal = GregorianCalendar()
cal.timeInMillis = message.timeStamp cal.timeInMillis = message.timeStamp
val fmt: DateFormat = if (isToday(message.timeStamp)) val fmt: DateFormat = if (isToday(message.timeStamp))
@ -424,12 +434,17 @@ private fun Chats(
@Composable @Composable
private fun NewChatPeer(navController: NavController, account: Account) { private fun NewChatPeer(navController: NavController, account: Account) {
val ctx = LocalContext.current
val alertTitle = remember { mutableStateOf("") } val alertTitle = remember { mutableStateOf("") }
val alertMessage = remember { mutableStateOf("") } val alertMessage = remember { mutableStateOf("") }
val showAlert = remember { mutableStateOf(false) } val showAlert = remember { mutableStateOf(false) }
val noticeText = stringResource(R.string.notice)
val noTelephonyProviderText = stringResource(R.string.no_telephony_provider)
val invalidSipOrTelUriText = stringResource(R.string.invalid_sip_or_tel_uri)
val contactNoTelUriText = stringResource(R.string.contact_no_tel_uri)
val contactNoSipOrTelUriText = stringResource(R.string.contact_no_sip_or_tel_uri)
fun makeChat(navController: NavController, account: Account, chatPeer: String) { fun makeChat(navController: NavController, account: Account, chatPeer: String) {
val peerUri = if (Utils.isTelNumber(chatPeer)) val peerUri = if (Utils.isTelNumber(chatPeer))
"tel:$chatPeer" "tel:$chatPeer"
@ -440,9 +455,9 @@ private fun NewChatPeer(navController: NavController, account: Account) {
peerUri peerUri
else else
if (account.telProvider == "") { if (account.telProvider == "") {
alertTitle.value = ctx.getString(R.string.notice) alertTitle.value = noticeText
alertMessage.value = alertMessage.value =
String.format(ctx.getString(R.string.no_telephony_provider), account.aor) String.format(noTelephonyProviderText, account.aor)
showAlert.value = true showAlert.value = true
"" ""
} else } else
@ -452,8 +467,8 @@ private fun NewChatPeer(navController: NavController, account: Account) {
Utils.uriComplete(peerUri, account.aor) Utils.uriComplete(peerUri, account.aor)
if (alertMessage.value.isEmpty()) { if (alertMessage.value.isEmpty()) {
if (!Utils.checkUri(uri)) { if (!Utils.checkUri(uri)) {
alertTitle.value = ctx.getString(R.string.notice) alertTitle.value = noticeText
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), uri) alertMessage.value = String.format(invalidSipOrTelUriText, uri)
showAlert.value = true showAlert.value = true
} }
else else
@ -483,8 +498,7 @@ private fun NewChatPeer(navController: NavController, account: Account) {
onNeutralClicked = {} onNeutralClicked = {}
) )
val suggestions by remember { contactNames } var filteredSuggestions by remember { mutableStateOf<List<Triple<Contact, AnnotatedString, Contact.ContactUri?>>>(emptyList()) }
var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }
var showSuggestions by remember { mutableStateOf(false) } var showSuggestions by remember { mutableStateOf(false) }
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
@ -518,23 +532,40 @@ private fun NewChatPeer(navController: NavController, account: Account) {
) { ) {
items( items(
items = filteredSuggestions, items = filteredSuggestions,
key = { suggestion -> suggestion.toString() } key = { (contact, _, _) -> "${contact.id()}" }
) { suggestion -> ) { (contact, annotatedName, matchingUri) ->
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
newPeer = suggestion.toString() val uri = matchingUri?.uri ?: contact.uris().firstOrNull()?.uri ?: contact.name()
newPeer = uri.substringAfter(":")
showSuggestions = false showSuggestions = false
} }
.padding(12.dp) .padding(12.dp)
) { ) {
Text( Column(modifier = Modifier.fillMaxWidth()) {
text = suggestion, Text(
modifier = Modifier.fillMaxWidth(), text = annotatedName,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 18.sp fontSize = 18.sp
) )
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
newPeer.filter { c -> c.isDigit() || c == '+' })
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
},
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
} }
} }
} }
@ -552,14 +583,25 @@ private fun NewChatPeer(navController: NavController, account: Account) {
emptyList() emptyList()
else { else {
val normalizedInput = Utils.unaccent(it) val normalizedInput = Utils.unaccent(it)
suggestions val numericInput = it.filter { c -> c.isDigit() || c == '+' }
.filter { suggestion -> BaresipService.contacts.mapNotNull { contact ->
it.length > 1 && val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
Utils.unaccent(suggestion).contains(normalizedInput, ignoreCase = true) var matchingUri: Contact.ContactUri? = null
if (numericInput.isNotEmpty()) {
matchingUri = contact.uris().find { u ->
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
}
} }
.map { suggestion -> if (nameMatch || matchingUri != null) {
Utils.buildAnnotatedStringWithHighlight(suggestion, it) val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
} }
}
} }
}, },
modifier = Modifier.padding(end = 6.dp).fillMaxWidth(), modifier = Modifier.padding(end = 6.dp).fillMaxWidth(),
@ -595,11 +637,11 @@ private fun NewChatPeer(navController: NavController, account: Account) {
val uris = Contact.contactContactUris(peerText, account.isMobile) val uris = Contact.contactContactUris(peerText, account.isMobile)
if (uris.isEmpty()) { if (uris.isEmpty()) {
if (Contact.nameExists(peerText, BaresipService.contacts, true)) { if (Contact.nameExists(peerText, BaresipService.contacts, true)) {
alertTitle.value = ctx.getString(R.string.notice) alertTitle.value = noticeText
alertMessage.value = if (account.isMobile) alertMessage.value = if (account.isMobile)
String.format(ctx.getString(R.string.contact_no_tel_uri), peerText) String.format(contactNoTelUriText, peerText)
else else
String.format(ctx.getString(R.string.contact_no_sip_or_tel_uri), peerText) String.format(contactNoSipOrTelUriText, peerText)
showAlert.value = true showAlert.value = true
} else { } else {
makeChat(navController, account, peerText) makeChat(navController, account, peerText)

View File

@ -135,6 +135,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.AnnotatedString 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.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
@ -153,7 +154,6 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import com.tutpro.baresip.BaresipService.Companion.circleGreen import com.tutpro.baresip.BaresipService.Companion.circleGreen
import com.tutpro.baresip.BaresipService.Companion.colorblind import com.tutpro.baresip.BaresipService.Companion.colorblind
import com.tutpro.baresip.BaresipService.Companion.contactNames
import com.tutpro.baresip.BaresipService.Companion.uas import com.tutpro.baresip.BaresipService.Companion.uas
import com.tutpro.baresip.BaresipService.Companion.uasStatus import com.tutpro.baresip.BaresipService.Companion.uasStatus
import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.AlertDialog
@ -1177,8 +1177,7 @@ private fun CallUriRow(
val isDialer = dialerState != null val isDialer = dialerState != null
val suggestions by remember { contactNames } var filteredSuggestions by remember { mutableStateOf<List<Triple<Contact, AnnotatedString, Contact.ContactUri?>>>(emptyList()) }
var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val isDialpadVisible by viewModel.isDialpadVisible.collectAsState() val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()
@ -1201,16 +1200,28 @@ private fun CallUriRow(
dialerState.showCallButton.value = true dialerState.showCallButton.value = true
dialerState.showCallConferenceButton.value = true dialerState.showCallConferenceButton.value = true
} }
val normalizedInput = Utils.unaccent(it) if (it.length > 1) {
filteredSuggestions = suggestions val normalizedInput = Utils.unaccent(it)
.filter { suggestion -> val numericInput = it.filter { c -> c.isDigit() || c == '+' }
it.length > 1 && filteredSuggestions = BaresipService.contacts.mapNotNull { contact ->
Utils.unaccent(suggestion) val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
.contains(normalizedInput, ignoreCase = true) var matchingUri: Contact.ContactUri? = null
} if (numericInput.isNotEmpty()) {
.map { suggestion -> matchingUri = contact.uris().find { u ->
Utils.buildAnnotatedStringWithHighlight(suggestion, it) u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
}
}
if (nameMatch || matchingUri != null) {
val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
}
} }
}
dialerState.showSuggestions.value = it.length > 1 dialerState.showSuggestions.value = it.length > 1
} }
}, },
@ -1305,23 +1316,40 @@ private fun CallUriRow(
) { ) {
items( items(
items = filteredSuggestions, items = filteredSuggestions,
key = { suggestion -> suggestion.toString() } key = { (contact, _, _) -> "${contact.id()}" }
) { suggestion -> ) { (contact, annotatedName, matchingUri) ->
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
dialerState.callUri.value = suggestion.toString() val uri = matchingUri?.uri ?: contact.uris().firstOrNull()?.uri ?: contact.name()
dialerState.callUri.value = uri.substringAfter(":")
dialerState.showSuggestions.value = false dialerState.showSuggestions.value = false
} }
.padding(12.dp) .padding(12.dp)
) { ) {
Text( Column(modifier = Modifier.fillMaxWidth()) {
text = suggestion, Text(
modifier = Modifier.fillMaxWidth(), text = annotatedName,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 18.sp fontSize = 18.sp
) )
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
dialerState.callUri.value.filter { c -> c.isDigit() || c == '+' })
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
},
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
} }
} }
} }
@ -1617,8 +1645,7 @@ private fun CallRow(
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )
var transferUri by remember { mutableStateOf("") } var transferUri by remember { mutableStateOf("") }
val suggestions by remember { contactNames } var filteredSuggestions by remember { mutableStateOf<List<Triple<Contact, AnnotatedString, Contact.ContactUri?>>>(emptyList()) }
var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
OutlinedTextField( OutlinedTextField(
@ -1629,14 +1656,25 @@ private fun CallRow(
transferUri = it transferUri = it
if (it.length > 1) { if (it.length > 1) {
val normalizedInput = Utils.unaccent(it) val normalizedInput = Utils.unaccent(it)
filteredSuggestions = suggestions val numericInput = it.filter { c -> c.isDigit() || c == '+' }
.filter { suggestion -> filteredSuggestions = BaresipService.contacts.mapNotNull { contact ->
Utils.unaccent(suggestion) val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
.contains(normalizedInput, ignoreCase = true) var matchingUri: Contact.ContactUri? = null
if (numericInput.isNotEmpty()) {
matchingUri = contact.uris().find { u ->
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
}
} }
.map { suggestion -> if (nameMatch || matchingUri != null) {
Utils.buildAnnotatedStringWithHighlight(suggestion, it) val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
} }
}
} }
call.showSuggestions.value = transferUri.length > 1 call.showSuggestions.value = transferUri.length > 1
} }
@ -1688,23 +1726,40 @@ private fun CallRow(
) { ) {
items( items(
items = filteredSuggestions, items = filteredSuggestions,
key = { suggestion -> suggestion.toString() } key = { (contact, _, _) -> "${contact.id()}" }
) { suggestion -> ) { (contact, annotatedName, matchingUri) ->
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
transferUri = suggestion.toString() val uri = matchingUri?.uri ?: contact.uris().firstOrNull()?.uri ?: contact.name()
transferUri = uri.substringAfter(":")
call.showSuggestions.value = false call.showSuggestions.value = false
} }
.padding(12.dp) .padding(12.dp)
) { ) {
Text( Column(modifier = Modifier.fillMaxWidth()) {
text = suggestion, Text(
modifier = Modifier.fillMaxWidth(), text = annotatedName,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 18.sp fontSize = 18.sp
) )
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
transferUri.filter { c -> c.isDigit() || c == '+' })
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
},
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
} }
} }
} }

View File

@ -158,6 +158,13 @@ object Utils {
fun friendlyUri(ctx: Context, uri: String, account: Account, e164Check: Boolean = true, fun friendlyUri(ctx: Context, uri: String, account: Account, e164Check: Boolean = true,
includeLabel: Boolean = true): String { includeLabel: Boolean = true): String {
return friendlyUri(uri, account, e164Check, includeLabel,
ctx.getString(R.string.anonymous), ctx.getString(R.string.unknown))
}
fun friendlyUri(uri: String, account: Account, e164Check: Boolean = true,
includeLabel: Boolean = true, anonymous: String = "Anonymous",
unknown: String = "Unknown"): String {
var u = Contact.contactName(uri, includeLabel) var u = Contact.contactName(uri, includeLabel)
if (u != uri) if (u != uri)
return u return u
@ -175,9 +182,9 @@ object Utils {
return if (host == aorDomain(account.aor) || params.contains("user=phone")) return if (host == aorDomain(account.aor) || params.contains("user=phone"))
user user
else if (host == "anonymous.invalid") else if (host == "anonymous.invalid")
ctx.getString(R.string.anonymous) anonymous
else if (host == "unknown.invalid") else if (host == "unknown.invalid")
ctx.getString(R.string.unknown) unknown
else if (params.isEmpty()) else if (params.isEmpty())
"$user@$host" "$user@$host"
else else