1922 lines
74 KiB
Kotlin
1922 lines
74 KiB
Kotlin
package com.tutpro.baresip
|
|
|
|
import android.content.Context
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.PaddingValues
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.WindowInsets
|
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.imePadding
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.foundation.layout.statusBars
|
|
import androidx.compose.foundation.rememberScrollState
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
import androidx.compose.foundation.verticalScroll
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
|
import androidx.compose.material.icons.filled.Check
|
|
import androidx.compose.material.icons.filled.Visibility
|
|
import androidx.compose.material.icons.filled.VisibilityOff
|
|
import androidx.compose.material3.CircularProgressIndicator
|
|
import androidx.compose.material3.DropdownMenu
|
|
import androidx.compose.material3.DropdownMenuItem
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
import androidx.compose.material3.HorizontalDivider
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.IconButton
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.OutlinedTextField
|
|
import androidx.compose.material3.OutlinedTextFieldDefaults
|
|
import androidx.compose.material3.Scaffold
|
|
import androidx.compose.material3.Switch
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.material3.TopAppBar
|
|
import androidx.compose.material3.TopAppBarDefaults
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.collectAsState
|
|
import androidx.compose.runtime.derivedStateOf
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableLongStateOf
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
import androidx.compose.ui.platform.SoftwareKeyboardController
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.compose.ui.text.TextStyle
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
import androidx.compose.ui.text.input.KeyboardType
|
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|
import androidx.compose.ui.text.input.VisualTransformation
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.sp
|
|
import androidx.compose.runtime.DisposableEffect
|
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
import androidx.lifecycle.Lifecycle
|
|
import androidx.lifecycle.LifecycleEventObserver
|
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
import androidx.navigation.NavController
|
|
import androidx.navigation.NavGraphBuilder
|
|
import androidx.navigation.NavType
|
|
import androidx.navigation.compose.composable
|
|
import androidx.navigation.navArgument
|
|
import com.tutpro.baresip.CustomElements.AlertDialog
|
|
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.launch
|
|
import org.xmlpull.v1.XmlPullParser
|
|
import org.xmlpull.v1.XmlPullParserFactory
|
|
import java.io.File
|
|
import java.io.StringReader
|
|
import java.net.URL
|
|
import java.util.Locale
|
|
import javax.net.ssl.HttpsURLConnection
|
|
import android.telephony.SubscriptionManager
|
|
|
|
fun NavGraphBuilder.accountScreenRoute(navController: NavController) {
|
|
composable(
|
|
route = "account/{aor}/{kind}",
|
|
arguments = listOf(
|
|
navArgument("aor") { type = NavType.StringType },
|
|
navArgument("kind") { type = NavType.StringType }
|
|
)
|
|
) { backStackEntry ->
|
|
val ctx = LocalContext.current
|
|
val viewModel = viewModel<AccountViewModel>()
|
|
val aor = backStackEntry.arguments?.getString("aor")!!
|
|
val kind = backStackEntry.arguments?.getString("kind")!!
|
|
val ua = UserAgent.ofAor(aor)!!
|
|
AccountScreen(
|
|
viewModel = viewModel,
|
|
navController = navController,
|
|
onBack = { navController.navigateUp() },
|
|
checkOnClick = {
|
|
val ok = checkOnClick(ctx, viewModel, ua)
|
|
if (ok) {
|
|
if (reRegister) ua.reRegister()
|
|
navController.navigateUp()
|
|
}
|
|
},
|
|
aor = aor,
|
|
kind = kind
|
|
)
|
|
}
|
|
}
|
|
|
|
private var keyboardController: SoftwareKeyboardController? = null
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@Composable
|
|
private fun AccountScreen(
|
|
viewModel: AccountViewModel,
|
|
navController: NavController,
|
|
onBack: () -> Unit,
|
|
checkOnClick: () -> Unit,
|
|
aor: String,
|
|
kind: String
|
|
) {
|
|
val ua = UserAgent.ofAor(aor)!!
|
|
val acc = ua.account
|
|
val lifecycleOwner = LocalLifecycleOwner.current
|
|
var resumeToggle by remember { mutableLongStateOf(0L) }
|
|
|
|
DisposableEffect(lifecycleOwner) {
|
|
val observer = LifecycleEventObserver { _, event ->
|
|
if (event == Lifecycle.Event.ON_RESUME) {
|
|
resumeToggle = System.currentTimeMillis()
|
|
}
|
|
}
|
|
lifecycleOwner.lifecycle.addObserver(observer)
|
|
onDispose {
|
|
lifecycleOwner.lifecycle.removeObserver(observer)
|
|
}
|
|
}
|
|
|
|
var isAccountAvailable by remember { mutableStateOf(false) }
|
|
var isAccountLoaded by remember { mutableStateOf(false) }
|
|
|
|
LaunchedEffect(kind, acc) {
|
|
if (kind == "new")
|
|
initAccountFromNetwork(acc) { isAccountAvailable = true }
|
|
else
|
|
isAccountAvailable = true
|
|
}
|
|
|
|
if (isAccountAvailable)
|
|
LaunchedEffect(acc) {
|
|
viewModel.loadAccount(acc)
|
|
isAccountLoaded = true
|
|
}
|
|
|
|
Scaffold(
|
|
modifier = Modifier.fillMaxSize().imePadding(),
|
|
containerColor = MaterialTheme.colorScheme.background,
|
|
topBar = {
|
|
Column(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.background(MaterialTheme.colorScheme.background)
|
|
.padding(
|
|
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
)
|
|
) {
|
|
TopAppBar(
|
|
title = {
|
|
Text(
|
|
text = acc.text(),
|
|
fontWeight = FontWeight.Bold
|
|
)
|
|
},
|
|
colors = TopAppBarDefaults.topAppBarColors(
|
|
containerColor = MaterialTheme.colorScheme.primary,
|
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
|
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
|
|
),
|
|
navigationIcon = {
|
|
IconButton(onClick = onBack) {
|
|
Icon(
|
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
contentDescription = null,
|
|
)
|
|
}
|
|
},
|
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
|
actions = {
|
|
IconButton(onClick = checkOnClick) {
|
|
Icon(
|
|
imageVector = Icons.Filled.Check,
|
|
contentDescription = "Check"
|
|
)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
) { contentPadding ->
|
|
if (isAccountLoaded)
|
|
AccountContent(viewModel, navController, contentPadding, ua, resumeToggle)
|
|
else
|
|
Box(
|
|
modifier = Modifier.fillMaxSize(),
|
|
contentAlignment = Alignment.Center
|
|
) {
|
|
CircularProgressIndicator()
|
|
}
|
|
}
|
|
}
|
|
|
|
private val password = mutableStateOf("")
|
|
private val showPasswordDialog = mutableStateOf(false)
|
|
|
|
private val alertTitle = mutableStateOf("")
|
|
private val alertMessage = mutableStateOf("")
|
|
private val showAlert = mutableStateOf(false)
|
|
|
|
private var reRegister = false
|
|
|
|
@Composable
|
|
private fun AccountContent(
|
|
viewModel: AccountViewModel,
|
|
navController: NavController,
|
|
contentPadding: PaddingValues,
|
|
ua: UserAgent,
|
|
resumeToggle: Long
|
|
) {
|
|
val ctx = LocalContext.current
|
|
val aor = ua.account.aor
|
|
|
|
val mediaNat by viewModel.mediaNat.collectAsState()
|
|
val showStun by remember { derivedStateOf { mediaNat.isNotEmpty() } }
|
|
|
|
@Composable
|
|
fun AoRField(value: String, label: String) {
|
|
OutlinedTextField(
|
|
value = value,
|
|
enabled = false,
|
|
onValueChange = {},
|
|
modifier = Modifier.fillMaxWidth(),
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = {
|
|
Text(
|
|
text = label,
|
|
fontWeight = FontWeight.Bold
|
|
)
|
|
},
|
|
colors = OutlinedTextFieldDefaults.colors(
|
|
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
|
disabledBorderColor = MaterialTheme.colorScheme.outline,
|
|
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
)
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
fun AoR(toggle: Long) {
|
|
Column(
|
|
Modifier
|
|
.fillMaxWidth()
|
|
.padding(top = 8.dp, end = 10.dp),
|
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
) {
|
|
if (ua.account.isMobile && android.os.Build.VERSION.SDK_INT >= 29) {
|
|
val voiceSubId = remember(toggle) { SubscriptionManager.getDefaultVoiceSubscriptionId() }
|
|
val smsSubId = remember(toggle) { SubscriptionManager.getDefaultSmsSubscriptionId() }
|
|
|
|
if (voiceSubId != smsSubId &&
|
|
voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
|
|
smsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
|
) {
|
|
val voiceNumber = remember(voiceSubId, toggle) { Utils.getLine1Number(ctx, voiceSubId) }
|
|
AoRField(
|
|
value = if (voiceNumber != null) "tel:$voiceNumber" else stringResource(R.string.not_available),
|
|
label = stringResource(R.string.tel_uri_calls)
|
|
)
|
|
val smsNumber = remember(smsSubId, toggle) { Utils.getLine1Number(ctx, smsSubId) }
|
|
AoRField(
|
|
value = if (smsNumber != null) "tel:$smsNumber" else stringResource(R.string.not_available),
|
|
label = stringResource(R.string.tel_uri_messages)
|
|
)
|
|
} else {
|
|
val currentSubId = remember(toggle) {
|
|
if (voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
|
voiceSubId
|
|
else
|
|
smsSubId
|
|
}
|
|
val currentNumber = remember(currentSubId, toggle) {
|
|
if (currentSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
|
Utils.getLine1Number(ctx, currentSubId)
|
|
else
|
|
Utils.getLine1Number(ctx)
|
|
}
|
|
AoRField(
|
|
value = if (currentNumber != null) "tel:$currentNumber" else stringResource(R.string.not_available),
|
|
label = stringResource(R.string.tel_uri)
|
|
)
|
|
}
|
|
} else {
|
|
AoRField(
|
|
value = if (ua.account.isMobile) stringResource(R.string.not_available) else ua.account.luri,
|
|
label = stringResource(if (ua.account.isMobile) R.string.tel_uri else R.string.sip_uri)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Nickname() {
|
|
val nickNameTitle = stringResource(R.string.nickname)
|
|
val nickNameHelp = stringResource(R.string.account_nickname_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val nickName by viewModel.nickName.collectAsState()
|
|
OutlinedTextField(
|
|
value = nickName,
|
|
placeholder = { Text(nickNameTitle) },
|
|
onValueChange = { viewModel.nickName.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = nickNameTitle
|
|
alertMessage.value = nickNameHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(nickNameTitle) },
|
|
keyboardOptions = KeyboardOptions(
|
|
capitalization = KeyboardCapitalization.Words,
|
|
keyboardType = KeyboardType.Text),
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun DisplayName() {
|
|
val displayNameTitle = stringResource(R.string.display_name)
|
|
val displayNameHelp = stringResource(R.string.display_name_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val displayName by viewModel.displayName.collectAsState()
|
|
OutlinedTextField(
|
|
value = displayName,
|
|
placeholder = { Text(displayNameTitle) },
|
|
onValueChange = { viewModel.displayName.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = displayNameTitle
|
|
alertMessage.value = displayNameHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(displayNameTitle) },
|
|
keyboardOptions = KeyboardOptions(
|
|
capitalization = KeyboardCapitalization.Sentences,
|
|
keyboardType = KeyboardType.Text
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun AuthUser() {
|
|
val authenticationUserNameTitle = stringResource(R.string.authentication_username)
|
|
val authenticationUserNameHelp = stringResource(R.string.authentication_username_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val authUser by viewModel.authUser.collectAsState()
|
|
OutlinedTextField(
|
|
value = authUser,
|
|
placeholder = { Text(authenticationUserNameTitle) },
|
|
onValueChange = { viewModel.authUser.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = authenticationUserNameTitle
|
|
alertMessage.value = authenticationUserNameHelp
|
|
showAlert.value = true
|
|
},
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(authenticationUserNameTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun AuthPass() {
|
|
val authenticationPasswordTitle = stringResource(R.string.authentication_password)
|
|
val authenticationPasswordHelp = stringResource(R.string.authentication_password_help)
|
|
val showPassword = remember { mutableStateOf(false) }
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val authPass by viewModel.authPass.collectAsState()
|
|
OutlinedTextField(
|
|
value = authPass,
|
|
placeholder = { Text(authenticationPasswordTitle) },
|
|
onValueChange = { viewModel.authPass.value = it },
|
|
singleLine = true,
|
|
visualTransformation = if (showPassword.value)
|
|
VisualTransformation.None
|
|
else
|
|
PasswordVisualTransformation(),
|
|
trailingIcon = {
|
|
IconButton(onClick = {
|
|
showPassword.value = !showPassword.value
|
|
}) {
|
|
Icon(
|
|
if (showPassword.value)
|
|
Icons.Filled.Visibility
|
|
else
|
|
Icons.Filled.VisibilityOff,
|
|
contentDescription = "Visibility",
|
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
},
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = authenticationPasswordTitle
|
|
alertMessage.value = authenticationPasswordHelp
|
|
showAlert.value = true
|
|
},
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(authenticationPasswordTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun AskPassword(ctx: Context, navController: NavController, ua: UserAgent) {
|
|
CustomElements.PasswordDialog(
|
|
ctx = ctx,
|
|
showPasswordDialog = showPasswordDialog,
|
|
password = password,
|
|
keyboardController = keyboardController,
|
|
title = stringResource(R.string.authentication_password),
|
|
okAction = {
|
|
if (password.value != "") {
|
|
BaresipService.aorPasswords[ua.account.aor] = password.value
|
|
Api.account_set_auth_pass(ua.account.accp, password.value)
|
|
password.value = ""
|
|
ua.reRegister()
|
|
navController.navigateUp()
|
|
}
|
|
},
|
|
cancelAction = {
|
|
ua.reRegister()
|
|
navController.navigateUp()
|
|
}
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
fun Outbound() {
|
|
val outboundProxiesTitle = stringResource(R.string.outbound_proxies)
|
|
val outboundProxiesHelp = stringResource(R.string.outbound_proxies_help)
|
|
Text(text = stringResource(R.string.outbound_proxies),
|
|
fontSize = 18.sp,
|
|
modifier = Modifier
|
|
.padding(top = 8.dp)
|
|
.clickable {
|
|
alertTitle.value = outboundProxiesTitle
|
|
alertMessage.value = outboundProxiesHelp
|
|
showAlert.value = true
|
|
}
|
|
)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val outbound1 by viewModel.outbound1.collectAsState()
|
|
val sipUriOfProxyServerTitle = stringResource(R.string.sip_uri_of_proxy_server)
|
|
OutlinedTextField(
|
|
value = outbound1,
|
|
placeholder = { Text(sipUriOfProxyServerTitle) },
|
|
onValueChange = { viewModel.outbound1.value = it },
|
|
modifier = Modifier.fillMaxWidth(),
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(sipUriOfProxyServerTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val outbound2 by viewModel.outbound2.collectAsState()
|
|
val anotherProxyServerTitle = stringResource(R.string.sip_uri_of_another_proxy_server)
|
|
OutlinedTextField(
|
|
value = outbound2,
|
|
placeholder = { Text(anotherProxyServerTitle) },
|
|
onValueChange = { viewModel.outbound2.value = it },
|
|
modifier = Modifier.fillMaxWidth(),
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(anotherProxyServerTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Register() {
|
|
val registerTitle = stringResource(R.string.register)
|
|
val registerHelp = stringResource(R.string.register_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = registerTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = registerTitle
|
|
alertMessage.value = registerHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp,
|
|
)
|
|
val register by viewModel.register.collectAsState()
|
|
Switch(
|
|
checked = register,
|
|
onCheckedChange = { viewModel.register.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun RegInt() {
|
|
val regIntTitle = stringResource(R.string.reg_int)
|
|
val regIntHelp = stringResource(R.string.reg_int_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val regInt by viewModel.regInt.collectAsState()
|
|
OutlinedTextField(
|
|
value = regInt,
|
|
placeholder = { Text(regIntTitle) },
|
|
onValueChange = { viewModel.regInt.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = regIntTitle
|
|
alertMessage.value = regIntHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(regIntTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun CheckOrigin() {
|
|
val checkOriginTitle = stringResource(R.string.check_origin)
|
|
val checkOriginHelp = stringResource(R.string.check_origin_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = checkOriginTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = checkOriginTitle
|
|
alertMessage.value = checkOriginHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp,
|
|
)
|
|
val checkOrigin by viewModel.checkOrigin.collectAsState()
|
|
Switch(
|
|
checked = checkOrigin,
|
|
onCheckedChange = { viewModel.checkOrigin.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun BlockUnknown() {
|
|
val blockUnknownTitle = stringResource(R.string.block_unknown)
|
|
val blockUnknownHelp = stringResource(R.string.block_unknown_help)
|
|
val block by viewModel.blockUnknown.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = blockUnknownTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = blockUnknownTitle
|
|
alertMessage.value = blockUnknownHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
Switch(
|
|
checked = block,
|
|
onCheckedChange = { viewModel.blockUnknown.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun BlockHidden() {
|
|
val blockHiddenTitle = stringResource(R.string.block_hidden)
|
|
val blockHiddenHelp = stringResource(R.string.block_hidden_help)
|
|
val block by viewModel.blockHidden.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = blockHiddenTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = blockHiddenTitle
|
|
alertMessage.value = blockHiddenHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
Switch(
|
|
checked = block,
|
|
onCheckedChange = { viewModel.blockHidden.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun AudioCodecs(navController: NavController, aor: String) {
|
|
Row(
|
|
Modifier.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(
|
|
text = stringResource(R.string.audio_codecs),
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
val route = "codecs/$aor/audio"
|
|
navController.navigate(route)
|
|
},
|
|
fontSize = 18.sp,
|
|
fontWeight = FontWeight.Bold
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun MediaEnc() {
|
|
val mediaEncryptionTitle = stringResource(R.string.media_encryption)
|
|
val mediaEncryptionHelp = stringResource(R.string.media_encryption_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 2.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = mediaEncryptionTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = mediaEncryptionTitle
|
|
alertMessage.value = mediaEncryptionHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
val isDropDownExpanded = remember { mutableStateOf(false) }
|
|
val mediaEnc by viewModel.mediaEnc.collectAsState()
|
|
Box {
|
|
Row(
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
modifier = Modifier.clickable {
|
|
isDropDownExpanded.value = true
|
|
}
|
|
) {
|
|
Text(text = mediaEncMap[mediaEnc]!!)
|
|
Icon(
|
|
imageVector = Icons.Filled.ArrowDropDown,
|
|
contentDescription = null,
|
|
modifier = Modifier.size(36.dp)
|
|
)
|
|
}
|
|
DropdownMenu(
|
|
expanded = isDropDownExpanded.value,
|
|
onDismissRequest = {
|
|
isDropDownExpanded.value = false
|
|
}) {
|
|
var index = 0
|
|
mediaEncMap.forEach {
|
|
DropdownMenuItem(text = { Text(text = it.value) },
|
|
onClick = {
|
|
isDropDownExpanded.value = false
|
|
viewModel.mediaEnc.value = it.key
|
|
}
|
|
)
|
|
if (index < 4)
|
|
HorizontalDivider(thickness = 1.dp)
|
|
index++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun MediaNat() {
|
|
val mediaNatTitle = stringResource(R.string.media_nat)
|
|
val mediaNatHelp = stringResource(R.string.media_nat_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(mediaNatTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = mediaNatTitle
|
|
alertMessage.value = mediaNatHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
val isDropDownExpanded = remember { mutableStateOf(false) }
|
|
val mediaNat by viewModel.mediaNat.collectAsState()
|
|
Box {
|
|
Row(
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
modifier = Modifier.clickable {
|
|
isDropDownExpanded.value = true
|
|
}
|
|
) {
|
|
Text(text = mediaNatMap[mediaNat]!!)
|
|
Icon(
|
|
imageVector = Icons.Filled.ArrowDropDown,
|
|
contentDescription = null,
|
|
modifier = Modifier.size(36.dp)
|
|
)
|
|
}
|
|
DropdownMenu(
|
|
expanded = isDropDownExpanded.value,
|
|
onDismissRequest = {
|
|
isDropDownExpanded.value = false
|
|
}
|
|
) {
|
|
var index = 0
|
|
mediaNatMap.forEach {
|
|
DropdownMenuItem(text = { Text(text = it.value) },
|
|
onClick = {
|
|
isDropDownExpanded.value = false
|
|
viewModel.mediaNat.value = it.key
|
|
}
|
|
)
|
|
if (index < 3)
|
|
HorizontalDivider(thickness = 1.dp)
|
|
index++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun StunServer() {
|
|
val stunServerTitle = stringResource(R.string.stun_server)
|
|
val stunServerHelp = stringResource(R.string.stun_server_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val stunServer by viewModel.stunServer.collectAsState()
|
|
OutlinedTextField(
|
|
value = stunServer,
|
|
placeholder = { Text(stunServerTitle) },
|
|
onValueChange = { viewModel.stunServer.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = stunServerTitle
|
|
alertMessage.value = stunServerHelp
|
|
showAlert.value = true
|
|
},
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(stunServerTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun StunUser() {
|
|
val stunUsernameTitle = stringResource(R.string.stun_username)
|
|
val stunUsernameHelp = stringResource(R.string.stun_username_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val stunUser by viewModel.stunUser.collectAsState()
|
|
OutlinedTextField(
|
|
value = stunUser,
|
|
placeholder = { Text(stunUsernameTitle) },
|
|
onValueChange = { viewModel.stunUser.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = stunUsernameTitle
|
|
alertMessage.value = stunUsernameHelp
|
|
showAlert.value = true
|
|
},
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(stunUsernameTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun StunPass() {
|
|
val stunPasswordTitle = stringResource(R.string.stun_password)
|
|
val stunPasswordHelp = stringResource(R.string.stun_password_help)
|
|
val showPassword = remember { mutableStateOf(false) }
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val stunPass by viewModel.stunPass.collectAsState()
|
|
OutlinedTextField(
|
|
value = stunPass,
|
|
placeholder = { Text(stunPasswordTitle) },
|
|
onValueChange = { viewModel.stunPass.value = it },
|
|
singleLine = true,
|
|
visualTransformation = if (showPassword.value)
|
|
VisualTransformation.None
|
|
else
|
|
PasswordVisualTransformation(),
|
|
trailingIcon = {
|
|
IconButton(onClick = { showPassword.value = !showPassword.value }) {
|
|
Icon(
|
|
if (showPassword.value)
|
|
Icons.Filled.Visibility
|
|
else
|
|
Icons.Filled.VisibilityOff,
|
|
contentDescription = "Visibility",
|
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
},
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = stunPasswordTitle
|
|
alertMessage.value = stunPasswordHelp
|
|
showAlert.value = true
|
|
},
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(stunPasswordTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun RtcpMux() {
|
|
val rtcpMuxTitle = stringResource(R.string.rtcp_mux)
|
|
val rtcpMuxHelp = stringResource(R.string.rtcp_mux_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val rtcpMux by viewModel.rtcpMux.collectAsState()
|
|
Text(text = rtcpMuxTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = rtcpMuxTitle
|
|
alertMessage.value = rtcpMuxHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
Switch(
|
|
checked = rtcpMux,
|
|
onCheckedChange = { viewModel.rtcpMux.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Rel100() {
|
|
val rel100Title = stringResource(R.string.rel_100)
|
|
val rel100Help = stringResource(R.string.rel_100_help)
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val rel100 by viewModel.rel100.collectAsState()
|
|
Text(
|
|
text = rel100Title,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = rel100Title
|
|
alertMessage.value = rel100Help
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
Switch(
|
|
checked = rel100,
|
|
onCheckedChange = { viewModel.rel100.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Dtmf() {
|
|
val dtmfModeTitle = stringResource(R.string.dtmf_mode)
|
|
val dtmfModeHelp = stringResource(R.string.dtmf_mode_help)
|
|
val dtmfInbandText = stringResource(R.string.dtmf_inband)
|
|
val dtmfInfoText = stringResource(R.string.dtmf_info)
|
|
val dtmfAutoText = stringResource(R.string.dtmf_auto)
|
|
val dtmfMode by viewModel.dtmfMode.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val dtmfModeMap = mapOf(Api.DTMFMODE_RTP_EVENT to dtmfInbandText,
|
|
Api.DTMFMODE_SIP_INFO to dtmfInfoText,
|
|
Api.DTMFMODE_AUTO to dtmfAutoText)
|
|
Text(text = dtmfModeTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = dtmfModeTitle
|
|
alertMessage.value = dtmfModeHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
val isDropDownExpanded = remember { mutableStateOf(false) }
|
|
Box {
|
|
Row(
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
modifier = Modifier.clickable {
|
|
isDropDownExpanded.value = true
|
|
}
|
|
) {
|
|
Text(text = dtmfModeMap[dtmfMode]!!)
|
|
Icon(
|
|
imageVector = Icons.Filled.ArrowDropDown,
|
|
contentDescription = null,
|
|
modifier = Modifier.size(36.dp)
|
|
)
|
|
}
|
|
DropdownMenu(
|
|
expanded = isDropDownExpanded.value,
|
|
onDismissRequest = {
|
|
isDropDownExpanded.value = false
|
|
}) {
|
|
var index = 0
|
|
dtmfModeMap.forEach {
|
|
DropdownMenuItem(text = { Text(text = it.value) },
|
|
onClick = {
|
|
isDropDownExpanded.value = false
|
|
viewModel.dtmfMode.value = it.key
|
|
}
|
|
)
|
|
if (index < 2)
|
|
HorizontalDivider(thickness = 1.dp)
|
|
index++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
val manualText = stringResource(R.string.manual)
|
|
val autoText = stringResource(R.string.auto)
|
|
|
|
@Composable
|
|
fun Answer() {
|
|
val answerModeTitle = stringResource(R.string.answer_mode)
|
|
val answerModeHelp = stringResource(R.string.answer_mode_help)
|
|
val answerMode by viewModel.answerMode.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val answerModeMap = mapOf(Api.ANSWERMODE_MANUAL to manualText,
|
|
Api.ANSWERMODE_AUTO to autoText)
|
|
Text(text = answerModeTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = answerModeTitle
|
|
alertMessage.value = answerModeHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
val isDropDownExpanded = remember { mutableStateOf(false) }
|
|
Box {
|
|
Row(
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
modifier = Modifier.clickable { isDropDownExpanded.value = true }
|
|
) {
|
|
Text(text = answerModeMap[answerMode]!!)
|
|
Icon(
|
|
imageVector = Icons.Filled.ArrowDropDown,
|
|
contentDescription = null,
|
|
modifier = Modifier.size(36.dp)
|
|
)
|
|
}
|
|
DropdownMenu(
|
|
expanded = isDropDownExpanded.value,
|
|
onDismissRequest = { isDropDownExpanded.value = false }
|
|
) {
|
|
var index = 0
|
|
answerModeMap.forEach {
|
|
DropdownMenuItem(text = { Text(text = it.value) },
|
|
onClick = {
|
|
isDropDownExpanded.value = false
|
|
viewModel.answerMode.value = it.key
|
|
})
|
|
if (index < 1)
|
|
HorizontalDivider(thickness = 1.dp)
|
|
index++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Redirect() {
|
|
val redirectModeTitle = stringResource(R.string.redirect_mode)
|
|
val redirectModeHelp = stringResource(R.string.redirect_mode_help)
|
|
val autoRedirect by viewModel.autoRedirect.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
val redirectModeMap = mapOf(false to manualText,
|
|
true to autoText)
|
|
Text(text = redirectModeTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = redirectModeTitle
|
|
alertMessage.value = redirectModeHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
val isDropDownExpanded = remember { mutableStateOf(false) }
|
|
Box {
|
|
Row(
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
modifier = Modifier.clickable { isDropDownExpanded.value = true }
|
|
) {
|
|
Text(text = redirectModeMap[autoRedirect]!!)
|
|
Icon(
|
|
imageVector = Icons.Filled.ArrowDropDown,
|
|
contentDescription = null,
|
|
modifier = Modifier.size(36.dp)
|
|
)
|
|
}
|
|
DropdownMenu(
|
|
expanded = isDropDownExpanded.value,
|
|
onDismissRequest = { isDropDownExpanded.value = false }
|
|
) {
|
|
var index = 0
|
|
redirectModeMap.forEach {
|
|
DropdownMenuItem(
|
|
text = { Text(text = it.value) },
|
|
onClick = {
|
|
isDropDownExpanded.value = false
|
|
viewModel.autoRedirect.value = it.key
|
|
}
|
|
)
|
|
if (index < 1)
|
|
HorizontalDivider(thickness = 1.dp)
|
|
index++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun Voicemail() {
|
|
val voicemailUriTitle = stringResource(R.string.voicemail_uri)
|
|
val voicemailUriHelp = if (ua.account.isMobile)
|
|
stringResource(R.string.voicemain_tel_uri_help)
|
|
else
|
|
stringResource(R.string.voicemain_uri_help)
|
|
val vmUri by viewModel.vmUri.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
OutlinedTextField(
|
|
value = vmUri,
|
|
placeholder = { Text(voicemailUriTitle) },
|
|
onValueChange = { viewModel.vmUri.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = voicemailUriTitle
|
|
alertMessage.value = voicemailUriHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(voicemailUriTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun CountryCode() {
|
|
val countryCodeTitle = stringResource(R.string.country_code)
|
|
val countryCodeHelp = stringResource(R.string.country_code_help)
|
|
val countryCode by viewModel.countryCode.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
OutlinedTextField(
|
|
value = countryCode,
|
|
placeholder = { Text(countryCodeTitle) },
|
|
onValueChange = { viewModel.countryCode.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = countryCodeTitle
|
|
alertMessage.value = countryCodeHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(countryCodeTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun TelProvider() {
|
|
val telephonyProviderTitle = stringResource(R.string.telephony_provider)
|
|
val telephonyProviderHelp = stringResource(R.string.telephony_provider_help)
|
|
val telProvider by viewModel.telProvider.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
OutlinedTextField(
|
|
value = telProvider,
|
|
placeholder = { Text(telephonyProviderTitle) },
|
|
onValueChange = { viewModel.telProvider.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = telephonyProviderTitle
|
|
alertMessage.value = telephonyProviderHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(telephonyProviderTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun NumericKeypad() {
|
|
val numericKeypadTitle = stringResource(R.string.numeric_keypad)
|
|
val numericKeypadHelp = stringResource(R.string.numeric_keypad_help)
|
|
val numericKeypad by viewModel.numericKeypad.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = numericKeypadTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = numericKeypadTitle
|
|
alertMessage.value = numericKeypadHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
Switch(
|
|
checked = numericKeypad,
|
|
onCheckedChange = { viewModel.numericKeypad.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun DefaultAccount() {
|
|
val defaultAccountTitle = stringResource(R.string.default_account)
|
|
val defaultAccountHelp = stringResource(R.string.default_account_help)
|
|
val defaultAccount by viewModel.defaultAccount.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
Text(text = defaultAccountTitle,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.clickable {
|
|
alertTitle.value = defaultAccountTitle
|
|
alertMessage.value = defaultAccountHelp
|
|
showAlert.value = true
|
|
},
|
|
fontSize = 18.sp
|
|
)
|
|
Switch(
|
|
checked = defaultAccount,
|
|
onCheckedChange = { viewModel.defaultAccount.value = it }
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun CustomParams() {
|
|
val customParamsTitle = stringResource(R.string.custom_parameters)
|
|
val customParamsHelp = stringResource(R.string.custom_parameters_help)
|
|
val customParams by viewModel.customParams.collectAsState()
|
|
Row(
|
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.Start
|
|
) {
|
|
OutlinedTextField(
|
|
value = customParams,
|
|
placeholder = { Text(customParamsTitle) },
|
|
onValueChange = { viewModel.customParams.value = it },
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable {
|
|
alertTitle.value = customParamsTitle
|
|
alertMessage.value = customParamsHelp
|
|
showAlert.value = true
|
|
},
|
|
singleLine = true,
|
|
textStyle = TextStyle(fontSize = 18.sp),
|
|
label = { Text(customParamsTitle) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
|
)
|
|
}
|
|
}
|
|
|
|
if (showAlert.value) {
|
|
AlertDialog(
|
|
showDialog = showAlert,
|
|
title = alertTitle.value,
|
|
message = alertMessage.value,
|
|
lastButtonText = stringResource(R.string.ok),
|
|
)
|
|
}
|
|
|
|
keyboardController = LocalSoftwareKeyboardController.current
|
|
|
|
val scrollState = rememberScrollState()
|
|
|
|
Column(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(contentPadding)
|
|
.padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 16.dp)
|
|
.verticalScrollbar(scrollState)
|
|
.verticalScroll(state = scrollState),
|
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
) {
|
|
AoR(resumeToggle)
|
|
Nickname()
|
|
if (!ua.account.isMobile) {
|
|
DisplayName()
|
|
AuthUser()
|
|
AuthPass()
|
|
if (showPasswordDialog.value)
|
|
AskPassword(ctx, navController, ua)
|
|
Outbound()
|
|
Register()
|
|
if (viewModel.register.collectAsState().value) {
|
|
RegInt()
|
|
CheckOrigin()
|
|
}
|
|
}
|
|
BlockUnknown()
|
|
BlockHidden()
|
|
if (!ua.account.isMobile) {
|
|
AudioCodecs(navController, aor)
|
|
MediaEnc()
|
|
MediaNat()
|
|
if (showStun) {
|
|
StunServer()
|
|
StunUser()
|
|
StunPass()
|
|
}
|
|
RtcpMux()
|
|
Rel100()
|
|
Dtmf()
|
|
Redirect()
|
|
}
|
|
Answer()
|
|
Voicemail()
|
|
CountryCode()
|
|
if (!ua.account.isMobile)
|
|
TelProvider()
|
|
NumericKeypad()
|
|
DefaultAccount()
|
|
if (!ua.account.isMobile)
|
|
CustomParams()
|
|
}
|
|
}
|
|
|
|
private fun checkOnClick(ctx: Context, viewModel: AccountViewModel, ua: UserAgent): Boolean {
|
|
|
|
val acc = ua.account
|
|
val noticeTitle = ctx.getString(R.string.notice)
|
|
|
|
val nn = viewModel.nickName.value.trim()
|
|
if (nn != acc.nickName) {
|
|
if (Account.checkDisplayName(nn)) {
|
|
if (nn == "" || Account.uniqueNickName(nn)) {
|
|
acc.nickName = nn
|
|
Log.d(TAG, "New nickname is ${acc.nickName}")
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.non_unique_account_nickname), nn)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_account_nickname), nn)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
val dn = viewModel.displayName.value.trim()
|
|
if (dn != acc.displayName) {
|
|
if (Account.checkDisplayName(dn)) {
|
|
if (Api.account_set_display_name(acc.accp, dn) == 0) {
|
|
acc.displayName = Api.account_display_name(acc.accp)
|
|
Log.d(TAG, "New display name is ${acc.displayName}")
|
|
} else {
|
|
Log.e(TAG, "Setting of display name failed")
|
|
}
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_display_name), dn)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
val au = viewModel.authUser.value.trim()
|
|
if (au != acc.authUser) {
|
|
if (Account.checkAuthUser(au)) {
|
|
if (Api.account_set_auth_user(acc.accp, au) == 0) {
|
|
acc.authUser = Api.account_auth_user(acc.accp)
|
|
Log.d(TAG, "New auth user is ${acc.authUser}")
|
|
if (acc.regint > 0)
|
|
reRegister = true
|
|
}
|
|
else {
|
|
Log.e(TAG, "Setting of auth user failed")
|
|
}
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_authentication_username), au)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
val ap = viewModel.authPass.value.trim()
|
|
if (ap != "") {
|
|
if (ap != acc.authPass) {
|
|
if (Account.checkAuthPass(ap)) {
|
|
if (Api.account_set_auth_pass(acc.accp, ap) == 0) {
|
|
acc.authPass = Api.account_auth_pass(acc.accp)
|
|
if (acc.regint > 0)
|
|
reRegister = true
|
|
}
|
|
else
|
|
Log.e(TAG, "Setting of auth pass failed")
|
|
BaresipService.aorPasswords.remove(acc.aor)
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_authentication_password), ap)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
else
|
|
BaresipService.aorPasswords.remove(acc.aor)
|
|
}
|
|
else { // ap == ""
|
|
if (acc.authPass != NO_AUTH_PASS && acc.authPass != BaresipService.aorPasswords[acc.aor])
|
|
if (Api.account_set_auth_pass(acc.accp, "") == 0) {
|
|
acc.authPass = NO_AUTH_PASS
|
|
BaresipService.aorPasswords[acc.aor] = NO_AUTH_PASS
|
|
}
|
|
}
|
|
|
|
val ob = ArrayList<String>()
|
|
var ob1 = viewModel.outbound1.value.trim().replace(" ", "")
|
|
if (ob1 != "") {
|
|
if (!ob1.startsWith("sip:"))
|
|
ob1 = "sip:$ob1"
|
|
if (checkOutboundUri(ob1)) {
|
|
ob.add(ob1)
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_proxy_server_uri), ob1)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
var ob2 = viewModel.outbound2.value.trim().replace(" ", "")
|
|
if (ob2 != "") {
|
|
if (!ob2.startsWith("sip:"))
|
|
ob2 = "sip:$ob2"
|
|
if (checkOutboundUri(ob2))
|
|
ob.add(ob2)
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_proxy_server_uri), ob2)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
if (ob != acc.outbound) {
|
|
for (i in 0..1) {
|
|
val uri = if (ob.size > i)
|
|
ob[i]
|
|
else
|
|
""
|
|
if (Api.account_set_outbound(acc.accp, uri, i) != 0)
|
|
Log.e(TAG, "Setting of outbound proxy $i uri '$uri' failed")
|
|
}
|
|
Log.d(TAG, "New outbound proxies are $ob")
|
|
acc.outbound = ob
|
|
if (ob.isEmpty())
|
|
Api.account_set_sipnat(acc.accp, "")
|
|
else
|
|
Api.account_set_sipnat(acc.accp, "outbound")
|
|
if (acc.regint > 0)
|
|
reRegister = true
|
|
}
|
|
|
|
val regInt = try {
|
|
viewModel.regInt.value.trim().toInt()
|
|
} catch (_: NumberFormatException) {
|
|
0
|
|
}
|
|
if (regInt !in 60..3600) {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_reg_int), "$regInt")
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
val reReg = (viewModel.register.value != acc.regint > 0) ||
|
|
(viewModel.register.value && regInt != acc.configuredRegInt)
|
|
if (reReg) {
|
|
if (Api.account_set_regint(acc.accp,
|
|
if (viewModel.register.value) regInt else 0) != 0) {
|
|
Log.e(TAG, "Setting of regint failed")
|
|
} else {
|
|
acc.regint = Api.account_regint(acc.accp)
|
|
acc.configuredRegInt = regInt
|
|
Log.d(TAG, "New regint is ${acc.regint}")
|
|
reRegister = true
|
|
}
|
|
}
|
|
else {
|
|
if (regInt != acc.configuredRegInt)
|
|
acc.configuredRegInt = regInt
|
|
}
|
|
|
|
val newCheckOrigin = viewModel.checkOrigin.value
|
|
if (newCheckOrigin != acc.checkOrigin) {
|
|
Api.account_set_check_origin(ua.account.accp, newCheckOrigin)
|
|
acc.checkOrigin = Api.account_check_origin(ua.account.accp)
|
|
Log.d(TAG, "New checkOrigin is ${acc.checkOrigin}")
|
|
}
|
|
|
|
val newMediaEnc = viewModel.mediaEnc.value
|
|
if (newMediaEnc != acc.mediaEnc) {
|
|
if (Api.account_set_mediaenc(acc.accp, newMediaEnc) == 0) {
|
|
acc.mediaEnc = Api.account_mediaenc(acc.accp)
|
|
Log.d(TAG, "New mediaenc is ${acc.mediaEnc}")
|
|
}
|
|
else
|
|
Log.e(TAG, "Setting of mediaenc $newMediaEnc failed")
|
|
}
|
|
|
|
val newMediaNat = viewModel.mediaNat.value
|
|
if (newMediaNat != acc.mediaNat) {
|
|
if (Api.account_set_medianat(acc.accp, newMediaNat) == 0) {
|
|
acc.mediaNat = Api.account_medianat(acc.accp)
|
|
Log.d(TAG, "New medianat is ${acc.mediaNat}")
|
|
}
|
|
else
|
|
Log.e(TAG, "Setting of medianat $newMediaNat failed")
|
|
}
|
|
|
|
var newStunServer = viewModel.stunServer.value.trim()
|
|
if (newMediaNat != "") {
|
|
if ((newMediaNat == "stun" || newMediaNat == "ice") && newStunServer == "")
|
|
newStunServer = ctx.getString(R.string.stun_server_default)
|
|
if (!Utils.checkStunUri(newStunServer) ||
|
|
(newMediaNat == "turn" &&
|
|
newStunServer.substringBefore(":") !in setOf("turn", "turns"))) {
|
|
alertTitle.value = ctx.getString(R.string.notice)
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_server),
|
|
newStunServer)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
if (acc.stunServer != newStunServer) {
|
|
if (Api.account_set_stun_uri(acc.accp, newStunServer) == 0) {
|
|
acc.stunServer = Api.account_stun_uri(acc.accp)
|
|
Log.d(TAG, "New STUN/TURN server URI is '${acc.stunServer}'")
|
|
} else {
|
|
Log.e(TAG, "Setting of STUN/TURN URI server $newStunServer failed")
|
|
}
|
|
}
|
|
|
|
val newStunUser = viewModel.stunUser.value.trim()
|
|
if (acc.stunUser != newStunUser) {
|
|
if (Account.checkAuthUser(newStunUser)) {
|
|
if (Api.account_set_stun_user(acc.accp, newStunUser) == 0) {
|
|
acc.stunUser = Api.account_stun_user(acc.accp)
|
|
Log.d(TAG, "New STUN/TURN user is ${acc.stunUser}")
|
|
}
|
|
else
|
|
Log.e(TAG, "Setting of STUN/TURN user $newStunUser failed")
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_username),
|
|
newStunUser)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
val newStunPass = viewModel.stunPass.value.trim()
|
|
if (acc.stunPass != newStunPass) {
|
|
if (newStunPass.isEmpty() || Account.checkAuthPass(newStunPass)) {
|
|
if (Api.account_set_stun_pass(acc.accp, newStunPass) == 0)
|
|
acc.stunPass = Api.account_stun_pass(acc.accp)
|
|
else
|
|
Log.e(TAG, "Setting of stun pass failed")
|
|
}
|
|
else {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_password),
|
|
newStunPass)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
val newRtcpMux = viewModel.rtcpMux.value
|
|
if (newRtcpMux != acc.rtcpMux)
|
|
if (Api.account_set_rtcp_mux(acc.accp, newRtcpMux) == 0) {
|
|
acc.rtcpMux = Api.account_rtcp_mux(acc.accp)
|
|
Log.d(TAG, "New rtcpMux is ${acc.rtcpMux}")
|
|
} else {
|
|
Log.e(TAG, "Setting of account_rtc_mux $newRtcpMux failed")
|
|
}
|
|
|
|
val new100Rel = viewModel.rel100.value
|
|
if (new100Rel != (acc.rel100Mode == Api.REL100_ENABLED)) {
|
|
val mode = if (new100Rel) Api.REL100_ENABLED else Api.REL100_DISABLED
|
|
if (Api.account_set_rel100_mode(acc.accp, mode) == 0) {
|
|
acc.rel100Mode = Api.account_rel100_mode(acc.accp)
|
|
Api.ua_update_account(ua.uap)
|
|
Log.d(TAG, "New rel100Mode is ${acc.rel100Mode}")
|
|
} else {
|
|
Log.e(TAG, "Setting of account_rel100Mode $mode failed")
|
|
}
|
|
}
|
|
|
|
val newDtmfMode = viewModel.dtmfMode.value
|
|
if (newDtmfMode != acc.dtmfMode) {
|
|
if (Api.account_set_dtmfmode(acc.accp, newDtmfMode) == 0) {
|
|
acc.dtmfMode = Api.account_dtmfmode(acc.accp)
|
|
Log.d(TAG, "New dtmfmode is ${acc.dtmfMode}")
|
|
} else {
|
|
Log.e(TAG, "Setting of dtmfmode $newDtmfMode failed")
|
|
}
|
|
}
|
|
|
|
val newAnswerMode = viewModel.answerMode.value
|
|
if (newAnswerMode != acc.answerMode) {
|
|
if (Api.account_set_answermode(acc.accp, newAnswerMode) == 0) {
|
|
acc.answerMode = Api.account_answermode(acc.accp)
|
|
Log.d(TAG, "New answermode is ${acc.answerMode}")
|
|
} else {
|
|
Log.e(TAG, "Setting of answermode $newAnswerMode failed")
|
|
}
|
|
}
|
|
|
|
val newAutoRedirect = viewModel.autoRedirect.value
|
|
if (newAutoRedirect != acc.autoRedirect) {
|
|
Api.account_set_sip_autoredirect(acc.accp, newAutoRedirect)
|
|
acc.autoRedirect = newAutoRedirect
|
|
Log.d(TAG, "New autoRedirect is ${acc.autoRedirect}")
|
|
}
|
|
|
|
val newBlockUnknown = viewModel.blockUnknown.value
|
|
if (newBlockUnknown != acc.blockUnknown) {
|
|
acc.blockUnknown = newBlockUnknown
|
|
Log.d(TAG, "New blockUnknown is ${acc.blockUnknown}")
|
|
}
|
|
|
|
val newBlockHidden = viewModel.blockHidden.value
|
|
if (newBlockHidden != acc.blockHidden) {
|
|
acc.blockHidden = newBlockHidden
|
|
Log.d(TAG, "New blockHidden is ${acc.blockHidden}")
|
|
}
|
|
|
|
var newVmUri = viewModel.vmUri.value.trim()
|
|
if (newVmUri != acc.vmUri) {
|
|
if (newVmUri != "") {
|
|
if (acc.isMobile) {
|
|
if (!newVmUri.startsWith("tel:")) newVmUri = "tel:$newVmUri"
|
|
}
|
|
else {
|
|
if (!newVmUri.startsWith("sip:")) newVmUri = "sip:$newVmUri"
|
|
if (!newVmUri.contains("@")) newVmUri = "$newVmUri@${acc.host()}"
|
|
}
|
|
if (!Utils.checkUri(newVmUri)) {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri),
|
|
newVmUri)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
Api.account_set_mwi(acc.accp, true)
|
|
}
|
|
else
|
|
Api.account_set_mwi(acc.accp, false)
|
|
acc.vmUri = newVmUri
|
|
Log.d(TAG, "New voicemail URI is ${acc.vmUri}")
|
|
}
|
|
|
|
val newCountryCode = viewModel.countryCode.value.trim()
|
|
if (newCountryCode != acc.countryCode) {
|
|
if (newCountryCode != "" && !Utils.checkCountryCode(newCountryCode)) {
|
|
alertTitle.value = ctx.getString(R.string.notice)
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_country_code),
|
|
newCountryCode)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
acc.countryCode = newCountryCode
|
|
Log.d(TAG, "New country code is ${acc.countryCode}")
|
|
}
|
|
|
|
val hostPart = viewModel.telProvider.value.trim()
|
|
if (hostPart != acc.telProvider) {
|
|
if (hostPart != "" && !Utils.checkHostPortParams(hostPart)) {
|
|
alertTitle.value = noticeTitle
|
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_uri_hostpart), hostPart)
|
|
showAlert.value = true
|
|
return false
|
|
}
|
|
acc.telProvider = hostPart
|
|
Log.d(TAG, "New tel provider is ${acc.telProvider}")
|
|
}
|
|
|
|
val newNumericKeypad = viewModel.numericKeypad.value
|
|
if (newNumericKeypad != acc.numericKeypad) {
|
|
acc.numericKeypad = newNumericKeypad
|
|
Log.d(TAG, "New numericKeyboard is ${acc.numericKeypad}")
|
|
}
|
|
|
|
val newCustomParams = viewModel.customParams.value
|
|
if (newCustomParams != acc.customParams) {
|
|
acc.customParams = newCustomParams
|
|
Log.d(TAG, "New customParams is ${acc.customParams}")
|
|
}
|
|
|
|
if (viewModel.defaultAccount.value) ua.makeDefault()
|
|
|
|
// Api.account_debug(acc.accp)
|
|
|
|
Account.saveAccounts()
|
|
|
|
if (acc.authUser != "" && BaresipService.aorPasswords[acc.aor] == NO_AUTH_PASS) {
|
|
showPasswordDialog.value = true
|
|
return false
|
|
}
|
|
else
|
|
return true
|
|
}
|
|
|
|
private fun initAccountFromNetwork(acc: Account, onConfigLoaded: () -> Unit) {
|
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
|
scope.launch(Dispatchers.IO) {
|
|
val url = "https://${Utils.uriHostPart(acc.aor)}/baresip/account_config.xml"
|
|
val urlConnection = URL(url).openConnection() as HttpsURLConnection
|
|
urlConnection.connectTimeout = 5000
|
|
urlConnection.readTimeout = 3000
|
|
val caFile = File(BaresipService.filesPath + "/ca_certs.crt")
|
|
val template = try {
|
|
if (caFile.exists())
|
|
Utils.readUrlWithCustomCAs(urlConnection, caFile)
|
|
else
|
|
urlConnection.inputStream.bufferedReader().use { it.readText() }
|
|
} catch (e: java.lang.Exception) {
|
|
Log.d(TAG, "Failed to get account template from network: ${e.message}")
|
|
null
|
|
}
|
|
if (template != null) {
|
|
Log.d(TAG, "Got account template '$template'")
|
|
val parserFactory: XmlPullParserFactory = XmlPullParserFactory.newInstance()
|
|
val parser: XmlPullParser = parserFactory.newPullParser()
|
|
parser.setInput(StringReader(template))
|
|
var tag: String?
|
|
var text = ""
|
|
var event = parser.eventType
|
|
val audioCodecs = ArrayList(Api.audio_codecs().split(","))
|
|
val videoCodecs = ArrayList(Api.video_codecs().split(","))
|
|
|
|
while (event != XmlPullParser.END_DOCUMENT) {
|
|
tag = parser.name
|
|
when (event) {
|
|
XmlPullParser.TEXT ->
|
|
text = parser.text
|
|
|
|
XmlPullParser.START_TAG -> {
|
|
if (tag == "audio-codecs")
|
|
acc.audioCodec.clear()
|
|
if (tag == "video-codecs")
|
|
acc.videoCodec.clear()
|
|
}
|
|
|
|
XmlPullParser.END_TAG ->
|
|
when (tag) {
|
|
"outbound-proxy-1" ->
|
|
if (text.isNotEmpty())
|
|
acc.outbound.add(text)
|
|
|
|
"outbound-proxy-2" ->
|
|
if (text.isNotEmpty())
|
|
acc.outbound.add(text)
|
|
|
|
"registration-interval" ->
|
|
acc.configuredRegInt = try {
|
|
text.toInt()
|
|
} catch (_: NumberFormatException) {
|
|
900
|
|
}
|
|
|
|
"register" -> {
|
|
acc.regint = if (text == "yes") acc.configuredRegInt else 0
|
|
if (acc.regint > 0)
|
|
acc.checkOrigin = true
|
|
}
|
|
|
|
"audio-codec" ->
|
|
if (text in audioCodecs)
|
|
acc.audioCodec.add(text)
|
|
|
|
"video-codec" ->
|
|
if (text in videoCodecs)
|
|
acc.videoCodec.add(text)
|
|
|
|
"media-encoding" -> {
|
|
val enc = text.lowercase(Locale.ROOT)
|
|
if (enc in mediaEncMap.keys && enc.isNotEmpty())
|
|
acc.mediaEnc = enc
|
|
}
|
|
|
|
"media-nat" -> {
|
|
val nat = text.lowercase(Locale.ROOT)
|
|
if (nat in mediaNatMap.keys && nat.isNotEmpty())
|
|
acc.mediaNat = nat
|
|
}
|
|
|
|
"stun-turn-server" ->
|
|
if (text.isNotEmpty())
|
|
acc.stunServer = text
|
|
|
|
"rtcp-mux" ->
|
|
acc.rtcpMux = text == "yes"
|
|
|
|
"100rel-mode" ->
|
|
acc.rel100Mode = if (text == "yes")
|
|
Api.REL100_ENABLED
|
|
else
|
|
Api.REL100_DISABLED
|
|
|
|
"dtmf-mode" ->
|
|
if (text in arrayOf("rtp-event", "sip-info", "auto"))
|
|
acc.dtmfMode = when (text) {
|
|
"rtp-event" -> Api.DTMFMODE_RTP_EVENT
|
|
"sip-info" -> Api.DTMFMODE_SIP_INFO
|
|
else -> Api.DTMFMODE_AUTO
|
|
}
|
|
|
|
"answer-mode" ->
|
|
if (text in arrayOf("manual", "auto"))
|
|
acc.answerMode = if (text == "manual")
|
|
Api.ANSWERMODE_MANUAL
|
|
else
|
|
Api.ANSWERMODE_AUTO
|
|
|
|
"redirect-mode" ->
|
|
acc.autoRedirect = text == "yes"
|
|
|
|
"voicemail-uri" ->
|
|
if (text.isNotEmpty())
|
|
acc.vmUri = text
|
|
|
|
"country-code" ->
|
|
acc.countryCode = text
|
|
|
|
"tel-provider" ->
|
|
acc.telProvider = text
|
|
}
|
|
}
|
|
event = parser.next()
|
|
}
|
|
}
|
|
onConfigLoaded()
|
|
}
|
|
}
|
|
|
|
private fun checkOutboundUri(uri: String): Boolean {
|
|
if (!uri.startsWith("sip:")) return false
|
|
return Utils.checkHostPortParams(uri.substring(4))
|
|
} |