From 874b7c87e55cc9757b9f7ca334965ee362a0cf24 Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Sat, 12 Apr 2025 17:51:28 +0300 Subject: [PATCH] Composed chats activity alert dialogs --- .../com/tutpro/baresip/ChatsActivity.kt | 298 +++++++++--------- .../com/tutpro/baresip/CustomElements.kt | 79 +++++ 2 files changed, 236 insertions(+), 141 deletions(-) diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt index 7198eb9d..128935ca 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt @@ -1,7 +1,6 @@ package com.tutpro.baresip import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.graphics.BitmapFactory import android.os.Bundle @@ -77,12 +76,13 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tutpro.baresip.BaresipService.Companion.contactNames +import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.ImageAvatar import com.tutpro.baresip.CustomElements.Text import com.tutpro.baresip.CustomElements.TextAvatar import com.tutpro.baresip.CustomElements.verticalScrollbar +import com.tutpro.baresip.CustomElements.SelectableAlertDialog import java.text.DateFormat import java.util.GregorianCalendar @@ -92,7 +92,9 @@ class ChatsActivity: ComponentActivity() { internal lateinit var account: Account private lateinit var chatRequest: ActivityResultLauncher private var _uaMessages = mutableStateOf>(emptyList()) - private var uaMessages : List by _uaMessages + private var uaMessages: List by _uaMessages + private var showNotice by mutableStateOf(false) + private var noticeMessage = mutableStateOf("") private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { @@ -158,8 +160,21 @@ class ChatsActivity: ComponentActivity() { var expanded by remember { mutableStateOf(false) } val delete = String.format(getString(R.string.delete)) - val disable = String.format(getString(R.string.disable_history)) - val enable = String.format(getString(R.string.enable_history)) + + val showDialog = remember { mutableStateOf(false) } + val positiveAction = remember { mutableStateOf({}) } + + AlertDialog( + showDialog = showDialog, + title = getString(R.string.confirmation), + message = String.format( + getString(R.string.delete_chats_alert), + aor.substringAfter(":") + ), + positiveButtonText = getString(R.string.delete), + onPositiveClicked = positiveAction.value, + negativeButtonText = getString(R.string.cancel), + ) TopAppBar( title = { @@ -192,34 +207,22 @@ class ChatsActivity: ComponentActivity() { tint = LocalCustomColors.current.light ) } - CustomElements.DropdownMenu(expanded, + CustomElements.DropdownMenu( + expanded, { expanded = false }, - listOf(delete, if (account.callHistory) disable else enable), + listOf(delete), onItemClick = { selectedItem -> expanded = false when (selectedItem) { - delete -> - with( - MaterialAlertDialogBuilder( - this@ChatsActivity, - R.style.AlertDialogTheme - ) - ) { - setTitle(R.string.confirmation) - setMessage(String.format(getString(R.string.delete_chats_alert), - aor.substringAfter(":"))) - setPositiveButton(delete) { dialog, _ -> - Message.clearMessagesOfAor(aor) - Message.save() - _uaMessages.value = listOf() - account.unreadMessages = false - dialog.dismiss() - } - setNeutralButton(getText(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - } - show() + delete -> { + positiveAction.value = { + Message.clearMessagesOfAor(aor) + Message.save() + _uaMessages.value = listOf() + account.unreadMessages = false } + showDialog.value = true + } } } ) @@ -236,8 +239,10 @@ class ChatsActivity: ComponentActivity() { verticalArrangement = Arrangement.Top ) { Account(account) - // Spacer(modifier = Modifier.weight(1f)) Chats(ctx, account) + if (showNotice) + Notice() + } } @@ -261,6 +266,25 @@ class ChatsActivity: ComponentActivity() { @OptIn(ExperimentalFoundationApi::class) @Composable fun Chats(ctx: Context, account: Account) { + + val showDialog = remember { mutableStateOf(false) } + val dialogMessage = remember { mutableStateOf("") } + val positiveButtonText = remember { mutableStateOf("") } + val positiveAction = remember { mutableStateOf({}) } + val neutralButtonText = remember { mutableStateOf("") } + val neutralAction = remember { mutableStateOf({}) } + + AlertDialog( + showDialog = showDialog, + title = getString(R.string.confirmation), + message = dialogMessage.value, + positiveButtonText = positiveButtonText.value, + onPositiveClicked = positiveAction.value, + neutralButtonText = neutralButtonText.value, + onNeutralClicked = neutralAction.value, + negativeButtonText = getString(R.string.cancel) + ) + val lazyListState = rememberLazyListState() LazyColumn( modifier = Modifier @@ -287,6 +311,7 @@ class ChatsActivity: ComponentActivity() { else TextAvatar(contact.name, contact.color) } + is Contact.AndroidContact -> { val thumbNailUri = contact.thumbnailUri if (thumbNailUri != null) @@ -301,6 +326,7 @@ class ChatsActivity: ComponentActivity() { else TextAvatar(contact.name, contact.color) } + null -> { val avatarImage = BitmapFactory .decodeResource(ctx.resources, R.drawable.person_image) @@ -318,72 +344,40 @@ class ChatsActivity: ComponentActivity() { chatRequest.launch(i) }, onLongClick = { - val dialogClickListener = - DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_POSITIVE -> { - val i = - Intent(ctx, BaresipContactActivity::class.java) - val b = Bundle() - b.putBoolean("new", true) - b.putString("uri", message.peerUri) - i.putExtras(b) - startActivity(i) - } - DialogInterface.BUTTON_NEGATIVE -> { - Message.deleteAorPeerMessages(aor, message.peerUri) - } - DialogInterface.BUTTON_NEUTRAL -> { - } - } - } - val builder = MaterialAlertDialogBuilder( - this@ChatsActivity, - R.style.AlertDialogTheme - ) val peer = Utils.friendlyUri(ctx, message.peerUri, account) - if (!Contact.nameExists(peer, BaresipService.contacts, false)) - with(builder) { - setTitle(R.string.confirmation) - setMessage( - String.format( - getString(R.string.long_chat_question), - peer - ) - ) - setNeutralButton( - getText(R.string.cancel), - dialogClickListener - ) - setNegativeButton( - getText(R.string.delete), - dialogClickListener - ) - setPositiveButton( - getText(R.string.add_contact), - dialogClickListener - ) - show() + val contactExists = + Contact.nameExists(peer, BaresipService.contacts, false) + if (contactExists) { + dialogMessage.value = String.format( + getString(R.string.short_chat_question), + peer + ) + positiveButtonText.value = getString(R.string.delete) + positiveAction.value = { + Message.deleteAorPeerMessages(aor, message.peerUri) + _uaMessages.value = uaMessages(aor) } - else - with(builder) { - setTitle(R.string.confirmation) - setMessage( - String.format( - getString(R.string.short_chat_question), - peer - ) - ) - setNeutralButton( - getText(R.string.cancel), - dialogClickListener - ) - setNegativeButton( - getText(R.string.delete), - dialogClickListener - ) - show() + neutralButtonText.value = "" + } else { + dialogMessage.value = String.format( + getString(R.string.long_chat_question), + peer + ) + positiveButtonText.value = getString(R.string.add_contact) + positiveAction.value = { + val i = Intent(ctx, BaresipContactActivity::class.java) + val b = Bundle() + b.putBoolean("new", true) + b.putString("uri", message.peerUri) + i.putExtras(b) + startActivity(i) } + neutralButtonText.value = getString(R.string.delete) + neutralAction.value = { + Message.deleteAorPeerMessages(aor, message.peerUri) + } + } + showDialog.value = true }, modifier = Modifier .fillMaxWidth() @@ -393,14 +387,13 @@ class ChatsActivity: ComponentActivity() { RoundedCornerShape(50.dp, 20.dp, 20.dp, 10.dp) else RoundedCornerShape(20.dp, 10.dp, 50.dp, 20.dp), - color = //ButtonDefaults.buttonColors(containerColor = + color = if (message.direction == MESSAGE_DOWN) { if (BaresipService.darkTheme.value) LocalCustomColors.current.secondaryDark else LocalCustomColors.current.secondaryLight - } - else { + } else { if (BaresipService.darkTheme.value) LocalCustomColors.current.primaryDark else @@ -423,8 +416,7 @@ class ChatsActivity: ComponentActivity() { LocalCustomColors.current.secondaryLight else LocalCustomColors.current.secondaryDark - } - else { + } else { if (BaresipService.darkTheme.value) LocalCustomColors.current.primaryLight else @@ -456,6 +448,20 @@ class ChatsActivity: ComponentActivity() { @Composable fun NewChatPeer() { + + val showDialog = remember { mutableStateOf(false) } + val items = remember { mutableStateOf(listOf()) } + val itemAction = remember { mutableStateOf<(Int) -> Unit>({ index -> {} }) } + + SelectableAlertDialog( + openDialog = showDialog, + title = "Choose Destination", + items = items.value, + onItemClicked = itemAction.value, + neutralButtonText = getString(R.string.cancel), + onNeutralClicked = {} + ) + val suggestions by remember { contactNames } var filteredSuggestions by remember { mutableStateOf(suggestions) } var showSuggestions by remember { mutableStateOf(false) } @@ -477,8 +483,10 @@ class ChatsActivity: ComponentActivity() { Column( modifier = Modifier .shadow(8.dp, RoundedCornerShape(8.dp)) - .background(LocalCustomColors.current.grayLight, - shape = RoundedCornerShape(8.dp)) + .background( + LocalCustomColors.current.grayLight, + shape = RoundedCornerShape(8.dp) + ) .animateContentSize() ) { Box(modifier = Modifier.fillMaxWidth()) { @@ -565,7 +573,10 @@ class ChatsActivity: ComponentActivity() { fontSize = 18.sp, color = LocalCustomColors.current.itemText ), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done) + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done + ) ) } Spacer(Modifier.width(4.dp)) @@ -573,10 +584,21 @@ class ChatsActivity: ComponentActivity() { modifier = Modifier.padding(end = 4.dp), onClick = { showSuggestions = false - if (makeChat(newPeer, true)) { - newPeer = "" - focusManager.clearFocus() + var peerUri = newPeer.trim() + if (peerUri.isNotEmpty()) { + val uris = Contact.contactUris(peerUri) + if (uris.size == 1) { + makeChat(uris[0]) + } else if (uris.size > 1) { + items.value = uris + itemAction.value = { index -> + makeChat(uris[index]) + } + } + showDialog.value = true } + newPeer = "" + focusManager.clearFocus() }, containerColor = LocalCustomColors.current.accent, contentColor = LocalCustomColors.current.background @@ -590,54 +612,48 @@ class ChatsActivity: ComponentActivity() { } } - private fun makeChat(peer: String, lookForContact: Boolean = true): Boolean { - var peerUri = peer.trim() - if (peerUri.isNotEmpty()) { - if (lookForContact) { - val uris = Contact.contactUris(peerUri) - if (uris.size == 1) - peerUri = uris[0] - else if (uris.size > 1) { - val builder = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) - with(builder) { - setTitle("Choose Destination") - setItems(uris.toTypedArray()) { _, which -> - peerUri = uris[which] - makeChat(peerUri, false) - } - setNeutralButton(getString(R.string.cancel)) { _: DialogInterface, _: Int -> } - show() - } - return true - } - } - if (Utils.isTelNumber(peerUri)) - peerUri = "tel:$peerUri" - val uri = if (Utils.isTelUri(peerUri)) { - if (account.telProvider == "") { - Utils.alertView(this, getString(R.string.notice), - String.format(getString(R.string.no_telephony_provider), account.aor)) - return false - } + + @Composable + fun Notice() { + AlertDialog( + showDialog = remember { mutableStateOf(true) }, + title = getString(R.string.notice), + message = noticeMessage.value, + positiveButtonText = getString(R.string.ok), + onPositiveClicked = { showNotice = false }, + ) + } + + private fun makeChat(chatPeer: String) { + + val peerUri = if (Utils.isTelNumber(chatPeer)) + "tel:$chatPeer" + else + chatPeer + val uri = if (Utils.isTelUri(peerUri)) { + if (account.telProvider == "") { + noticeMessage.value = + String.format(getString(R.string.no_telephony_provider), account.aor) + showNotice = true + "" + } else Utils.telToSip(peerUri, account) - } else { - Utils.uriComplete(peerUri, aor) - } + } else + Utils.uriComplete(peerUri, aor) + if (noticeMessage.value.isEmpty()) { if (!Utils.checkUri(uri)) { - Utils.alertView(this, getString(R.string.notice), - String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) - return false - } else { + noticeMessage.value = String.format(getString(R.string.invalid_sip_or_tel_uri), uri) + showNotice = true + } + else { val i = Intent(this@ChatsActivity, ChatActivity::class.java) val b = Bundle() b.putString("aor", aor) b.putString("peer", uri) i.putExtras(b) chatRequest.launch(i) - return true } } - return false } override fun onPause() { diff --git a/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt b/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt index c1a66c2f..07d6e87f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt @@ -24,7 +24,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions @@ -330,6 +333,7 @@ object CustomElements { onNeutralClicked: () -> Unit = {} ) { if (showDialog.value) { + Log.d(TAG, "*********** message = '$message'") BasicAlertDialog( onDismissRequest = { showDialog.value = false @@ -440,6 +444,81 @@ object CustomElements { } } + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun SelectableAlertDialog( + openDialog: MutableState, + title: String, + items: List, + onItemClicked: (Int) -> Unit, + neutralButtonText: String = "", + onNeutralClicked: () -> Unit = {} + ) { + if (openDialog.value) { + BasicAlertDialog( + onDismissRequest = { + openDialog.value = false + }, + content = { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = LocalCustomColors.current.cardBackground + ) + ) { + Column(modifier = Modifier.padding(16.dp)) { + androidx.compose.material3.Text( + text = title, + fontSize = 20.sp, + color = LocalCustomColors.current.alert, + textAlign = TextAlign.Start, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + val lazyListState = rememberLazyListState() + LazyColumn( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.End, + state = lazyListState, + ) { + itemsIndexed(items) { index, item -> + TextButton( + onClick = { + onItemClicked(index) + openDialog.value = false + }, + modifier = Modifier.align(Alignment.End) + ) { + androidx.compose.material3.Text( + text = item, + color = LocalCustomColors.current.itemText, + ) + } + } + } + if (neutralButtonText.isNotEmpty()) { + TextButton( + onClick = { + onNeutralClicked() + openDialog.value = false + }, + modifier = Modifier.align(Alignment.End)){ + androidx.compose.material3.Text( + text = neutralButtonText.uppercase(), + color = LocalCustomColors.current.gray, + ) + } + } + } + } + } + ) + } + } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun PasswordDialog(