Completed composting alert of dialogs (testing still needed)

UI improvements
This commit is contained in:
Juha Heinanen
2025-04-15 18:10:58 +03:00
parent c1b7e4b01e
commit fb35f9fbd8
12 changed files with 580 additions and 702 deletions

View File

@ -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)
)
}

View File

@ -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,

View File

@ -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)
)
}

View File

@ -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")

View File

@ -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,

View File

@ -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(

View File

@ -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)
)
}

View File

@ -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()
}

View File

@ -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)
)
}
}

View File

@ -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<String>()
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<String>())
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
}

View File

@ -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

View File

@ -1,38 +0,0 @@
<resources>
<style name="AlertDialogTheme" parent="@style/ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<item name="materialAlertDialogTitleTextStyle">@style/Alert.Title</item>
<item name="materialAlertDialogBodyTextStyle">@style/Alert.Message</item>
<item name="buttonBarPositiveButtonStyle">@style/Alert.Button.Positive</item>
<item name="buttonBarNegativeButtonStyle">@style/Alert.Button.Positive</item>
<item name="buttonBarNeutralButtonStyle">@style/Alert.Button.Neutral</item>
<item name="android:windowBackground">@color/colorCardBackground</item>
<item name="android:background">@color/colorCardBackground</item>
<item name="android:textSize">14sp</item>
</style>
<style name="Alert.Title" parent="@style/MaterialAlertDialog.Material3.Title.Text">
<item name="android:textColor">@color/colorAlert</item>
<item name="android:textSize">20sp</item>
</style>
<style name="Alert.Message" parent="@style/MaterialAlertDialog.Material3.Body.Text">
<item name="android:textColor">@color/colorItemText</item>
<item name="android:textSize">16sp</item>
</style>
<style name="Alert.Button.Positive" parent="Widget.Material3.Button.TextButton">
<item name="android:textColor">@color/colorAlert</item>
<item name="rippleColor">@color/colorAccent</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
</style>
<style name="Alert.Button.Neutral" parent="Widget.Material3.Button.TextButton">
<item name="rippleColor">@color/colorAccent</item>
<item name="android:textColor">@color/colorGray</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
</style>
</resources>