From fb35f9fbd8179210c44b31ca398bd9c629f92353 Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Tue, 15 Apr 2025 18:10:58 +0300 Subject: [PATCH] Completed composting alert of dialogs (testing still needed) UI improvements --- .../com/tutpro/baresip/AccountActivity.kt | 32 +- .../com/tutpro/baresip/AccountsActivity.kt | 8 +- .../com/tutpro/baresip/AudioActivity.kt | 7 +- .../tutpro/baresip/BaresipContactActivity.kt | 436 ++++++------ .../kotlin/com/tutpro/baresip/ChatActivity.kt | 4 +- .../com/tutpro/baresip/ChatsActivity.kt | 7 +- .../com/tutpro/baresip/ConfigActivity.kt | 7 +- .../main/kotlin/com/tutpro/baresip/Contact.kt | 10 +- .../com/tutpro/baresip/ContactsActivity.kt | 2 +- .../kotlin/com/tutpro/baresip/MainActivity.kt | 655 +++++++++--------- .../main/kotlin/com/tutpro/baresip/Utils.kt | 76 -- app/src/main/res/values/styles.xml | 38 - 12 files changed, 580 insertions(+), 702 deletions(-) delete mode 100644 app/src/main/res/values/styles.xml diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt index b4b10a83..e42db255 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt @@ -63,6 +63,7 @@ import androidx.compose.ui.unit.sp import com.tutpro.baresip.BaresipService.Companion.uas import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.Checkbox +import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.verticalScrollbar class AccountActivity : ComponentActivity() { @@ -334,7 +335,10 @@ class AccountActivity : ComponentActivity() { fontSize = 18.sp, color = LocalCustomColors.current.itemText ), - label = { Text(stringResource(R.string.sip_uri)) } + label = { + LabelText(text = stringResource(R.string.sip_uri), + fontWeight = FontWeight.Bold) + } ) } } @@ -362,7 +366,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.nickname)) }, + label = { LabelText(stringResource(R.string.nickname)) }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, keyboardType = KeyboardType.Text), @@ -393,7 +397,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.display_name)) }, + label = { LabelText(stringResource(R.string.display_name)) }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, keyboardType = KeyboardType.Text), @@ -424,7 +428,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.authentication_username)) }, + label = { LabelText(stringResource(R.string.authentication_username)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -474,7 +478,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.authentication_password)) }, + label = { LabelText(stringResource(R.string.authentication_password)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -509,7 +513,7 @@ class AccountActivity : ComponentActivity() { singleLine = true, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.sip_uri_of_proxy_server)) }, + label = { LabelText(stringResource(R.string.sip_uri_of_proxy_server)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -531,7 +535,7 @@ class AccountActivity : ComponentActivity() { singleLine = true, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.sip_uri_of_another_proxy_server)) }, + label = { LabelText(stringResource(R.string.sip_uri_of_another_proxy_server)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -587,7 +591,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.reg_int)) }, + label = { LabelText(stringResource(R.string.reg_int)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ) } @@ -764,7 +768,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.stun_server)) }, + label = { LabelText(stringResource(R.string.stun_server)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -793,7 +797,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.stun_username)) }, + label = { LabelText(stringResource(R.string.stun_username)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -847,7 +851,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.stun_password)) }, + label = { LabelText(stringResource(R.string.stun_password)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -1111,7 +1115,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.voicemail_uri)) }, + label = { LabelText(stringResource(R.string.voicemail_uri)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -1140,7 +1144,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.country_code)) }, + label = { LabelText(stringResource(R.string.country_code)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -1169,7 +1173,7 @@ class AccountActivity : ComponentActivity() { showAlert.value = true }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.telephony_provider)) }, + label = { LabelText(stringResource(R.string.telephony_provider)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt index 4ab04da2..c195d2c6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tutpro.baresip.CustomElements.AlertDialog +import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.verticalScrollbar import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -283,12 +284,7 @@ class AccountsActivity : ComponentActivity() { ) } }, - label = { - CustomElements.Text( - text = getString(R.string.new_account), - fontSize = 18.sp - ) - }, + label = { LabelText(stringResource(R.string.new_account)) }, textStyle = TextStyle(fontSize = 18.sp), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Text, diff --git a/app/src/main/kotlin/com/tutpro/baresip/AudioActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AudioActivity.kt index cde0a7dd..90c14664 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AudioActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AudioActivity.kt @@ -1,6 +1,5 @@ package com.tutpro.baresip -import android.content.Context import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback @@ -103,7 +102,7 @@ class AudioActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = LocalCustomColors.current.background ) { - AudioScreen(this, title) { goBack() } + AudioScreen(title) { goBack() } } } } @@ -112,7 +111,7 @@ class AudioActivity : ComponentActivity() { } @Composable - fun AudioScreen(ctx: Context, title: String, navigateBack: () -> Unit) { + fun AudioScreen(title: String, navigateBack: () -> Unit) { Scaffold( modifier = Modifier .fillMaxHeight() @@ -448,7 +447,7 @@ class AudioActivity : ComponentActivity() { }, textStyle = androidx.compose.ui.text.TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { LabelText(getString(R.string.audio_delay)) }, + label = { LabelText(stringResource(R.string.audio_delay)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipContactActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipContactActivity.kt index 243b0019..a4cb5573 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipContactActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipContactActivity.kt @@ -75,6 +75,7 @@ import androidx.compose.ui.unit.sp import androidx.core.graphics.scale import androidx.exifinterface.media.ExifInterface import coil.compose.rememberAsyncImagePainter +import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.Checkbox import java.io.ByteArrayOutputStream import java.io.File @@ -86,7 +87,6 @@ class BaresipContactActivity : ComponentActivity() { private var new = false private var favorite = false private var android = false - private var avatarImage: Bitmap? = null private var newName = "" private var newUri = "" private var newFavorite = false @@ -94,9 +94,17 @@ class BaresipContactActivity : ComponentActivity() { private var uriOrName = "" private var color = 0 private var id: Long = 0 - private var newAvatar = "" + private var newId: Long = 0 private var tmpFile: File? = null + private val alertTitle = mutableStateOf("") + private val alertMessage = mutableStateOf("") + private val showAlert = mutableStateOf(false) + + private val textAvatarText = mutableStateOf("") + private val textAvatarColor = mutableIntStateOf(0) + private val imageAvatarUri = mutableStateOf("") + private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { goBack() @@ -117,23 +125,29 @@ class BaresipContactActivity : ComponentActivity() { name = "" uri = intent.getStringExtra("uri")!! favorite = false - avatarImage = null android = BaresipService.contactsMode == "android" title = getString(R.string.new_contact) uriOrName = uri color = Utils.randomColor() id = System.currentTimeMillis() - } else { + newId = id + } + else { name = intent.getStringExtra("name")!! val contact = Contact.baresipContact(name)!! uri = contact.uri favorite = contact.favorite - avatarImage = contact.avatarImage android = false title = name uriOrName = name color = contact.color id = contact.id + newId = id + if (contact.avatarImage != null) { + val avatarFile = File(BaresipService.filesPath, "${newId}.png") + if (avatarFile.exists()) + imageAvatarUri.value = Uri.fromFile(avatarFile).toString() + } } setContent { @@ -159,7 +173,7 @@ class BaresipContactActivity : ComponentActivity() { containerColor = LocalCustomColors.current.background, topBar = { TopAppBar(ctx, title, navigateBack) }, content = { contentPadding -> - ContactContent(ctx, contentPadding) + ContactContent(contentPadding) } ) } @@ -203,13 +217,60 @@ class BaresipContactActivity : ComponentActivity() { @OptIn(ExperimentalFoundationApi::class) @Composable - fun ContactContent(ctx: Context, contentPadding: PaddingValues) { + fun ContactContent(contentPadding: PaddingValues) { - var avatarType by remember { mutableStateOf(if (avatarImage != null) "image" else "text") } - var textAvatarText by remember { mutableStateOf("") } - var textAvatarColor by remember { mutableIntStateOf(0) } - var imageAvatarUri by remember { mutableStateOf("") } + if (showAlert.value) { + AlertDialog( + showDialog = showAlert, + title = alertTitle.value, + message = alertMessage.value, + positiveButtonText = stringResource(R.string.ok), + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .background(LocalCustomColors.current.background) + .padding(contentPadding) + .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Avatar() + ContactName() + ContactUri() + Favorite() + Android() + } + } + + + @Composable + fun TextAvatar(size: Int) { + Box( + modifier = Modifier.size(size.dp), + contentAlignment = Alignment.Center + ) { + Canvas(modifier = Modifier.fillMaxSize()) { + drawCircle(SolidColor(Color(textAvatarColor.intValue))) + } + Text(textAvatarText.value, fontSize = 72.sp, color = Color.White) + } + } + + @Composable + fun ImageAvatar(size: Int) { + Image( + painter = rememberAsyncImagePainter(model = imageAvatarUri.value), + contentDescription = stringResource(R.string.avatar_image), + contentScale = ContentScale.Crop, + modifier = Modifier.size(size.dp).clip(CircleShape) + ) + } + + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun Avatar() { val avatarRequest = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { if (it != null) @@ -224,208 +285,151 @@ class BaresipContactActivity : ComponentActivity() { ExifInterface.ORIENTATION_NORMAL ) val rotatedBitmap = rotateBitmap(scaledBitmap, orientation) - tmpFile = File( - BaresipService.filesPath + "/tmp", - "${System.currentTimeMillis()}.png" - ) + if (tmpFile != null && tmpFile!!.exists()) + Utils.deleteFile(tmpFile!!) + newId = System.currentTimeMillis() + tmpFile = File(BaresipService.filesPath, "${newId}.png") if (Utils.saveBitmap(rotatedBitmap, tmpFile!!)) { - newAvatar = "image" - avatarType = newAvatar - imageAvatarUri = Uri.fromFile(tmpFile).toString() + imageAvatarUri.value = Uri.fromFile(tmpFile).toString() } } catch (e: Exception) { Log.e(TAG, "Could not read avatar image: ${e.message}") } } - - @Composable - fun TextAvatar(size: Int) { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { Box( - modifier = Modifier.size(size.dp), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, + modifier = Modifier.size(96.dp) + .combinedClickable( + onClick = { + avatarRequest.launch("image/*") + }, + onLongClick = { + textAvatarColor.intValue = Utils.randomColor() + color = textAvatarColor.intValue + imageAvatarUri.value = "" + } + ), ) { - Canvas(modifier = Modifier.fillMaxSize()) { - drawCircle(SolidColor(Color(textAvatarColor))) - } - textAvatarText = if (name == "") "?" else name[0].toString() - Text(textAvatarText, fontSize = 72.sp, color = Color.White) - } - } - - @Composable - fun ImageAvatar(size: Int) { - Image( - painter = rememberAsyncImagePainter(model = imageAvatarUri), - contentDescription = stringResource(R.string.avatar_image), - contentScale = ContentScale.Crop, - modifier = Modifier.size(size.dp).clip(CircleShape) - ) - } - - @Composable - fun avatar() { - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - if (new) - textAvatarColor = color - else { - val avatarImage = avatarImage - val avatarFile = File(BaresipService.filesPath, "${id}.png") - if (avatarImage != null && (newAvatar == "" || newAvatar == "image")) { - imageAvatarUri = if (tmpFile != null && tmpFile!!.exists()) - Uri.fromFile(tmpFile).toString() - else - Uri.fromFile(avatarFile).toString() - } - else { - textAvatarText = "${name[0]}" - textAvatarColor = color - } - } - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.size(96.dp) - .combinedClickable( - onClick = { - avatarRequest.launch("image/*") - }, - onLongClick = { - textAvatarColor = Utils.randomColor() - color = textAvatarColor - newAvatar = "text" - avatarType = newAvatar - } - ), - ) { - if (avatarType == "text") - TextAvatar(96) + if (imageAvatarUri.value == "") { + textAvatarText.value = if (new) + "?" else - ImageAvatar(96) + "${name[0]}" + textAvatarColor.intValue = color + TextAvatar(96) + } else + ImageAvatar(96) + } + } + } + + @Composable + fun ContactName() { + val focusRequester = FocusRequester() + var contactName by remember { mutableStateOf(name) } + newName = contactName + OutlinedTextField( + value = contactName, + placeholder = { Text(stringResource(R.string.contact_name)) }, + onValueChange = { + contactName = it + newName = contactName + }, + modifier = Modifier.fillMaxWidth().focusRequester(focusRequester), + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = 18.sp, color = LocalCustomColors.current.itemText + ), + label = { Text(stringResource(R.string.contact_name)) }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + capitalization = KeyboardCapitalization.Sentences + ) + ) + LaunchedEffect(new) { + if (new) + focusRequester.requestFocus() + } + } + + @Composable + fun ContactUri() { + var contactUri by remember { mutableStateOf(uri) } + newUri = contactUri + OutlinedTextField( + value = contactUri, + placeholder = { Text(stringResource(R.string.user_domain_or_number)) }, + onValueChange = { + contactUri = it + newUri = contactUri + }, + modifier = Modifier.fillMaxWidth(), + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = 18.sp, color = LocalCustomColors.current.itemText + ), + label = { Text(stringResource(R.string.sip_or_tel_uri)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) + } + + @Composable + fun Favorite() { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + Text( + text = stringResource(R.string.favorite), + modifier = Modifier.weight(1f) + .clickable { + alertTitle.value = getString(R.string.favorite) + alertMessage.value = getString(R.string.favorite_help) + showAlert.value = true + }, + color = LocalCustomColors.current.itemText, + ) + var favoriteContact by remember { mutableStateOf(favorite) } + newFavorite = favoriteContact + Checkbox( + checked = favoriteContact, + onCheckedChange = { + favoriteContact = it + newFavorite = favoriteContact } - } - } - - @Composable - fun contactName() { - val focusRequester = FocusRequester() - var contactName by remember { mutableStateOf(name) } - newName = contactName - OutlinedTextField( - value = contactName, - placeholder = { Text(stringResource(R.string.contact_name)) }, - onValueChange = { - contactName = it - newName = contactName - }, - modifier = Modifier.fillMaxWidth().focusRequester(focusRequester), - textStyle = androidx.compose.ui.text.TextStyle( - fontSize = 18.sp, color = LocalCustomColors.current.itemText - ), - label = { Text(stringResource(R.string.contact_name)) }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Text, - capitalization = KeyboardCapitalization.Sentences - ) - ) - LaunchedEffect(new) { - if (new) - focusRequester.requestFocus() - } - } - - @Composable - fun contactUri() { - var contactUri by remember { mutableStateOf(uri) } - newUri = contactUri - OutlinedTextField( - value = contactUri, - placeholder = { Text(stringResource(R.string.user_domain_or_number)) }, - onValueChange = { - contactUri = it - newUri = contactUri - }, - modifier = Modifier.fillMaxWidth(), - textStyle = androidx.compose.ui.text.TextStyle( - fontSize = 18.sp, color = LocalCustomColors.current.itemText - ), - label = { Text(stringResource(R.string.sip_or_tel_uri)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } + } - @Composable - fun favorite(ctx: Context) { + @Composable + fun Android() { + if (new && BaresipService.contactsMode != "baresip") Row( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start ) { Text( - text = stringResource(R.string.favorite), - modifier = Modifier.weight(1f) - .clickable { - Utils.alertView( - ctx, getString(R.string.favorite), getString(R.string.favorite_help) - ) - }, - color = LocalCustomColors.current.itemText, + text = stringResource(R.string.android), + modifier = Modifier.weight(1f), + color = LocalCustomColors.current.itemText ) - var favoriteContact by remember { mutableStateOf(favorite) } - newFavorite = favoriteContact + var androidContact by remember { mutableStateOf(android) } + newAndroid = androidContact Checkbox( - checked = favoriteContact, + checked = androidContact, onCheckedChange = { - favoriteContact = it - newFavorite = favoriteContact + if (BaresipService.contactsMode != "android") { + androidContact = it + newAndroid = androidContact + } } ) } - } - - @Composable - fun android() { - if (new && BaresipService.contactsMode != "baresip") - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start - ) { - Text( - text = stringResource(R.string.android), - modifier = Modifier.weight(1f), - color = LocalCustomColors.current.itemText - ) - var androidContact by remember { mutableStateOf(android) } - newAndroid = androidContact - Checkbox( - checked = androidContact, - onCheckedChange = { - if (BaresipService.contactsMode != "android") { - androidContact = it - newAndroid = androidContact - } - } - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .background(LocalCustomColors.current.background) - .padding(contentPadding) - .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - avatar() - contactName() - contactUri() - favorite(ctx) - android() - } } private fun checkOnClick(ctx: Context) { @@ -439,20 +443,18 @@ class BaresipContactActivity : ComponentActivity() { else "sip:$newUri" if (!Utils.checkUri(newUri)) { - Utils.alertView( - ctx, getString(R.string.notice), - String.format(getString(R.string.invalid_sip_or_tel_uri), newUri) - ) + alertTitle.value = getString(R.string.notice) + alertMessage.value = String.format(getString(R.string.invalid_sip_or_tel_uri), newUri) + showAlert.value = true return } newName = newName.trim() if (newName == "") newName = newUri.substringAfter(":") if (!Utils.checkName(newName)) { - Utils.alertView( - ctx, getString(R.string.notice), - String.format(getString(R.string.invalid_contact), newName) - ) + alertTitle.value = getString(R.string.notice) + alertMessage.value = String.format(getString(R.string.invalid_contact), newName) + showAlert.value = true return } @@ -462,39 +464,25 @@ class BaresipContactActivity : ComponentActivity() { (uriOrName != newName) && Contact.nameExists(newName, BaresipService.contacts, false) } if (alert) { - Utils.alertView( - ctx, getString(R.string.notice), - String.format(getString(R.string.contact_already_exists), newName)) + alertTitle.value = getString(R.string.notice) + alertMessage.value = String.format(getString(R.string.contact_already_exists), newName) + showAlert.value = true return } - val contact: Contact.BaresipContact + val contact: Contact.BaresipContact = + Contact.BaresipContact(newName, newUri, color, newId, newFavorite) - if (new) { - contact = Contact.BaresipContact(newName, newUri, color, id, newFavorite) - } else { - contact = Contact.baresipContact(name)!! - contact.uri = newUri - contact.name = newName - contact.color = color - contact.favorite = newFavorite - } - - when (newAvatar) { - "text" -> { - if (contact.avatarImage != null) { - contact.avatarImage = null - Utils.deleteFile(File(BaresipService.filesPath, "${contact.id}.png")) - } - } - "image" -> { - val imageFilePath = BaresipService.filesPath + "/${contact.id}.png" - val imageFile = File(imageFilePath) - tmpFile!!.copyTo(target = imageFile, overwrite = true) - contact.avatarImage = BitmapFactory.decodeFile(imageFilePath) + if (imageAvatarUri.value == "") { + if (contact.avatarImage != null) { + contact.avatarImage = null + Utils.deleteFile(File(BaresipService.filesPath, "${newId}.png")) } } - + else { + val imageFilePath = BaresipService.filesPath + "/${newId}.png" + contact.avatarImage = BitmapFactory.decodeFile(imageFilePath) + } if (newAndroid) addOrUpdateAndroidContact(ctx, contact) @@ -502,7 +490,7 @@ class BaresipContactActivity : ComponentActivity() { if (new) Contact.addBaresipContact(contact) else - Contact.updateBaresipContact(contact) + Contact.updateBaresipContact(id, contact) } BaresipService.activities.remove("baresip contact,$new,$uriOrName") diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt index 465bf9b4..8c060eab 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight @@ -74,6 +75,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.Observer import com.tutpro.baresip.CustomElements.AlertDialog +import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.Text import com.tutpro.baresip.CustomElements.verticalScrollbar import kotlinx.coroutines.launch @@ -530,7 +532,7 @@ class ChatActivity : ComponentActivity() { modifier = Modifier.clickable { newMessage = "" } ) } }, - label = { Text(text = getString(R.string.new_message), fontSize = 18.sp) }, + label = { LabelText(text = stringResource(R.string.new_message), fontSize = 16.sp) }, textStyle = TextStyle(fontSize = 18.sp), keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt index d044e6af..eb4c73c3 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt @@ -78,6 +78,7 @@ import coil.compose.AsyncImage import com.tutpro.baresip.BaresipService.Companion.contactNames import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.ImageAvatar +import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.Text import com.tutpro.baresip.CustomElements.TextAvatar import com.tutpro.baresip.CustomElements.verticalScrollbar @@ -574,9 +575,9 @@ class ChatsActivity: ComponentActivity() { ) }, label = { - Text( - text = getString(R.string.new_chat_peer), - fontSize = 18.sp + LabelText( + text = stringResource(R.string.new_chat_peer), + fontSize = 16.sp ) }, textStyle = TextStyle( diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt index 9e551e99..568a99d2 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt @@ -65,6 +65,7 @@ import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.Checkbox +import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.verticalScrollbar import com.tutpro.baresip.Utils.copyInputStreamToFile import java.io.File @@ -476,7 +477,7 @@ class ConfigActivity : ComponentActivity() { }, textStyle = androidx.compose.ui.text.TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.listen_address)) }, + label = { LabelText(stringResource(R.string.listen_address)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -573,7 +574,7 @@ class ConfigActivity : ComponentActivity() { }, textStyle = androidx.compose.ui.text.TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.dns_servers)) }, + label = { LabelText(stringResource(R.string.dns_servers)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } @@ -781,7 +782,7 @@ class ConfigActivity : ComponentActivity() { }, textStyle = androidx.compose.ui.text.TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.itemText), - label = { Text(stringResource(R.string.user_agent)) }, + label = { LabelText(stringResource(R.string.user_agent)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) ) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt index 0d520dc6..a2bc9939 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt @@ -272,13 +272,11 @@ sealed class Contact { contactsUpdate() } - fun updateBaresipContact(contact: BaresipContact) { + fun updateBaresipContact(id: Long, contact: BaresipContact) { val updatedContacts = BaresipService.baresipContacts.value.toMutableList() - val updatedContact = contact.copy() as BaresipContact - updatedContact.id = System.currentTimeMillis() - updatedContacts.removeIf { it.id == contact.id } - updatedContacts.add(updatedContact) - BaresipService.baresipContacts.value = updatedContacts + updatedContacts.removeIf { it.id == id } + updatedContacts.add(contact) + BaresipService.baresipContacts.value = updatedContacts.toList() saveBaresipContacts() contactsUpdate() } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt index 89077af3..50021ea6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt @@ -369,7 +369,7 @@ class ContactsActivity : ComponentActivity() { Icon( imageVector = Icons.Filled.Edit, modifier = Modifier.size(28.dp), - contentDescription = stringResource(R.string.add) + contentDescription = stringResource(R.string.edit) ) } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 6a9b21a1..4dca4074 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -12,7 +12,6 @@ import android.app.KeyguardManager import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.content.Intent.ACTION_CALL import android.content.Intent.ACTION_DIAL @@ -58,11 +57,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size @@ -130,13 +129,15 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.lifecycle.Observer -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tutpro.baresip.BaresipService.Companion.contactNames import com.tutpro.baresip.BaresipService.Companion.uas import com.tutpro.baresip.BaresipService.Companion.uasStatus +import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.Checkbox import com.tutpro.baresip.CustomElements.DropdownMenu +import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.PullToRefreshBox +import com.tutpro.baresip.CustomElements.SelectableAlertDialog import com.tutpro.baresip.CustomElements.Text import com.tutpro.baresip.CustomElements.verticalScrollbar import kotlinx.coroutines.delay @@ -220,6 +221,22 @@ class MainActivity : ComponentActivity() { private var passwordAccounts = mutableListOf() private var passwordTitle by mutableStateOf("") + private val alertTitle = mutableStateOf("") + private val alertMessage = mutableStateOf("") + private val showAlert = mutableStateOf(false) + + private val dialogTitle = mutableStateOf("") + private val dialogMessage = mutableStateOf("") + private val positiveText = mutableStateOf("") + private val onPositiveClicked = mutableStateOf({}) + private val negativeText = mutableStateOf("") + private val onNegativeClicked = mutableStateOf({}) + private val showDialog = mutableStateOf(false) + + private val showSelectItemDialog = mutableStateOf(false) + val items = mutableStateOf(listOf()) + val itemAction = mutableStateOf<(Int) -> Unit>({ index -> {} }) + private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { moveTaskToBack(true) @@ -330,18 +347,14 @@ class MainActivity : ComponentActivity() { configRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if ((it.data != null) && it.data!!.hasExtra("restart")) { - with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setTitle(R.string.restart_request) - setMessage(getString(R.string.config_restart)) - setPositiveButton(getText(R.string.restart)) { dialog, _ -> - dialog.dismiss() - quitRestart(true) - } - setNeutralButton(getText(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - } - show() + dialogTitle.value = getString(R.string.restart_request) + dialogMessage.value = getString(R.string.config_restart) + positiveText.value = getString(R.string.restart) + onPositiveClicked.value = { + quitRestart(true) } + negativeText.value = getString(R.string.cancel) + showDialog.value = true } val displayTheme = Preferences(applicationContext).displayTheme if (displayTheme != AppCompatDelegate.getDefaultNightMode()) { @@ -436,21 +449,26 @@ class MainActivity : ComponentActivity() { shouldShow.add(permission.key) } } - if (denied.contains(POST_NOTIFICATIONS) && - !shouldShow.contains(POST_NOTIFICATIONS)) { - with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setTitle(getString(R.string.notice)) - setMessage(getString(R.string.no_notifications)) - setPositiveButton(getString(R.string.ok)) { _, _ -> - quitRestart(false) - } - show() + if (denied.contains(POST_NOTIFICATIONS) && !shouldShow.contains(POST_NOTIFICATIONS)) { + dialogTitle.value = getString(R.string.notice) + dialogMessage.value = getString(R.string.no_notifications) + positiveText.value = getString(R.string.ok) + onPositiveClicked.value = { + quitRestart(false) } + negativeText.value = "" + showDialog.value = true } else { - if (shouldShow.isNotEmpty()) - Utils.alertView(this, getString(R.string.permissions_rationale), - getString(R.string.audio_permissions) - ) { requestPermissionsLauncher.launch(permissions) } + if (shouldShow.isNotEmpty()) { + dialogTitle.value = getString(R.string.permissions_rationale) + dialogMessage.value = getString(R.string.audio_permissions) + positiveText.value = getString(R.string.ok) + onPositiveClicked.value = { + requestPermissionsLauncher.launch(permissions) + } + negativeText.value = "" + showDialog.value = true + } else startBaresip() } @@ -486,6 +504,7 @@ class MainActivity : ComponentActivity() { @Composable fun MainContent(ctx: Context, contentPadding: PaddingValues) { + var isRefreshing by remember { mutableStateOf(false) } LaunchedEffect(isRefreshing) { if (isRefreshing) { @@ -493,6 +512,18 @@ class MainActivity : ComponentActivity() { isRefreshing = false } } + + if (showDialog.value) + AlertDialog( + showDialog = showDialog, + title = getString(R.string.confirmation), + message = dialogMessage.value, + positiveButtonText = positiveText.value, + onPositiveClicked = onPositiveClicked.value, + negativeButtonText = getString(R.string.cancel), + onNegativeClicked = onNegativeClicked.value + ) + PullToRefreshBox( isRefreshing = isRefreshing, modifier = Modifier.fillMaxSize(), @@ -508,21 +539,27 @@ class MainActivity : ComponentActivity() { } } ) { + SelectableAlertDialog( + openDialog = showSelectItemDialog, + title = getString(R.string.choose_destination_uri), + items = items.value, + onItemClicked = itemAction.value, + neutralButtonText = getString(R.string.cancel), + onNeutralClicked = {} + ) + Column( modifier = Modifier - .consumeWindowInsets(contentPadding) - .padding( - PaddingValues( - top = 76.dp, bottom = 6.dp, - start = 16.dp, end = 16.dp - ) - ) + .imePadding() + .fillMaxWidth() + .padding(contentPadding) + .padding(top = 18.dp, bottom = 6.dp, start = 16.dp, end = 16.dp) .fillMaxSize() .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, ) { AccountSpinner(ctx) - CallUriRow(ctx) + CallUriRow() CallRow(ctx) OnHoldNotice() AskPasswords(ctx) @@ -668,10 +705,16 @@ class MainActivity : ComponentActivity() { passwordTitle = getString(R.string.encrypt_password) showPasswordDialog.value = true } - shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE) -> - Utils.alertView(ctx, getString(R.string.notice), getString(R.string.no_backup)) { + shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE) -> { + dialogTitle.value = getString(R.string.notice) + dialogMessage.value = getString(R.string.no_backup) + positiveText.value = getString(R.string.ok) + onPositiveClicked.value = { requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) } + negativeText.value = "" + showDialog.value = true + } else -> requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) } @@ -688,10 +731,16 @@ class MainActivity : ComponentActivity() { passwordTitle = getString(R.string.decrypt_password) showPasswordDialog.value = true } - shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE) -> - Utils.alertView(ctx, getString(R.string.notice), getString(R.string.no_restore)) { + shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE) -> { + dialogTitle.value = getString(R.string.notice) + dialogMessage.value = getString(R.string.no_restore) + positiveText.value = getString(R.string.ok) + onPositiveClicked.value = { requestPermissionLauncher.launch(READ_EXTERNAL_STORAGE) } + negativeText.value = "" + showDialog.value = true + } else -> requestPermissionLauncher.launch(READ_EXTERNAL_STORAGE) } @@ -885,7 +934,7 @@ class MainActivity : ComponentActivity() { } @Composable - fun CallUriRow(ctx: Context) { + fun CallUriRow() { val suggestions by remember { contactNames } var filteredSuggestions by remember { mutableStateOf(suggestions) } val focusRequester = remember { FocusRequester() } @@ -929,7 +978,7 @@ class MainActivity : ComponentActivity() { .padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp) .focusRequester(focusRequester), label = { - Text( + LabelText( text = callUriLabel.value, fontSize = 18.sp, ) @@ -1018,47 +1067,34 @@ class MainActivity : ComponentActivity() { .clickable { when (securityIcon.intValue) { R.drawable.unlocked -> { - Utils.alertView( - ctx, getString(R.string.alert), - getString(R.string.call_not_secure) - ) + alertTitle.value = getString(R.string.alert) + alertMessage.value = getString(R.string.call_not_secure) + showAlert.value = true } - R.drawable.locked_yellow -> { - Utils.alertView( - ctx, getString(R.string.alert), - getString(R.string.peer_not_verified) - ) + alertTitle.value = getString(R.string.alert) + alertMessage.value = getString(R.string.peer_not_verified) + showAlert.value = true } - R.drawable.locked_green -> { - with( - MaterialAlertDialogBuilder( - ctx, - R.style.AlertDialogTheme - ) - ) { - setTitle(R.string.info) - setMessage(getString(R.string.call_is_secure)) - setPositiveButton(getString(R.string.unverify)) { dialog, _ -> - val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! - val call = ua.currentCall() - if (call != null) { - if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) - Log.e( - TAG, - "Command 'zrtp_unverify ${call.zid}' failed" - ) - else - securityIcon.intValue = R.drawable.locked_yellow - } - dialog.dismiss() + dialogTitle.value = getString(R.string.info) + dialogMessage.value = getString(R.string.call_is_secure) + positiveText.value = getString(R.string.unverify) + onPositiveClicked.value = { + val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! + val call = ua.currentCall() + if (call != null) { + if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) + Log.e( + TAG, + "Command 'zrtp_unverify ${call.zid}' failed" + ) + else + securityIcon.intValue = R.drawable.locked_yellow } - setNeutralButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - } - show() } + negativeText.value = getString(R.string.cancel) + showDialog.value = true } } }, @@ -1168,11 +1204,11 @@ class MainActivity : ComponentActivity() { val call = ua.currentCall() if (call != null) { if (call.onHoldCall != null) { - if (!call.executeTransfer()) - Utils.alertView( - ctx, getString(R.string.notice), - String.format(getString(R.string.transfer_failed)) - ) + if (!call.executeTransfer()) { + alertTitle.value = getString(R.string.notice) + alertMessage.value = getString(R.string.transfer_failed) + showAlert.value = true + } } else showTransferDialog = true @@ -1254,13 +1290,7 @@ class MainActivity : ComponentActivity() { bottom = 2.dp ) .focusRequester(focusRequester), - label = { - Text( - text = stringResource(R.string.transfer_destination), - fontSize = 18.sp, - color = LocalCustomColors.current.dark - ) - }, + label = { LabelText(stringResource(R.string.transfer_destination)) }, textStyle = TextStyle( fontSize = 18.sp, color = LocalCustomColors.current.dark @@ -1365,30 +1395,24 @@ class MainActivity : ComponentActivity() { } TextButton( onClick = { + showSuggestions = false var uriText = transferUri.trim() if (uriText.isNotEmpty()) { val uris = Contact.contactUris(uriText) if (uris.size > 1) { - val destinationBuilder = - MaterialAlertDialogBuilder( - ctx, - R.style.AlertDialogTheme + items.value = uris + itemAction.value = { index -> + val uriText = uris[index] + transfer( + ua, + if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText, + !blindChecked.value ) - with(destinationBuilder) { - setTitle(R.string.choose_destination_uri) - setItems(uris.toTypedArray()) { _, which -> - uriText = uris[which] - transfer( - ua, - if (Utils.isTelNumber(uriText)) - "tel:$uriText" - else - uriText, - !blindChecked.value - ) - } + showSelectItemDialog.value = false } - } else { + showSelectItemDialog.value = true + } + else { if (uris.size == 1) uriText = uris[0] } @@ -1441,7 +1465,7 @@ class MainActivity : ComponentActivity() { .focusRequester(focusRequester), enabled = dtmfEnabled.value, textStyle = TextStyle(fontSize = 16.sp), - label = { Text(getString(R.string.dtmf), fontSize = 16.sp) }, + label = { LabelText(stringResource(R.string.dtmf)) }, singleLine = true ) LaunchedEffect(shouldRequestFocus) { @@ -1468,8 +1492,8 @@ class MainActivity : ComponentActivity() { val duration = call.duration() val txCodec = codecs.split(',')[0].split("/") val rxCodec = codecs.split(',')[1].split("/") - Utils.alertView( - ctx, getString(R.string.call_info), + alertTitle.value = getString(R.string.call_info) + alertMessage.value = "${String.format(getString(R.string.duration), duration)}\n" + "${getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/" + "${rxCodec[0]} ch ${rxCodec[2]}\n" + @@ -1483,12 +1507,11 @@ class MainActivity : ComponentActivity() { "${getString(R.string.packets)}: ${parts[2]}\n" + "${getString(R.string.lost)}: ${parts[3]}\n" + String.format(getString(R.string.jitter), parts[4]) - ) + showAlert.value = true } else { - Utils.alertView( - ctx, getString(R.string.call_info), - getString(R.string.call_info_not_available) - ) + alertTitle.value = getString(R.string.call_info) + alertMessage.value = getString(R.string.call_info_not_available) + showAlert.value = true } }, ) { @@ -1539,91 +1562,91 @@ class MainActivity : ComponentActivity() { private fun callClick(ctx: Context) { if (viewModel.selectedAor.value != "") { - if (Utils.checkPermissions(ctx, arrayOf(RECORD_AUDIO))) - makeCall() + if (Utils.checkPermissions(ctx, arrayOf(RECORD_AUDIO))) { + if (Call.inCall()) + return + var uriText = callUri.value.trim() + if (uriText.isNotEmpty()) { + val uris = Contact.contactUris(uriText) + if (uris.isEmpty()) + makeCall(uriText) + else if (uris.size == 1) + makeCall(uris[0]) + else { + items.value = uris + itemAction.value = { index -> + makeCall(uris[index]) + } + showSelectItemDialog.value = true + } + } + else { + val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! + val latestPeerUri = CallHistoryNew.aorLatestPeerUri(ua.account.aor) + if (latestPeerUri != null) + callUri.value = Utils.friendlyUri(this, latestPeerUri, ua.account) + } + } else Toast.makeText(applicationContext, R.string.no_calls, Toast.LENGTH_SHORT).show() } } - private fun makeCall(lookForContact: Boolean = true) { - val ua = UserAgent.ofAor(viewModel.selectedAor.value) - val aor = ua!!.account.aor - if (!Call.inCall()) { - var uriText = callUri.value.trim() - if (uriText.isNotEmpty()) { - if (lookForContact) { - val uris = Contact.contactUris(uriText) - if (uris.size == 1) - uriText = uris[0] - else if (uris.size > 1) { - val builder = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) - with(builder) { - setTitle(R.string.choose_destination_uri) - setItems(uris.toTypedArray()) { _, which -> - callUri.value = uris[which] - makeCall(false) + private fun makeCall(uriText: String) { + val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! + val aor = ua.account.aor + val peerUri = if (Utils.isTelNumber(uriText)) + "tel:$uriText" + else + uriText + val uri = if (Utils.isTelUri(peerUri)) { + if (ua.account.telProvider == "") { + alertTitle.value = getString(R.string.notice) + alertMessage.value = String.format(getString(R.string.no_telephony_provider), aor) + showAlert.value = true + return + } + Utils.telToSip(uriText, ua.account) + } else { + Utils.uriComplete(uriText, aor) + } + if (!Utils.checkUri(uri)) { + alertTitle.value = getString(R.string.notice) + alertMessage.value = String.format(getString(R.string.invalid_sip_or_tel_uri), uri) + showAlert.value = true + } else if (!BaresipService.requestAudioFocus(applicationContext)) { + Toast.makeText(applicationContext, R.string.audio_focus_denied, + Toast.LENGTH_SHORT).show() + } else { + callUriEnabled.value = false + showCallButton.value = false + showHangupButton.value = true + if (Build.VERSION.SDK_INT < 31) { + Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") + am.mode = AudioManager.MODE_IN_COMMUNICATION + runCall(ua, uri) + } else { + if (am.mode == AudioManager.MODE_IN_COMMUNICATION) { + runCall(ua, uri) + } else { + audioModeChangedListener = AudioManager.OnModeChangedListener { mode -> + if (mode == AudioManager.MODE_IN_COMMUNICATION) { + Log.d(TAG, "Audio mode changed to MODE_IN_COMMUNICATION using " + + "device ${am.communicationDevice!!.type}") + if (audioModeChangedListener != null) { + am.removeOnModeChangedListener(audioModeChangedListener!!) + audioModeChangedListener = null } - setNeutralButton(getString(R.string.cancel)) { _: DialogInterface, _: Int -> } - show() - } - return - } - } - if (Utils.isTelNumber(uriText)) - uriText = "tel:$uriText" - val uri = if (Utils.isTelUri(uriText)) { - if (ua.account.telProvider == "") { - Utils.alertView(this, getString(R.string.notice), - String.format(getString(R.string.no_telephony_provider), aor)) - return - } - Utils.telToSip(uriText, ua.account) - } else { - Utils.uriComplete(uriText, aor) - } - if (!Utils.checkUri(uri)) { - Utils.alertView(this, getString(R.string.notice), - String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) - } else if (!BaresipService.requestAudioFocus(applicationContext)) { - Toast.makeText(applicationContext, R.string.audio_focus_denied, - Toast.LENGTH_SHORT).show() - } else { - callUriEnabled.value = false - showCallButton.value = false - showHangupButton.value = true - if (Build.VERSION.SDK_INT < 31) { - Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") - am.mode = AudioManager.MODE_IN_COMMUNICATION - runCall(ua, uri) - } else { - if (am.mode == AudioManager.MODE_IN_COMMUNICATION) { runCall(ua, uri) } else { - audioModeChangedListener = AudioManager.OnModeChangedListener { mode -> - if (mode == AudioManager.MODE_IN_COMMUNICATION) { - Log.d(TAG, "Audio mode changed to MODE_IN_COMMUNICATION using " + - "device ${am.communicationDevice!!.type}") - if (audioModeChangedListener != null) { - am.removeOnModeChangedListener(audioModeChangedListener!!) - audioModeChangedListener = null - } - runCall(ua, uri) - } else { - Log.d(TAG, "Audio mode changed to mode ${am.mode} using " + - "device ${am.communicationDevice!!.type}") - } - } - am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!) - Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") - am.mode = AudioManager.MODE_IN_COMMUNICATION + Log.d(TAG, "Audio mode changed to mode ${am.mode} using " + + "device ${am.communicationDevice!!.type}") } } + am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!) + Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") + am.mode = AudioManager.MODE_IN_COMMUNICATION } - } else { - val latestPeerUri = CallHistoryNew.aorLatestPeerUri(aor) - if (latestPeerUri != null) - callUri.value = Utils.friendlyUri(this, latestPeerUri, ua.account) } } } @@ -1668,28 +1691,21 @@ class MainActivity : ComponentActivity() { val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! val acc = ua.account if (acc.vmUri != "") { - val dialogClickListener = DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_POSITIVE -> { - val i = Intent(ctx, MainActivity::class.java) - i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP - i.putExtra("action", "call") - i.putExtra("uap", ua.uap) - i.putExtra("peer", acc.vmUri) - startActivity(i) - } - DialogInterface.BUTTON_NEGATIVE -> { - } - } - } - with(MaterialAlertDialogBuilder(ctx, R.style.AlertDialogTheme)) { - setTitle(R.string.voicemail_messages) - setMessage(acc.vmMessages(ctx)) - setPositiveButton(getString(R.string.listen), dialogClickListener) - setNeutralButton(getString(R.string.cancel), dialogClickListener) - show() + dialogTitle.value = getString(R.string.voicemail_messages) + dialogMessage.value = acc.vmMessages(ctx) + positiveText.value = getString(R.string.listen) + onPositiveClicked.value = { + val i = Intent(ctx, MainActivity::class.java) + i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + i.putExtra("action", "call") + i.putExtra("uap", ua.uap) + i.putExtra("peer", acc.vmUri) + startActivity(i) } + negativeText.value = getString(R.string.cancel) + onNegativeClicked.value = {} + showDialog.value = true } } }, @@ -2074,8 +2090,9 @@ class MainActivity : ComponentActivity() { val ev = action.split(",") when (ev[0]) { "no network" -> { - Utils.alertView(this, getString(R.string.notice), - getString(R.string.no_network)) + alertTitle.value = getString(R.string.notice) + alertMessage.value = getString(R.string.no_network) + showAlert.value = true return } "call", "dial" -> { @@ -2205,8 +2222,11 @@ class MainActivity : ComponentActivity() { if (event == "stopped") { Log.d(TAG, "Handling service event 'stopped' with start error '${params[0]}'") if (params[0] != "") { - Utils.alertView(this, getString(R.string.notice), getString(R.string.start_failed)) - } else { + alertTitle.value = getString(R.string.notice) + alertMessage.value = getString(R.string.start_failed) + showAlert.value = true + } + else { finishAndRemoveTask() if (restart) reStart() @@ -2262,19 +2282,17 @@ class MainActivity : ComponentActivity() { String.format(getString(R.string.redirect_notice), target), Toast.LENGTH_SHORT ).show() - } else { - with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setTitle(R.string.redirect_request) - setMessage(String.format(getString(R.string.redirect_request_query), target)) - setPositiveButton(getString(R.string.yes)) { dialog, _ -> - redirect(ua, target) - dialog.dismiss() - } - setNeutralButton(getString(R.string.no)) { dialog, _ -> - dialog.dismiss() - } - show() + } + else { + dialogTitle.value = getString(R.string.redirect_request) + dialogMessage.value = String.format(getString(R.string.redirect_request_query), target) + positiveText.value = getString(R.string.yes) + onPositiveClicked.value = { + redirect(ua, target) } + negativeText.value = getString(R.string.no) + onNegativeClicked.value = {} + showDialog.value = true } showCall(ua) } @@ -2294,29 +2312,27 @@ class MainActivity : ComponentActivity() { handleNextEvent("Call $callp to be verified is not found") return } - with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setTitle(R.string.verify) - setMessage(String.format(getString(R.string.verify_sas), ev[1])) - setPositiveButton(getString(R.string.yes)) { dialog, _ -> - call.security = if (Api.cmd_exec("zrtp_verify ${ev[2]}") != 0) { - Log.e(TAG, "Command 'zrtp_verify ${ev[2]}' failed") - R.drawable.locked_yellow - } else { - R.drawable.locked_green - } - call.zid = ev[2] - if (aor == viewModel.selectedAor.value) - securityIcon.intValue = call.security - dialog.dismiss() + dialogTitle.value = getString(R.string.verify) + dialogMessage.value = String.format(getString(R.string.verify_sas), call.zid) + positiveText.value = getString(R.string.yes) + onPositiveClicked.value = { + call.security = if (Api.cmd_exec("zrtp_verify ${ev[2]}") != 0) { + Log.e(TAG, "Command 'zrtp_verify ${ev[2]}' failed") + R.drawable.locked_yellow + } else { + R.drawable.locked_green } - setNeutralButton(getString(R.string.no)) { dialog, _ -> - call.security = R.drawable.locked_yellow - call.zid = ev[2] - if (aor == viewModel.selectedAor.value) - securityIcon.intValue = R.drawable.locked_yellow - dialog.dismiss() - } - show() + call.zid = ev[2] + if (aor == viewModel.selectedAor.value) + securityIcon.intValue = call.security + } + negativeText.value = getString(R.string.no) + onNegativeClicked.value = { + call.security = R.drawable.locked_yellow + call.zid = ev[2] + if (aor == viewModel.selectedAor.value) + securityIcon.intValue = R.drawable.locked_yellow + onNegativeClicked.value = {} } } "call verified", "call secure" -> { @@ -2344,40 +2360,28 @@ class MainActivity : ComponentActivity() { } val call = Call.ofCallp(callp) val target = Utils.friendlyUri(this, ev[1], acc) - with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - if (call != null) { - setTitle(R.string.transfer_request) - setMessage( - String.format( - getString(R.string.transfer_request_query), - target - ) - ) - } else { - setTitle(R.string.call_request) - setMessage( - String.format( - getString(R.string.call_request_query), - target - ) - ) + dialogTitle.value = if (call != null) + getString(R.string.transfer_request) + else + getString(R.string.call_request) + dialogMessage.value = if (call != null) + String.format(getString(R.string.transfer_request_query), target) + else + String.format(getString(R.string.call_request_query), target) + positiveText.value = getString(R.string.yes) + onPositiveClicked.value = { + if (call in Call.calls()) + acceptTransfer(ua, call!!, ev[1]) + else + makeCall(ev[1]) } - setPositiveButton(getString(R.string.yes)) { dialog, _ -> - if (call in Call.calls()) - acceptTransfer(ua, call!!, ev[1]) - else { - callUri.value = ev[1] - makeCall() - } - dialog.dismiss() - } - setNeutralButton(getString(R.string.no)) { dialog, _ -> - if (call in Call.calls()) - call!!.notifySipfrag(603, "Decline") - dialog.dismiss() - } - show() + negativeText.value = getString(R.string.no) + onNegativeClicked.value = { + if (call in Call.calls()) + call!!.notifySipfrag(603, "Decline") + onNegativeClicked.value = {} } + showDialog.value = true } "transfer accept" -> { val callp = params[1] as Long @@ -2509,9 +2513,11 @@ class MainActivity : ComponentActivity() { else Utils.uriComplete(uriText, ua.account.aor) if (!Utils.checkUri(uri)) { - Utils.alertView(this@MainActivity, getString(R.string.notice), - String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) - } else { + alertTitle.value = getString(R.string.notice) + alertMessage.value = String.format(getString(R.string.invalid_sip_or_tel_uri), uri) + showAlert.value = true + } + else { val call = ua.currentCall() if (call != null) { if (attended) { @@ -2519,10 +2525,12 @@ class MainActivity : ComponentActivity() { call.referTo = uri call(ua, uri, call) } - } else { + } + else { if (!call.transfer(uri)) { - Utils.alertView(this@MainActivity, getString(R.string.notice), - String.format(getString(R.string.transfer_failed))) + alertTitle.value = getString(R.string.notice) + alertMessage.value = getString(R.string.transfer_failed) + showAlert.value = true } } showCall(ua) @@ -2550,31 +2558,35 @@ class MainActivity : ComponentActivity() { val zipFilePath = BaresipService.filesPath + "/$zipFile" if (!Utils.zip(files, zipFile)) { Log.w(TAG, "Failed to write zip file '$zipFile'") - Utils.alertView(this, getString(R.string.error), - String.format(getString(R.string.backup_failed), - Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) + alertTitle.value = getString(R.string.error) + alertMessage.value = String.format(getString(R.string.backup_failed), + Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!)) + showAlert.value = true downloadsOutputUri = null return } val content = Utils.getFileContents(zipFilePath) if (content == null) { Log.w(TAG, "Failed to read zip file '$zipFile'") - Utils.alertView(this, getString(R.string.error), - String.format(getString(R.string.backup_failed), - Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) + alertTitle.value = getString(R.string.error) + alertMessage.value = String.format(getString(R.string.backup_failed), + Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!)) + showAlert.value = true downloadsOutputUri = null return } if (!Utils.encryptToUri(applicationContext, downloadsOutputUri!!, content, password)) { - Utils.alertView(this, getString(R.string.error), - String.format(getString(R.string.backup_failed), - Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) + alertTitle.value = getString(R.string.error) + alertMessage.value = String.format(getString(R.string.backup_failed), + Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!)) + showAlert.value = true downloadsOutputUri = null return } - Utils.alertView(this, getString(R.string.info), - String.format(getString(R.string.backed_up), - Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) + alertTitle.value = getString(R.string.info) + alertMessage.value = String.format(getString(R.string.backed_up), + Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!)) + showAlert.value = true Utils.deleteFile(File(zipFilePath)) downloadsOutputUri = null } @@ -2584,34 +2596,27 @@ class MainActivity : ComponentActivity() { val zipFilePath = BaresipService.filesPath + "/$zipFile" val zipData = Utils.decryptFromUri(applicationContext, downloadsInputUri!!, password) if (zipData == null) { - Utils.alertView( - this, getString(R.string.error), - String.format( - getString(R.string.restore_failed), - Utils.fileNameOfUri(applicationContext, downloadsInputUri!!) - ) - ) + alertTitle.value = getString(R.string.error) + alertMessage.value = String.format(getString(R.string.restore_failed), + Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!)) + showAlert.value = true downloadsOutputUri = null return } if (!Utils.putFileContents(zipFilePath, zipData)) { Log.w(TAG, "Failed to write zip file '$zipFile'") - Utils.alertView( - this, getString(R.string.error), - String.format( - getString(R.string.restore_failed), - Utils.fileNameOfUri(applicationContext, downloadsInputUri!!) - ) - ) + alertTitle.value = getString(R.string.error) + alertMessage.value = String.format(getString(R.string.restore_failed), + Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!)) + showAlert.value = true downloadsOutputUri = null return } if (!Utils.unZip(zipFilePath)) { Log.w(TAG, "Failed to unzip file '$zipFile'") - Utils.alertView( - this, getString(R.string.error), - String.format(getString(R.string.restore_unzip_failed), "baresip", "60.0.0") - ) + alertTitle.value = getString(R.string.error) + alertMessage.value = String.format(getString(R.string.restore_unzip_failed), "baresip", BuildConfig.VERSION_NAME) + showAlert.value = true downloadsOutputUri = null return } @@ -2627,18 +2632,16 @@ class MainActivity : ComponentActivity() { h.recording = arrayOf("", "") CallHistoryNew.save() - with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setTitle(getString(R.string.info)) - setMessage(getString(R.string.restored)) - setPositiveButton(getText(R.string.restart)) { dialog, _ -> - quitRestart(true) - dialog.dismiss() - } - setNeutralButton(getText(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - } - show() + dialogTitle.value = getString(R.string.info) + dialogMessage.value = getString(R.string.restored) + positiveText.value = getString(R.string.restart) + onPositiveClicked.value = { + quitRestart(true) } + negativeText.value = getString(R.string.cancel) + onNegativeClicked.value = {} + showDialog.value = true + downloadsOutputUri = null } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index d17c863c..bf969982 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -8,8 +8,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas import android.media.AudioAttributes import android.media.AudioDeviceInfo import android.media.AudioManager @@ -25,21 +23,14 @@ import android.provider.DocumentsContract import android.provider.MediaStore import android.provider.OpenableColumns import android.text.format.DateUtils -import android.view.View -import android.widget.ImageView -import android.widget.TextView import androidx.activity.result.ActivityResultLauncher import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.runtime.Composable import androidx.core.content.ContextCompat -import androidx.core.graphics.createBitmap import androidx.core.net.toUri import androidx.core.text.isDigitsOnly import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner -import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.File @@ -92,18 +83,6 @@ object Utils { return result } - fun alertView(context: Context, title: String, message: String, action: () -> (Unit) = {}) { - with(MaterialAlertDialogBuilder(context, R.style.AlertDialogTheme)) { - setTitle(title) - setMessage(message) - setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - action() - } - show() - } - } - fun uriHostPart(uri: String): String { return if (uri.contains("@")) { uri.substringAfter("@") @@ -407,46 +386,6 @@ object Utils { return Regex("^[-a-zA-Z0-9.!%*_+`'~]+\$").matches(token) } - fun setAvatar(ctx: Context, imageView: ImageView, textView: TextView, uri: String) { - - when (val contact = Contact.findContact(uri)) { - - is Contact.BaresipContact -> { - val avatarImage = contact.avatarImage - if (avatarImage != null) { - imageView.setImageBitmap(avatarImage) - } else { - textView.background.setTint(contact.color) - if (contact.name.isNotEmpty()) - textView.text = "${contact.name[0]}" - else - textView.text = "" - imageView.setImageBitmap(bitmapFromView(textView)) - } - } - - is Contact.AndroidContact -> { - val thumbnailUri = contact.thumbnailUri - if (thumbnailUri != null) { - imageView.setImageURI(thumbnailUri) - } else { - textView.background.setTint(contact.color) - if (contact.name.isNotEmpty()) - textView.text = "${contact.name[0]}" - else - textView.text = "" - imageView.setImageBitmap(bitmapFromView(textView)) - } - } - - null -> { - val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image) - imageView.setImageBitmap(bitmap) - } - - } - } - @Suppress("unused") fun checkIfName(name: String): Boolean { if ((name.length < 2) || !name.first().isLetter()) return false @@ -883,13 +822,6 @@ object Utils { Configuration.UI_MODE_NIGHT_YES } - @Composable - fun isNightMode() = when (AppCompatDelegate.getDefaultNightMode()) { - AppCompatDelegate.MODE_NIGHT_NO -> false - AppCompatDelegate.MODE_NIGHT_YES -> true - else -> isSystemInDarkTheme() - } - fun relativeTime(ctx: Context, time: GregorianCalendar): String { return if (DateUtils.isToday(time.timeInMillis)) { val fmt = DateFormat.getTimeInstance(DateFormat.SHORT) @@ -907,14 +839,6 @@ object Utils { } } - private fun bitmapFromView(view: View): Bitmap { - val bitmap = createBitmap(view.layoutParams.width, view.layoutParams.height) - val canvas = Canvas(bitmap) - view.layout(0, 0, view.layoutParams.width, view.layoutParams.height) - view.draw(canvas) - return bitmap - } - fun isSpeakerPhoneOn(am: AudioManager): Boolean { return if (Build.VERSION.SDK_INT >= 31) am.communicationDevice!!.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index d2e59ebb..00000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - -