Improved edge-to-edge implementation on all screens
Large number of code formatting improvements
This commit is contained in:
@ -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(
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user