diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt index 865334c8..0ae0784f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt @@ -10,6 +10,7 @@ import androidx.activity.enableEdgeToEdge 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.fillMaxSize @@ -17,9 +18,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState +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.Check @@ -30,7 +31,6 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -58,7 +58,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tutpro.baresip.BaresipService.Companion.uas import com.tutpro.baresip.CustomElements.Checkbox -import com.tutpro.baresip.CustomElements.verticalScrollbar class AccountActivity : ComponentActivity() { @@ -225,19 +224,13 @@ class AccountActivity : ComponentActivity() { LocalCustomColors.current.grayLight else LocalCustomColors.current.black - val lazyListState = rememberLazyListState() - LazyColumn( + Column( modifier = Modifier .imePadding() .fillMaxWidth() .padding(contentPadding) .padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 4.dp) - .verticalScrollbar( - state = lazyListState, - width = 4.dp, - color = LocalCustomColors.current.gray - ), - state = lazyListState, + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp), ) { oldNickname = acc.nickName.value @@ -246,7 +239,7 @@ class AccountActivity : ComponentActivity() { if (BaresipService.aorPasswords[aor] == null && // check if OK acc.authPass != NO_AUTH_PASS) oldAuthPass = acc.authPass - if (acc.outbound.size > 0) { + if (acc.outbound.isNotEmpty()) { oldOutbound1 = acc.outbound[0] if (acc.outbound.size > 1) oldOutbound2 = acc.outbound[1] @@ -267,30 +260,29 @@ class AccountActivity : ComponentActivity() { oldCountryCode = acc.countryCode oldTelProvider = acc.telProvider oldDefaultAccount = UserAgent.findAorIndex(aor)!! == 0 - - item { AoR() } - item { Nickname(ctx) } - item { Displayname(ctx) } - item { AuthUser(ctx) } - item { AuthPass(ctx) } - item { Outbound(ctx) } - item { Register(ctx) } - item { RegInt(ctx) } - item { AudioCodecs(ctx) } - item { MediaEnc(ctx) } - item { MediaNat(ctx) } - item { StunServer(ctx) } - item { StunUser(ctx) } - item { StunPass(ctx) } - item { RtcpMux() } - item { Rel100() } - item { Dtmf(ctx) } - item { Answer(ctx) } - item { Redirect(ctx) } - item { Voicemail(ctx) } - item { CountryCode(ctx) } - item { TelProvider(ctx) } - item { DefaultAccount() } + AoR() + Nickname(ctx) + Displayname(ctx) + AuthUser(ctx) + AuthPass(ctx) + Outbound(ctx) + Register(ctx) + RegInt(ctx) + AudioCodecs(ctx) + MediaEnc(ctx) + MediaNat(ctx) + StunServer(ctx) + StunUser(ctx) + StunPass(ctx) + RtcpMux() + Rel100() + Dtmf(ctx) + Answer(ctx) + Redirect(ctx) + Voicemail(ctx) + CountryCode(ctx) + TelProvider(ctx) + DefaultAccount() } } @@ -1231,10 +1223,10 @@ class AccountActivity : ComponentActivity() { } } else { // ap == "" if (acc.authPass != NO_AUTH_PASS && - acc.authPass != BaresipService.aorPasswords[acc.aor]) + acc.authPass != BaresipService.aorPasswords[acc.aor]) if (Api.account_set_auth_pass(acc.accp, "") == 0) { acc.authPass = NO_AUTH_PASS - BaresipService.aorPasswords[aor] = NO_AUTH_PASS + BaresipService.aorPasswords[acc.aor] = NO_AUTH_PASS } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index a9e8437e..29f46577 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -32,12 +32,9 @@ import android.os.SystemClock import android.provider.DocumentsContract import android.provider.MediaStore import android.view.KeyEvent -import android.view.LayoutInflater import android.view.WindowManager import android.view.inputmethod.InputMethodManager import android.widget.Chronometer -import android.widget.EditText -import android.widget.TextView import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback @@ -122,7 +119,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle @@ -206,6 +202,7 @@ class MainActivity : ComponentActivity() { private var showHangupButton = mutableStateOf(false) private var showOnHoldNotice = mutableStateOf(false) private var showPasswordDialog = mutableStateOf(false) + private var showPasswordsDialog = mutableStateOf(false) private var holdIcon = mutableIntStateOf(R.drawable.call_hold) private var transferButtonEnabled = mutableStateOf(false) private var transferIcon = mutableIntStateOf(R.drawable.call_transfer) @@ -221,10 +218,8 @@ class MainActivity : ComponentActivity() { private var dialpad by mutableStateOf(false) private var dialpadButtonEnabled by mutableStateOf(true) private var pullToRefreshEnabled by mutableStateOf(true) - private var passwordUa: UserAgent? = null private var passwordAccounts = mutableListOf() private var passwordTitle by mutableStateOf("") - private var password by mutableStateOf("") private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { @@ -321,7 +316,11 @@ class MainActivity : ComponentActivity() { updateIcons(ua.account) if (it.resultCode == RESULT_OK) if (BaresipService.aorPasswords[activityAor] == NO_AUTH_PASS) { - askPassword(getString(R.string.authentication_password), ua) + passwordAccounts = String( + Utils.getFileContents(filesDir.absolutePath + "/accounts")!!, + Charsets.UTF_8 + ).lines().toMutableList() + showPasswordsDialog.value = true } } @@ -407,7 +406,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = LocalCustomColors.current.background ) { - MainContent(this) + MainScreen(this) } } } @@ -472,8 +471,7 @@ class MainActivity : ComponentActivity() { Utils.getFileContents(filesDir.absolutePath + "/accounts")!!, Charsets.UTF_8 ).lines().toMutableList() - //showPasswordDialog.value = true - askPasswords(passwordAccounts) + showPasswordsDialog.value = true } else { // Baresip is started for the first time requestPermissionsLauncher.launch(permissions) @@ -483,59 +481,65 @@ class MainActivity : ComponentActivity() { } // OnCreate @Composable - private fun MainContent(ctx: Context) { + private fun MainScreen(ctx: Context) { Scaffold( modifier = Modifier.safeDrawingPadding(), containerColor = LocalCustomColors.current.background, topBar = { TopAppBar(ctx, String.format(getString(R.string.baresip))) }, - bottomBar = { Buttons(ctx) }, + bottomBar = { BottomBar(ctx) }, content = { contentPadding -> - var isRefreshing by remember { mutableStateOf(false) } - LaunchedEffect(isRefreshing) { - if (isRefreshing) { - delay(1000) - isRefreshing = false - } - } - PullToRefreshBox( - isRefreshing = isRefreshing, - modifier = Modifier.fillMaxSize(), - enabled = pullToRefreshEnabled, - onRefresh = { - isRefreshing = true - if (uas.value.isNotEmpty()) { - if (viewModel.selectedAor.value == "") - spinToAor(uas.value.first().account.aor) - val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! - if (ua.account.regint > 0) - Api.ua_register(ua.uap) - } - } - ) { - Column( - modifier = Modifier - .consumeWindowInsets(contentPadding) - .padding( - PaddingValues( - top = 76.dp, bottom = 6.dp, - start = 16.dp, end = 16.dp - ) - ) - .fillMaxSize() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AccountSpinner(this@MainActivity) - CallUriRow(this@MainActivity) - CallRow(this@MainActivity) - OnHoldNotice() - AskPassword(this@MainActivity) - } - } + MainContent(ctx, contentPadding) } ) } + @Composable + fun MainContent(ctx: Context, contentPadding: PaddingValues) { + var isRefreshing by remember { mutableStateOf(false) } + LaunchedEffect(isRefreshing) { + if (isRefreshing) { + delay(1000) + isRefreshing = false + } + } + PullToRefreshBox( + isRefreshing = isRefreshing, + modifier = Modifier.fillMaxSize(), + enabled = pullToRefreshEnabled, + onRefresh = { + isRefreshing = true + if (uas.value.isNotEmpty()) { + if (viewModel.selectedAor.value == "") + spinToAor(uas.value.first().account.aor) + val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! + if (ua.account.regint > 0) + Api.ua_register(ua.uap) + } + } + ) { + Column( + modifier = Modifier + .consumeWindowInsets(contentPadding) + .padding( + PaddingValues( + top = 76.dp, bottom = 6.dp, + start = 16.dp, end = 16.dp + ) + ) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AccountSpinner(ctx) + CallUriRow(ctx) + CallRow(ctx) + OnHoldNotice() + AskPasswords(ctx) + AskPassword(ctx) + } + } + } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun TopAppBar(ctx: Context, title: String) { @@ -1658,7 +1662,7 @@ class MainActivity : ComponentActivity() { } @Composable - fun Buttons(ctx: Context) { + fun BottomBar(ctx: Context) { val buttonSize = 48.dp Row( modifier = Modifier .fillMaxWidth() @@ -1811,34 +1815,34 @@ class MainActivity : ComponentActivity() { @Composable fun AskPasswords(ctx: Context) { - if (showPasswordDialog.value && passwordAccounts.isNotEmpty()) { - val account = passwordAccounts.removeAt(0) - val params = account.substringAfter(">") - if (Utils.paramValue(params, "auth_user") != "" && Utils.paramValue(params, "auth_pass") == "") { - val aor = account.substringAfter("<").substringBefore(">") - val showPassword = remember { mutableStateOf(false) } - BasicAlertDialog( - onDismissRequest = { - keyboardController?.hide() - showPasswordDialog.value = false - } - ) { - Surface( - modifier = Modifier - .wrapContentWidth() - .wrapContentHeight(), - color = LocalCustomColors.current.grayLight, - shape = MaterialTheme.shapes.large, - tonalElevation = AlertDialogDefaults.TonalElevation + if (showPasswordsDialog.value) { + if (passwordAccounts.isNotEmpty()) { + val account = passwordAccounts.removeAt(0) + val params = account.substringAfter(">") + if (Utils.paramValue(params, "auth_user") != "" && Utils.paramValue(params, "auth_pass") == "") { + val aor = account.substringAfter("<").substringBefore(">") + val showPassword = remember { mutableStateOf(false) } + BasicAlertDialog( + onDismissRequest = { + keyboardController?.hide() + showPasswordsDialog.value = false + } ) { - Column(modifier = Modifier.padding(16.dp)) { - Text( - text = passwordTitle, - fontSize = 20.sp, - modifier = Modifier.padding(16.dp), - color = LocalCustomColors.current.primary, - ) - if (passwordTitle == getString(R.string.authentication_password)) { + Surface( + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight(), + color = LocalCustomColors.current.background, + shape = MaterialTheme.shapes.large, + tonalElevation = AlertDialogDefaults.TonalElevation + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = getString(R.string.authentication_password), + fontSize = 20.sp, + modifier = Modifier.padding(top = 16.dp, bottom = 16.dp), + color = LocalCustomColors.current.alert, + ) val message = getString(R.string.account) + " " + Utils.plainAor(aor) Text( @@ -1847,103 +1851,109 @@ class MainActivity : ComponentActivity() { modifier = Modifier.padding(16.dp), color = LocalCustomColors.current.itemText, ) - } - var password by remember { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } - OutlinedTextField( - value = password, - singleLine = true, - onValueChange = { - password = it - }, - visualTransformation = if (showPassword.value) - VisualTransformation.None - else - PasswordVisualTransformation(), - trailingIcon = { - val (icon, iconColor) = if (showPassword.value) { - Pair( - ImageVector.vectorResource(R.drawable.visibility), - colorResource(id = R.color.colorAccent) + var password by remember { mutableStateOf("") } + val focusRequester = remember { FocusRequester() } + OutlinedTextField( + value = password, + singleLine = true, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = LocalCustomColors.current.textFieldBackground, + unfocusedContainerColor = LocalCustomColors.current.textFieldBackground, + cursorColor = LocalCustomColors.current.primary, + ), + onValueChange = { + password = it + }, + visualTransformation = if (showPassword.value) + VisualTransformation.None + else + PasswordVisualTransformation(), + trailingIcon = { + IconButton(onClick = { + showPassword.value = !showPassword.value + }) { + Icon( + if (showPassword.value) + ImageVector.vectorResource(R.drawable.visibility) + else + ImageVector.vectorResource(R.drawable.visibility_off), + contentDescription = "Visibility", + tint = LocalCustomColors.current.grayDark + + ) + } + }, + modifier = Modifier + .fillMaxWidth() + .padding( + start = 4.dp, + end = 4.dp, + top = 12.dp, + bottom = 2.dp ) - } else { - Pair( - ImageVector.vectorResource(R.drawable.visibility_off), - colorResource(id = R.color.colorWhite) + .focusRequester(focusRequester), + textStyle = TextStyle( + fontSize = 18.sp, + color = LocalCustomColors.current.dark + ), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) + Row( + horizontalArrangement = Arrangement.Start + ) { + TextButton( + onClick = { + keyboardController?.hide() + showPasswordsDialog.value = false + showPasswordsDialog.value = true + }, + modifier = Modifier.padding(8.dp), + ) { + Text( + text = stringResource(R.string.cancel), + color = LocalCustomColors.current.gray ) } - IconButton(onClick = { - showPassword.value = !showPassword.value - }) { - Icon( - icon, - contentDescription = "Visibility", - tint = iconColor + Spacer(modifier = Modifier.weight(1f)) + TextButton( + onClick = { + keyboardController?.hide() + showPasswordsDialog.value = false + password = password.trim() + if (!Account.checkAuthPass(password)) { + Toast.makeText( + ctx, + String.format( + getString(R.string.invalid_authentication_password), + password + ), + Toast.LENGTH_SHORT + ).show() + password = "" + passwordAccounts.add(0, account) + } else + BaresipService.aorPasswords[aor] = password + showPasswordsDialog.value = true + }, + modifier = Modifier.padding(8.dp), + ) { + Text( + text = stringResource(R.string.ok), + color = LocalCustomColors.current.alert ) } - }, - modifier = Modifier - .fillMaxWidth() - .padding( - start = 4.dp, - end = 4.dp, - top = 12.dp, - bottom = 2.dp - ) - .focusRequester(focusRequester), - textStyle = TextStyle( - fontSize = 18.sp, - color = LocalCustomColors.current.dark - ), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) - ) - Row( - horizontalArrangement = Arrangement.Absolute.SpaceEvenly - ) { - TextButton( - onClick = { - keyboardController?.hide() - showPasswordDialog.value = false - }, - modifier = Modifier.padding(8.dp), - ) { - Text( - text = stringResource(R.string.cancel), - color = LocalCustomColors.current.gray - ) - } - TextButton( - onClick = { - keyboardController?.hide() - showPasswordDialog.value = false - password = password.trim() - if (!Account.checkAuthPass(password)) { - Toast.makeText( - ctx, - String.format( - getString(R.string.invalid_authentication_password), - password - ), - Toast.LENGTH_SHORT - ).show() - password = "" - passwordAccounts.add(0, account) - } else - BaresipService.aorPasswords[aor] = password - showPasswordDialog.value = true - }, - modifier = Modifier.padding(8.dp), - ) { - Text( - text = stringResource(R.string.ok), - color = LocalCustomColors.current.primary - ) } } } } } + else { + showPasswordsDialog.value = false + showPasswordsDialog.value = true + } } + else + requestPermissionsLauncher.launch(permissions) } } @@ -2734,120 +2744,6 @@ class MainActivity : ComponentActivity() { } } - private fun askPassword(title: String, ua: UserAgent? = null) { - val layout = LayoutInflater.from(this) - .inflate(R.layout.password_dialog, findViewById(android.R.id.content), - false) - val titleView: TextView = layout.findViewById(R.id.title) - titleView.text = title - if (ua != null) { - val messageView: TextView = layout.findViewById(R.id.message) - val message = getString(R.string.account) + " " + Utils.plainAor(activityAor) - messageView.text = message - } - val input: EditText = layout.findViewById(R.id.password) - input.requestFocus() - with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setView(layout) - setPositiveButton(android.R.string.ok) { dialog, _ -> - imm.hideSoftInputFromWindow(input.windowToken, 0) - dialog.dismiss() - var password = input.text.toString().trim() - if (!Account.checkAuthPass(password)) { - Toast.makeText( - applicationContext, - String.format(getString(R.string.invalid_authentication_password), password), - Toast.LENGTH_SHORT - ).show() - password = "" - } - when (title) { - getString(R.string.encrypt_password) -> - if (password != "") backup(password) - getString(R.string.decrypt_password) -> - if (password != "") restore(password) - else -> - if (password == "") { - askPassword(title, ua!!) - } else { - Api.account_set_auth_pass(ua!!.account.accp, password) - ua.account.authPass = Api.account_auth_pass(ua.account.accp) - BaresipService.aorPasswords[ua.account.aor] = ua.account.authPass - if (ua.account.regint == 0) - Api.ua_unregister(ua.uap) - else - Api.ua_register(ua.uap) - } - } - } - setNeutralButton(android.R.string.cancel) { dialog, _ -> - imm.hideSoftInputFromWindow(input.windowToken, 0) - dialog.cancel() - } - val dialog = this.create() - dialog.setCancelable(false) - dialog.setCanceledOnTouchOutside(false) - dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - dialog.show() - } - } - - private fun askPasswords(accounts: MutableList) { - if (accounts.isNotEmpty()) { - val account = accounts.removeAt(0) - val params = account.substringAfter(">") - if ((Utils.paramValue(params, "auth_user") != "") && - (Utils.paramValue(params, "auth_pass") == "")) { - val aor = account.substringAfter("<").substringBefore(">") - val layout = LayoutInflater.from(this) - .inflate(R.layout.password_dialog, findViewById(android.R.id.content), - false) - val titleView: TextView = layout.findViewById(R.id.title) - titleView.text = getString(R.string.authentication_password) - val messageView: TextView = layout.findViewById(R.id.message) - val message = getString(R.string.account) + " " + Utils.plainAor(aor) - messageView.text = message - val input: EditText = layout.findViewById(R.id.password) - input.requestFocus() - with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { - setView(layout) - setPositiveButton(android.R.string.ok) { dialog, _ -> - imm.hideSoftInputFromWindow(input.windowToken, 0) - dialog.dismiss() - val password = input.text.toString().trim() - if (!Account.checkAuthPass(password)) { - Toast.makeText( - applicationContext, - String.format(getString(R.string.invalid_authentication_password), - password), - Toast.LENGTH_SHORT - ).show() - accounts.add(0, account) - } else { - BaresipService.aorPasswords[aor] = password - } - askPasswords(accounts) - } - setNeutralButton(android.R.string.cancel) { dialog, _ -> - imm.hideSoftInputFromWindow(input.windowToken, 0) - dialog.cancel() - BaresipService.aorPasswords[aor] = NO_AUTH_PASS - askPasswords(accounts) - } - val dialog = this.create() - dialog.setCancelable(false) - dialog.setCanceledOnTouchOutside(false) - dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - dialog.show() - } - } else { - askPasswords(accounts) - } - } else { - requestPermissionsLauncher.launch(permissions) - } - } - private fun startBaresip() { if (!BaresipService.isStartReceived) { baresipService.action = "Start" diff --git a/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt b/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt index 1fa879d2..402cf3ff 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt @@ -14,4 +14,5 @@ class ViewModel(application: Application) : AndroidViewModel(application) { fun updateSelectedAor(newValue: String) { _selectedAor.value = newValue } + }