Improved edge-to-edge implementation on all screens

Large number of code formatting improvements
This commit is contained in:
Juha Heinanen
2026-06-18 08:33:12 +03:00
parent 074eaecb4f
commit 66e6656161
14 changed files with 549 additions and 971 deletions

View File

@ -2,13 +2,13 @@ package com.tutpro.baresip
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@ -56,17 +56,11 @@ fun AboutScreen(onBack: () -> Unit) {
}
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(

View File

@ -8,14 +8,15 @@ 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.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
@ -137,9 +138,8 @@ private fun AccountScreen(
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
if (event == Lifecycle.Event.ON_RESUME)
resumeToggle = System.currentTimeMillis()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
@ -164,23 +164,14 @@ private fun AccountScreen(
}
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
text = acc.text(),
fontWeight = FontWeight.Bold
)
Text(text = acc.text(), fontWeight = FontWeight.Bold)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
@ -199,10 +190,7 @@ private fun AccountScreen(
windowInsets = WindowInsets(0, 0, 0, 0),
actions = {
IconButton(onClick = checkOnClick) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Check"
)
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
}
},
)
@ -253,10 +241,7 @@ private fun AccountContent(
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(fontSize = 18.sp),
label = {
Text(
text = label,
fontWeight = FontWeight.Bold
)
Text(text = label, fontWeight = FontWeight.Bold)
},
colors = OutlinedTextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
@ -271,9 +256,7 @@ private fun AccountContent(
@Composable
fun AoR(toggle: Long) {
Column(
Modifier
.fillMaxWidth()
.padding(top = 8.dp, end = 10.dp),
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) {
@ -294,7 +277,8 @@ private fun AccountContent(
value = if (smsNumber != null) "tel:$smsNumber" else stringResource(R.string.not_available),
label = stringResource(R.string.tel_uri_messages)
)
} else {
}
else {
val currentSubId = remember(toggle) {
if (voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
voiceSubId
@ -312,12 +296,12 @@ private fun AccountContent(
label = stringResource(R.string.tel_uri)
)
}
} else {
}
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)
)
}
}
}

View File

@ -7,8 +7,8 @@ import androidx.compose.foundation.layout.Arrangement
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.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
@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
@ -69,23 +69,14 @@ fun NavGraphBuilder.accountsScreenRoute(navController: NavController) {
@Composable
fun AccountsScreen(navController: NavController) {
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
text = stringResource(R.string.accounts),
fontWeight = FontWeight.Bold
)
Text(text = stringResource(R.string.accounts), fontWeight = FontWeight.Bold)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,

View File

@ -17,13 +17,13 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
@ -101,23 +101,14 @@ private fun AudioScreen(
checkOnClick: () -> Unit,
) {
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
text = stringResource(R.string.audio_settings),
fontWeight = FontWeight.Bold
)
Text(text = stringResource(R.string.audio_settings), fontWeight = FontWeight.Bold)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
@ -136,10 +127,7 @@ private fun AudioScreen(
windowInsets = WindowInsets(0, 0, 0, 0),
actions = {
IconButton(onClick = checkOnClick) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Check"
)
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
}
},
)
@ -319,9 +307,8 @@ private fun ToneCountry() {
isDropDownExpanded.value = false
}) {
countryNames.forEachIndexed { index, name ->
DropdownMenuItem(text = {
Text(text = name)
},
DropdownMenuItem(
text = { Text(text = name) },
onClick = {
isDropDownExpanded.value = false
itemPosition.intValue = index
@ -355,8 +342,7 @@ private fun SpeakerPhone() {
var speakerPhone by remember { mutableStateOf(oldSpeakerPhone) }
Switch(
checked = speakerPhone,
onCheckedChange = {
speakerPhone = it
onCheckedChange = { speakerPhone = it
newSpeakerPhone = speakerPhone
}
)
@ -409,9 +395,8 @@ private fun CallVolume() {
isDropDownExpanded.value = false
}) {
volNames.forEachIndexed { index, vol ->
DropdownMenuItem(text = {
Text(text = vol)
},
DropdownMenuItem(
text = { Text(text = vol) },
onClick = {
isDropDownExpanded.value = false
itemPosition.intValue = index
@ -440,8 +425,7 @@ private fun MicGain() {
OutlinedTextField(
value = micGain,
placeholder = { Text(microphoneGainTitle) },
onValueChange = {
micGain = it
onValueChange = { micGain = it
newMicGain = micGain
},
modifier = Modifier
@ -626,7 +610,8 @@ private fun checkOnClick(ctx: Context): Result {
Api.module_unload("augain")
Config.removeVariableValue("module", "augain.so")
Config.replaceVariable("augain", "1.0")
} else {
}
else {
if (oldMicGain == "1.0") {
if (Api.module_load("augain") != 0) {
alertTitle.value = ctx.getString(R.string.error)

View File

@ -11,12 +11,12 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
@ -108,17 +108,11 @@ private fun BlockedScreen(navController: NavController, request: String, aor: St
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(navController, account, request, blocked)
}
},
@ -187,10 +181,7 @@ private fun TopAppBar(
IconButton(
onClick = { expanded = !expanded }
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Menu",
)
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
}
CustomElements.DropdownMenu(
expanded,
@ -237,9 +228,7 @@ private fun BlockedContent(
private fun Account(account: Account) {
Text(
text = stringResource(R.string.account) + " " + account.text(),
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center
@ -292,18 +281,17 @@ private fun Blocked(ctx: Context, navController: NavController, blocked: Mutable
}
)
) {
Text(text = "\u2022",
Text(
text = "\u2022",
modifier = Modifier.padding(start = 8.dp, end = 4.dp),
fontSize = 18.sp)
Text(text = peerUri.replace("sip:", ""),
Text(
text = peerUri.replace("sip:", ""),
fontSize = 18.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.weight(1f))
val calendar = GregorianCalendar()
calendar.timeInMillis = blocked.timeStamp
Text(
@ -324,9 +312,8 @@ private fun loadBlocked(request: String, aor: String): MutableList<Blocked> {
val res = mutableListOf<Blocked>()
for (i in BaresipService.blocked.indices.reversed()) {
val b = BaresipService.blocked[i]
if (b.aor == aor && b.request == request) {
if (b.aor == aor && b.request == request)
res.add(Blocked("", b.peerUri, "", b.timeStamp))
}
}
Log.d(TAG, "Loaded ${res.size} blocked $request requests")
return res

View File

@ -18,13 +18,13 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -80,6 +80,7 @@ import java.io.File
import java.io.FileInputStream
import java.text.DateFormat
import java.util.GregorianCalendar
import kotlin.time.Duration.Companion.milliseconds
fun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewModel: ViewModel) {
composable("call_details") { _ ->
@ -93,15 +94,11 @@ fun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewMod
private fun CallDetailsScreen(navController: NavController, callRow: CallRow) {
val detailsState = remember { callRow.details.toMutableStateList() }
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
@ -252,22 +249,26 @@ private fun startTime(detail: Details, onDelete: (Details) -> Unit): String {
val stopText = if (DateUtils.isToday(stopTime.timeInMillis)) {
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
stringResource(R.string.today) + " " + fmt.format(stopTime.time)
} else {
}
else {
val fmt = DateFormat.getDateTimeInstance()
fmt.format(stopTime.time)
}
if (startTime == GregorianCalendar(0, 0, 0)) {
startTimeText = stopText
durationText = "?"
} else {
}
else {
if (startTime == null || detail.direction == CALL_DOWN_BLUE) {
startTimeText = stopText
durationText = ""
} else {
}
else {
val startText = if (DateUtils.isToday(startTime.timeInMillis)) {
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
stringResource(R.string.today) + " " + fmt.format(startTime.time)
} else {
}
else {
val fmt = DateFormat.getDateTimeInstance()
fmt.format(startTime.time)
}
@ -294,9 +295,7 @@ private fun startTime(detail: Details, onDelete: (Details) -> Unit): String {
text = startTimeText,
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
showDialog.value = true
}
onLongClick = { showDialog.value = true }
)
)
@ -313,7 +312,7 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
val mediaPlayer = remember { MediaPlayer() }
val scope = rememberCoroutineScope()
// 1. Setup the File Saver Launcher
// 1. Set up the File Saver Launcher
val saveLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("audio/x-wav")
) { uri ->
@ -335,12 +334,12 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
Toast.LENGTH_SHORT
).show()
}
} else {
}
else
withContext(Dispatchers.Main) {
Toast.makeText(ctx, "Source file not found", Toast.LENGTH_SHORT)
.show()
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to save file: $e")
@ -381,16 +380,14 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
)
val hasRecording = detail.recording.isNotEmpty() && detail.recording[0].isNotEmpty()
if (hasRecording) {
if (hasRecording)
Text(
text = durationText,
color = MaterialTheme.colorScheme.error,
modifier = Modifier
.padding(end = 12.dp)
.combinedClickable(
onLongClick = {
showDownloadDialog.value = true
},
onLongClick = { showDownloadDialog.value = true },
onClick = {
if (!mediaPlayer.isPlaying) {
mediaPlayer.reset()
@ -425,15 +422,15 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
)
finalFile = fallbackMerged
// Update state to match reality by creating a new List
detail.recording =
listOf(fallbackMerged.absolutePath, "")
} else {
detail.recording = listOf(fallbackMerged.absolutePath, "")
}
else
Log.e(
TAG,
"Raw file missing and fallback not found: ${currentRecording[0]}"
)
}
} else {
}
else {
// Normal Raw processing
val mergedFileName =
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
@ -442,20 +439,16 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
mergedFileName
)
if (mergedFile.exists()) {
Log.d(
TAG,
"Using already merged file: ${mergedFile.name}"
)
Log.d(TAG, "Using already merged file: ${mergedFile.name}")
finalFile = mergedFile
} else {
}
else
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile))
finalFile = mergedFile
}
// If merge successful, update state and delete originals
if (finalFile != null && finalFile.exists()) {
detail.recording =
listOf(finalFile.absolutePath, "")
detail.recording = listOf(finalFile.absolutePath, "")
try {
if (fileIn.exists()) fileIn.delete()
if (fileOut.exists()) fileOut.delete()
@ -468,21 +461,27 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
}
}
}
} else if (currentIsMerged) {
val f = File(currentRecording[0])
if (f.exists()) {
Log.d(TAG, "Using already merged file: ${currentRecording[0]}")
finalFile = f
} else {
Log.e(
TAG,
"Merged file record exists but file is missing: ${currentRecording[0]}"
)
}
else {
if (currentIsMerged) {
val f = File(currentRecording[0])
if (f.exists()) {
Log.d(
TAG,
"Using already merged file: ${currentRecording[0]}"
)
finalFile = f
}
else
Log.e(
TAG,
"Merged file record exists but file is missing: ${currentRecording[0]}"
)
}
}
withContext(Dispatchers.Main) {
if (finalFile != null && finalFile.exists()) {
if (finalFile != null && finalFile.exists())
try {
mediaPlayer.apply {
setAudioAttributes(
@ -511,15 +510,15 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
Toast.LENGTH_SHORT
).show()
}
} else {
else
Toast.makeText(
ctx, "Failed to process audio file",
Toast.LENGTH_SHORT
).show()
}
}
}
} else {
}
else {
mediaPlayer.stop()
mediaPlayer.reset()
showPlaybackDialog.value = false
@ -527,9 +526,8 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
}
)
)
} else {
else
Text(text = durationText, modifier = Modifier.padding(end = 12.dp))
}
}
@Composable
@ -549,24 +547,18 @@ private fun PlaybackDialog(
if (mediaPlayer.isPlaying) {
val duration = mediaPlayer.duration
totalDurationText = DateUtils.formatElapsedTime(duration / 1000L)
while (mediaPlayer.isPlaying) {
val current = mediaPlayer.currentPosition
currentProgress = if (duration > 0) current.toFloat() / duration.toFloat() else 0f
currentPositionText = DateUtils.formatElapsedTime(current / 1000L)
delay(100) // Poll every 100ms
delay(100.milliseconds) // Poll every 100ms
}
}
}
AlertDialog(
onDismissRequest = {
// If user clicks outside, stop playback
onStop()
},
title = {
Text(text = stringResource(R.string.playing_recording))
},
onDismissRequest = { onStop() }, // If user clicks outside, stop playback
title = { Text(text = stringResource(R.string.playing_recording)) },
text = {
Column(
modifier = Modifier.fillMaxWidth(),
@ -587,9 +579,7 @@ private fun PlaybackDialog(
}
},
confirmButton = {
TextButton(
onClick = { onStop() }
) {
TextButton(onClick = { onStop() }) {
Text(stringResource(R.string.stop))
}
}

View File

@ -14,13 +14,13 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -121,15 +121,11 @@ private fun CallsScreen(navController: NavController, viewModel: ViewModel, aor:
}
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(navController, ua, callHistory)
}
},
@ -203,10 +199,7 @@ private fun TopAppBar(navController: NavController, ua: UserAgent, callHistory:
IconButton(
onClick = { expanded = !expanded }
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Menu",
)
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
}
CustomElements.DropdownMenu(
expanded,
@ -353,15 +346,12 @@ private fun Calls(
)
secondButtonText.value = ctx.getString(R.string.call)
secondAction.value = {
if (ua.account.isMobile && ua.status != circleGreen.getValue(
colorblind
)
) {
if (ua.account.isMobile && ua.status != circleGreen.getValue(colorblind)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value =
ctx.getString(R.string.airplane_mode)
alertMessage.value = ctx.getString(R.string.airplane_mode)
showAlert.value = true
} else {
}
else {
handleIntent(ctx, viewModel, intent, "call")
navController.navigate("main") {
popUpTo("main")
@ -377,16 +367,20 @@ private fun Calls(
alertMessage.value =
ctx.getString(R.string.airplane_mode)
showAlert.value = true
} else if (!Utils.isDefaultSmsApp(ctx)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value =
ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
} else {
}
else
if (!Utils.isDefaultSmsApp(ctx)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value =
ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
}
else {
handleIntent(ctx, viewModel, intent, "message")
navController.navigateUp()
}
} else {
}
else {
handleIntent(ctx, viewModel, intent, "message")
navController.navigateUp()
}
@ -400,13 +394,11 @@ private fun Calls(
try {
ctx.startActivity(emailIntent)
} catch (e: Exception) {
Log.e(
TAG,
"Failed to start email activity: ${e.message}"
)
Log.e(TAG, "Failed to start email activity: ${e.message}")
}
}
} else
}
else
lastButtonText.value = ""
showDialog.value = true
}
@ -426,13 +418,9 @@ private fun Calls(
)
secondButtonText.value = ""
thirdButtonText.value = ctx.getString(R.string.copy_uri)
thirdAction.value = {
Utils.copyToClipboard(ctx, peerUri)
}
thirdAction.value = { Utils.copyToClipboard(ctx, peerUri) }
lastButtonText.value = ctx.getString(R.string.delete)
lastAction.value = {
removeFromHistory(callHistory, callRow)
}
lastAction.value = { removeFromHistory(callHistory, callRow) }
}
else {
message.value = String.format(
@ -446,13 +434,10 @@ private fun Calls(
navController.navigate("contact/$uri/new")
}
thirdButtonText.value = ctx.getString(R.string.copy_uri)
thirdAction.value = {
Utils.copyToClipboard(ctx, peerUri)
thirdAction.value = { Utils.copyToClipboard(ctx, peerUri)
}
lastButtonText.value = ctx.getString(R.string.delete)
lastAction.value = {
removeFromHistory(callHistory, callRow)
}
lastAction.value = { removeFromHistory(callHistory, callRow) }
}
showDialog.value = true
}
@ -509,7 +494,8 @@ private fun Calls(
}
if (count > 3)
Text("...", color = MaterialTheme.colorScheme.onBackground)
Text(text = Utils.friendlyUri(ctx, peerUri, ua.account),
Text(
text = Utils.friendlyUri(ctx, peerUri, ua.account),
modifier = Modifier.padding(start = 8.dp),
fontSize = 18.sp,
maxLines = 1,

View File

@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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
@ -22,7 +21,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -136,14 +135,10 @@ private fun ChatScreen(
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
var areMessagesLoaded by remember(aor, peerUri) {
mutableStateOf(false)
}
var areMessagesLoaded by remember(aor, peerUri) { mutableStateOf(false) }
val reloadMessages = {
Log.d(TAG, "Reloading messages for $aor peer $peerUri")
@ -175,16 +170,11 @@ private fun ChatScreen(
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.imePadding(),
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(ctx, navController, viewModel, account, peerUri)
}
},
@ -199,7 +189,8 @@ private fun ChatScreen(
},
content = { contentPadding ->
if (areMessagesLoaded)
ChatContent(ctx,
ChatContent(
ctx,
navController,
contentPadding,
account, peerUri,
@ -223,8 +214,7 @@ private fun TopAppBar(
TopAppBar(
title = {
Text(
text = format(ctx.getString(R.string.chat_with),
Utils.friendlyUri(ctx, peerUri, account)),
text = format(ctx.getString(R.string.chat_with), Utils.friendlyUri(ctx, peerUri, account)),
fontSize = 22.sp,
fontWeight = FontWeight.Bold
)
@ -237,10 +227,7 @@ private fun TopAppBar(
),
navigationIcon = {
IconButton(onClick = { backAction(navController, account, peerUri) }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
windowInsets = WindowInsets(0, 0, 0, 0),
@ -290,10 +277,7 @@ private fun TopAppBar(
Log.w(TAG, "Call button onClick listener did not find UA for $aor")
}
) {
Icon(
imageVector = Icons.Outlined.Email,
contentDescription = "Email",
)
Icon(imageVector = Icons.Outlined.Email, contentDescription = "Email")
}
}
)
@ -310,9 +294,7 @@ private fun ChatContent(
onMessageDeleted: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding),
modifier = Modifier.fillMaxWidth().padding(contentPadding),
verticalArrangement = Arrangement.Bottom
) {
Account(ctx, account)
@ -325,9 +307,7 @@ private fun ChatContent(
private fun Account(ctx: Context, account: Account) {
Text(
text = ctx.getString(R.string.account) + " " + account.text(),
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, bottom = 8.dp),
modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp),
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center
@ -368,11 +348,8 @@ private fun Messages(
LaunchedEffect(messages) {
// Scroll to the bottom when new messages are added
if (messages.isNotEmpty()) {
coroutineScope.launch {
lazyListState.scrollToItem(0)
}
}
if (messages.isNotEmpty())
coroutineScope.launch { lazyListState.scrollToItem(0) }
}
LazyColumn(
@ -420,9 +397,7 @@ private fun Messages(
peerUri
)
secondButtonText.value = ctx.getString(R.string.add_contact)
secondAction.value = {
navController.navigate("contact/$peerUri/new")
}
secondAction.value = { navController.navigate("contact/$peerUri/new") }
lastButtonText.value = ctx.getString(R.string.delete)
lastAction.value = {
message.delete()
@ -588,14 +563,16 @@ private fun NewMessage(
if (ua.status != circleGreen.getValue(colorblind)) {
dialogMessage.value = ctx.getString(R.string.airplane_mode)
showDialog.value = true
} else {
}
else {
val destination = Utils.uriUserPart(peerUri)
if (Utils.sendSms(ctx, destination, msgText)) {
msg.direction = MESSAGE_UP
newMessage.value = TextFieldValue("")
viewModel.updateAorPeerMessage(aor, peerUri, "")
keyboardController?.hide()
} else {
}
else {
Toast.makeText(
ctx, "${ctx.getString(R.string.message_failed)}!",
Toast.LENGTH_SHORT
@ -604,38 +581,36 @@ private fun NewMessage(
msg.responseReason = ctx.getString(R.string.message_failed)
}
}
} else {
if (Utils.isTelUri(peerUri))
}
else {
if (Utils.isTelUri(peerUri)) {
if (ua.account.telProvider == "") {
dialogMessage.value = String.format(
ctx.getString(R.string.no_telephony_provider),
Utils.plainAor(aor)
)
showDialog.value = true
} else {
msgUri = Utils.telToSip(peerUri, ua.account)
}
else
msgUri = Utils.telToSip(peerUri, ua.account)
}
else
msgUri = peerUri
if (msgUri != "")
if (Api.message_send(
ua.uap,
msgUri,
msgText,
time.toString()
) != 0
) {
if (msgUri != "") {
if (Api.message_send(ua.uap, msgUri, msgText, time.toString()) != 0) {
Toast.makeText(
ctx, "${ctx.getString(R.string.message_failed)}!",
Toast.LENGTH_SHORT
).show()
msg.direction = MESSAGE_UP_FAIL
msg.responseReason = ctx.getString(R.string.message_failed)
} else {
}
else {
newMessage.value = TextFieldValue("")
viewModel.updateAorPeerMessage(aor, peerUri, "")
keyboardController?.hide()
}
}
}
}
},

View File

@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.height
@ -23,7 +22,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
@ -127,23 +126,15 @@ private fun ChatsScreen(navController: NavController, aor: String) {
refreshTrigger++
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.imePadding(),
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(navController, account, uaMessages)
}
},
@ -188,10 +179,7 @@ private fun TopAppBar(
),
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
windowInsets = WindowInsets(0, 0, 0, 0),
@ -199,10 +187,7 @@ private fun TopAppBar(
IconButton(
onClick = { menuExpanded = !menuExpanded }
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Menu",
)
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
}
DropdownMenu (
expanded = menuExpanded,
@ -218,9 +203,8 @@ private fun TopAppBar(
}
showDialog.value = true
}
blocked -> {
blocked ->
navController.navigate("blocked/message/${account.aor}")
}
}
}
)
@ -237,9 +221,7 @@ private fun ChatsContent(
uaMessages: MutableState<List<Message>>
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding),
modifier = Modifier.fillMaxWidth().padding(contentPadding),
verticalArrangement = Arrangement.Top
) {
Account(account)
@ -308,7 +290,6 @@ private fun Chats(
else
CustomElements.TextAvatar(contact.name, contact.color)
}
is Contact.AndroidContact -> {
val thumbNailUri = contact.thumbnailUri
if (thumbNailUri != null)
@ -321,7 +302,6 @@ private fun Chats(
else
CustomElements.TextAvatar(contact.name, contact.color)
}
null -> {
Icon(
imageVector = Icons.Filled.AccountCircle,
@ -332,20 +312,16 @@ private fun Chats(
}
}
Spacer(modifier = Modifier.width(6.dp))
val buttonShape = if (message.direction == MESSAGE_DOWN) {
val buttonShape = if (message.direction == MESSAGE_DOWN)
RoundedCornerShape(50.dp, 20.dp, 20.dp, 10.dp)
} else {
else
RoundedCornerShape(20.dp, 10.dp, 50.dp, 20.dp)
}
val borderStroke = if (account.unreadMessages && Message.unreadMessagesFromPeer(aor, message.peerUri)) {
val borderStroke = if (account.unreadMessages && Message.unreadMessagesFromPeer(aor, message.peerUri))
BorderStroke(width = 2.dp, color = MaterialTheme.colorScheme.error)
} else {
else
null
}
CustomElements.Button(
onClick = {
navController.navigate("chat/${aor}/${message.peerUri}")
},
onClick = { navController.navigate("chat/${aor}/${message.peerUri}") },
onLongClick = {
val peerName = Utils.friendlyUri(ctx, message.peerUri, account, includeLabel = false)
val peerNameWithLabel = Utils.friendlyUri(ctx, message.peerUri, account)
@ -361,20 +337,17 @@ private fun Chats(
lastAction.value = {
deleteMessages(uaMessages, account, message.peerUri)
}
} else {
}
else {
dialogMessage.value =
String.format(
ctx.getString(R.string.long_chat_question),
peerName
)
secondButtonText.value = ctx.getString(R.string.delete)
secondAction.value = {
deleteMessages(uaMessages, account, message.peerUri)
}
secondAction.value = { deleteMessages(uaMessages, account, message.peerUri) }
lastButtonText.value = ctx.getString(R.string.add_contact)
lastAction.value = {
navController.navigate("contact/${message.peerUri}/new")
}
lastAction.value = { navController.navigate("contact/${message.peerUri}/new") }
}
showDialog.value = true
},
@ -497,10 +470,7 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
verticalAlignment = Alignment.CenterVertically,
) {
var newPeer by remember { mutableStateOf("") }
Column(
horizontalAlignment = Alignment.Start,
modifier = Modifier.weight(1f)
) {
Column(horizontalAlignment = Alignment.Start, modifier = Modifier.weight(1f)) {
if (showSuggestions && filteredSuggestions.isNotEmpty()) {
Column(
modifier = Modifier
@ -511,8 +481,7 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
)
.animateContentSize()
) {
Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)
) {
Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
@ -553,9 +522,9 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
onValueChange = {
newPeer = it
showSuggestions = newPeer.length > 1
filteredSuggestions = if (it.isEmpty()) {
filteredSuggestions = if (it.isEmpty())
emptyList()
} else {
else {
val normalizedInput = Utils.unaccent(it)
suggestions
.filter { suggestion ->
@ -609,7 +578,8 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
} else {
makeChat(ctx, navController, account, peerText)
}
} else if (uris.size == 1)
}
else if (uris.size == 1)
makeChat(ctx, navController, account, uris[0].uri)
else {
items.value = uris.map { it.label.ifEmpty { it.uri.substringAfter(":") } }
@ -656,9 +626,7 @@ private fun loadMessages(account: Account) : List<Message> {
private fun deleteMessages(uaMessages: MutableState<List<Message>>, account: Account, peerUri: String) {
val updatedMessages = BaresipService.messages.toMutableList()
updatedMessages.removeAll {
it.aor == account.aor && (peerUri == "" || it.peerUri == peerUri)
}
updatedMessages.removeAll { it.aor == account.aor && (peerUri == "" || it.peerUri == peerUri) }
BaresipService.messages = updatedMessages.toList()
Message.save()
uaMessages.value = loadMessages(account)

View File

@ -5,13 +5,14 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
@ -98,16 +99,14 @@ private fun CodecsScreen(
val codecs = remember { mutableStateListOf<Codec>() }
LaunchedEffect(acc, media) {
val allCodecs: List<String> = if (media == "audio") {
val allCodecs: List<String> = if (media == "audio")
Api.audio_codecs().split(",")
} else {
else
Api.video_codecs().split(",").distinct()
}
val accCodecs: List<String> = if (media == "audio") {
val accCodecs: List<String> = if (media == "audio")
acc.audioCodec
} else {
else
acc.videoCodec
}
val currentCodecs = mutableListOf<Codec>()
for (codec in accCodecs)
currentCodecs.add(Codec(codec, mutableStateOf(true)))
@ -119,17 +118,11 @@ private fun CodecsScreen(
}
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
@ -146,7 +139,6 @@ private fun CodecsScreen(
titleContentColor = MaterialTheme.colorScheme.onPrimary,
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
),
windowInsets = WindowInsets(0, 0, 0, 0),
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
@ -155,24 +147,18 @@ private fun CodecsScreen(
)
}
},
windowInsets = WindowInsets(0, 0, 0, 0),
actions = {
IconButton(onClick = {
checkOnClick(codecs)
}) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Check"
)
IconButton(
onClick = { checkOnClick(codecs) }) {
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
}
}
)
}
},
content = { contentPadding ->
CodecsContent(
contentPadding,
codecs
)
CodecsContent(contentPadding, codecs)
},
)
}
@ -204,9 +190,7 @@ private fun Codecs(codecs: SnapshotStateList<Codec>) {
)
LazyColumn(
modifier = Modifier
.padding(end = 4.dp)
.verticalScrollbar(state = draggableState.listState),
modifier = Modifier.padding(end = 4.dp).verticalScrollbar(state = draggableState.listState),
state = draggableState.listState,
contentPadding = PaddingValues(start = 12.dp, end = 12.dp),
) {
@ -236,7 +220,8 @@ private fun Codecs(codecs: SnapshotStateList<Codec>) {
val index = codecs.indexOf(item)
codecs.removeAt(index)
codecs.add(0, item)
} else {
}
else {
val index = codecs.indexOf(item)
codecs.removeAt(index)
codecs.add(item)
@ -247,10 +232,7 @@ private fun Codecs(codecs: SnapshotStateList<Codec>) {
},
trailingContent = {
Icon(
modifier = Modifier.dragHandle(
state = draggableState,
key = item.name
),
modifier = Modifier.dragHandle(state = draggableState, key = item.name),
imageVector =Icons.Filled.Reorder,
contentDescription = null
)

View File

@ -26,14 +26,15 @@ 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.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardOptions
@ -170,7 +171,8 @@ private fun ContactScreen(
isLoading = false,
isBaresipContact = true
)
} else {
}
else {
val baresipContact = Contact.baresipContact(uriOrNameArg)
val androidContact = Contact.androidContact(uriOrNameArg)
val contact = baresipContact ?: androidContact
@ -226,7 +228,8 @@ private fun ContactScreen(
null,
tmpAvatarFile = null
)
} else {
}
else {
screenState.tmpAvatarFile?.let { tempFile ->
if (tempFile.exists()) {
Log.d(TAG, "Back pressed, deleting temp avatar: ${tempFile.name}")
@ -238,16 +241,13 @@ private fun ContactScreen(
}
val onCheck: () -> Unit = {
val result = checkOnClick(
ctx = ctx,
currentState = screenState,
uriOrNameArg = uriOrNameArg,
)
val result = checkOnClick(ctx = ctx, currentState = screenState, uriOrNameArg = uriOrNameArg)
if (result) {
if (screenState.new) {
navController.previousBackStackEntry?.savedStateHandle?.set("scrollToContact", screenState.name)
navController.navigateUp()
} else {
}
else {
// Update UI state with saved values
val contact = Contact.baresipContact(screenState.name)!!
val avatarFile = File(BaresipService.filesPath, "${contact.id}.png")
@ -270,26 +270,16 @@ private fun ContactScreen(
}
}
val onEdit: () -> Unit = {
screenState = screenState.copy(isEditing = true)
}
val onEdit: () -> Unit = { screenState = screenState.copy(isEditing = true) }
BackHandler(enabled = true) {
onBack()
}
BackHandler(enabled = true) { onBack() }
Scaffold(
modifier = Modifier
.fillMaxSize()
.imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = title,
isEditing = screenState.isEditing,
@ -301,7 +291,7 @@ private fun ContactScreen(
}
},
content = { contentPadding ->
if (!screenState.isLoading) {
if (!screenState.isLoading)
ContactContent(
ctx = ctx,
viewModel = viewModel,
@ -310,11 +300,10 @@ private fun ContactScreen(
screenState = screenState,
onStateChange = { newState -> screenState = newState }
)
} else {
else
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
}
)
}
@ -340,28 +329,21 @@ private fun TopAppBar(
windowInsets = WindowInsets(0, 0, 0, 0),
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
actions = {
if (isEditing) {
if (isEditing)
IconButton(onClick = onCheck) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Save"
)
Icon(imageVector = Icons.Filled.Check, contentDescription = "Save")
}
} else if (isBaresipContact) {
else if (isBaresipContact)
IconButton(onClick = onEdit) {
Icon(
imageVector = Icons.Filled.Edit,
contentDescription = "Edit"
)
}
}
}
)
}
@ -510,9 +492,9 @@ private fun AvatarSection(
val newImageId = System.currentTimeMillis()
val tempNewImageFile = File(BaresipService.filesPath, "${newImageId}.png")
if (saveBitmap(rotatedBitmap, tempNewImageFile)) {
if (saveBitmap(rotatedBitmap, tempNewImageFile))
onNewAvatarChosen(tempNewImageFile, newImageId)
} else {
else {
Log.e(TAG, "Failed to save processed avatar image")
if (tempNewImageFile.exists()) Utils.deleteFile(tempNewImageFile)
}
@ -534,24 +516,18 @@ private fun AvatarSection(
.clip(CircleShape)
.background(if (currentAvatarUri == null) Color(color) else Color.Transparent)
.let { modifier ->
if (isEditing) {
if (isEditing)
modifier.combinedClickable(
onClick = { avatarImagePicker.launch("image/*") },
onLongClick = { onAvatarColorChange(Utils.randomColor()) }
)
} else {
else
modifier
}
}
) {
if (currentAvatarUri == null) {
Box(
modifier = Modifier.size(avatarSize.dp),
contentAlignment = Alignment.Center
) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(SolidColor(Color(color)))
}
Box(modifier = Modifier.size(avatarSize.dp), contentAlignment = Alignment.Center) {
Canvas(modifier = Modifier.fillMaxSize()) { drawCircle(SolidColor(Color(color))) }
val text = if (name.isNotBlank()) name.take(1).uppercase() else "?"
Text(text, fontSize = 72.sp, color = Color.White)
}
@ -575,9 +551,7 @@ private fun ContactNameSection(name: String, isEditing: Boolean, new: Boolean, o
value = name,
placeholder = { Text(stringResource(R.string.contact_name)) },
onValueChange = onNameChange,
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
label = { Text(stringResource(R.string.contact_name)) },
keyboardOptions = KeyboardOptions(
@ -588,7 +562,8 @@ private fun ContactNameSection(name: String, isEditing: Boolean, new: Boolean, o
LaunchedEffect(new) {
if (new) focusRequester.requestFocus()
}
} else {
}
else
Row(
Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
@ -601,7 +576,6 @@ private fun ContactNameSection(name: String, isEditing: Boolean, new: Boolean, o
textAlign = TextAlign.Center
)
}
}
}
@Composable
@ -615,16 +589,10 @@ private fun UrisSection(
) {
val selectedAor by viewModel.selectedAor.collectAsState()
if (isEditing) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (isEditing)
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
uris.forEachIndexed { index, contactUri ->
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Column (modifier = Modifier.weight(1f)) {
OutlinedTextField(
value = contactUri.uri,
@ -656,7 +624,7 @@ private fun UrisSection(
)
)
}
if (uris.size > 1) {
if (uris.size > 1)
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
IconButton(
onClick = {
@ -674,7 +642,6 @@ private fun UrisSection(
)
}
}
}
}
}
IconButton(
@ -692,11 +659,8 @@ private fun UrisSection(
)
}
}
} else {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
else
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
uris.forEachIndexed { index, contactUri ->
Row(verticalAlignment = Alignment.CenterVertically) {
val uri = contactUri.uri
@ -735,11 +699,13 @@ private fun UrisSection(
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.airplane_mode)
showAlert.value = true
} else if (ua.account.isMobile && !Utils.isDefaultSmsApp(ctx)) {
}
else if (ua.account.isMobile && !Utils.isDefaultSmsApp(ctx)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
} else {
}
else {
val intent = Intent(ctx, MainActivity::class.java)
intent.putExtra("uap", ua.uap)
intent.putExtra("peer", uri)
@ -760,17 +726,20 @@ private fun UrisSection(
}
// Call Button
if (ua != null && (if (uri.startsWith("tel:"))
if (ua != null &&
if (uri.startsWith("tel:"))
ua.account.isMobile || ua.account.telProvider != ""
else
!ua.account.isMobile))
!ua.account.isMobile
)
IconButton(
onClick = {
if (ua.account.isMobile && ua.status != circleGreen.getValue(colorblind)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.airplane_mode)
showAlert.value = true
} else {
}
else {
val intent = Intent(ctx, MainActivity::class.java)
intent.putExtra("uap", ua.uap)
intent.putExtra("peer", uri)
@ -792,12 +761,11 @@ private fun UrisSection(
}
}
}
}
}
@Composable
private fun EmailSection(ctx: Context, email: String, isEditing: Boolean, onEmailChange: (String) -> Unit) {
if (isEditing) {
if (isEditing)
OutlinedTextField(
value = email,
placeholder = { Text(stringResource(R.string.email)) },
@ -807,7 +775,7 @@ private fun EmailSection(ctx: Context, email: String, isEditing: Boolean, onEmai
label = { Text(stringResource(R.string.email)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
)
} else if (email.isNotEmpty()) {
else if (email.isNotEmpty())
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
@ -846,7 +814,6 @@ private fun EmailSection(ctx: Context, email: String, isEditing: Boolean, onEmai
)
}
}
}
}
@Composable
@ -865,10 +832,7 @@ private fun FavoriteSection(ctx: Context, favorite: Boolean, onFavoriteChange: (
showAlert.value = true
},
)
Switch(
checked = favorite,
onCheckedChange = onFavoriteChange
)
Switch(checked = favorite, onCheckedChange = onFavoriteChange)
}
}
@ -888,10 +852,7 @@ private fun AndroidSection(ctx: Context, android: Boolean, onAndroidChange: (Boo
showAlert.value = true
},
)
Switch(
checked = android,
onCheckedChange = onAndroidChange
)
Switch(checked = android, onCheckedChange = onAndroidChange)
}
}
@ -906,7 +867,6 @@ private fun checkOnClick(
if (u == "") continue
if (!u.startsWith("sip:") && !u.startsWith("tel:"))
u = if (Utils.isTelNumber(u)) "tel:$u" else "sip:$u"
if (!Utils.checkUri(u)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), u)
@ -924,7 +884,10 @@ private fun checkOnClick(
}
var newName = currentState.name.trim()
if (newName == "") newName = if (newUris.isNotEmpty()) newUris[0].uri.substringAfter(":") else currentState.email
if (newName == "") newName = if (newUris.isNotEmpty())
newUris[0].uri.substringAfter(":")
else
currentState.email
if (!Utils.checkName(newName)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = String.format(ctx.getString(R.string.invalid_contact), newName)
@ -934,9 +897,8 @@ private fun checkOnClick(
val alert: Boolean = if (currentState.new)
Contact.nameExists(newName, BaresipService.contacts, true)
else {
else
(uriOrNameArg != newName) && Contact.nameExists(newName, BaresipService.contacts, false)
}
if (alert) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = String.format(ctx.getString(R.string.contact_already_exists), newName)
@ -951,7 +913,8 @@ private fun checkOnClick(
if (oldAvatar.exists()) Utils.deleteFile(oldAvatar)
idToUse = currentState.newId
}
} else if (currentState.avatarImageUri == null) {
}
else if (currentState.avatarImageUri == null) {
val avatarFile = File(BaresipService.filesPath, "$idToUse.png")
if (avatarFile.exists()) Utils.deleteFile(avatarFile)
}
@ -979,12 +942,12 @@ private fun checkOnClick(
Log.e(TAG, "Update of Android favorite failed: ${e.message}")
}
}
} else {
}
else
if (currentState.new)
Contact.addBaresipContact(contact)
else
Contact.updateBaresipContact(currentState.id, contact)
}
return true
}
@ -1010,7 +973,15 @@ private fun rotateBitmap(bitmap: Bitmap, orientation: Int): Bitmap {
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
else -> return bitmap
}
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
val rotatedBitmap = Bitmap.createBitmap(
bitmap,
0,
0,
bitmap.width,
bitmap.height,
matrix,
true
)
bitmap.recycle()
return rotatedBitmap
}
@ -1036,11 +1007,10 @@ private fun addOrUpdateAndroidContact(ctx: Context, contact: Contact.BaresipCont
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
CommonDataKinds.StructuredName.DISPLAY_NAME + "='" + contact.name + "'"
val c: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, null, null)
if (c != null && c.moveToFirst()) {
if (c != null && c.moveToFirst())
updateAndroidContact(ctx, c.getLong(0), contact)
} else {
else
addAndroidContact(ctx, contact)
}
c?.close()
}
@ -1053,17 +1023,19 @@ private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Bo
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name).build())
if (contact.email.isNotEmpty()) {
if (contact.email.isNotEmpty())
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.Email.ADDRESS, contact.email)
.withValue(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_HOME).build())
}
for (contactUri in contact.uris) {
val uri = contactUri.uri
val label = contactUri.label
val mimeType = if (uri.startsWith("sip:")) CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE else CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(Data.MIMETYPE, mimeType)
@ -1078,12 +1050,11 @@ private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Bo
}
if (contact.avatarImage != null) {
val photoData = bitmapToPNGByteArray(contact.avatarImage!!)
if (photoData != null) {
if (photoData != null)
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.Photo.PHOTO, photoData).build())
}
}
try {
ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
@ -1095,17 +1066,24 @@ private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Bo
}
private fun updateAndroidContact(ctx: Context, rawContactId: Long, contact: Contact.BaresipContact) {
if (contact.email.isNotEmpty()) {
if (updateAndroidEmail(ctx, rawContactId, contact.email) == 0) addAndroidEmail(ctx, rawContactId, contact.email)
}
for (contactUri in contact.uris) if (updateAndroidUri(ctx, rawContactId, contactUri) == 0) addAndroidUri(ctx, rawContactId, contactUri)
if (updateAndroidPhoto(ctx, rawContactId, contact.avatarImage) == 0) if (contact.avatarImage != null) addAndroidPhoto(ctx, rawContactId, contact.avatarImage!!)
if (contact.email.isNotEmpty())
if (updateAndroidEmail(ctx, rawContactId, contact.email) == 0)
addAndroidEmail(ctx, rawContactId, contact.email)
for (contactUri in contact.uris)
if (updateAndroidUri(ctx, rawContactId, contactUri) == 0)
addAndroidUri(ctx, rawContactId, contactUri)
if (updateAndroidPhoto(ctx, rawContactId, contact.avatarImage) == 0)
if (contact.avatarImage != null)
addAndroidPhoto(ctx, rawContactId, contact.avatarImage!!)
}
private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri) {
val uri = contactUri.uri
val label = contactUri.label
val mimeType = if (uri.startsWith("sip:")) CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE else CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValue(Data.RAW_CONTACT_ID, rawContactId)
.withValue(Data.MIMETYPE, mimeType)
@ -1128,7 +1106,10 @@ private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.
private fun updateAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri): Int {
val uri = contactUri.uri
val label = contactUri.label
val mimeType = if (uri.startsWith("sip:")) CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE else CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val contentValues = ContentValues()
contentValues.put(ContactsContract.Data.DATA1, uri.substringAfter(":"))
if (mimeType == CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
@ -1137,7 +1118,8 @@ private fun updateAndroidUri(ctx: Context, rawContactId: Long, contactUri: Conta
if (type == CommonDataKinds.Phone.TYPE_CUSTOM)
contentValues.put(CommonDataKinds.Phone.LABEL, label)
}
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and ${ContactsContract.Data.MIMETYPE}='$mimeType'"
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and " +
"${ContactsContract.Data.MIMETYPE}='$mimeType'"
return try {
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
} catch (e: Exception) {
@ -1179,7 +1161,8 @@ private fun addAndroidEmail(ctx: Context, rawContactId: Long, email: String) {
private fun updateAndroidEmail(ctx: Context, rawContactId: Long, email: String): Int {
val contentValues = ContentValues()
contentValues.put(CommonDataKinds.Email.ADDRESS, email)
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and ${ContactsContract.Data.MIMETYPE}='${CommonDataKinds.Email.CONTENT_ITEM_TYPE}'"
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and " +
"${ContactsContract.Data.MIMETYPE}='${CommonDataKinds.Email.CONTENT_ITEM_TYPE}'"
return try {
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
} catch (_: Exception) {
@ -1191,7 +1174,8 @@ private fun updateAndroidPhoto(ctx: Context, rawContactId: Long, photoBits: Bitm
val photoBytes = if (photoBits == null) null else bitmapToPNGByteArray(photoBits)
val contentValues = ContentValues()
contentValues.put(CommonDataKinds.Photo.PHOTO, photoBytes)
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and ${ContactsContract.Data.MIMETYPE}='${CommonDataKinds.Photo.CONTENT_ITEM_TYPE}'"
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and " +
"${ContactsContract.Data.MIMETYPE}='${CommonDataKinds.Photo.CONTENT_ITEM_TYPE}'"
return try {
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
} catch (_: Exception) {

View File

@ -22,15 +22,15 @@ import androidx.compose.foundation.layout.Arrangement
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.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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@ -128,7 +128,7 @@ private fun ContactsScreen(navController: NavController) {
val vcfExportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("text/vcard")
) { uri: android.net.Uri? ->
if (uri != null) {
if (uri != null)
try {
ctx.contentResolver.openOutputStream(uri)?.use { outputStream ->
val writer = outputStream.bufferedWriter()
@ -155,7 +155,8 @@ private fun ContactsScreen(navController: NavController) {
writer.write("TEL;X-${u.label}:${u.uri.substring(4)}\n")
else
writer.write("TEL:${u.uri.substring(4)}\n")
} else if (u.uri.startsWith("sip:")) {
}
else if (u.uri.startsWith("sip:")) {
if (u.label.isNotEmpty())
writer.write("X-SIP;X-${u.label}:${u.uri.substring(4)}\n")
else
@ -171,25 +172,23 @@ private fun ContactsScreen(navController: NavController) {
Log.e(TAG, "Failed to export VCF: ${e.message}")
Toast.makeText(ctx, R.string.contact_export_failure, Toast.LENGTH_SHORT).show()
}
}
}
val vcfImportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument()
) { uri: android.net.Uri? ->
if (uri != null) {
if (uri != null)
try {
ctx.contentResolver.openInputStream(uri)?.use { inputStream ->
val reader = inputStream.bufferedReader()
val lines = mutableListOf<String>()
reader.forEachLine { line ->
if (line.startsWith(" ") || line.startsWith("\t")) {
if (lines.isNotEmpty()) {
if (lines.isNotEmpty())
lines[lines.size - 1] = lines.last() + line.trim()
}
} else {
lines.add(line)
}
else
lines.add(line)
}
var name = ""
@ -199,7 +198,7 @@ private fun ContactsScreen(navController: NavController) {
var contactNo = 0
val newBaresipContacts = BaresipService.baresipContacts.value.toMutableList()
for (line in lines) {
for (line in lines)
when {
line.startsWith("BEGIN:VCARD", ignoreCase = true) -> {
name = ""
@ -241,7 +240,7 @@ private fun ContactsScreen(navController: NavController) {
if (name.isNotEmpty() && (uris.isNotEmpty() || email.isNotEmpty())) {
val existingContact = newBaresipContacts.find { it.name == name }
val contactId = existingContact?.id ?: (System.currentTimeMillis() + contactNo++)
if (photoBase64.isNotEmpty()) {
if (photoBase64.isNotEmpty())
try {
val decodedString = Base64.decode(photoBase64, Base64.DEFAULT)
val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
@ -254,17 +253,14 @@ private fun ContactsScreen(navController: NavController) {
} catch (e: Exception) {
Log.e(TAG, "Failed to decode photo for $name: ${e.message}")
}
}
if (existingContact != null) {
for (u in uris) {
if (existingContact.uris.none { it.uri == u.uri }) {
for (u in uris)
if (existingContact.uris.none { it.uri == u.uri })
existingContact.uris.add(u)
}
}
if (existingContact.email.isEmpty() && email.isNotEmpty()) {
if (existingContact.email.isEmpty() && email.isNotEmpty())
existingContact.email = email
}
} else {
}
else
newBaresipContacts.add(
Contact.BaresipContact(
name,
@ -275,30 +271,19 @@ private fun ContactsScreen(navController: NavController) {
false
)
)
}
}
}
}
}
BaresipService.baresipContacts.value = newBaresipContacts.toList()
Contact.saveBaresipContacts()
Contact.restoreBaresipContacts()
Contact.contactsUpdate()
Toast.makeText(
ctx,
R.string.contact_import_success,
Toast.LENGTH_SHORT
).show()
Toast.makeText(ctx, R.string.contact_import_success, Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to import VCF: ${e.message}")
Toast.makeText(
ctx,
R.string.contact_import_failure,
Toast.LENGTH_SHORT
).show()
Toast.makeText(ctx, R.string.contact_import_failure, Toast.LENGTH_SHORT).show()
}
}
}
val contactNames = remember(BaresipService.contactsMode) {
@ -373,11 +358,9 @@ private fun ContactsScreen(navController: NavController) {
contract = ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
if (permissions[Manifest.permission.READ_CONTACTS] == true &&
permissions[Manifest.permission.WRITE_CONTACTS] == true) {
if (pendingMode.isNotEmpty()) {
permissions[Manifest.permission.WRITE_CONTACTS] == true)
if (pendingMode.isNotEmpty())
setContactsMode(pendingMode)
}
}
pendingMode = ""
}
@ -388,10 +371,7 @@ private fun ContactsScreen(navController: NavController) {
lastButtonText = ok,
onLastClicked = {
requestPermissionsLauncher.launch(
arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
)
arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
)
}
)
@ -400,20 +380,11 @@ private fun ContactsScreen(navController: NavController) {
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
text = stringResource(R.string.contacts),
fontWeight = FontWeight.Bold
)
Text(text = stringResource(R.string.contacts), fontWeight = FontWeight.Bold)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
@ -425,18 +396,13 @@ private fun ContactsScreen(navController: NavController) {
IconButton(
onClick = { navController.navigateUp() }
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
windowInsets = WindowInsets(0, 0, 0, 0),
actions = {
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Menu"
)
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
}
CustomElements.DropdownMenu(
expanded = expanded,
@ -498,7 +464,8 @@ private fun ContactsScreen(navController: NavController) {
) {
Log.d(TAG, "Contacts permissions already granted")
setContactsMode(mode)
} else {
}
else {
if (shouldShowRequestPermissionRationale(
activity, Manifest.permission.READ_CONTACTS
) ||
@ -508,20 +475,20 @@ private fun ContactsScreen(navController: NavController) {
) {
pendingMode = mode
showNoticeDialog.value = true
} else {
}
else {
pendingMode = mode
requestPermissionsLauncher.launch(contactsPermissions)
}
}
}
showDialog.value = true
} else {
setContactsMode(mode)
}
else
setContactsMode(mode)
}
)
},
windowInsets = WindowInsets(0, 0, 0, 0),
}
)
}
},
@ -594,10 +561,7 @@ private fun BottomBar(
},
label = { Text(stringResource(R.string.search)) },
textStyle = TextStyle(fontSize = 18.sp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done
)
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done)
)
SmallFloatingActionButton(
onClick = { navController.navigate("contact//new") },
@ -641,18 +605,17 @@ private fun ContactsContent(
var lastSearchQuery by remember { mutableStateOf("") }
LaunchedEffect(searchQuery) {
if (searchQuery.isBlank() && lastSearchQuery.isNotBlank()) {
if (searchQuery.isBlank() && lastSearchQuery.isNotBlank())
lazyListState.scrollToItem(0)
}
lastSearchQuery = searchQuery
}
val filteredContacts = remember(BaresipService.contacts, searchQuery) {
if (searchQuery.isBlank()) {
if (searchQuery.isBlank())
BaresipService.contacts.map { contact ->
Pair(contact, buildAnnotatedString { append(contact.name()) })
}
} else {
else {
val normalizedQuery = Utils.unaccent(searchQuery)
BaresipService.contacts
.filter { contact ->
@ -667,9 +630,8 @@ private fun ContactsContent(
LaunchedEffect(scrollToContact?.value, filteredContacts) {
scrollToContact?.value?.let { name ->
val index = filteredContacts.indexOfFirst { it.first.name() == name }
if (index != -1) {
if (index != -1)
lazyListState.scrollToItem(index)
}
navController.currentBackStackEntry?.savedStateHandle?.remove<String>("scrollToContact")
}
}
@ -687,13 +649,11 @@ private fun ContactsContent(
itemsIndexed(
filteredContacts, key = { index, (contact, _) -> "${contact.id()}_$index" }
) { _, (contact, annotatedName) ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
when (contact) {
is Contact.BaresipContact -> {
val avatarImage = contact.avatarImage
if (avatarImage != null)
@ -708,7 +668,6 @@ private fun ContactsContent(
else
TextAvatar(contact.name(), contact.color)
}
is Contact.AndroidContact -> {
val thumbNailUri = contact.thumbnailUri
if (thumbNailUri != null)
@ -724,45 +683,32 @@ private fun ContactsContent(
TextAvatar(contact.name(), contact.color)
}
}
when (contact) {
is Contact.BaresipContact -> {
Text(text = annotatedName,
Text(
text = annotatedName,
fontSize = 20.sp,
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,
modifier = Modifier
.weight(1f)
.padding(start = 10.dp)
.combinedClickable(
onClick = {
navController.navigate("contact/${contact.name()}/old")
},
onClick = { navController.navigate("contact/${contact.name()}/old") },
onLongClick = {
title.value = confirmationText
message.value = String.format(
contactDeleteQuestion,
contact.name()
)
message.value = String.format(contactDeleteQuestion, contact.name())
firstButtonText.value = cancelText
onFirstClicked.value = { }
lastButtonText.value = deleteText
onLastClicked.value = {
val id = contact.id
val avatarFile = File(
BaresipService.filesPath,
"$id.png"
)
if (avatarFile.exists()) {
val avatarFile = File(BaresipService.filesPath, "$id.png")
if (avatarFile.exists())
try {
avatarFile.delete()
} catch (e: IOException) {
Log.e(
TAG,
"Could not delete file $id.png: ${e.message}"
)
Log.e(TAG, "Could not delete file $id.png: ${e.message}")
}
}
Contact.removeBaresipContact(contact)
}
showDialog.value = true
@ -770,7 +716,6 @@ private fun ContactsContent(
)
)
}
is Contact.AndroidContact -> {
Text(text = annotatedName,
fontSize = 20.sp,
@ -779,15 +724,10 @@ private fun ContactsContent(
.weight(1f)
.padding(start = 10.dp, top = 4.dp, bottom = 4.dp)
.combinedClickable(
onClick = {
navController.navigate("contact/${contact.name()}/old")
},
onClick = { navController.navigate("contact/${contact.name()}/old") },
onLongClick = {
title.value = confirmationText
message.value = String.format(
contactDeleteQuestion,
contact.name()
)
message.value = String.format(contactDeleteQuestion, contact.name())
firstButtonText.value = cancelText
onFirstClicked.value = { }
lastButtonText.value = deleteText

View File

@ -41,7 +41,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.height
@ -50,7 +49,7 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -242,12 +241,10 @@ private fun MainScreen(
BaresipService.isMainVisible = true
viewModel.updateSpeakerPhoneStatus(BaresipService.speakerPhone)
viewModel.updateCalls(Call.calls().toList())
if (Call.inCall())
(ctx as? Activity)?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
else
(ctx as? Activity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
(Call.call("incoming") ?: Call.calls().lastOrNull())?.let {
spinToAor(viewModel, it.ua.account.aor, it)
} ?: run {
@ -453,7 +450,8 @@ private fun MainScreen(
showPasswordsDialog.value = true
}
)
} else {
}
else {
showPasswordsDialog.value = false
showPasswordsDialog.value = true
}
@ -471,27 +469,19 @@ private fun MainScreen(
Charsets.UTF_8
).lines().toMutableList()
showPasswordsDialog.value = true
} else {
}
else
// Baresip is started for the first time
onRequestPermissions()
}
}
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.imePadding(),
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
viewModel = viewModel,
navController = navController,
@ -541,11 +531,7 @@ private fun TopAppBar(
TopAppBar(
title = {
Text(
text = stringResource(R.string.baresip),
fontSize = 22.sp,
fontWeight = FontWeight.Bold
)
Text(text = stringResource(R.string.baresip), fontSize = 22.sp, fontWeight = FontWeight.Bold)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
@ -736,10 +722,7 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
val buttonSize = 48.dp
Row(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(bottom = 16.dp),
modifier = Modifier.fillMaxWidth().navigationBarsPadding().padding(bottom = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
@ -757,7 +740,8 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
intent.putExtra("uap", ua.uap)
intent.putExtra("peer", acc.vmUri)
handleIntent(ctx, viewModel, intent, "call")
} else {
}
else {
dialogTitle.value = ctx.getString(R.string.voicemail_messages)
dialogMessage.value = acc.vmMessages(ctx)
firstText.value = ctx.getString(R.string.cancel)
@ -774,23 +758,22 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
}
}
},
modifier = Modifier
.weight(1f)
.size(buttonSize)
modifier = Modifier.weight(1f).size(buttonSize)
) {
Icon(
imageVector = Icons.Filled.Voicemail,
contentDescription = null,
Modifier.size(buttonSize),
tint = if (hasNewVoicemail) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
tint = if (hasNewVoicemail)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.secondary
)
}
IconButton(
onClick = { navController.navigate("contacts") },
modifier = Modifier
.weight(1f)
.size(buttonSize)
modifier = Modifier.weight(1f).size(buttonSize)
) {
Icon(
imageVector = Icons.Filled.Person,
@ -803,13 +786,11 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
IconButton(
enabled = aor.isNotEmpty(),
onClick = {
if (isMobile) {
if (!Utils.isDefaultSmsApp(ctx)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
return@IconButton
}
if (isMobile && !Utils.isDefaultSmsApp(ctx)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
showAlert.value = true
return@IconButton
}
navController.navigate("chats/$aor")
},
@ -828,12 +809,8 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
IconButton(
enabled = aor.isNotEmpty(),
onClick = {
navController.navigate("calls/$aor")
},
modifier = Modifier
.weight(1f)
.size(buttonSize)
onClick = { navController.navigate("calls/$aor") },
modifier = Modifier.weight(1f).size(buttonSize)
) {
Icon(
imageVector = Icons.Filled.History,
@ -845,9 +822,7 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
IconButton(
onClick = { viewModel.toggleDialpadVisibility() },
modifier = Modifier
.weight(1f)
.size(buttonSize),
modifier = Modifier.weight(1f).size(buttonSize),
enabled = dialpadButtonEnabled.value
) {
Icon(
@ -985,7 +960,8 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
showCall(ctx, viewModel, ua)
}
}
} else if (offset > swipeThreshold) {
}
else if (offset > swipeThreshold) {
if (uas.value.isNotEmpty()) {
val curPos = UserAgent.findAorIndex(viewModel.selectedAor.value)
val newPos = when (curPos) {
@ -1046,26 +1022,18 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
val selected = viewModel.selectedAor.value
if (uas.value.isEmpty()) {
if (selected != "") viewModel.updateSelectedAor("")
} else {
if (selected == "" || UserAgent.ofAor(selected) == null) {
viewModel.updateSelectedAor(uas.value.first().account.aor)
}
}
else if (selected == "" || UserAgent.ofAor(selected) == null)
viewModel.updateSelectedAor(uas.value.first().account.aor)
val ua = UserAgent.ofAor(viewModel.selectedAor.value)
if (ua != null) {
if (ua != null)
showCall(ctx, viewModel, ua, viewModel.focusedCall.value)
}
}
if (selected == "") {
OutlinedButton(
onClick = {
navController.navigate("accounts")
},
modifier = Modifier
.padding(horizontal = 4.dp)
.height(50.dp)
.fillMaxWidth(),
onClick = { navController.navigate("accounts") },
modifier = Modifier.padding(horizontal = 4.dp).height(50.dp).fillMaxWidth(),
colors = ButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
@ -1080,18 +1048,14 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
}
else
OutlinedButton(
onClick = {
expanded = !expanded
},
onClick = { expanded = !expanded },
enabled = true,
modifier = Modifier
.padding(horizontal = 4.dp)
.height(50.dp)
.pointerInput(Unit) {
detectTapGestures(
onPress = {
expanded = true
},
onPress = { expanded = true },
onLongPress = {
val ua = UserAgent.ofAor(selected)
if (ua != null) {
@ -1100,11 +1064,9 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
if (Api.account_regint(acc.accp) > 0) {
Api.account_set_regint(acc.accp, 0)
Api.ua_unregister(ua.uap)
} else {
Api.account_set_regint(
acc.accp,
acc.configuredRegInt
)
}
else {
Api.account_set_regint(acc.accp, acc.configuredRegInt)
Api.ua_register(ua.uap)
}
acc.regint = Api.account_regint(acc.accp)
@ -1153,7 +1115,8 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
if (Api.account_regint(acc.accp) > 0) {
Api.account_set_regint(acc.accp, 0)
Api.ua_unregister(ua.uap)
} else {
}
else {
Api.account_set_regint(
acc.accp,
acc.configuredRegInt
@ -1186,11 +1149,9 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
expanded = false
spinToAor(viewModel, acc.aor)
},
text = { Text(
text = acc.text(),
fontSize = 17.sp,
fontWeight = FontWeight.Bold
) },
text = {
Text(text = acc.text(), fontSize = 17.sp, fontWeight = FontWeight.Bold)
},
leadingIcon = {
Icon(
imageVector = ImageVector.vectorResource(uasStatus.value[acc.aor]!!),
@ -1199,9 +1160,8 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
)
}
)
if (index < uas.value.size - 1) {
if (index < uas.value.size - 1)
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
}
}
}
}
@ -1224,21 +1184,16 @@ private fun CallUriRow(
val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp, bottom = 8.dp),
modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally) {
OutlinedTextField(
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
singleLine = true,
onValueChange = {
if (isDialer) {
if (isDialer)
if (it != dialerState.callUri.value) {
dialerState.callUri.value = it
dialerState.redialUri = ""
@ -1258,7 +1213,6 @@ private fun CallUriRow(
}
dialerState.showSuggestions.value = it.length > 1
}
}
},
trailingIcon = {
if (isDialer && dialerState.callUriEnabled.value && dialerState.callUri.value.isNotEmpty())
@ -1322,10 +1276,7 @@ private fun CallUriRow(
.fillMaxWidth()
.padding(start = 4.dp, end = 4.dp, top = 2.dp, bottom = 2.dp),
label = {
Text(
text = call.callUriLabel2.value,
fontSize = 18.sp
)
Text(text = call.callUriLabel2.value, fontSize = 18.sp)
},
textStyle = TextStyle(fontSize = 18.sp)
)
@ -1343,9 +1294,7 @@ private fun CallUriRow(
) {
if (isDialer && dialerState.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)
modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)
) {
LazyColumn(
modifier = Modifier
@ -1403,13 +1352,11 @@ private fun CallUriRow(
alertMessage.value = ctx.getString(R.string.call_not_secure)
showAlert.value = true
}
R.color.colorTrafficYellow -> {
alertTitle.value = ctx.getString(R.string.alert)
alertMessage.value = ctx.getString(R.string.peer_not_verified)
showAlert.value = true
}
R.color.colorTrafficGreen -> {
dialogTitle.value = ctx.getString(R.string.info)
dialogMessage.value = ctx.getString(R.string.call_is_secure)
@ -1419,10 +1366,7 @@ private fun CallUriRow(
lastText.value = ctx.getString(R.string.unverify)
onLastClicked.value = {
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0)
Log.e(
TAG,
"Command 'zrtp_unverify ${call.zid}' failed"
)
Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed")
else
call.securityIconTint.value = R.color.colorTrafficYellow
}
@ -1446,10 +1390,7 @@ private fun CallUriRow(
}
@Composable
private fun CallTimer(
initialDurationSeconds: Long,
modifier: Modifier = Modifier
) {
private fun CallTimer(initialDurationSeconds: Long, modifier: Modifier = Modifier) {
val startTime = remember(initialDurationSeconds) {
SystemClock.elapsedRealtime() - (initialDurationSeconds * 1000L)
}
@ -1489,11 +1430,12 @@ private fun CallRow(
Row( modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = if (isDialer || call?.showCancelButton?.value == true ||
call?.showAnswerRejectButtons?.value == true)
Arrangement.Center
else
Arrangement.SpaceBetween
horizontalArrangement =
if (isDialer || call?.showCancelButton?.value == true ||
call?.showAnswerRejectButtons?.value == true)
Arrangement.Center
else
Arrangement.SpaceBetween
) {
if (isDialer) {
if (dialerState.showCallButton.value)
@ -1686,14 +1628,14 @@ private fun CallRow(
transferUri = it
if (it.length > 1) {
val normalizedInput = Utils.unaccent(it)
filteredSuggestions =
suggestions.filter { suggestion ->
filteredSuggestions = suggestions
.filter { suggestion ->
Utils.unaccent(suggestion)
.contains(normalizedInput, ignoreCase = true)
}
.map { suggestion ->
Utils.buildAnnotatedStringWithHighlight(suggestion, it)
}
.map { suggestion ->
Utils.buildAnnotatedStringWithHighlight(suggestion, it)
}
}
call.showSuggestions.value = transferUri.length > 1
}
@ -1714,12 +1656,7 @@ private fun CallRow(
},
modifier = Modifier
.fillMaxWidth()
.padding(
start = 4.dp,
end = 4.dp,
top = 12.dp,
bottom = 2.dp
)
.padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp)
.focusRequester(focusRequester),
label = { Text(stringResource(R.string.transfer_destination)) },
textStyle = TextStyle(fontSize = 18.sp),
@ -1740,16 +1677,11 @@ private fun CallRow(
.animateContentSize()
) {
if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
Box(modifier = Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)) {
Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.verticalScrollbar(
state = lazyListState,
width = 6.dp,
),
.verticalScrollbar(state = lazyListState, width = 6.dp),
horizontalAlignment = Alignment.Start,
state = lazyListState,
) {
@ -1761,10 +1693,8 @@ private fun CallRow(
modifier = Modifier
.fillMaxWidth()
.clickable {
transferUri =
suggestion.toString()
call.showSuggestions.value =
false
transferUri = suggestion.toString()
call.showSuggestions.value = false
}
.padding(12.dp)
) {
@ -1793,9 +1723,7 @@ private fun CallRow(
)
Switch(
checked = blindChecked.value,
onCheckedChange = {
blindChecked.value = true
}
onCheckedChange = { blindChecked.value = true }
)
}
Spacer(modifier = Modifier.width(8.dp))
@ -1807,9 +1735,7 @@ private fun CallRow(
)
Switch(
checked = !blindChecked.value,
onCheckedChange = {
blindChecked.value = false
}
onCheckedChange = { blindChecked.value = false }
)
}
}
@ -1902,14 +1828,9 @@ private fun CallRow(
}
call.dtmfText.value = newText
},
modifier = Modifier
.width(80.dp)
.focusRequester(focusRequester),
modifier = Modifier.width(80.dp).focusRequester(focusRequester),
enabled = call.dtmfEnabled.value,
textStyle = TextStyle(
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurface
),
textStyle = TextStyle(fontSize = 16.sp, color = MaterialTheme.colorScheme.onSurface),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
singleLine = true,
@ -1923,17 +1844,9 @@ private fun CallRow(
enabled = call.dtmfEnabled.value,
interactionSource = interactionSource,
label = {
Text(
stringResource(R.string.dtmf),
style = TextStyle(fontSize = 12.sp)
)
Text(stringResource(R.string.dtmf), style = TextStyle(fontSize = 12.sp))
},
contentPadding = PaddingValues(
start = 4.dp,
end = 4.dp,
top = 8.dp,
bottom = 8.dp
),
contentPadding = PaddingValues(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 8.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
@ -1979,7 +1892,8 @@ private fun CallRow(
"${ctx.getString(R.string.lost)}: ${parts[3]}\n" +
String.format(ctx.getString(R.string.jitter), parts[4])
showAlert.value = true
} else {
}
else {
alertTitle.value = ctx.getString(R.string.call_info)
alertMessage.value = ctx.getString(R.string.call_info_not_available)
showAlert.value = true
@ -1995,12 +1909,9 @@ private fun CallRow(
}
if (call.showAnswerRejectButtons.value) {
IconButton(
modifier = Modifier.size(48.dp),
onClick = {
answer(ctx, call)
},
onClick = { answer(ctx, call) },
) {
Icon(
imageVector = Icons.Filled.Call,
@ -2009,14 +1920,10 @@ private fun CallRow(
contentDescription = null,
)
}
Spacer(Modifier.width(48.dp))
IconButton(
modifier = Modifier.size(48.dp),
onClick = {
reject(call)
},
onClick = { reject(call) },
) {
Icon(
imageVector = Icons.Filled.CallEnd,
@ -2039,10 +1946,7 @@ private fun OnHoldNotice() {
modifier = Modifier.padding(16.dp),
shape = RoundedCornerShape(20)
) {
Text(
text = stringResource(R.string.call_is_on_hold),
fontSize = 18.sp
)
Text(text = stringResource(R.string.call_is_on_hold), fontSize = 18.sp)
}
}
}
@ -2252,8 +2156,7 @@ private fun transfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, uriText:
}
private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCall: Call? = null) {
if (ua == null)
return
if (ua == null) return
val call = showCall ?: ua.currentCall()
val aor = ua.account.aor
val callp = call?.callp ?: 0L
@ -2278,7 +2181,8 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
BaresipService.isMicMuted = false
viewModel.updateMicIcon(Icons.Filled.Mic)
}
} else {
}
else {
viewModel.dialerState.callUri.value = ""
pullToRefreshEnabled.value = false
call.callUriEnabled.value = false
@ -2323,9 +2227,8 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
call.callUriLabel2.value = ctx.getString(R.string.diverted_by_dots)
call.callUri2.value = Utils.friendlyUri(ctx, uri, ua.account)
}
else {
else
call.callUri2.value = ""
}
call.showCallButton.value = false
call.showCancelButton.value = false
call.showHangupButton.value = false
@ -2388,13 +2291,10 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
Log.d(TAG, "Handling service event 'started' with URI '$uriString'")
if (uriString != "")
callAction(ctx, viewModel, uriString.toUri(), "dial")
else {
if (viewModel.selectedAor.value == "" && uas.value.isNotEmpty())
viewModel.updateSelectedAor(uas.value.first().account.aor)
}
if (Preferences(ctx).displayTheme != AppCompatDelegate.getDefaultNightMode()) {
else if (viewModel.selectedAor.value == "" && uas.value.isNotEmpty())
viewModel.updateSelectedAor(uas.value.first().account.aor)
if (Preferences(ctx).displayTheme != AppCompatDelegate.getDefaultNightMode())
AppCompatDelegate.setDefaultNightMode(Preferences(ctx).displayTheme)
}
handleNextEvent()
return
}
@ -2430,8 +2330,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
val aor = ua.account.aor
when (ev[0]) {
"call rejected" -> {
}
"call rejected" -> {}
"call outgoing" -> {
val callp = params[1] as Long
if (!BaresipService.isMainVisible)
@ -2467,9 +2366,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
secondText.value = ctx.getString(R.string.no)
onSecondClicked.value = { }
lastText.value = ctx.getString(R.string.yes)
onLastClicked.value = {
redirect(ctx, viewModel, ua, redirectUri)
}
onLastClicked.value = { redirect(ctx, viewModel, ua, redirectUri) }
showDialog.value = true
}
}
@ -2483,8 +2380,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
call.dtmfText.value = ""
}
}
"call update" -> {
}
"call update" -> {}
"call verify" -> {
val callp = params[1] as Long
val call = Call.ofCallp(callp)
@ -2508,9 +2404,9 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
call.security = if (Api.cmd_exec("zrtp_verify ${ev[2]}") != 0) {
Log.e(TAG, "Command 'zrtp_verify ${ev[2]}' failed")
R.color.colorTrafficYellow
} else {
R.color.colorTrafficGreen
}
else
R.color.colorTrafficGreen
call.zid = ev[2]
if (aor == viewModel.selectedAor.value)
call.securityIconTint.value = call.security
@ -2524,9 +2420,8 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
handleNextEvent("Call $callp that is verified is not found")
return
}
if (aor == viewModel.selectedAor.value) {
if (aor == viewModel.selectedAor.value)
call.securityIconTint.value = call.security
}
}
"call transfer", "transfer show" -> {
if (!BaresipService.isMainVisible)
@ -2594,14 +2489,13 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
}
"mwi notify" -> {
val lines = ev[1].split("\n")
for (line in lines) {
for (line in lines)
if (line.startsWith("Voice-Message:")) {
val counts = (line.split(" ")[1]).split("/")
acc.vmNew = counts[0].toInt()
acc.vmOld = counts[1].toInt()
break
}
}
}
else -> Log.e(TAG, "Unknown event '${ev[0]}'")
}
@ -2648,10 +2542,8 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
if (ev[0] == "call answer")
answer(ctx, call)
else
BaresipService.postServiceEvent(ServiceEvent(
"call incoming",
arrayListOf(call.ua.uap, callp),
System.nanoTime())
BaresipService.postServiceEvent(
ServiceEvent("call incoming", arrayListOf(call.ua.uap, callp), System.nanoTime())
)
}
"call missed" -> {
@ -2676,10 +2568,8 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
ev[1]
else
intent.getStringExtra("uri")!!
BaresipService.postServiceEvent(ServiceEvent(
ev[0] + "," + uri,
arrayListOf(call.ua.uap, callp),
System.nanoTime())
BaresipService.postServiceEvent(
ServiceEvent(ev[0] + "," + uri, arrayListOf(call.ua.uap, callp), System.nanoTime())
)
}
"message", "message show", "message reply" -> {
@ -2690,10 +2580,8 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
return
}
spinToAor(viewModel, ua.account.aor)
BaresipService.postServiceEvent(ServiceEvent(
ev[0],
arrayListOf(uap, intent.getStringExtra("peer")!!),
System.nanoTime())
BaresipService.postServiceEvent(
ServiceEvent(ev[0], arrayListOf(uap, intent.getStringExtra("peer")!!), System.nanoTime())
)
}
}
@ -2710,8 +2598,7 @@ fun handleDialog(ctx: Context, title: String, message: String, action: () -> Uni
}
fun callAction(ctx: Context, viewModel: ViewModel, uri: Uri?, action: String) {
if (Call.inCall() || uas.value.isEmpty())
return
if (Call.inCall() || uas.value.isEmpty()) return
Log.d(TAG, "Action $action to $uri")
if (uri != null) {
var uriStr: String
@ -2772,11 +2659,13 @@ private fun acceptTransfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, ca
if (ua.account.aor != viewModel.selectedAor.value)
spinToAor(viewModel, ua.account.aor)
showCall(ctx, viewModel, ua)
} else {
}
else {
Log.w(TAG, "call_connect $newCallp failed")
call.notifySipfrag(500, "Call Error")
}
} else {
}
else {
Log.w(TAG, "callAlloc for ua ${ua.uap} call ${call.callp} transfer failed")
call.notifySipfrag(500, "Call Error")
}
@ -2794,8 +2683,10 @@ private fun backup(ctx: Context, password: String) {
if (!Utils.zip(files, zipFile)) {
Log.w(TAG, "Failed to write zip file '$zipFile'")
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = String.format(ctx.getString(R.string.backup_failed),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
alertMessage.value = String.format(
ctx.getString(R.string.backup_failed),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
)
showAlert.value = true
downloadsOutputUri = null
return
@ -2804,23 +2695,29 @@ private fun backup(ctx: Context, password: String) {
if (content == null) {
Log.w(TAG, "Failed to read zip file '$zipFile'")
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = String.format(ctx.getString(R.string.backup_failed),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
alertMessage.value = String.format(
ctx.getString(R.string.backup_failed),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
)
showAlert.value = true
downloadsOutputUri = null
return
}
if (!Utils.encryptToUri(ctx, downloadsOutputUri!!, content, password)) {
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = String.format(ctx.getString(R.string.backup_failed),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
alertMessage.value = String.format(
ctx.getString(R.string.backup_failed),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
)
showAlert.value = true
downloadsOutputUri = null
return
}
alertTitle.value = ctx.getString(R.string.info)
alertMessage.value = String.format(ctx.getString(R.string.backed_up),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
alertMessage.value = String.format(
ctx.getString(R.string.backed_up),
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
)
showAlert.value = true
Utils.deleteFile(File(zipFilePath))
downloadsInputUri = null
@ -2832,8 +2729,10 @@ private fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {
val zipData = Utils.decryptFromUri(ctx, downloadsInputUri!!, password)
if (zipData == null) {
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = String.format(ctx.getString(R.string.restore_failed),
Utils.fileNameOfUri(ctx, downloadsInputUri!!))
alertMessage.value = String.format(
ctx.getString(R.string.restore_failed),
Utils.fileNameOfUri(ctx, downloadsInputUri!!)
)
showAlert.value = true
downloadsInputUri = null
return
@ -2841,8 +2740,10 @@ private fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {
if (!Utils.putFileContents(zipFilePath, zipData)) {
Log.w(TAG, "Failed to write zip file '$zipFile'")
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = String.format(ctx.getString(R.string.restore_failed),
Utils.fileNameOfUri(ctx, downloadsInputUri!!))
alertMessage.value = String.format(
ctx.getString(R.string.restore_failed),
Utils.fileNameOfUri(ctx, downloadsInputUri!!)
)
showAlert.value = true
downloadsInputUri = null
return
@ -2871,9 +2772,7 @@ private fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {
dialogTitle.value = ctx.getString(R.string.info)
dialogMessage.value = ctx.getString(R.string.restored)
firstText.value = ctx.getString(R.string.cancel)
onFirstClicked.value = {
showDialog.value = false
}
onFirstClicked.value = { showDialog.value = false }
secondText.value = ""
lastText.value = ctx.getString(R.string.restart)
onLastClicked.value = {

View File

@ -25,14 +25,15 @@ 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.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
@ -155,25 +156,14 @@ private fun SettingsScreen(
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.imePadding(),
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Spacer(Modifier.statusBarsPadding())
TopAppBar(
title = {
Text(
text = stringResource(R.string.configuration),
fontWeight = FontWeight.Bold
)
Text(text = stringResource(R.string.configuration), fontWeight = FontWeight.Bold)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
@ -185,17 +175,14 @@ private fun SettingsScreen(
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
contentDescription = "Back",
)
}
},
windowInsets = WindowInsets(0, 0, 0, 0),
actions = {
IconButton(onClick = checkOnClick) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Check"
)
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
}
},
)
@ -209,13 +196,9 @@ private fun SettingsScreen(
title = stringResource(R.string.restart_request),
message = stringResource(R.string.config_restart),
firstButtonText = stringResource(R.string.cancel),
onFirstClicked = {
navController.navigateUp()
},
onFirstClicked = { navController.navigateUp() },
lastButtonText = stringResource(R.string.restart),
onLastClicked = {
onRestartApp()
},
onLastClicked = { onRestartApp() },
)
}
@ -304,9 +287,7 @@ private fun SettingsContent(
val startAutomaticallyHelp = stringResource(R.string.start_automatically_help)
val appearOnTopPermissionMessage = stringResource(R.string.appear_on_top_permission)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -355,9 +336,7 @@ private fun SettingsContent(
val addressFamilyTitle = stringResource(R.string.address_family)
val addressFamilyHelp = stringResource(R.string.address_family_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -372,9 +351,7 @@ private fun SettingsContent(
},
fontSize = 18.sp
)
val isDropDownExpanded = remember {
mutableStateOf(false)
}
val isDropDownExpanded = remember { mutableStateOf(false) }
val familyNames = listOf("--", "IPv4", "IPv6")
val familyValues = listOf("", "ipv4", "ipv6")
val itemPosition = remember { mutableIntStateOf(familyValues.indexOf(addressFamily)) }
@ -382,9 +359,7 @@ private fun SettingsContent(
Row(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
isDropDownExpanded.value = true
}
modifier = Modifier.clickable { isDropDownExpanded.value = true }
) {
Text(text = familyNames[itemPosition.intValue])
Icon(
@ -396,9 +371,8 @@ private fun SettingsContent(
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = {
isDropDownExpanded.value = false
}) {
onDismissRequest = { isDropDownExpanded.value = false }
) {
familyNames.forEachIndexed { index, family ->
DropdownMenuItem(
text = { Text(text = family) },
@ -420,9 +394,7 @@ private fun SettingsContent(
val listenAddressTitle = stringResource(R.string.listen_address)
val listenAddressHelp = stringResource(R.string.listen_address_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start,
) {
@ -430,9 +402,7 @@ private fun SettingsContent(
OutlinedTextField(
value = listenAddress,
placeholder = { Text(stringResource(R.string._0_0_0_0_5060)) },
onValueChange = {
viewModel.listenAddress.value = it
},
onValueChange = { viewModel.listenAddress.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
@ -453,18 +423,14 @@ private fun SettingsContent(
val transportProtocolsTitle = stringResource(R.string.transport_protocols)
val transportProtocolsHelp = stringResource(R.string.transport_protocols_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val transportProtocols by viewModel.transportProtocols.collectAsState()
OutlinedTextField(
value = transportProtocols,
onValueChange = {
viewModel.transportProtocols.value = it
},
onValueChange = { viewModel.transportProtocols.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
@ -484,18 +450,14 @@ private fun SettingsContent(
val dnsServersTitle = stringResource(R.string.dns_servers)
val dnsServersHelp = stringResource(R.string.dns_servers_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val dnsServers by viewModel.dnsServers.collectAsState()
OutlinedTextField(
value = dnsServers,
onValueChange = {
viewModel.dnsServers.value = it
},
onValueChange = { viewModel.dnsServers.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
@ -555,9 +517,7 @@ private fun SettingsContent(
val showAlertDialog = remember { mutableStateOf(false) }
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -635,9 +595,7 @@ private fun SettingsContent(
val verifyServerTitle = stringResource(R.string.verify_server)
val verifyServerHelp = stringResource(R.string.verify_server_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -697,9 +655,7 @@ private fun SettingsContent(
}
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -719,7 +675,7 @@ private fun SettingsContent(
checked = caFile,
onCheckedChange = {
viewModel.caFile.value = it
if (it) {
if (it)
if (VERSION.SDK_INT < 29) {
viewModel.caFile.value = false
val permission = Manifest.permission.READ_EXTERNAL_STORAGE
@ -754,7 +710,6 @@ private fun SettingsContent(
}
else
Utils.selectInputFile(caCertsRequest)
}
else {
Utils.deleteFile(File(BaresipService.filesPath + "/ca_certs.crt"))
restart = true
@ -769,9 +724,7 @@ private fun SettingsContent(
val userAgentTitle = stringResource(R.string.user_agent)
val userAgentHelp = stringResource(R.string.user_agent_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -799,9 +752,7 @@ private fun SettingsContent(
val uniqueContactUriTitle = stringResource(R.string.unique_contact_uri)
val uniqueContactUriHelp = stringResource(R.string.unique_contact_uri_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -826,17 +777,13 @@ private fun SettingsContent(
@Composable
fun AudioSettings(navController: NavController) {
Row(
Modifier
.fillMaxWidth()
.padding(top = 12.dp),
Modifier.fillMaxWidth().padding(top = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(
text = stringResource(R.string.audio_settings),
modifier = Modifier
.weight(1f)
.clickable { navController.navigate("audio") },
modifier = Modifier.weight(1f).clickable { navController.navigate("audio") },
fontSize = 18.sp,
fontWeight = FontWeight. Bold
)
@ -848,9 +795,7 @@ private fun SettingsContent(
val batteryOptimizationsTitle = stringResource(R.string.battery_optimizations)
val batteryOptimizationsHelp = stringResource(R.string.battery_optimizations_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -889,9 +834,7 @@ private fun SettingsContent(
val darkThemeTitle = stringResource(R.string.dark_theme)
val darkThemeHelp = stringResource(R.string.dark_theme_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -918,9 +861,7 @@ private fun SettingsContent(
val dynamicColorsTitle = stringResource(R.string.dynamic_colors)
val dynamicColorsHelp = stringResource(R.string.dynamic_colors_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -947,9 +888,7 @@ private fun SettingsContent(
val colorBlindTitle = stringResource(R.string.colorblind)
val colorBlindHelp = stringResource(R.string.colorblind_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -976,9 +915,7 @@ private fun SettingsContent(
val proximitySensingTitle = stringResource(R.string.proximity_sensing)
val proximitySensingHelp = stringResource(R.string.proximity_sensing_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -1007,9 +944,7 @@ private fun SettingsContent(
val defaultPhoneAppHelp = stringResource(R.string.default_phone_app_help)
val dialerRoleNotAvailableMessage = stringResource(R.string.dialer_role_not_available)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -1041,14 +976,13 @@ private fun SettingsContent(
val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
viewModel.defaultDialer.value = isHeld
if (isHeld) {
if (Utils.checkPermissions(ctx, arrayOf(Manifest.permission.READ_PHONE_NUMBERS))) {
if (Utils.checkPermissions(ctx, arrayOf(Manifest.permission.READ_PHONE_NUMBERS)))
BaresipService.instance?.addMobileUserAgent()
} else {
else
requestPermissionLauncher.launch(Manifest.permission.READ_PHONE_NUMBERS)
}
} else {
BaresipService.instance?.addMobileUserAgent()
}
else
BaresipService.instance?.addMobileUserAgent()
}
Switch(
checked = defaultDialer,
@ -1063,7 +997,8 @@ private fun SettingsContent(
else
if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER))
dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER))
} else {
}
else {
viewModel.defaultMessaging.value = false
try {
dialerRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
@ -1083,9 +1018,7 @@ private fun SettingsContent(
val defaultMessagingAppHelp = stringResource(R.string.default_messaging_app_help)
val messagingRoleNotAvailableMessage = stringResource(R.string.messaging_role_not_available)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -1122,13 +1055,13 @@ private fun SettingsContent(
else
if (!roleManager.isRoleHeld(RoleManager.ROLE_SMS))
messagingRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS))
} else {
}
else
try {
messagingRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "ActivityNotFound exception: ${e.message}")
}
}
}
)
}
@ -1139,9 +1072,7 @@ private fun SettingsContent(
val debugTitle = stringResource(R.string.debug)
val debugHelp = stringResource(R.string.debug_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -1168,9 +1099,7 @@ private fun SettingsContent(
val sipTraceTitle = stringResource(R.string.sip_trace)
val sipTraceHelp = stringResource(R.string.sip_trace_help)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -1199,9 +1128,7 @@ private fun SettingsContent(
val resetConfigAlert = stringResource(R.string.reset_config_alert)
val resetButtonText = stringResource(R.string.reset)
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
@ -1222,9 +1149,7 @@ private fun SettingsContent(
dialogTitle.value = confirmationText
dialogMessage.value = resetConfigAlert
firstButtonText.value = cancelButtonText
onFirstClicked.value = {
reset = false
}
onFirstClicked.value = { reset = false }
lastButtonText.value = resetButtonText
onLastClicked.value = {
Config.reset()
@ -1281,10 +1206,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
val noticeTitle = ctx.getString(R.string.notice)
if ((Config.variable("auto_start") == "yes") != viewModel.autoStart.value) {
Config.replaceVariable(
"auto_start",
if (viewModel.autoStart.value) "yes" else "no"
)
Config.replaceVariable("auto_start", if (viewModel.autoStart.value) "yes" else "no")
save = true
}
@ -1344,7 +1266,8 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
showAlert.value = true
return false
}
} else {
}
else {
Config.replaceVariable("dyn_dns", "yes")
Config.updateDnsServers(BaresipService.dnsServers)
}
@ -1353,8 +1276,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
}
if ((Config.variable("sip_verify_server") == "yes") != viewModel.verifyServer.value) {
Config.replaceVariable("sip_verify_server",
if (viewModel.verifyServer.value) "yes" else "no")
Config.replaceVariable("sip_verify_server", if (viewModel.verifyServer.value) "yes" else "no")
Api.config_verify_server_set(viewModel.verifyServer.value)
save = true
}
@ -1377,8 +1299,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
}
if ((Config.variable("sip_cuser_random") == "yes") != viewModel.uniqueContactUri.value) {
Config.replaceVariable("sip_cuser_random",
if (viewModel.uniqueContactUri.value) "yes" else "no")
Config.replaceVariable("sip_cuser_random", if (viewModel.uniqueContactUri.value) "yes" else "no")
save = true
restart = true
}
@ -1392,25 +1313,20 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
Preferences(ctx).displayTheme = newDisplayTheme
BaresipService.darkTheme.value = darkTheme
AppCompatDelegate.setDefaultNightMode(newDisplayTheme)
Config.replaceVariable("dark_theme",
if (darkTheme) "yes" else "no")
Config.replaceVariable("dark_theme", if (darkTheme) "yes" else "no")
save = true
}
val dynamicColors = viewModel.dynamicColors.value
if (BaresipService.dynamicColors.value != dynamicColors) {
BaresipService.dynamicColors.value = dynamicColors
Config.replaceVariable("dynamic_colors",
if (dynamicColors) "yes" else "no")
Config.replaceVariable("dynamic_colors", if (dynamicColors) "yes" else "no")
save = true
}
val colorblind = viewModel.colorblind.value
if ((Config.variable("colorblind") == "yes") != colorblind) {
Config.replaceVariable(
"colorblind",
if (colorblind) "yes" else "no"
)
Config.replaceVariable("colorblind", if (colorblind) "yes" else "no")
BaresipService.colorblind = colorblind
UserAgent.updateColorblindStatus()
val baresipService = Intent(ctx, BaresipService::class.java)
@ -1421,10 +1337,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
val proximitySensing = viewModel.proximitySensing.value
if ((Config.variable("proximity_sensing") == "yes") != proximitySensing) {
Config.replaceVariable(
"proximity_sensing",
if (proximitySensing) "yes" else "no"
)
Config.replaceVariable("proximity_sensing", if (proximitySensing) "yes" else "no")
BaresipService.proximitySensing = proximitySensing
save = true
}
@ -1457,9 +1370,9 @@ private fun addMissingPorts(addressList: String): String {
if (addressList == "") return ""
var result = ""
for (addr in addressList.split(","))
result = if (Utils.checkIpPort(addr)) {
result = if (Utils.checkIpPort(addr))
"$result,$addr"
} else {
else {
if (Utils.checkIpV4(addr))
"$result,$addr:53"
else