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.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -56,17 +56,11 @@ fun AboutScreen(onBack: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -137,9 +138,8 @@ private fun AccountScreen(
|
|||||||
|
|
||||||
DisposableEffect(lifecycleOwner) {
|
DisposableEffect(lifecycleOwner) {
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME)
|
||||||
resumeToggle = System.currentTimeMillis()
|
resumeToggle = System.currentTimeMillis()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
onDispose {
|
onDispose {
|
||||||
@@ -164,23 +164,14 @@ private fun AccountScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(text = acc.text(), fontWeight = FontWeight.Bold)
|
||||||
text = acc.text(),
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -199,10 +190,7 @@ private fun AccountScreen(
|
|||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = checkOnClick) {
|
IconButton(onClick = checkOnClick) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
|
||||||
imageVector = Icons.Filled.Check,
|
|
||||||
contentDescription = "Check"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -253,10 +241,7 @@ private fun AccountContent(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
textStyle = TextStyle(fontSize = 18.sp),
|
textStyle = TextStyle(fontSize = 18.sp),
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(text = label, fontWeight = FontWeight.Bold)
|
||||||
text = label,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = OutlinedTextFieldDefaults.colors(
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||||
@@ -271,9 +256,7 @@ private fun AccountContent(
|
|||||||
@Composable
|
@Composable
|
||||||
fun AoR(toggle: Long) {
|
fun AoR(toggle: Long) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 8.dp, end = 10.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
if (ua.account.isMobile && android.os.Build.VERSION.SDK_INT >= 29) {
|
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),
|
value = if (smsNumber != null) "tel:$smsNumber" else stringResource(R.string.not_available),
|
||||||
label = stringResource(R.string.tel_uri_messages)
|
label = stringResource(R.string.tel_uri_messages)
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val currentSubId = remember(toggle) {
|
val currentSubId = remember(toggle) {
|
||||||
if (voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
if (voiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||||
voiceSubId
|
voiceSubId
|
||||||
@@ -312,12 +296,12 @@ private fun AccountContent(
|
|||||||
label = stringResource(R.string.tel_uri)
|
label = stringResource(R.string.tel_uri)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
AoRField(
|
AoRField(
|
||||||
value = if (ua.account.isMobile) stringResource(R.string.not_available) else ua.account.luri,
|
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)
|
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.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
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.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -69,23 +69,14 @@ fun NavGraphBuilder.accountsScreenRoute(navController: NavController) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun AccountsScreen(navController: NavController) {
|
fun AccountsScreen(navController: NavController) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(text = stringResource(R.string.accounts), fontWeight = FontWeight.Bold)
|
||||||
text = stringResource(R.string.accounts),
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
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.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -101,23 +101,14 @@ private fun AudioScreen(
|
|||||||
checkOnClick: () -> Unit,
|
checkOnClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(text = stringResource(R.string.audio_settings), fontWeight = FontWeight.Bold)
|
||||||
text = stringResource(R.string.audio_settings),
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -136,10 +127,7 @@ private fun AudioScreen(
|
|||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = checkOnClick) {
|
IconButton(onClick = checkOnClick) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
|
||||||
imageVector = Icons.Filled.Check,
|
|
||||||
contentDescription = "Check"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -319,9 +307,8 @@ private fun ToneCountry() {
|
|||||||
isDropDownExpanded.value = false
|
isDropDownExpanded.value = false
|
||||||
}) {
|
}) {
|
||||||
countryNames.forEachIndexed { index, name ->
|
countryNames.forEachIndexed { index, name ->
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(
|
||||||
Text(text = name)
|
text = { Text(text = name) },
|
||||||
},
|
|
||||||
onClick = {
|
onClick = {
|
||||||
isDropDownExpanded.value = false
|
isDropDownExpanded.value = false
|
||||||
itemPosition.intValue = index
|
itemPosition.intValue = index
|
||||||
@@ -355,8 +342,7 @@ private fun SpeakerPhone() {
|
|||||||
var speakerPhone by remember { mutableStateOf(oldSpeakerPhone) }
|
var speakerPhone by remember { mutableStateOf(oldSpeakerPhone) }
|
||||||
Switch(
|
Switch(
|
||||||
checked = speakerPhone,
|
checked = speakerPhone,
|
||||||
onCheckedChange = {
|
onCheckedChange = { speakerPhone = it
|
||||||
speakerPhone = it
|
|
||||||
newSpeakerPhone = speakerPhone
|
newSpeakerPhone = speakerPhone
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -409,9 +395,8 @@ private fun CallVolume() {
|
|||||||
isDropDownExpanded.value = false
|
isDropDownExpanded.value = false
|
||||||
}) {
|
}) {
|
||||||
volNames.forEachIndexed { index, vol ->
|
volNames.forEachIndexed { index, vol ->
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(
|
||||||
Text(text = vol)
|
text = { Text(text = vol) },
|
||||||
},
|
|
||||||
onClick = {
|
onClick = {
|
||||||
isDropDownExpanded.value = false
|
isDropDownExpanded.value = false
|
||||||
itemPosition.intValue = index
|
itemPosition.intValue = index
|
||||||
@@ -440,8 +425,7 @@ private fun MicGain() {
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = micGain,
|
value = micGain,
|
||||||
placeholder = { Text(microphoneGainTitle) },
|
placeholder = { Text(microphoneGainTitle) },
|
||||||
onValueChange = {
|
onValueChange = { micGain = it
|
||||||
micGain = it
|
|
||||||
newMicGain = micGain
|
newMicGain = micGain
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -626,7 +610,8 @@ private fun checkOnClick(ctx: Context): Result {
|
|||||||
Api.module_unload("augain")
|
Api.module_unload("augain")
|
||||||
Config.removeVariableValue("module", "augain.so")
|
Config.removeVariableValue("module", "augain.so")
|
||||||
Config.replaceVariable("augain", "1.0")
|
Config.replaceVariable("augain", "1.0")
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (oldMicGain == "1.0") {
|
if (oldMicGain == "1.0") {
|
||||||
if (Api.module_load("augain") != 0) {
|
if (Api.module_load("augain") != 0) {
|
||||||
alertTitle.value = ctx.getString(R.string.error)
|
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.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -108,17 +108,11 @@ private fun BlockedScreen(navController: NavController, request: String, aor: St
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
|
||||||
) {
|
|
||||||
TopAppBar(navController, account, request, blocked)
|
TopAppBar(navController, account, request, blocked)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -187,10 +181,7 @@ private fun TopAppBar(
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = { expanded = !expanded }
|
onClick = { expanded = !expanded }
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
|
||||||
imageVector = Icons.Filled.Menu,
|
|
||||||
contentDescription = "Menu",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
CustomElements.DropdownMenu(
|
CustomElements.DropdownMenu(
|
||||||
expanded,
|
expanded,
|
||||||
@@ -237,9 +228,7 @@ private fun BlockedContent(
|
|||||||
private fun Account(account: Account) {
|
private fun Account(account: Account) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.account) + " " + account.text(),
|
text = stringResource(R.string.account) + " " + account.text(),
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 8.dp),
|
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
textAlign = TextAlign.Center
|
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),
|
modifier = Modifier.padding(start = 8.dp, end = 4.dp),
|
||||||
fontSize = 18.sp)
|
fontSize = 18.sp)
|
||||||
|
Text(
|
||||||
Text(text = peerUri.replace("sip:", ""),
|
text = peerUri.replace("sip:", ""),
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
val calendar = GregorianCalendar()
|
val calendar = GregorianCalendar()
|
||||||
calendar.timeInMillis = blocked.timeStamp
|
calendar.timeInMillis = blocked.timeStamp
|
||||||
Text(
|
Text(
|
||||||
@@ -324,9 +312,8 @@ private fun loadBlocked(request: String, aor: String): MutableList<Blocked> {
|
|||||||
val res = mutableListOf<Blocked>()
|
val res = mutableListOf<Blocked>()
|
||||||
for (i in BaresipService.blocked.indices.reversed()) {
|
for (i in BaresipService.blocked.indices.reversed()) {
|
||||||
val b = BaresipService.blocked[i]
|
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))
|
res.add(Blocked("", b.peerUri, "", b.timeStamp))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Loaded ${res.size} blocked $request requests")
|
Log.d(TAG, "Loaded ${res.size} blocked $request requests")
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
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.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -80,6 +80,7 @@ import java.io.File
|
|||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.GregorianCalendar
|
import java.util.GregorianCalendar
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
fun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewModel: ViewModel) {
|
fun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewModel: ViewModel) {
|
||||||
composable("call_details") { _ ->
|
composable("call_details") { _ ->
|
||||||
@@ -93,15 +94,11 @@ fun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewMod
|
|||||||
private fun CallDetailsScreen(navController: NavController, callRow: CallRow) {
|
private fun CallDetailsScreen(navController: NavController, callRow: CallRow) {
|
||||||
val detailsState = remember { callRow.details.toMutableStateList() }
|
val detailsState = remember { callRow.details.toMutableStateList() }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
@@ -252,22 +249,26 @@ private fun startTime(detail: Details, onDelete: (Details) -> Unit): String {
|
|||||||
val stopText = if (DateUtils.isToday(stopTime.timeInMillis)) {
|
val stopText = if (DateUtils.isToday(stopTime.timeInMillis)) {
|
||||||
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
||||||
stringResource(R.string.today) + " " + fmt.format(stopTime.time)
|
stringResource(R.string.today) + " " + fmt.format(stopTime.time)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val fmt = DateFormat.getDateTimeInstance()
|
val fmt = DateFormat.getDateTimeInstance()
|
||||||
fmt.format(stopTime.time)
|
fmt.format(stopTime.time)
|
||||||
}
|
}
|
||||||
if (startTime == GregorianCalendar(0, 0, 0)) {
|
if (startTime == GregorianCalendar(0, 0, 0)) {
|
||||||
startTimeText = stopText
|
startTimeText = stopText
|
||||||
durationText = "?"
|
durationText = "?"
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (startTime == null || detail.direction == CALL_DOWN_BLUE) {
|
if (startTime == null || detail.direction == CALL_DOWN_BLUE) {
|
||||||
startTimeText = stopText
|
startTimeText = stopText
|
||||||
durationText = ""
|
durationText = ""
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val startText = if (DateUtils.isToday(startTime.timeInMillis)) {
|
val startText = if (DateUtils.isToday(startTime.timeInMillis)) {
|
||||||
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
||||||
stringResource(R.string.today) + " " + fmt.format(startTime.time)
|
stringResource(R.string.today) + " " + fmt.format(startTime.time)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val fmt = DateFormat.getDateTimeInstance()
|
val fmt = DateFormat.getDateTimeInstance()
|
||||||
fmt.format(startTime.time)
|
fmt.format(startTime.time)
|
||||||
}
|
}
|
||||||
@@ -294,9 +295,7 @@ private fun startTime(detail: Details, onDelete: (Details) -> Unit): String {
|
|||||||
text = startTimeText,
|
text = startTimeText,
|
||||||
modifier = Modifier.combinedClickable(
|
modifier = Modifier.combinedClickable(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {
|
onLongClick = { showDialog.value = true }
|
||||||
showDialog.value = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -313,7 +312,7 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
val mediaPlayer = remember { MediaPlayer() }
|
val mediaPlayer = remember { MediaPlayer() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
// 1. Setup the File Saver Launcher
|
// 1. Set up the File Saver Launcher
|
||||||
val saveLauncher = rememberLauncherForActivityResult(
|
val saveLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.CreateDocument("audio/x-wav")
|
contract = ActivityResultContracts.CreateDocument("audio/x-wav")
|
||||||
) { uri ->
|
) { uri ->
|
||||||
@@ -335,12 +334,12 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Toast.makeText(ctx, "Source file not found", Toast.LENGTH_SHORT)
|
Toast.makeText(ctx, "Source file not found", Toast.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to save file: $e")
|
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()
|
val hasRecording = detail.recording.isNotEmpty() && detail.recording[0].isNotEmpty()
|
||||||
if (hasRecording) {
|
if (hasRecording)
|
||||||
Text(
|
Text(
|
||||||
text = durationText,
|
text = durationText,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 12.dp)
|
.padding(end = 12.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = {
|
onLongClick = { showDownloadDialog.value = true },
|
||||||
showDownloadDialog.value = true
|
|
||||||
},
|
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!mediaPlayer.isPlaying) {
|
if (!mediaPlayer.isPlaying) {
|
||||||
mediaPlayer.reset()
|
mediaPlayer.reset()
|
||||||
@@ -425,15 +422,15 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
)
|
)
|
||||||
finalFile = fallbackMerged
|
finalFile = fallbackMerged
|
||||||
// Update state to match reality by creating a new List
|
// Update state to match reality by creating a new List
|
||||||
detail.recording =
|
detail.recording = listOf(fallbackMerged.absolutePath, "")
|
||||||
listOf(fallbackMerged.absolutePath, "")
|
}
|
||||||
} else {
|
else
|
||||||
Log.e(
|
Log.e(
|
||||||
TAG,
|
TAG,
|
||||||
"Raw file missing and fallback not found: ${currentRecording[0]}"
|
"Raw file missing and fallback not found: ${currentRecording[0]}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
// Normal Raw processing
|
// Normal Raw processing
|
||||||
val mergedFileName =
|
val mergedFileName =
|
||||||
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||||
@@ -442,20 +439,16 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
mergedFileName
|
mergedFileName
|
||||||
)
|
)
|
||||||
if (mergedFile.exists()) {
|
if (mergedFile.exists()) {
|
||||||
Log.d(
|
Log.d(TAG, "Using already merged file: ${mergedFile.name}")
|
||||||
TAG,
|
|
||||||
"Using already merged file: ${mergedFile.name}"
|
|
||||||
)
|
|
||||||
finalFile = mergedFile
|
finalFile = mergedFile
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile))
|
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile))
|
||||||
finalFile = mergedFile
|
finalFile = mergedFile
|
||||||
}
|
|
||||||
|
|
||||||
// If merge successful, update state and delete originals
|
// If merge successful, update state and delete originals
|
||||||
if (finalFile != null && finalFile.exists()) {
|
if (finalFile != null && finalFile.exists()) {
|
||||||
detail.recording =
|
detail.recording = listOf(finalFile.absolutePath, "")
|
||||||
listOf(finalFile.absolutePath, "")
|
|
||||||
try {
|
try {
|
||||||
if (fileIn.exists()) fileIn.delete()
|
if (fileIn.exists()) fileIn.delete()
|
||||||
if (fileOut.exists()) fileOut.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])
|
else {
|
||||||
if (f.exists()) {
|
if (currentIsMerged) {
|
||||||
Log.d(TAG, "Using already merged file: ${currentRecording[0]}")
|
val f = File(currentRecording[0])
|
||||||
finalFile = f
|
if (f.exists()) {
|
||||||
} else {
|
Log.d(
|
||||||
Log.e(
|
TAG,
|
||||||
TAG,
|
"Using already merged file: ${currentRecording[0]}"
|
||||||
"Merged file record exists but file is missing: ${currentRecording[0]}"
|
)
|
||||||
)
|
finalFile = f
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Merged file record exists but file is missing: ${currentRecording[0]}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (finalFile != null && finalFile.exists()) {
|
if (finalFile != null && finalFile.exists())
|
||||||
try {
|
try {
|
||||||
mediaPlayer.apply {
|
mediaPlayer.apply {
|
||||||
setAudioAttributes(
|
setAudioAttributes(
|
||||||
@@ -511,15 +510,15 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
} else {
|
else
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
ctx, "Failed to process audio file",
|
ctx, "Failed to process audio file",
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
mediaPlayer.stop()
|
mediaPlayer.stop()
|
||||||
mediaPlayer.reset()
|
mediaPlayer.reset()
|
||||||
showPlaybackDialog.value = false
|
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))
|
Text(text = durationText, modifier = Modifier.padding(end = 12.dp))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -549,24 +547,18 @@ private fun PlaybackDialog(
|
|||||||
if (mediaPlayer.isPlaying) {
|
if (mediaPlayer.isPlaying) {
|
||||||
val duration = mediaPlayer.duration
|
val duration = mediaPlayer.duration
|
||||||
totalDurationText = DateUtils.formatElapsedTime(duration / 1000L)
|
totalDurationText = DateUtils.formatElapsedTime(duration / 1000L)
|
||||||
|
|
||||||
while (mediaPlayer.isPlaying) {
|
while (mediaPlayer.isPlaying) {
|
||||||
val current = mediaPlayer.currentPosition
|
val current = mediaPlayer.currentPosition
|
||||||
currentProgress = if (duration > 0) current.toFloat() / duration.toFloat() else 0f
|
currentProgress = if (duration > 0) current.toFloat() / duration.toFloat() else 0f
|
||||||
currentPositionText = DateUtils.formatElapsedTime(current / 1000L)
|
currentPositionText = DateUtils.formatElapsedTime(current / 1000L)
|
||||||
delay(100) // Poll every 100ms
|
delay(100.milliseconds) // Poll every 100ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = { onStop() }, // If user clicks outside, stop playback
|
||||||
// If user clicks outside, stop playback
|
title = { Text(text = stringResource(R.string.playing_recording)) },
|
||||||
onStop()
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
Text(text = stringResource(R.string.playing_recording))
|
|
||||||
},
|
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -587,9 +579,7 @@ private fun PlaybackDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(onClick = { onStop() }) {
|
||||||
onClick = { onStop() }
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.stop))
|
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.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -121,15 +121,11 @@ private fun CallsScreen(navController: NavController, viewModel: ViewModel, aor:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
|
||||||
) {
|
|
||||||
TopAppBar(navController, ua, callHistory)
|
TopAppBar(navController, ua, callHistory)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -203,10 +199,7 @@ private fun TopAppBar(navController: NavController, ua: UserAgent, callHistory:
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = { expanded = !expanded }
|
onClick = { expanded = !expanded }
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
|
||||||
imageVector = Icons.Filled.Menu,
|
|
||||||
contentDescription = "Menu",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
CustomElements.DropdownMenu(
|
CustomElements.DropdownMenu(
|
||||||
expanded,
|
expanded,
|
||||||
@@ -353,15 +346,12 @@ private fun Calls(
|
|||||||
)
|
)
|
||||||
secondButtonText.value = ctx.getString(R.string.call)
|
secondButtonText.value = ctx.getString(R.string.call)
|
||||||
secondAction.value = {
|
secondAction.value = {
|
||||||
if (ua.account.isMobile && ua.status != circleGreen.getValue(
|
if (ua.account.isMobile && ua.status != circleGreen.getValue(colorblind)) {
|
||||||
colorblind
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
alertMessage.value =
|
alertMessage.value = ctx.getString(R.string.airplane_mode)
|
||||||
ctx.getString(R.string.airplane_mode)
|
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
handleIntent(ctx, viewModel, intent, "call")
|
handleIntent(ctx, viewModel, intent, "call")
|
||||||
navController.navigate("main") {
|
navController.navigate("main") {
|
||||||
popUpTo("main")
|
popUpTo("main")
|
||||||
@@ -377,16 +367,20 @@ private fun Calls(
|
|||||||
alertMessage.value =
|
alertMessage.value =
|
||||||
ctx.getString(R.string.airplane_mode)
|
ctx.getString(R.string.airplane_mode)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
} else if (!Utils.isDefaultSmsApp(ctx)) {
|
}
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
else
|
||||||
alertMessage.value =
|
if (!Utils.isDefaultSmsApp(ctx)) {
|
||||||
ctx.getString(R.string.enable_default_messaging)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
showAlert.value = true
|
alertMessage.value =
|
||||||
} else {
|
ctx.getString(R.string.enable_default_messaging)
|
||||||
|
showAlert.value = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
handleIntent(ctx, viewModel, intent, "message")
|
||||||
navController.navigateUp()
|
navController.navigateUp()
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
handleIntent(ctx, viewModel, intent, "message")
|
||||||
navController.navigateUp()
|
navController.navigateUp()
|
||||||
}
|
}
|
||||||
@@ -400,13 +394,11 @@ private fun Calls(
|
|||||||
try {
|
try {
|
||||||
ctx.startActivity(emailIntent)
|
ctx.startActivity(emailIntent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(
|
Log.e(TAG, "Failed to start email activity: ${e.message}")
|
||||||
TAG,
|
|
||||||
"Failed to start email activity: ${e.message}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
lastButtonText.value = ""
|
lastButtonText.value = ""
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
@@ -426,13 +418,9 @@ private fun Calls(
|
|||||||
)
|
)
|
||||||
secondButtonText.value = ""
|
secondButtonText.value = ""
|
||||||
thirdButtonText.value = ctx.getString(R.string.copy_uri)
|
thirdButtonText.value = ctx.getString(R.string.copy_uri)
|
||||||
thirdAction.value = {
|
thirdAction.value = { Utils.copyToClipboard(ctx, peerUri) }
|
||||||
Utils.copyToClipboard(ctx, peerUri)
|
|
||||||
}
|
|
||||||
lastButtonText.value = ctx.getString(R.string.delete)
|
lastButtonText.value = ctx.getString(R.string.delete)
|
||||||
lastAction.value = {
|
lastAction.value = { removeFromHistory(callHistory, callRow) }
|
||||||
removeFromHistory(callHistory, callRow)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
message.value = String.format(
|
message.value = String.format(
|
||||||
@@ -446,13 +434,10 @@ private fun Calls(
|
|||||||
navController.navigate("contact/$uri/new")
|
navController.navigate("contact/$uri/new")
|
||||||
}
|
}
|
||||||
thirdButtonText.value = ctx.getString(R.string.copy_uri)
|
thirdButtonText.value = ctx.getString(R.string.copy_uri)
|
||||||
thirdAction.value = {
|
thirdAction.value = { Utils.copyToClipboard(ctx, peerUri)
|
||||||
Utils.copyToClipboard(ctx, peerUri)
|
|
||||||
}
|
}
|
||||||
lastButtonText.value = ctx.getString(R.string.delete)
|
lastButtonText.value = ctx.getString(R.string.delete)
|
||||||
lastAction.value = {
|
lastAction.value = { removeFromHistory(callHistory, callRow) }
|
||||||
removeFromHistory(callHistory, callRow)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
@@ -509,7 +494,8 @@ private fun Calls(
|
|||||||
}
|
}
|
||||||
if (count > 3)
|
if (count > 3)
|
||||||
Text("...", color = MaterialTheme.colorScheme.onBackground)
|
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),
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
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.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -136,14 +135,10 @@ private fun ChatScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
onDispose {
|
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var areMessagesLoaded by remember(aor, peerUri) {
|
var areMessagesLoaded by remember(aor, peerUri) { mutableStateOf(false) }
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val reloadMessages = {
|
val reloadMessages = {
|
||||||
Log.d(TAG, "Reloading messages for $aor peer $peerUri")
|
Log.d(TAG, "Reloading messages for $aor peer $peerUri")
|
||||||
@@ -175,16 +170,11 @@ private fun ChatScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(modifier = Modifier
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
.fillMaxWidth()
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
|
||||||
) {
|
|
||||||
TopAppBar(ctx, navController, viewModel, account, peerUri)
|
TopAppBar(ctx, navController, viewModel, account, peerUri)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -199,7 +189,8 @@ private fun ChatScreen(
|
|||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
if (areMessagesLoaded)
|
if (areMessagesLoaded)
|
||||||
ChatContent(ctx,
|
ChatContent(
|
||||||
|
ctx,
|
||||||
navController,
|
navController,
|
||||||
contentPadding,
|
contentPadding,
|
||||||
account, peerUri,
|
account, peerUri,
|
||||||
@@ -223,8 +214,7 @@ private fun TopAppBar(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = format(ctx.getString(R.string.chat_with),
|
text = format(ctx.getString(R.string.chat_with), Utils.friendlyUri(ctx, peerUri, account)),
|
||||||
Utils.friendlyUri(ctx, peerUri, account)),
|
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
@@ -237,10 +227,7 @@ private fun TopAppBar(
|
|||||||
),
|
),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = { backAction(navController, account, peerUri) }) {
|
IconButton(onClick = { backAction(navController, account, peerUri) }) {
|
||||||
Icon(
|
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
||||||
contentDescription = "Back",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
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")
|
Log.w(TAG, "Call button onClick listener did not find UA for $aor")
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Outlined.Email, contentDescription = "Email")
|
||||||
imageVector = Icons.Outlined.Email,
|
|
||||||
contentDescription = "Email",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -310,9 +294,7 @@ private fun ChatContent(
|
|||||||
onMessageDeleted: () -> Unit
|
onMessageDeleted: () -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(contentPadding),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(contentPadding),
|
|
||||||
verticalArrangement = Arrangement.Bottom
|
verticalArrangement = Arrangement.Bottom
|
||||||
) {
|
) {
|
||||||
Account(ctx, account)
|
Account(ctx, account)
|
||||||
@@ -325,9 +307,7 @@ private fun ChatContent(
|
|||||||
private fun Account(ctx: Context, account: Account) {
|
private fun Account(ctx: Context, account: Account) {
|
||||||
Text(
|
Text(
|
||||||
text = ctx.getString(R.string.account) + " " + account.text(),
|
text = ctx.getString(R.string.account) + " " + account.text(),
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 8.dp, bottom = 8.dp),
|
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -368,11 +348,8 @@ private fun Messages(
|
|||||||
|
|
||||||
LaunchedEffect(messages) {
|
LaunchedEffect(messages) {
|
||||||
// Scroll to the bottom when new messages are added
|
// Scroll to the bottom when new messages are added
|
||||||
if (messages.isNotEmpty()) {
|
if (messages.isNotEmpty())
|
||||||
coroutineScope.launch {
|
coroutineScope.launch { lazyListState.scrollToItem(0) }
|
||||||
lazyListState.scrollToItem(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -420,9 +397,7 @@ private fun Messages(
|
|||||||
peerUri
|
peerUri
|
||||||
)
|
)
|
||||||
secondButtonText.value = ctx.getString(R.string.add_contact)
|
secondButtonText.value = ctx.getString(R.string.add_contact)
|
||||||
secondAction.value = {
|
secondAction.value = { navController.navigate("contact/$peerUri/new") }
|
||||||
navController.navigate("contact/$peerUri/new")
|
|
||||||
}
|
|
||||||
lastButtonText.value = ctx.getString(R.string.delete)
|
lastButtonText.value = ctx.getString(R.string.delete)
|
||||||
lastAction.value = {
|
lastAction.value = {
|
||||||
message.delete()
|
message.delete()
|
||||||
@@ -588,14 +563,16 @@ private fun NewMessage(
|
|||||||
if (ua.status != circleGreen.getValue(colorblind)) {
|
if (ua.status != circleGreen.getValue(colorblind)) {
|
||||||
dialogMessage.value = ctx.getString(R.string.airplane_mode)
|
dialogMessage.value = ctx.getString(R.string.airplane_mode)
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val destination = Utils.uriUserPart(peerUri)
|
val destination = Utils.uriUserPart(peerUri)
|
||||||
if (Utils.sendSms(ctx, destination, msgText)) {
|
if (Utils.sendSms(ctx, destination, msgText)) {
|
||||||
msg.direction = MESSAGE_UP
|
msg.direction = MESSAGE_UP
|
||||||
newMessage.value = TextFieldValue("")
|
newMessage.value = TextFieldValue("")
|
||||||
viewModel.updateAorPeerMessage(aor, peerUri, "")
|
viewModel.updateAorPeerMessage(aor, peerUri, "")
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
ctx, "${ctx.getString(R.string.message_failed)}!",
|
ctx, "${ctx.getString(R.string.message_failed)}!",
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
@@ -604,38 +581,36 @@ private fun NewMessage(
|
|||||||
msg.responseReason = ctx.getString(R.string.message_failed)
|
msg.responseReason = ctx.getString(R.string.message_failed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (Utils.isTelUri(peerUri))
|
else {
|
||||||
|
if (Utils.isTelUri(peerUri)) {
|
||||||
if (ua.account.telProvider == "") {
|
if (ua.account.telProvider == "") {
|
||||||
dialogMessage.value = String.format(
|
dialogMessage.value = String.format(
|
||||||
ctx.getString(R.string.no_telephony_provider),
|
ctx.getString(R.string.no_telephony_provider),
|
||||||
Utils.plainAor(aor)
|
Utils.plainAor(aor)
|
||||||
)
|
)
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
} else {
|
|
||||||
msgUri = Utils.telToSip(peerUri, ua.account)
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
msgUri = Utils.telToSip(peerUri, ua.account)
|
||||||
|
}
|
||||||
else
|
else
|
||||||
msgUri = peerUri
|
msgUri = peerUri
|
||||||
if (msgUri != "")
|
if (msgUri != "") {
|
||||||
if (Api.message_send(
|
if (Api.message_send(ua.uap, msgUri, msgText, time.toString()) != 0) {
|
||||||
ua.uap,
|
|
||||||
msgUri,
|
|
||||||
msgText,
|
|
||||||
time.toString()
|
|
||||||
) != 0
|
|
||||||
) {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
ctx, "${ctx.getString(R.string.message_failed)}!",
|
ctx, "${ctx.getString(R.string.message_failed)}!",
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
msg.direction = MESSAGE_UP_FAIL
|
msg.direction = MESSAGE_UP_FAIL
|
||||||
msg.responseReason = ctx.getString(R.string.message_failed)
|
msg.responseReason = ctx.getString(R.string.message_failed)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
newMessage.value = TextFieldValue("")
|
newMessage.value = TextFieldValue("")
|
||||||
viewModel.updateAorPeerMessage(aor, peerUri, "")
|
viewModel.updateAorPeerMessage(aor, peerUri, "")
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -127,23 +126,15 @@ private fun ChatsScreen(navController: NavController, aor: String) {
|
|||||||
refreshTrigger++
|
refreshTrigger++
|
||||||
}
|
}
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
onDispose {
|
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
|
||||||
) {
|
|
||||||
TopAppBar(navController, account, uaMessages)
|
TopAppBar(navController, account, uaMessages)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -188,10 +179,7 @@ private fun TopAppBar(
|
|||||||
),
|
),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = { navController.navigateUp() }) {
|
IconButton(onClick = { navController.navigateUp() }) {
|
||||||
Icon(
|
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
@@ -199,10 +187,7 @@ private fun TopAppBar(
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = { menuExpanded = !menuExpanded }
|
onClick = { menuExpanded = !menuExpanded }
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
|
||||||
imageVector = Icons.Filled.Menu,
|
|
||||||
contentDescription = "Menu",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
DropdownMenu (
|
DropdownMenu (
|
||||||
expanded = menuExpanded,
|
expanded = menuExpanded,
|
||||||
@@ -218,9 +203,8 @@ private fun TopAppBar(
|
|||||||
}
|
}
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
blocked -> {
|
blocked ->
|
||||||
navController.navigate("blocked/message/${account.aor}")
|
navController.navigate("blocked/message/${account.aor}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -237,9 +221,7 @@ private fun ChatsContent(
|
|||||||
uaMessages: MutableState<List<Message>>
|
uaMessages: MutableState<List<Message>>
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(contentPadding),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(contentPadding),
|
|
||||||
verticalArrangement = Arrangement.Top
|
verticalArrangement = Arrangement.Top
|
||||||
) {
|
) {
|
||||||
Account(account)
|
Account(account)
|
||||||
@@ -308,7 +290,6 @@ private fun Chats(
|
|||||||
else
|
else
|
||||||
CustomElements.TextAvatar(contact.name, contact.color)
|
CustomElements.TextAvatar(contact.name, contact.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Contact.AndroidContact -> {
|
is Contact.AndroidContact -> {
|
||||||
val thumbNailUri = contact.thumbnailUri
|
val thumbNailUri = contact.thumbnailUri
|
||||||
if (thumbNailUri != null)
|
if (thumbNailUri != null)
|
||||||
@@ -321,7 +302,6 @@ private fun Chats(
|
|||||||
else
|
else
|
||||||
CustomElements.TextAvatar(contact.name, contact.color)
|
CustomElements.TextAvatar(contact.name, contact.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
null -> {
|
null -> {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.AccountCircle,
|
imageVector = Icons.Filled.AccountCircle,
|
||||||
@@ -332,20 +312,16 @@ private fun Chats(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(6.dp))
|
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)
|
RoundedCornerShape(50.dp, 20.dp, 20.dp, 10.dp)
|
||||||
} else {
|
else
|
||||||
RoundedCornerShape(20.dp, 10.dp, 50.dp, 20.dp)
|
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)
|
BorderStroke(width = 2.dp, color = MaterialTheme.colorScheme.error)
|
||||||
} else {
|
else
|
||||||
null
|
null
|
||||||
}
|
|
||||||
CustomElements.Button(
|
CustomElements.Button(
|
||||||
onClick = {
|
onClick = { navController.navigate("chat/${aor}/${message.peerUri}") },
|
||||||
navController.navigate("chat/${aor}/${message.peerUri}")
|
|
||||||
},
|
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
val peerName = Utils.friendlyUri(ctx, message.peerUri, account, includeLabel = false)
|
val peerName = Utils.friendlyUri(ctx, message.peerUri, account, includeLabel = false)
|
||||||
val peerNameWithLabel = Utils.friendlyUri(ctx, message.peerUri, account)
|
val peerNameWithLabel = Utils.friendlyUri(ctx, message.peerUri, account)
|
||||||
@@ -361,20 +337,17 @@ private fun Chats(
|
|||||||
lastAction.value = {
|
lastAction.value = {
|
||||||
deleteMessages(uaMessages, account, message.peerUri)
|
deleteMessages(uaMessages, account, message.peerUri)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
dialogMessage.value =
|
dialogMessage.value =
|
||||||
String.format(
|
String.format(
|
||||||
ctx.getString(R.string.long_chat_question),
|
ctx.getString(R.string.long_chat_question),
|
||||||
peerName
|
peerName
|
||||||
)
|
)
|
||||||
secondButtonText.value = ctx.getString(R.string.delete)
|
secondButtonText.value = ctx.getString(R.string.delete)
|
||||||
secondAction.value = {
|
secondAction.value = { deleteMessages(uaMessages, account, message.peerUri) }
|
||||||
deleteMessages(uaMessages, account, message.peerUri)
|
|
||||||
}
|
|
||||||
lastButtonText.value = ctx.getString(R.string.add_contact)
|
lastButtonText.value = ctx.getString(R.string.add_contact)
|
||||||
lastAction.value = {
|
lastAction.value = { navController.navigate("contact/${message.peerUri}/new") }
|
||||||
navController.navigate("contact/${message.peerUri}/new")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
},
|
},
|
||||||
@@ -497,10 +470,7 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
var newPeer by remember { mutableStateOf("") }
|
var newPeer by remember { mutableStateOf("") }
|
||||||
Column(
|
Column(horizontalAlignment = Alignment.Start, modifier = Modifier.weight(1f)) {
|
||||||
horizontalAlignment = Alignment.Start,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
if (showSuggestions && filteredSuggestions.isNotEmpty()) {
|
if (showSuggestions && filteredSuggestions.isNotEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -511,8 +481,7 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
|
|||||||
)
|
)
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)
|
Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)) {
|
||||||
) {
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -553,9 +522,9 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
|
|||||||
onValueChange = {
|
onValueChange = {
|
||||||
newPeer = it
|
newPeer = it
|
||||||
showSuggestions = newPeer.length > 1
|
showSuggestions = newPeer.length > 1
|
||||||
filteredSuggestions = if (it.isEmpty()) {
|
filteredSuggestions = if (it.isEmpty())
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
else {
|
||||||
val normalizedInput = Utils.unaccent(it)
|
val normalizedInput = Utils.unaccent(it)
|
||||||
suggestions
|
suggestions
|
||||||
.filter { suggestion ->
|
.filter { suggestion ->
|
||||||
@@ -609,7 +578,8 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc
|
|||||||
} else {
|
} else {
|
||||||
makeChat(ctx, navController, account, peerText)
|
makeChat(ctx, navController, account, peerText)
|
||||||
}
|
}
|
||||||
} else if (uris.size == 1)
|
}
|
||||||
|
else if (uris.size == 1)
|
||||||
makeChat(ctx, navController, account, uris[0].uri)
|
makeChat(ctx, navController, account, uris[0].uri)
|
||||||
else {
|
else {
|
||||||
items.value = uris.map { it.label.ifEmpty { it.uri.substringAfter(":") } }
|
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) {
|
private fun deleteMessages(uaMessages: MutableState<List<Message>>, account: Account, peerUri: String) {
|
||||||
val updatedMessages = BaresipService.messages.toMutableList()
|
val updatedMessages = BaresipService.messages.toMutableList()
|
||||||
updatedMessages.removeAll {
|
updatedMessages.removeAll { it.aor == account.aor && (peerUri == "" || it.peerUri == peerUri) }
|
||||||
it.aor == account.aor && (peerUri == "" || it.peerUri == peerUri)
|
|
||||||
}
|
|
||||||
BaresipService.messages = updatedMessages.toList()
|
BaresipService.messages = updatedMessages.toList()
|
||||||
Message.save()
|
Message.save()
|
||||||
uaMessages.value = loadMessages(account)
|
uaMessages.value = loadMessages(account)
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
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.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
@@ -98,16 +99,14 @@ private fun CodecsScreen(
|
|||||||
val codecs = remember { mutableStateListOf<Codec>() }
|
val codecs = remember { mutableStateListOf<Codec>() }
|
||||||
|
|
||||||
LaunchedEffect(acc, media) {
|
LaunchedEffect(acc, media) {
|
||||||
val allCodecs: List<String> = if (media == "audio") {
|
val allCodecs: List<String> = if (media == "audio")
|
||||||
Api.audio_codecs().split(",")
|
Api.audio_codecs().split(",")
|
||||||
} else {
|
else
|
||||||
Api.video_codecs().split(",").distinct()
|
Api.video_codecs().split(",").distinct()
|
||||||
}
|
val accCodecs: List<String> = if (media == "audio")
|
||||||
val accCodecs: List<String> = if (media == "audio") {
|
|
||||||
acc.audioCodec
|
acc.audioCodec
|
||||||
} else {
|
else
|
||||||
acc.videoCodec
|
acc.videoCodec
|
||||||
}
|
|
||||||
val currentCodecs = mutableListOf<Codec>()
|
val currentCodecs = mutableListOf<Codec>()
|
||||||
for (codec in accCodecs)
|
for (codec in accCodecs)
|
||||||
currentCodecs.add(Codec(codec, mutableStateOf(true)))
|
currentCodecs.add(Codec(codec, mutableStateOf(true)))
|
||||||
@@ -119,17 +118,11 @@ private fun CodecsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
@@ -146,7 +139,6 @@ private fun CodecsScreen(
|
|||||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
|
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
),
|
),
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -155,24 +147,18 @@ private fun CodecsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = {
|
IconButton(
|
||||||
checkOnClick(codecs)
|
onClick = { checkOnClick(codecs) }) {
|
||||||
}) {
|
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Check,
|
|
||||||
contentDescription = "Check"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
CodecsContent(
|
CodecsContent(contentPadding, codecs)
|
||||||
contentPadding,
|
|
||||||
codecs
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -204,9 +190,7 @@ private fun Codecs(codecs: SnapshotStateList<Codec>) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(end = 4.dp).verticalScrollbar(state = draggableState.listState),
|
||||||
.padding(end = 4.dp)
|
|
||||||
.verticalScrollbar(state = draggableState.listState),
|
|
||||||
state = draggableState.listState,
|
state = draggableState.listState,
|
||||||
contentPadding = PaddingValues(start = 12.dp, end = 12.dp),
|
contentPadding = PaddingValues(start = 12.dp, end = 12.dp),
|
||||||
) {
|
) {
|
||||||
@@ -236,7 +220,8 @@ private fun Codecs(codecs: SnapshotStateList<Codec>) {
|
|||||||
val index = codecs.indexOf(item)
|
val index = codecs.indexOf(item)
|
||||||
codecs.removeAt(index)
|
codecs.removeAt(index)
|
||||||
codecs.add(0, item)
|
codecs.add(0, item)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val index = codecs.indexOf(item)
|
val index = codecs.indexOf(item)
|
||||||
codecs.removeAt(index)
|
codecs.removeAt(index)
|
||||||
codecs.add(item)
|
codecs.add(item)
|
||||||
@@ -247,10 +232,7 @@ private fun Codecs(codecs: SnapshotStateList<Codec>) {
|
|||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.dragHandle(
|
modifier = Modifier.dragHandle(state = draggableState, key = item.name),
|
||||||
state = draggableState,
|
|
||||||
key = item.name
|
|
||||||
),
|
|
||||||
imageVector =Icons.Filled.Reorder,
|
imageVector =Icons.Filled.Reorder,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,14 +26,15 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
@@ -170,7 +171,8 @@ private fun ContactScreen(
|
|||||||
isLoading = false,
|
isLoading = false,
|
||||||
isBaresipContact = true
|
isBaresipContact = true
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val baresipContact = Contact.baresipContact(uriOrNameArg)
|
val baresipContact = Contact.baresipContact(uriOrNameArg)
|
||||||
val androidContact = Contact.androidContact(uriOrNameArg)
|
val androidContact = Contact.androidContact(uriOrNameArg)
|
||||||
val contact = baresipContact ?: androidContact
|
val contact = baresipContact ?: androidContact
|
||||||
@@ -226,7 +228,8 @@ private fun ContactScreen(
|
|||||||
null,
|
null,
|
||||||
tmpAvatarFile = null
|
tmpAvatarFile = null
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
screenState.tmpAvatarFile?.let { tempFile ->
|
screenState.tmpAvatarFile?.let { tempFile ->
|
||||||
if (tempFile.exists()) {
|
if (tempFile.exists()) {
|
||||||
Log.d(TAG, "Back pressed, deleting temp avatar: ${tempFile.name}")
|
Log.d(TAG, "Back pressed, deleting temp avatar: ${tempFile.name}")
|
||||||
@@ -238,16 +241,13 @@ private fun ContactScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val onCheck: () -> Unit = {
|
val onCheck: () -> Unit = {
|
||||||
val result = checkOnClick(
|
val result = checkOnClick(ctx = ctx, currentState = screenState, uriOrNameArg = uriOrNameArg)
|
||||||
ctx = ctx,
|
|
||||||
currentState = screenState,
|
|
||||||
uriOrNameArg = uriOrNameArg,
|
|
||||||
)
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (screenState.new) {
|
if (screenState.new) {
|
||||||
navController.previousBackStackEntry?.savedStateHandle?.set("scrollToContact", screenState.name)
|
navController.previousBackStackEntry?.savedStateHandle?.set("scrollToContact", screenState.name)
|
||||||
navController.navigateUp()
|
navController.navigateUp()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// Update UI state with saved values
|
// Update UI state with saved values
|
||||||
val contact = Contact.baresipContact(screenState.name)!!
|
val contact = Contact.baresipContact(screenState.name)!!
|
||||||
val avatarFile = File(BaresipService.filesPath, "${contact.id}.png")
|
val avatarFile = File(BaresipService.filesPath, "${contact.id}.png")
|
||||||
@@ -270,26 +270,16 @@ private fun ContactScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val onEdit: () -> Unit = {
|
val onEdit: () -> Unit = { screenState = screenState.copy(isEditing = true) }
|
||||||
screenState = screenState.copy(isEditing = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = true) {
|
BackHandler(enabled = true) { onBack() }
|
||||||
onBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = title,
|
title = title,
|
||||||
isEditing = screenState.isEditing,
|
isEditing = screenState.isEditing,
|
||||||
@@ -301,7 +291,7 @@ private fun ContactScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
if (!screenState.isLoading) {
|
if (!screenState.isLoading)
|
||||||
ContactContent(
|
ContactContent(
|
||||||
ctx = ctx,
|
ctx = ctx,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -310,11 +300,10 @@ private fun ContactScreen(
|
|||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
onStateChange = { newState -> screenState = newState }
|
onStateChange = { newState -> screenState = newState }
|
||||||
)
|
)
|
||||||
} else {
|
else
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -340,28 +329,21 @@ private fun TopAppBar(
|
|||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(
|
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
||||||
contentDescription = "Back",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
if (isEditing) {
|
if (isEditing)
|
||||||
IconButton(onClick = onCheck) {
|
IconButton(onClick = onCheck) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Check, contentDescription = "Save")
|
||||||
imageVector = Icons.Filled.Check,
|
|
||||||
contentDescription = "Save"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else if (isBaresipContact) {
|
else if (isBaresipContact)
|
||||||
IconButton(onClick = onEdit) {
|
IconButton(onClick = onEdit) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Edit,
|
imageVector = Icons.Filled.Edit,
|
||||||
contentDescription = "Edit"
|
contentDescription = "Edit"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -510,9 +492,9 @@ private fun AvatarSection(
|
|||||||
val newImageId = System.currentTimeMillis()
|
val newImageId = System.currentTimeMillis()
|
||||||
val tempNewImageFile = File(BaresipService.filesPath, "${newImageId}.png")
|
val tempNewImageFile = File(BaresipService.filesPath, "${newImageId}.png")
|
||||||
|
|
||||||
if (saveBitmap(rotatedBitmap, tempNewImageFile)) {
|
if (saveBitmap(rotatedBitmap, tempNewImageFile))
|
||||||
onNewAvatarChosen(tempNewImageFile, newImageId)
|
onNewAvatarChosen(tempNewImageFile, newImageId)
|
||||||
} else {
|
else {
|
||||||
Log.e(TAG, "Failed to save processed avatar image")
|
Log.e(TAG, "Failed to save processed avatar image")
|
||||||
if (tempNewImageFile.exists()) Utils.deleteFile(tempNewImageFile)
|
if (tempNewImageFile.exists()) Utils.deleteFile(tempNewImageFile)
|
||||||
}
|
}
|
||||||
@@ -534,24 +516,18 @@ private fun AvatarSection(
|
|||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(if (currentAvatarUri == null) Color(color) else Color.Transparent)
|
.background(if (currentAvatarUri == null) Color(color) else Color.Transparent)
|
||||||
.let { modifier ->
|
.let { modifier ->
|
||||||
if (isEditing) {
|
if (isEditing)
|
||||||
modifier.combinedClickable(
|
modifier.combinedClickable(
|
||||||
onClick = { avatarImagePicker.launch("image/*") },
|
onClick = { avatarImagePicker.launch("image/*") },
|
||||||
onLongClick = { onAvatarColorChange(Utils.randomColor()) }
|
onLongClick = { onAvatarColorChange(Utils.randomColor()) }
|
||||||
)
|
)
|
||||||
} else {
|
else
|
||||||
modifier
|
modifier
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (currentAvatarUri == null) {
|
if (currentAvatarUri == null) {
|
||||||
Box(
|
Box(modifier = Modifier.size(avatarSize.dp), contentAlignment = Alignment.Center) {
|
||||||
modifier = Modifier.size(avatarSize.dp),
|
Canvas(modifier = Modifier.fillMaxSize()) { drawCircle(SolidColor(Color(color))) }
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
|
||||||
drawCircle(SolidColor(Color(color)))
|
|
||||||
}
|
|
||||||
val text = if (name.isNotBlank()) name.take(1).uppercase() else "?"
|
val text = if (name.isNotBlank()) name.take(1).uppercase() else "?"
|
||||||
Text(text, fontSize = 72.sp, color = Color.White)
|
Text(text, fontSize = 72.sp, color = Color.White)
|
||||||
}
|
}
|
||||||
@@ -575,9 +551,7 @@ private fun ContactNameSection(name: String, isEditing: Boolean, new: Boolean, o
|
|||||||
value = name,
|
value = name,
|
||||||
placeholder = { Text(stringResource(R.string.contact_name)) },
|
placeholder = { Text(stringResource(R.string.contact_name)) },
|
||||||
onValueChange = onNameChange,
|
onValueChange = onNameChange,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
|
||||||
.fillMaxWidth()
|
|
||||||
.focusRequester(focusRequester),
|
|
||||||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
|
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),
|
||||||
label = { Text(stringResource(R.string.contact_name)) },
|
label = { Text(stringResource(R.string.contact_name)) },
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
@@ -588,7 +562,8 @@ private fun ContactNameSection(name: String, isEditing: Boolean, new: Boolean, o
|
|||||||
LaunchedEffect(new) {
|
LaunchedEffect(new) {
|
||||||
if (new) focusRequester.requestFocus()
|
if (new) focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),
|
Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -601,7 +576,6 @@ private fun ContactNameSection(name: String, isEditing: Boolean, new: Boolean, o
|
|||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -615,16 +589,10 @@ private fun UrisSection(
|
|||||||
) {
|
) {
|
||||||
val selectedAor by viewModel.selectedAor.collectAsState()
|
val selectedAor by viewModel.selectedAor.collectAsState()
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing)
|
||||||
Column(
|
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
uris.forEachIndexed { index, contactUri ->
|
uris.forEachIndexed { index, contactUri ->
|
||||||
Row(
|
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column (modifier = Modifier.weight(1f)) {
|
Column (modifier = Modifier.weight(1f)) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = contactUri.uri,
|
value = contactUri.uri,
|
||||||
@@ -656,7 +624,7 @@ private fun UrisSection(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (uris.size > 1) {
|
if (uris.size > 1)
|
||||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -674,7 +642,6 @@ private fun UrisSection(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -692,11 +659,8 @@ private fun UrisSection(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
else
|
||||||
Column(
|
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
uris.forEachIndexed { index, contactUri ->
|
uris.forEachIndexed { index, contactUri ->
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
@@ -735,11 +699,13 @@ private fun UrisSection(
|
|||||||
alertTitle.value = ctx.getString(R.string.notice)
|
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
|
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)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val intent = Intent(ctx, MainActivity::class.java)
|
val intent = Intent(ctx, MainActivity::class.java)
|
||||||
intent.putExtra("uap", ua.uap)
|
intent.putExtra("uap", ua.uap)
|
||||||
intent.putExtra("peer", uri)
|
intent.putExtra("peer", uri)
|
||||||
@@ -760,17 +726,20 @@ private fun UrisSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call Button
|
// Call Button
|
||||||
if (ua != null && (if (uri.startsWith("tel:"))
|
if (ua != null &&
|
||||||
|
if (uri.startsWith("tel:"))
|
||||||
ua.account.isMobile || ua.account.telProvider != ""
|
ua.account.isMobile || ua.account.telProvider != ""
|
||||||
else
|
else
|
||||||
!ua.account.isMobile))
|
!ua.account.isMobile
|
||||||
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
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)
|
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
|
showAlert.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
val intent = Intent(ctx, MainActivity::class.java)
|
val intent = Intent(ctx, MainActivity::class.java)
|
||||||
intent.putExtra("uap", ua.uap)
|
intent.putExtra("uap", ua.uap)
|
||||||
intent.putExtra("peer", uri)
|
intent.putExtra("peer", uri)
|
||||||
@@ -792,12 +761,11 @@ private fun UrisSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun EmailSection(ctx: Context, email: String, isEditing: Boolean, onEmailChange: (String) -> Unit) {
|
private fun EmailSection(ctx: Context, email: String, isEditing: Boolean, onEmailChange: (String) -> Unit) {
|
||||||
if (isEditing) {
|
if (isEditing)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = email,
|
value = email,
|
||||||
placeholder = { Text(stringResource(R.string.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)) },
|
label = { Text(stringResource(R.string.email)) },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
|
||||||
)
|
)
|
||||||
} else if (email.isNotEmpty()) {
|
else if (email.isNotEmpty())
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -846,7 +814,6 @@ private fun EmailSection(ctx: Context, email: String, isEditing: Boolean, onEmai
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -865,10 +832,7 @@ private fun FavoriteSection(ctx: Context, favorite: Boolean, onFavoriteChange: (
|
|||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Switch(
|
Switch(checked = favorite, onCheckedChange = onFavoriteChange)
|
||||||
checked = favorite,
|
|
||||||
onCheckedChange = onFavoriteChange
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,10 +852,7 @@ private fun AndroidSection(ctx: Context, android: Boolean, onAndroidChange: (Boo
|
|||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Switch(
|
Switch(checked = android, onCheckedChange = onAndroidChange)
|
||||||
checked = android,
|
|
||||||
onCheckedChange = onAndroidChange
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,7 +867,6 @@ private fun checkOnClick(
|
|||||||
if (u == "") continue
|
if (u == "") continue
|
||||||
if (!u.startsWith("sip:") && !u.startsWith("tel:"))
|
if (!u.startsWith("sip:") && !u.startsWith("tel:"))
|
||||||
u = if (Utils.isTelNumber(u)) "tel:$u" else "sip:$u"
|
u = if (Utils.isTelNumber(u)) "tel:$u" else "sip:$u"
|
||||||
|
|
||||||
if (!Utils.checkUri(u)) {
|
if (!Utils.checkUri(u)) {
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), u)
|
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()
|
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)) {
|
if (!Utils.checkName(newName)) {
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.invalid_contact), newName)
|
alertMessage.value = String.format(ctx.getString(R.string.invalid_contact), newName)
|
||||||
@@ -934,9 +897,8 @@ private fun checkOnClick(
|
|||||||
|
|
||||||
val alert: Boolean = if (currentState.new)
|
val alert: Boolean = if (currentState.new)
|
||||||
Contact.nameExists(newName, BaresipService.contacts, true)
|
Contact.nameExists(newName, BaresipService.contacts, true)
|
||||||
else {
|
else
|
||||||
(uriOrNameArg != newName) && Contact.nameExists(newName, BaresipService.contacts, false)
|
(uriOrNameArg != newName) && Contact.nameExists(newName, BaresipService.contacts, false)
|
||||||
}
|
|
||||||
if (alert) {
|
if (alert) {
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.contact_already_exists), newName)
|
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)
|
if (oldAvatar.exists()) Utils.deleteFile(oldAvatar)
|
||||||
idToUse = currentState.newId
|
idToUse = currentState.newId
|
||||||
}
|
}
|
||||||
} else if (currentState.avatarImageUri == null) {
|
}
|
||||||
|
else if (currentState.avatarImageUri == null) {
|
||||||
val avatarFile = File(BaresipService.filesPath, "$idToUse.png")
|
val avatarFile = File(BaresipService.filesPath, "$idToUse.png")
|
||||||
if (avatarFile.exists()) Utils.deleteFile(avatarFile)
|
if (avatarFile.exists()) Utils.deleteFile(avatarFile)
|
||||||
}
|
}
|
||||||
@@ -979,12 +942,12 @@ private fun checkOnClick(
|
|||||||
Log.e(TAG, "Update of Android favorite failed: ${e.message}")
|
Log.e(TAG, "Update of Android favorite failed: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
if (currentState.new)
|
if (currentState.new)
|
||||||
Contact.addBaresipContact(contact)
|
Contact.addBaresipContact(contact)
|
||||||
else
|
else
|
||||||
Contact.updateBaresipContact(currentState.id, contact)
|
Contact.updateBaresipContact(currentState.id, contact)
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,7 +973,15 @@ private fun rotateBitmap(bitmap: Bitmap, orientation: Int): Bitmap {
|
|||||||
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
|
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
|
||||||
else -> return bitmap
|
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()
|
bitmap.recycle()
|
||||||
return rotatedBitmap
|
return rotatedBitmap
|
||||||
}
|
}
|
||||||
@@ -1036,11 +1007,10 @@ private fun addOrUpdateAndroidContact(ctx: Context, contact: Contact.BaresipCont
|
|||||||
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
|
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
|
||||||
CommonDataKinds.StructuredName.DISPLAY_NAME + "='" + contact.name + "'"
|
CommonDataKinds.StructuredName.DISPLAY_NAME + "='" + contact.name + "'"
|
||||||
val c: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selection, null, null)
|
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)
|
updateAndroidContact(ctx, c.getLong(0), contact)
|
||||||
} else {
|
else
|
||||||
addAndroidContact(ctx, contact)
|
addAndroidContact(ctx, contact)
|
||||||
}
|
|
||||||
c?.close()
|
c?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1053,17 +1023,19 @@ private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Bo
|
|||||||
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
||||||
.withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
.withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||||
.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name).build())
|
.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name).build())
|
||||||
if (contact.email.isNotEmpty()) {
|
if (contact.email.isNotEmpty())
|
||||||
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
|
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
|
||||||
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
||||||
.withValue(Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
|
.withValue(Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
|
||||||
.withValue(CommonDataKinds.Email.ADDRESS, contact.email)
|
.withValue(CommonDataKinds.Email.ADDRESS, contact.email)
|
||||||
.withValue(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_HOME).build())
|
.withValue(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_HOME).build())
|
||||||
}
|
|
||||||
for (contactUri in contact.uris) {
|
for (contactUri in contact.uris) {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label
|
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)
|
val builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
|
||||||
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
||||||
.withValue(Data.MIMETYPE, mimeType)
|
.withValue(Data.MIMETYPE, mimeType)
|
||||||
@@ -1078,12 +1050,11 @@ private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Bo
|
|||||||
}
|
}
|
||||||
if (contact.avatarImage != null) {
|
if (contact.avatarImage != null) {
|
||||||
val photoData = bitmapToPNGByteArray(contact.avatarImage!!)
|
val photoData = bitmapToPNGByteArray(contact.avatarImage!!)
|
||||||
if (photoData != null) {
|
if (photoData != null)
|
||||||
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
|
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
|
||||||
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
|
||||||
.withValue(Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
|
.withValue(Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
|
||||||
.withValue(CommonDataKinds.Photo.PHOTO, photoData).build())
|
.withValue(CommonDataKinds.Photo.PHOTO, photoData).build())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
|
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) {
|
private fun updateAndroidContact(ctx: Context, rawContactId: Long, contact: Contact.BaresipContact) {
|
||||||
if (contact.email.isNotEmpty()) {
|
if (contact.email.isNotEmpty())
|
||||||
if (updateAndroidEmail(ctx, rawContactId, contact.email) == 0) addAndroidEmail(ctx, rawContactId, contact.email)
|
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)
|
for (contactUri in contact.uris)
|
||||||
if (updateAndroidPhoto(ctx, rawContactId, contact.avatarImage) == 0) if (contact.avatarImage != null) addAndroidPhoto(ctx, rawContactId, contact.avatarImage!!)
|
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) {
|
private fun addAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri) {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label
|
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)
|
val builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
|
||||||
.withValue(Data.RAW_CONTACT_ID, rawContactId)
|
.withValue(Data.RAW_CONTACT_ID, rawContactId)
|
||||||
.withValue(Data.MIMETYPE, mimeType)
|
.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 {
|
private fun updateAndroidUri(ctx: Context, rawContactId: Long, contactUri: Contact.ContactUri): Int {
|
||||||
val uri = contactUri.uri
|
val uri = contactUri.uri
|
||||||
val label = contactUri.label
|
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()
|
val contentValues = ContentValues()
|
||||||
contentValues.put(ContactsContract.Data.DATA1, uri.substringAfter(":"))
|
contentValues.put(ContactsContract.Data.DATA1, uri.substringAfter(":"))
|
||||||
if (mimeType == CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
|
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)
|
if (type == CommonDataKinds.Phone.TYPE_CUSTOM)
|
||||||
contentValues.put(CommonDataKinds.Phone.LABEL, label)
|
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 {
|
return try {
|
||||||
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
|
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
|
||||||
} catch (e: Exception) {
|
} 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 {
|
private fun updateAndroidEmail(ctx: Context, rawContactId: Long, email: String): Int {
|
||||||
val contentValues = ContentValues()
|
val contentValues = ContentValues()
|
||||||
contentValues.put(CommonDataKinds.Email.ADDRESS, email)
|
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 {
|
return try {
|
||||||
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
|
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
|
||||||
} catch (_: Exception) {
|
} 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 photoBytes = if (photoBits == null) null else bitmapToPNGByteArray(photoBits)
|
||||||
val contentValues = ContentValues()
|
val contentValues = ContentValues()
|
||||||
contentValues.put(CommonDataKinds.Photo.PHOTO, photoBytes)
|
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 {
|
return try {
|
||||||
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
|
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -128,7 +128,7 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
val vcfExportLauncher = rememberLauncherForActivityResult(
|
val vcfExportLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.CreateDocument("text/vcard")
|
contract = ActivityResultContracts.CreateDocument("text/vcard")
|
||||||
) { uri: android.net.Uri? ->
|
) { uri: android.net.Uri? ->
|
||||||
if (uri != null) {
|
if (uri != null)
|
||||||
try {
|
try {
|
||||||
ctx.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
ctx.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||||
val writer = outputStream.bufferedWriter()
|
val writer = outputStream.bufferedWriter()
|
||||||
@@ -155,7 +155,8 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
writer.write("TEL;X-${u.label}:${u.uri.substring(4)}\n")
|
writer.write("TEL;X-${u.label}:${u.uri.substring(4)}\n")
|
||||||
else
|
else
|
||||||
writer.write("TEL:${u.uri.substring(4)}\n")
|
writer.write("TEL:${u.uri.substring(4)}\n")
|
||||||
} else if (u.uri.startsWith("sip:")) {
|
}
|
||||||
|
else if (u.uri.startsWith("sip:")) {
|
||||||
if (u.label.isNotEmpty())
|
if (u.label.isNotEmpty())
|
||||||
writer.write("X-SIP;X-${u.label}:${u.uri.substring(4)}\n")
|
writer.write("X-SIP;X-${u.label}:${u.uri.substring(4)}\n")
|
||||||
else
|
else
|
||||||
@@ -171,25 +172,23 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
Log.e(TAG, "Failed to export VCF: ${e.message}")
|
Log.e(TAG, "Failed to export VCF: ${e.message}")
|
||||||
Toast.makeText(ctx, R.string.contact_export_failure, Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, R.string.contact_export_failure, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val vcfImportLauncher = rememberLauncherForActivityResult(
|
val vcfImportLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.OpenDocument()
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
) { uri: android.net.Uri? ->
|
) { uri: android.net.Uri? ->
|
||||||
if (uri != null) {
|
if (uri != null)
|
||||||
try {
|
try {
|
||||||
ctx.contentResolver.openInputStream(uri)?.use { inputStream ->
|
ctx.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
val reader = inputStream.bufferedReader()
|
val reader = inputStream.bufferedReader()
|
||||||
val lines = mutableListOf<String>()
|
val lines = mutableListOf<String>()
|
||||||
reader.forEachLine { line ->
|
reader.forEachLine { line ->
|
||||||
if (line.startsWith(" ") || line.startsWith("\t")) {
|
if (line.startsWith(" ") || line.startsWith("\t")) {
|
||||||
if (lines.isNotEmpty()) {
|
if (lines.isNotEmpty())
|
||||||
lines[lines.size - 1] = lines.last() + line.trim()
|
lines[lines.size - 1] = lines.last() + line.trim()
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lines.add(line)
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
lines.add(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = ""
|
var name = ""
|
||||||
@@ -199,7 +198,7 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
var contactNo = 0
|
var contactNo = 0
|
||||||
val newBaresipContacts = BaresipService.baresipContacts.value.toMutableList()
|
val newBaresipContacts = BaresipService.baresipContacts.value.toMutableList()
|
||||||
|
|
||||||
for (line in lines) {
|
for (line in lines)
|
||||||
when {
|
when {
|
||||||
line.startsWith("BEGIN:VCARD", ignoreCase = true) -> {
|
line.startsWith("BEGIN:VCARD", ignoreCase = true) -> {
|
||||||
name = ""
|
name = ""
|
||||||
@@ -241,7 +240,7 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
if (name.isNotEmpty() && (uris.isNotEmpty() || email.isNotEmpty())) {
|
if (name.isNotEmpty() && (uris.isNotEmpty() || email.isNotEmpty())) {
|
||||||
val existingContact = newBaresipContacts.find { it.name == name }
|
val existingContact = newBaresipContacts.find { it.name == name }
|
||||||
val contactId = existingContact?.id ?: (System.currentTimeMillis() + contactNo++)
|
val contactId = existingContact?.id ?: (System.currentTimeMillis() + contactNo++)
|
||||||
if (photoBase64.isNotEmpty()) {
|
if (photoBase64.isNotEmpty())
|
||||||
try {
|
try {
|
||||||
val decodedString = Base64.decode(photoBase64, Base64.DEFAULT)
|
val decodedString = Base64.decode(photoBase64, Base64.DEFAULT)
|
||||||
val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
|
val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
|
||||||
@@ -254,17 +253,14 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to decode photo for $name: ${e.message}")
|
Log.e(TAG, "Failed to decode photo for $name: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (existingContact != null) {
|
if (existingContact != null) {
|
||||||
for (u in uris) {
|
for (u in uris)
|
||||||
if (existingContact.uris.none { it.uri == u.uri }) {
|
if (existingContact.uris.none { it.uri == u.uri })
|
||||||
existingContact.uris.add(u)
|
existingContact.uris.add(u)
|
||||||
}
|
if (existingContact.email.isEmpty() && email.isNotEmpty())
|
||||||
}
|
|
||||||
if (existingContact.email.isEmpty() && email.isNotEmpty()) {
|
|
||||||
existingContact.email = email
|
existingContact.email = email
|
||||||
}
|
}
|
||||||
} else {
|
else
|
||||||
newBaresipContacts.add(
|
newBaresipContacts.add(
|
||||||
Contact.BaresipContact(
|
Contact.BaresipContact(
|
||||||
name,
|
name,
|
||||||
@@ -275,30 +271,19 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
BaresipService.baresipContacts.value = newBaresipContacts.toList()
|
BaresipService.baresipContacts.value = newBaresipContacts.toList()
|
||||||
Contact.saveBaresipContacts()
|
Contact.saveBaresipContacts()
|
||||||
Contact.restoreBaresipContacts()
|
Contact.restoreBaresipContacts()
|
||||||
Contact.contactsUpdate()
|
Contact.contactsUpdate()
|
||||||
Toast.makeText(
|
Toast.makeText(ctx, R.string.contact_import_success, Toast.LENGTH_SHORT).show()
|
||||||
ctx,
|
|
||||||
R.string.contact_import_success,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to import VCF: ${e.message}")
|
Log.e(TAG, "Failed to import VCF: ${e.message}")
|
||||||
Toast.makeText(
|
Toast.makeText(ctx, R.string.contact_import_failure, Toast.LENGTH_SHORT).show()
|
||||||
ctx,
|
|
||||||
R.string.contact_import_failure,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val contactNames = remember(BaresipService.contactsMode) {
|
val contactNames = remember(BaresipService.contactsMode) {
|
||||||
@@ -373,11 +358,9 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
contract = ActivityResultContracts.RequestMultiplePermissions()
|
contract = ActivityResultContracts.RequestMultiplePermissions()
|
||||||
) { permissions ->
|
) { permissions ->
|
||||||
if (permissions[Manifest.permission.READ_CONTACTS] == true &&
|
if (permissions[Manifest.permission.READ_CONTACTS] == true &&
|
||||||
permissions[Manifest.permission.WRITE_CONTACTS] == true) {
|
permissions[Manifest.permission.WRITE_CONTACTS] == true)
|
||||||
if (pendingMode.isNotEmpty()) {
|
if (pendingMode.isNotEmpty())
|
||||||
setContactsMode(pendingMode)
|
setContactsMode(pendingMode)
|
||||||
}
|
|
||||||
}
|
|
||||||
pendingMode = ""
|
pendingMode = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,10 +371,7 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
lastButtonText = ok,
|
lastButtonText = ok,
|
||||||
onLastClicked = {
|
onLastClicked = {
|
||||||
requestPermissionsLauncher.launch(
|
requestPermissionsLauncher.launch(
|
||||||
arrayOf(
|
arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
|
||||||
Manifest.permission.READ_CONTACTS,
|
|
||||||
Manifest.permission.WRITE_CONTACTS
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -400,20 +380,11 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(text = stringResource(R.string.contacts), fontWeight = FontWeight.Bold)
|
||||||
text = stringResource(R.string.contacts),
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -425,18 +396,13 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = { navController.navigateUp() }
|
onClick = { navController.navigateUp() }
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
||||||
contentDescription = "Back",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { expanded = !expanded }) {
|
IconButton(onClick = { expanded = !expanded }) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Menu, contentDescription = "Menu")
|
||||||
imageVector = Icons.Filled.Menu,
|
|
||||||
contentDescription = "Menu"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
CustomElements.DropdownMenu(
|
CustomElements.DropdownMenu(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
@@ -498,7 +464,8 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
) {
|
) {
|
||||||
Log.d(TAG, "Contacts permissions already granted")
|
Log.d(TAG, "Contacts permissions already granted")
|
||||||
setContactsMode(mode)
|
setContactsMode(mode)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (shouldShowRequestPermissionRationale(
|
if (shouldShowRequestPermissionRationale(
|
||||||
activity, Manifest.permission.READ_CONTACTS
|
activity, Manifest.permission.READ_CONTACTS
|
||||||
) ||
|
) ||
|
||||||
@@ -508,20 +475,20 @@ private fun ContactsScreen(navController: NavController) {
|
|||||||
) {
|
) {
|
||||||
pendingMode = mode
|
pendingMode = mode
|
||||||
showNoticeDialog.value = true
|
showNoticeDialog.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
pendingMode = mode
|
pendingMode = mode
|
||||||
requestPermissionsLauncher.launch(contactsPermissions)
|
requestPermissionsLauncher.launch(contactsPermissions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
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)) },
|
label = { Text(stringResource(R.string.search)) },
|
||||||
textStyle = TextStyle(fontSize = 18.sp),
|
textStyle = TextStyle(fontSize = 18.sp),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done)
|
||||||
keyboardType = KeyboardType.Text,
|
|
||||||
imeAction = ImeAction.Done
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
SmallFloatingActionButton(
|
SmallFloatingActionButton(
|
||||||
onClick = { navController.navigate("contact//new") },
|
onClick = { navController.navigate("contact//new") },
|
||||||
@@ -641,18 +605,17 @@ private fun ContactsContent(
|
|||||||
|
|
||||||
var lastSearchQuery by remember { mutableStateOf("") }
|
var lastSearchQuery by remember { mutableStateOf("") }
|
||||||
LaunchedEffect(searchQuery) {
|
LaunchedEffect(searchQuery) {
|
||||||
if (searchQuery.isBlank() && lastSearchQuery.isNotBlank()) {
|
if (searchQuery.isBlank() && lastSearchQuery.isNotBlank())
|
||||||
lazyListState.scrollToItem(0)
|
lazyListState.scrollToItem(0)
|
||||||
}
|
|
||||||
lastSearchQuery = searchQuery
|
lastSearchQuery = searchQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
val filteredContacts = remember(BaresipService.contacts, searchQuery) {
|
val filteredContacts = remember(BaresipService.contacts, searchQuery) {
|
||||||
if (searchQuery.isBlank()) {
|
if (searchQuery.isBlank())
|
||||||
BaresipService.contacts.map { contact ->
|
BaresipService.contacts.map { contact ->
|
||||||
Pair(contact, buildAnnotatedString { append(contact.name()) })
|
Pair(contact, buildAnnotatedString { append(contact.name()) })
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
val normalizedQuery = Utils.unaccent(searchQuery)
|
val normalizedQuery = Utils.unaccent(searchQuery)
|
||||||
BaresipService.contacts
|
BaresipService.contacts
|
||||||
.filter { contact ->
|
.filter { contact ->
|
||||||
@@ -667,9 +630,8 @@ private fun ContactsContent(
|
|||||||
LaunchedEffect(scrollToContact?.value, filteredContacts) {
|
LaunchedEffect(scrollToContact?.value, filteredContacts) {
|
||||||
scrollToContact?.value?.let { name ->
|
scrollToContact?.value?.let { name ->
|
||||||
val index = filteredContacts.indexOfFirst { it.first.name() == name }
|
val index = filteredContacts.indexOfFirst { it.first.name() == name }
|
||||||
if (index != -1) {
|
if (index != -1)
|
||||||
lazyListState.scrollToItem(index)
|
lazyListState.scrollToItem(index)
|
||||||
}
|
|
||||||
navController.currentBackStackEntry?.savedStateHandle?.remove<String>("scrollToContact")
|
navController.currentBackStackEntry?.savedStateHandle?.remove<String>("scrollToContact")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -687,13 +649,11 @@ private fun ContactsContent(
|
|||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
filteredContacts, key = { index, (contact, _) -> "${contact.id()}_$index" }
|
filteredContacts, key = { index, (contact, _) -> "${contact.id()}_$index" }
|
||||||
) { _, (contact, annotatedName) ->
|
) { _, (contact, annotatedName) ->
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
when (contact) {
|
when (contact) {
|
||||||
|
|
||||||
is Contact.BaresipContact -> {
|
is Contact.BaresipContact -> {
|
||||||
val avatarImage = contact.avatarImage
|
val avatarImage = contact.avatarImage
|
||||||
if (avatarImage != null)
|
if (avatarImage != null)
|
||||||
@@ -708,7 +668,6 @@ private fun ContactsContent(
|
|||||||
else
|
else
|
||||||
TextAvatar(contact.name(), contact.color)
|
TextAvatar(contact.name(), contact.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Contact.AndroidContact -> {
|
is Contact.AndroidContact -> {
|
||||||
val thumbNailUri = contact.thumbnailUri
|
val thumbNailUri = contact.thumbnailUri
|
||||||
if (thumbNailUri != null)
|
if (thumbNailUri != null)
|
||||||
@@ -724,45 +683,32 @@ private fun ContactsContent(
|
|||||||
TextAvatar(contact.name(), contact.color)
|
TextAvatar(contact.name(), contact.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (contact) {
|
when (contact) {
|
||||||
|
|
||||||
is Contact.BaresipContact -> {
|
is Contact.BaresipContact -> {
|
||||||
Text(text = annotatedName,
|
Text(
|
||||||
|
text = annotatedName,
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,
|
fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 10.dp)
|
.padding(start = 10.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = { navController.navigate("contact/${contact.name()}/old") },
|
||||||
navController.navigate("contact/${contact.name()}/old")
|
|
||||||
},
|
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
title.value = confirmationText
|
title.value = confirmationText
|
||||||
message.value = String.format(
|
message.value = String.format(contactDeleteQuestion, contact.name())
|
||||||
contactDeleteQuestion,
|
|
||||||
contact.name()
|
|
||||||
)
|
|
||||||
firstButtonText.value = cancelText
|
firstButtonText.value = cancelText
|
||||||
onFirstClicked.value = { }
|
onFirstClicked.value = { }
|
||||||
lastButtonText.value = deleteText
|
lastButtonText.value = deleteText
|
||||||
onLastClicked.value = {
|
onLastClicked.value = {
|
||||||
val id = contact.id
|
val id = contact.id
|
||||||
val avatarFile = File(
|
val avatarFile = File(BaresipService.filesPath, "$id.png")
|
||||||
BaresipService.filesPath,
|
if (avatarFile.exists())
|
||||||
"$id.png"
|
|
||||||
)
|
|
||||||
if (avatarFile.exists()) {
|
|
||||||
try {
|
try {
|
||||||
avatarFile.delete()
|
avatarFile.delete()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(
|
Log.e(TAG, "Could not delete file $id.png: ${e.message}")
|
||||||
TAG,
|
|
||||||
"Could not delete file $id.png: ${e.message}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Contact.removeBaresipContact(contact)
|
Contact.removeBaresipContact(contact)
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
@@ -770,7 +716,6 @@ private fun ContactsContent(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Contact.AndroidContact -> {
|
is Contact.AndroidContact -> {
|
||||||
Text(text = annotatedName,
|
Text(text = annotatedName,
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
@@ -779,15 +724,10 @@ private fun ContactsContent(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 10.dp, top = 4.dp, bottom = 4.dp)
|
.padding(start = 10.dp, top = 4.dp, bottom = 4.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = { navController.navigate("contact/${contact.name()}/old") },
|
||||||
navController.navigate("contact/${contact.name()}/old")
|
|
||||||
},
|
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
title.value = confirmationText
|
title.value = confirmationText
|
||||||
message.value = String.format(
|
message.value = String.format(contactDeleteQuestion, contact.name())
|
||||||
contactDeleteQuestion,
|
|
||||||
contact.name()
|
|
||||||
)
|
|
||||||
firstButtonText.value = cancelText
|
firstButtonText.value = cancelText
|
||||||
onFirstClicked.value = { }
|
onFirstClicked.value = { }
|
||||||
lastButtonText.value = deleteText
|
lastButtonText.value = deleteText
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -242,12 +241,10 @@ private fun MainScreen(
|
|||||||
BaresipService.isMainVisible = true
|
BaresipService.isMainVisible = true
|
||||||
viewModel.updateSpeakerPhoneStatus(BaresipService.speakerPhone)
|
viewModel.updateSpeakerPhoneStatus(BaresipService.speakerPhone)
|
||||||
viewModel.updateCalls(Call.calls().toList())
|
viewModel.updateCalls(Call.calls().toList())
|
||||||
|
|
||||||
if (Call.inCall())
|
if (Call.inCall())
|
||||||
(ctx as? Activity)?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
(ctx as? Activity)?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
else
|
else
|
||||||
(ctx as? Activity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
(ctx as? Activity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
(Call.call("incoming") ?: Call.calls().lastOrNull())?.let {
|
(Call.call("incoming") ?: Call.calls().lastOrNull())?.let {
|
||||||
spinToAor(viewModel, it.ua.account.aor, it)
|
spinToAor(viewModel, it.ua.account.aor, it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
@@ -453,7 +450,8 @@ private fun MainScreen(
|
|||||||
showPasswordsDialog.value = true
|
showPasswordsDialog.value = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
showPasswordsDialog.value = false
|
showPasswordsDialog.value = false
|
||||||
showPasswordsDialog.value = true
|
showPasswordsDialog.value = true
|
||||||
}
|
}
|
||||||
@@ -471,27 +469,19 @@ private fun MainScreen(
|
|||||||
Charsets.UTF_8
|
Charsets.UTF_8
|
||||||
).lines().toMutableList()
|
).lines().toMutableList()
|
||||||
showPasswordsDialog.value = true
|
showPasswordsDialog.value = true
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
// Baresip is started for the first time
|
// Baresip is started for the first time
|
||||||
onRequestPermissions()
|
onRequestPermissions()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
@@ -541,11 +531,7 @@ private fun TopAppBar(
|
|||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(text = stringResource(R.string.baresip), fontSize = 22.sp, fontWeight = FontWeight.Bold)
|
||||||
text = stringResource(R.string.baresip),
|
|
||||||
fontSize = 22.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -736,10 +722,7 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
|||||||
val buttonSize = 48.dp
|
val buttonSize = 48.dp
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().navigationBarsPadding().padding(bottom = 16.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.navigationBarsPadding()
|
|
||||||
.padding(bottom = 16.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -757,7 +740,8 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
|||||||
intent.putExtra("uap", ua.uap)
|
intent.putExtra("uap", ua.uap)
|
||||||
intent.putExtra("peer", acc.vmUri)
|
intent.putExtra("peer", acc.vmUri)
|
||||||
handleIntent(ctx, viewModel, intent, "call")
|
handleIntent(ctx, viewModel, intent, "call")
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
dialogTitle.value = ctx.getString(R.string.voicemail_messages)
|
dialogTitle.value = ctx.getString(R.string.voicemail_messages)
|
||||||
dialogMessage.value = acc.vmMessages(ctx)
|
dialogMessage.value = acc.vmMessages(ctx)
|
||||||
firstText.value = ctx.getString(R.string.cancel)
|
firstText.value = ctx.getString(R.string.cancel)
|
||||||
@@ -774,23 +758,22 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f).size(buttonSize)
|
||||||
.weight(1f)
|
|
||||||
.size(buttonSize)
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Voicemail,
|
imageVector = Icons.Filled.Voicemail,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
Modifier.size(buttonSize),
|
Modifier.size(buttonSize),
|
||||||
tint = if (hasNewVoicemail) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
|
tint = if (hasNewVoicemail)
|
||||||
|
MaterialTheme.colorScheme.error
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { navController.navigate("contacts") },
|
onClick = { navController.navigate("contacts") },
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f).size(buttonSize)
|
||||||
.weight(1f)
|
|
||||||
.size(buttonSize)
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Person,
|
imageVector = Icons.Filled.Person,
|
||||||
@@ -803,13 +786,11 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
|||||||
IconButton(
|
IconButton(
|
||||||
enabled = aor.isNotEmpty(),
|
enabled = aor.isNotEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isMobile) {
|
if (isMobile && !Utils.isDefaultSmsApp(ctx)) {
|
||||||
if (!Utils.isDefaultSmsApp(ctx)) {
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
||||||
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
showAlert.value = true
|
||||||
showAlert.value = true
|
return@IconButton
|
||||||
return@IconButton
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
navController.navigate("chats/$aor")
|
navController.navigate("chats/$aor")
|
||||||
},
|
},
|
||||||
@@ -828,12 +809,8 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
|||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = aor.isNotEmpty(),
|
enabled = aor.isNotEmpty(),
|
||||||
onClick = {
|
onClick = { navController.navigate("calls/$aor") },
|
||||||
navController.navigate("calls/$aor")
|
modifier = Modifier.weight(1f).size(buttonSize)
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.size(buttonSize)
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.History,
|
imageVector = Icons.Filled.History,
|
||||||
@@ -845,9 +822,7 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
|
|||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { viewModel.toggleDialpadVisibility() },
|
onClick = { viewModel.toggleDialpadVisibility() },
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f).size(buttonSize),
|
||||||
.weight(1f)
|
|
||||||
.size(buttonSize),
|
|
||||||
enabled = dialpadButtonEnabled.value
|
enabled = dialpadButtonEnabled.value
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -985,7 +960,8 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (offset > swipeThreshold) {
|
}
|
||||||
|
else if (offset > swipeThreshold) {
|
||||||
if (uas.value.isNotEmpty()) {
|
if (uas.value.isNotEmpty()) {
|
||||||
val curPos = UserAgent.findAorIndex(viewModel.selectedAor.value)
|
val curPos = UserAgent.findAorIndex(viewModel.selectedAor.value)
|
||||||
val newPos = when (curPos) {
|
val newPos = when (curPos) {
|
||||||
@@ -1046,26 +1022,18 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
|
|||||||
val selected = viewModel.selectedAor.value
|
val selected = viewModel.selectedAor.value
|
||||||
if (uas.value.isEmpty()) {
|
if (uas.value.isEmpty()) {
|
||||||
if (selected != "") viewModel.updateSelectedAor("")
|
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)
|
val ua = UserAgent.ofAor(viewModel.selectedAor.value)
|
||||||
if (ua != null) {
|
if (ua != null)
|
||||||
showCall(ctx, viewModel, ua, viewModel.focusedCall.value)
|
showCall(ctx, viewModel, ua, viewModel.focusedCall.value)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected == "") {
|
if (selected == "") {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = { navController.navigate("accounts") },
|
||||||
navController.navigate("accounts")
|
modifier = Modifier.padding(horizontal = 4.dp).height(50.dp).fillMaxWidth(),
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 4.dp)
|
|
||||||
.height(50.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
colors = ButtonColors(
|
colors = ButtonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@@ -1080,18 +1048,14 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = { expanded = !expanded },
|
||||||
expanded = !expanded
|
|
||||||
},
|
|
||||||
enabled = true,
|
enabled = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 4.dp)
|
.padding(horizontal = 4.dp)
|
||||||
.height(50.dp)
|
.height(50.dp)
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onPress = {
|
onPress = { expanded = true },
|
||||||
expanded = true
|
|
||||||
},
|
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
val ua = UserAgent.ofAor(selected)
|
val ua = UserAgent.ofAor(selected)
|
||||||
if (ua != null) {
|
if (ua != null) {
|
||||||
@@ -1100,11 +1064,9 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
|
|||||||
if (Api.account_regint(acc.accp) > 0) {
|
if (Api.account_regint(acc.accp) > 0) {
|
||||||
Api.account_set_regint(acc.accp, 0)
|
Api.account_set_regint(acc.accp, 0)
|
||||||
Api.ua_unregister(ua.uap)
|
Api.ua_unregister(ua.uap)
|
||||||
} else {
|
}
|
||||||
Api.account_set_regint(
|
else {
|
||||||
acc.accp,
|
Api.account_set_regint(acc.accp, acc.configuredRegInt)
|
||||||
acc.configuredRegInt
|
|
||||||
)
|
|
||||||
Api.ua_register(ua.uap)
|
Api.ua_register(ua.uap)
|
||||||
}
|
}
|
||||||
acc.regint = Api.account_regint(acc.accp)
|
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) {
|
if (Api.account_regint(acc.accp) > 0) {
|
||||||
Api.account_set_regint(acc.accp, 0)
|
Api.account_set_regint(acc.accp, 0)
|
||||||
Api.ua_unregister(ua.uap)
|
Api.ua_unregister(ua.uap)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Api.account_set_regint(
|
Api.account_set_regint(
|
||||||
acc.accp,
|
acc.accp,
|
||||||
acc.configuredRegInt
|
acc.configuredRegInt
|
||||||
@@ -1186,11 +1149,9 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
|
|||||||
expanded = false
|
expanded = false
|
||||||
spinToAor(viewModel, acc.aor)
|
spinToAor(viewModel, acc.aor)
|
||||||
},
|
},
|
||||||
text = { Text(
|
text = {
|
||||||
text = acc.text(),
|
Text(text = acc.text(), fontSize = 17.sp, fontWeight = FontWeight.Bold)
|
||||||
fontSize = 17.sp,
|
},
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
) },
|
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = ImageVector.vectorResource(uasStatus.value[acc.aor]!!),
|
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)
|
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1224,21 +1184,16 @@ private fun CallUriRow(
|
|||||||
val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()
|
val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 8.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 4.dp, bottom = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
|
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
|
||||||
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
|
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (isDialer) {
|
if (isDialer)
|
||||||
if (it != dialerState.callUri.value) {
|
if (it != dialerState.callUri.value) {
|
||||||
dialerState.callUri.value = it
|
dialerState.callUri.value = it
|
||||||
dialerState.redialUri = ""
|
dialerState.redialUri = ""
|
||||||
@@ -1258,7 +1213,6 @@ private fun CallUriRow(
|
|||||||
}
|
}
|
||||||
dialerState.showSuggestions.value = it.length > 1
|
dialerState.showSuggestions.value = it.length > 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (isDialer && dialerState.callUriEnabled.value && dialerState.callUri.value.isNotEmpty())
|
if (isDialer && dialerState.callUriEnabled.value && dialerState.callUri.value.isNotEmpty())
|
||||||
@@ -1322,10 +1276,7 @@ private fun CallUriRow(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 4.dp, end = 4.dp, top = 2.dp, bottom = 2.dp),
|
.padding(start = 4.dp, end = 4.dp, top = 2.dp, bottom = 2.dp),
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(text = call.callUriLabel2.value, fontSize = 18.sp)
|
||||||
text = call.callUriLabel2.value,
|
|
||||||
fontSize = 18.sp
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
textStyle = TextStyle(fontSize = 18.sp)
|
textStyle = TextStyle(fontSize = 18.sp)
|
||||||
)
|
)
|
||||||
@@ -1343,9 +1294,7 @@ private fun CallUriRow(
|
|||||||
) {
|
) {
|
||||||
if (isDialer && dialerState.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
if (isDialer && dialerState.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(max = 150.dp)
|
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -1403,13 +1352,11 @@ private fun CallUriRow(
|
|||||||
alertMessage.value = ctx.getString(R.string.call_not_secure)
|
alertMessage.value = ctx.getString(R.string.call_not_secure)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.color.colorTrafficYellow -> {
|
R.color.colorTrafficYellow -> {
|
||||||
alertTitle.value = ctx.getString(R.string.alert)
|
alertTitle.value = ctx.getString(R.string.alert)
|
||||||
alertMessage.value = ctx.getString(R.string.peer_not_verified)
|
alertMessage.value = ctx.getString(R.string.peer_not_verified)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.color.colorTrafficGreen -> {
|
R.color.colorTrafficGreen -> {
|
||||||
dialogTitle.value = ctx.getString(R.string.info)
|
dialogTitle.value = ctx.getString(R.string.info)
|
||||||
dialogMessage.value = ctx.getString(R.string.call_is_secure)
|
dialogMessage.value = ctx.getString(R.string.call_is_secure)
|
||||||
@@ -1419,10 +1366,7 @@ private fun CallUriRow(
|
|||||||
lastText.value = ctx.getString(R.string.unverify)
|
lastText.value = ctx.getString(R.string.unverify)
|
||||||
onLastClicked.value = {
|
onLastClicked.value = {
|
||||||
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0)
|
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0)
|
||||||
Log.e(
|
Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed")
|
||||||
TAG,
|
|
||||||
"Command 'zrtp_unverify ${call.zid}' failed"
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
call.securityIconTint.value = R.color.colorTrafficYellow
|
call.securityIconTint.value = R.color.colorTrafficYellow
|
||||||
}
|
}
|
||||||
@@ -1446,10 +1390,7 @@ private fun CallUriRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallTimer(
|
private fun CallTimer(initialDurationSeconds: Long, modifier: Modifier = Modifier) {
|
||||||
initialDurationSeconds: Long,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val startTime = remember(initialDurationSeconds) {
|
val startTime = remember(initialDurationSeconds) {
|
||||||
SystemClock.elapsedRealtime() - (initialDurationSeconds * 1000L)
|
SystemClock.elapsedRealtime() - (initialDurationSeconds * 1000L)
|
||||||
}
|
}
|
||||||
@@ -1489,11 +1430,12 @@ private fun CallRow(
|
|||||||
Row( modifier = Modifier
|
Row( modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = if (isDialer || call?.showCancelButton?.value == true ||
|
horizontalArrangement =
|
||||||
call?.showAnswerRejectButtons?.value == true)
|
if (isDialer || call?.showCancelButton?.value == true ||
|
||||||
Arrangement.Center
|
call?.showAnswerRejectButtons?.value == true)
|
||||||
else
|
Arrangement.Center
|
||||||
Arrangement.SpaceBetween
|
else
|
||||||
|
Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
if (isDialer) {
|
if (isDialer) {
|
||||||
if (dialerState.showCallButton.value)
|
if (dialerState.showCallButton.value)
|
||||||
@@ -1686,14 +1628,14 @@ private fun CallRow(
|
|||||||
transferUri = it
|
transferUri = it
|
||||||
if (it.length > 1) {
|
if (it.length > 1) {
|
||||||
val normalizedInput = Utils.unaccent(it)
|
val normalizedInput = Utils.unaccent(it)
|
||||||
filteredSuggestions =
|
filteredSuggestions = suggestions
|
||||||
suggestions.filter { suggestion ->
|
.filter { suggestion ->
|
||||||
Utils.unaccent(suggestion)
|
Utils.unaccent(suggestion)
|
||||||
.contains(normalizedInput, ignoreCase = true)
|
.contains(normalizedInput, ignoreCase = true)
|
||||||
}
|
}
|
||||||
.map { suggestion ->
|
.map { suggestion ->
|
||||||
Utils.buildAnnotatedStringWithHighlight(suggestion, it)
|
Utils.buildAnnotatedStringWithHighlight(suggestion, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
call.showSuggestions.value = transferUri.length > 1
|
call.showSuggestions.value = transferUri.length > 1
|
||||||
}
|
}
|
||||||
@@ -1714,12 +1656,7 @@ private fun CallRow(
|
|||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp)
|
||||||
start = 4.dp,
|
|
||||||
end = 4.dp,
|
|
||||||
top = 12.dp,
|
|
||||||
bottom = 2.dp
|
|
||||||
)
|
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
label = { Text(stringResource(R.string.transfer_destination)) },
|
label = { Text(stringResource(R.string.transfer_destination)) },
|
||||||
textStyle = TextStyle(fontSize = 18.sp),
|
textStyle = TextStyle(fontSize = 18.sp),
|
||||||
@@ -1740,16 +1677,11 @@ private fun CallRow(
|
|||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
) {
|
) {
|
||||||
if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)) {
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(max = 150.dp)) {
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.verticalScrollbar(
|
.verticalScrollbar(state = lazyListState, width = 6.dp),
|
||||||
state = lazyListState,
|
|
||||||
width = 6.dp,
|
|
||||||
),
|
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
) {
|
) {
|
||||||
@@ -1761,10 +1693,8 @@ private fun CallRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
transferUri =
|
transferUri = suggestion.toString()
|
||||||
suggestion.toString()
|
call.showSuggestions.value = false
|
||||||
call.showSuggestions.value =
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
@@ -1793,9 +1723,7 @@ private fun CallRow(
|
|||||||
)
|
)
|
||||||
Switch(
|
Switch(
|
||||||
checked = blindChecked.value,
|
checked = blindChecked.value,
|
||||||
onCheckedChange = {
|
onCheckedChange = { blindChecked.value = true }
|
||||||
blindChecked.value = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
@@ -1807,9 +1735,7 @@ private fun CallRow(
|
|||||||
)
|
)
|
||||||
Switch(
|
Switch(
|
||||||
checked = !blindChecked.value,
|
checked = !blindChecked.value,
|
||||||
onCheckedChange = {
|
onCheckedChange = { blindChecked.value = false }
|
||||||
blindChecked.value = false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1902,14 +1828,9 @@ private fun CallRow(
|
|||||||
}
|
}
|
||||||
call.dtmfText.value = newText
|
call.dtmfText.value = newText
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.width(80.dp).focusRequester(focusRequester),
|
||||||
.width(80.dp)
|
|
||||||
.focusRequester(focusRequester),
|
|
||||||
enabled = call.dtmfEnabled.value,
|
enabled = call.dtmfEnabled.value,
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(fontSize = 16.sp, color = MaterialTheme.colorScheme.onSurface),
|
||||||
fontSize = 16.sp,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
),
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
@@ -1923,17 +1844,9 @@ private fun CallRow(
|
|||||||
enabled = call.dtmfEnabled.value,
|
enabled = call.dtmfEnabled.value,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(stringResource(R.string.dtmf), style = TextStyle(fontSize = 12.sp))
|
||||||
stringResource(R.string.dtmf),
|
|
||||||
style = TextStyle(fontSize = 12.sp)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 8.dp),
|
||||||
start = 4.dp,
|
|
||||||
end = 4.dp,
|
|
||||||
top = 8.dp,
|
|
||||||
bottom = 8.dp
|
|
||||||
),
|
|
||||||
colors = OutlinedTextFieldDefaults.colors(
|
colors = OutlinedTextFieldDefaults.colors(
|
||||||
focusedContainerColor = Color.Transparent,
|
focusedContainerColor = Color.Transparent,
|
||||||
unfocusedContainerColor = Color.Transparent,
|
unfocusedContainerColor = Color.Transparent,
|
||||||
@@ -1979,7 +1892,8 @@ private fun CallRow(
|
|||||||
"${ctx.getString(R.string.lost)}: ${parts[3]}\n" +
|
"${ctx.getString(R.string.lost)}: ${parts[3]}\n" +
|
||||||
String.format(ctx.getString(R.string.jitter), parts[4])
|
String.format(ctx.getString(R.string.jitter), parts[4])
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
alertTitle.value = ctx.getString(R.string.call_info)
|
alertTitle.value = ctx.getString(R.string.call_info)
|
||||||
alertMessage.value = ctx.getString(R.string.call_info_not_available)
|
alertMessage.value = ctx.getString(R.string.call_info_not_available)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
@@ -1995,12 +1909,9 @@ private fun CallRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (call.showAnswerRejectButtons.value) {
|
if (call.showAnswerRejectButtons.value) {
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = { answer(ctx, call) },
|
||||||
answer(ctx, call)
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Call,
|
imageVector = Icons.Filled.Call,
|
||||||
@@ -2009,14 +1920,10 @@ private fun CallRow(
|
|||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.width(48.dp))
|
Spacer(Modifier.width(48.dp))
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = { reject(call) },
|
||||||
reject(call)
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.CallEnd,
|
imageVector = Icons.Filled.CallEnd,
|
||||||
@@ -2039,10 +1946,7 @@ private fun OnHoldNotice() {
|
|||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
shape = RoundedCornerShape(20)
|
shape = RoundedCornerShape(20)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(text = stringResource(R.string.call_is_on_hold), fontSize = 18.sp)
|
||||||
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) {
|
private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCall: Call? = null) {
|
||||||
if (ua == null)
|
if (ua == null) return
|
||||||
return
|
|
||||||
val call = showCall ?: ua.currentCall()
|
val call = showCall ?: ua.currentCall()
|
||||||
val aor = ua.account.aor
|
val aor = ua.account.aor
|
||||||
val callp = call?.callp ?: 0L
|
val callp = call?.callp ?: 0L
|
||||||
@@ -2278,7 +2181,8 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
|
|||||||
BaresipService.isMicMuted = false
|
BaresipService.isMicMuted = false
|
||||||
viewModel.updateMicIcon(Icons.Filled.Mic)
|
viewModel.updateMicIcon(Icons.Filled.Mic)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
viewModel.dialerState.callUri.value = ""
|
viewModel.dialerState.callUri.value = ""
|
||||||
pullToRefreshEnabled.value = false
|
pullToRefreshEnabled.value = false
|
||||||
call.callUriEnabled.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.callUriLabel2.value = ctx.getString(R.string.diverted_by_dots)
|
||||||
call.callUri2.value = Utils.friendlyUri(ctx, uri, ua.account)
|
call.callUri2.value = Utils.friendlyUri(ctx, uri, ua.account)
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
call.callUri2.value = ""
|
call.callUri2.value = ""
|
||||||
}
|
|
||||||
call.showCallButton.value = false
|
call.showCallButton.value = false
|
||||||
call.showCancelButton.value = false
|
call.showCancelButton.value = false
|
||||||
call.showHangupButton.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'")
|
Log.d(TAG, "Handling service event 'started' with URI '$uriString'")
|
||||||
if (uriString != "")
|
if (uriString != "")
|
||||||
callAction(ctx, viewModel, uriString.toUri(), "dial")
|
callAction(ctx, viewModel, uriString.toUri(), "dial")
|
||||||
else {
|
else if (viewModel.selectedAor.value == "" && uas.value.isNotEmpty())
|
||||||
if (viewModel.selectedAor.value == "" && uas.value.isNotEmpty())
|
viewModel.updateSelectedAor(uas.value.first().account.aor)
|
||||||
viewModel.updateSelectedAor(uas.value.first().account.aor)
|
if (Preferences(ctx).displayTheme != AppCompatDelegate.getDefaultNightMode())
|
||||||
}
|
|
||||||
if (Preferences(ctx).displayTheme != AppCompatDelegate.getDefaultNightMode()) {
|
|
||||||
AppCompatDelegate.setDefaultNightMode(Preferences(ctx).displayTheme)
|
AppCompatDelegate.setDefaultNightMode(Preferences(ctx).displayTheme)
|
||||||
}
|
|
||||||
handleNextEvent()
|
handleNextEvent()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2430,8 +2330,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
val aor = ua.account.aor
|
val aor = ua.account.aor
|
||||||
|
|
||||||
when (ev[0]) {
|
when (ev[0]) {
|
||||||
"call rejected" -> {
|
"call rejected" -> {}
|
||||||
}
|
|
||||||
"call outgoing" -> {
|
"call outgoing" -> {
|
||||||
val callp = params[1] as Long
|
val callp = params[1] as Long
|
||||||
if (!BaresipService.isMainVisible)
|
if (!BaresipService.isMainVisible)
|
||||||
@@ -2467,9 +2366,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
secondText.value = ctx.getString(R.string.no)
|
secondText.value = ctx.getString(R.string.no)
|
||||||
onSecondClicked.value = { }
|
onSecondClicked.value = { }
|
||||||
lastText.value = ctx.getString(R.string.yes)
|
lastText.value = ctx.getString(R.string.yes)
|
||||||
onLastClicked.value = {
|
onLastClicked.value = { redirect(ctx, viewModel, ua, redirectUri) }
|
||||||
redirect(ctx, viewModel, ua, redirectUri)
|
|
||||||
}
|
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2483,8 +2380,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
call.dtmfText.value = ""
|
call.dtmfText.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"call update" -> {
|
"call update" -> {}
|
||||||
}
|
|
||||||
"call verify" -> {
|
"call verify" -> {
|
||||||
val callp = params[1] as Long
|
val callp = params[1] as Long
|
||||||
val call = Call.ofCallp(callp)
|
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) {
|
call.security = if (Api.cmd_exec("zrtp_verify ${ev[2]}") != 0) {
|
||||||
Log.e(TAG, "Command 'zrtp_verify ${ev[2]}' failed")
|
Log.e(TAG, "Command 'zrtp_verify ${ev[2]}' failed")
|
||||||
R.color.colorTrafficYellow
|
R.color.colorTrafficYellow
|
||||||
} else {
|
|
||||||
R.color.colorTrafficGreen
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
R.color.colorTrafficGreen
|
||||||
call.zid = ev[2]
|
call.zid = ev[2]
|
||||||
if (aor == viewModel.selectedAor.value)
|
if (aor == viewModel.selectedAor.value)
|
||||||
call.securityIconTint.value = call.security
|
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")
|
handleNextEvent("Call $callp that is verified is not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (aor == viewModel.selectedAor.value) {
|
if (aor == viewModel.selectedAor.value)
|
||||||
call.securityIconTint.value = call.security
|
call.securityIconTint.value = call.security
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"call transfer", "transfer show" -> {
|
"call transfer", "transfer show" -> {
|
||||||
if (!BaresipService.isMainVisible)
|
if (!BaresipService.isMainVisible)
|
||||||
@@ -2594,14 +2489,13 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
}
|
}
|
||||||
"mwi notify" -> {
|
"mwi notify" -> {
|
||||||
val lines = ev[1].split("\n")
|
val lines = ev[1].split("\n")
|
||||||
for (line in lines) {
|
for (line in lines)
|
||||||
if (line.startsWith("Voice-Message:")) {
|
if (line.startsWith("Voice-Message:")) {
|
||||||
val counts = (line.split(" ")[1]).split("/")
|
val counts = (line.split(" ")[1]).split("/")
|
||||||
acc.vmNew = counts[0].toInt()
|
acc.vmNew = counts[0].toInt()
|
||||||
acc.vmOld = counts[1].toInt()
|
acc.vmOld = counts[1].toInt()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> Log.e(TAG, "Unknown event '${ev[0]}'")
|
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")
|
if (ev[0] == "call answer")
|
||||||
answer(ctx, call)
|
answer(ctx, call)
|
||||||
else
|
else
|
||||||
BaresipService.postServiceEvent(ServiceEvent(
|
BaresipService.postServiceEvent(
|
||||||
"call incoming",
|
ServiceEvent("call incoming", arrayListOf(call.ua.uap, callp), System.nanoTime())
|
||||||
arrayListOf(call.ua.uap, callp),
|
|
||||||
System.nanoTime())
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"call missed" -> {
|
"call missed" -> {
|
||||||
@@ -2676,10 +2568,8 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
|
|||||||
ev[1]
|
ev[1]
|
||||||
else
|
else
|
||||||
intent.getStringExtra("uri")!!
|
intent.getStringExtra("uri")!!
|
||||||
BaresipService.postServiceEvent(ServiceEvent(
|
BaresipService.postServiceEvent(
|
||||||
ev[0] + "," + uri,
|
ServiceEvent(ev[0] + "," + uri, arrayListOf(call.ua.uap, callp), System.nanoTime())
|
||||||
arrayListOf(call.ua.uap, callp),
|
|
||||||
System.nanoTime())
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
"message", "message show", "message reply" -> {
|
"message", "message show", "message reply" -> {
|
||||||
@@ -2690,10 +2580,8 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
BaresipService.postServiceEvent(ServiceEvent(
|
BaresipService.postServiceEvent(
|
||||||
ev[0],
|
ServiceEvent(ev[0], arrayListOf(uap, intent.getStringExtra("peer")!!), System.nanoTime())
|
||||||
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) {
|
fun callAction(ctx: Context, viewModel: ViewModel, uri: Uri?, action: String) {
|
||||||
if (Call.inCall() || uas.value.isEmpty())
|
if (Call.inCall() || uas.value.isEmpty()) return
|
||||||
return
|
|
||||||
Log.d(TAG, "Action $action to $uri")
|
Log.d(TAG, "Action $action to $uri")
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
var uriStr: String
|
var uriStr: String
|
||||||
@@ -2772,11 +2659,13 @@ private fun acceptTransfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, ca
|
|||||||
if (ua.account.aor != viewModel.selectedAor.value)
|
if (ua.account.aor != viewModel.selectedAor.value)
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Log.w(TAG, "call_connect $newCallp failed")
|
Log.w(TAG, "call_connect $newCallp failed")
|
||||||
call.notifySipfrag(500, "Call Error")
|
call.notifySipfrag(500, "Call Error")
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Log.w(TAG, "callAlloc for ua ${ua.uap} call ${call.callp} transfer failed")
|
Log.w(TAG, "callAlloc for ua ${ua.uap} call ${call.callp} transfer failed")
|
||||||
call.notifySipfrag(500, "Call Error")
|
call.notifySipfrag(500, "Call Error")
|
||||||
}
|
}
|
||||||
@@ -2794,8 +2683,10 @@ private fun backup(ctx: Context, password: String) {
|
|||||||
if (!Utils.zip(files, zipFile)) {
|
if (!Utils.zip(files, zipFile)) {
|
||||||
Log.w(TAG, "Failed to write zip file '$zipFile'")
|
Log.w(TAG, "Failed to write zip file '$zipFile'")
|
||||||
alertTitle.value = ctx.getString(R.string.error)
|
alertTitle.value = ctx.getString(R.string.error)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.backup_failed),
|
alertMessage.value = String.format(
|
||||||
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
|
ctx.getString(R.string.backup_failed),
|
||||||
|
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
|
||||||
|
)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
downloadsOutputUri = null
|
downloadsOutputUri = null
|
||||||
return
|
return
|
||||||
@@ -2804,23 +2695,29 @@ private fun backup(ctx: Context, password: String) {
|
|||||||
if (content == null) {
|
if (content == null) {
|
||||||
Log.w(TAG, "Failed to read zip file '$zipFile'")
|
Log.w(TAG, "Failed to read zip file '$zipFile'")
|
||||||
alertTitle.value = ctx.getString(R.string.error)
|
alertTitle.value = ctx.getString(R.string.error)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.backup_failed),
|
alertMessage.value = String.format(
|
||||||
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
|
ctx.getString(R.string.backup_failed),
|
||||||
|
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
|
||||||
|
)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
downloadsOutputUri = null
|
downloadsOutputUri = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!Utils.encryptToUri(ctx, downloadsOutputUri!!, content, password)) {
|
if (!Utils.encryptToUri(ctx, downloadsOutputUri!!, content, password)) {
|
||||||
alertTitle.value = ctx.getString(R.string.error)
|
alertTitle.value = ctx.getString(R.string.error)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.backup_failed),
|
alertMessage.value = String.format(
|
||||||
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
|
ctx.getString(R.string.backup_failed),
|
||||||
|
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
|
||||||
|
)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
downloadsOutputUri = null
|
downloadsOutputUri = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
alertTitle.value = ctx.getString(R.string.info)
|
alertTitle.value = ctx.getString(R.string.info)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.backed_up),
|
alertMessage.value = String.format(
|
||||||
Utils.fileNameOfUri(ctx, downloadsOutputUri!!))
|
ctx.getString(R.string.backed_up),
|
||||||
|
Utils.fileNameOfUri(ctx, downloadsOutputUri!!)
|
||||||
|
)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
Utils.deleteFile(File(zipFilePath))
|
Utils.deleteFile(File(zipFilePath))
|
||||||
downloadsInputUri = null
|
downloadsInputUri = null
|
||||||
@@ -2832,8 +2729,10 @@ private fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {
|
|||||||
val zipData = Utils.decryptFromUri(ctx, downloadsInputUri!!, password)
|
val zipData = Utils.decryptFromUri(ctx, downloadsInputUri!!, password)
|
||||||
if (zipData == null) {
|
if (zipData == null) {
|
||||||
alertTitle.value = ctx.getString(R.string.error)
|
alertTitle.value = ctx.getString(R.string.error)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.restore_failed),
|
alertMessage.value = String.format(
|
||||||
Utils.fileNameOfUri(ctx, downloadsInputUri!!))
|
ctx.getString(R.string.restore_failed),
|
||||||
|
Utils.fileNameOfUri(ctx, downloadsInputUri!!)
|
||||||
|
)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
downloadsInputUri = null
|
downloadsInputUri = null
|
||||||
return
|
return
|
||||||
@@ -2841,8 +2740,10 @@ private fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {
|
|||||||
if (!Utils.putFileContents(zipFilePath, zipData)) {
|
if (!Utils.putFileContents(zipFilePath, zipData)) {
|
||||||
Log.w(TAG, "Failed to write zip file '$zipFile'")
|
Log.w(TAG, "Failed to write zip file '$zipFile'")
|
||||||
alertTitle.value = ctx.getString(R.string.error)
|
alertTitle.value = ctx.getString(R.string.error)
|
||||||
alertMessage.value = String.format(ctx.getString(R.string.restore_failed),
|
alertMessage.value = String.format(
|
||||||
Utils.fileNameOfUri(ctx, downloadsInputUri!!))
|
ctx.getString(R.string.restore_failed),
|
||||||
|
Utils.fileNameOfUri(ctx, downloadsInputUri!!)
|
||||||
|
)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
downloadsInputUri = null
|
downloadsInputUri = null
|
||||||
return
|
return
|
||||||
@@ -2871,9 +2772,7 @@ private fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {
|
|||||||
dialogTitle.value = ctx.getString(R.string.info)
|
dialogTitle.value = ctx.getString(R.string.info)
|
||||||
dialogMessage.value = ctx.getString(R.string.restored)
|
dialogMessage.value = ctx.getString(R.string.restored)
|
||||||
firstText.value = ctx.getString(R.string.cancel)
|
firstText.value = ctx.getString(R.string.cancel)
|
||||||
onFirstClicked.value = {
|
onFirstClicked.value = { showDialog.value = false }
|
||||||
showDialog.value = false
|
|
||||||
}
|
|
||||||
secondText.value = ""
|
secondText.value = ""
|
||||||
lastText.value = ctx.getString(R.string.restart)
|
lastText.value = ctx.getString(R.string.restart)
|
||||||
onLastClicked.value = {
|
onLastClicked.value = {
|
||||||
|
|||||||
@@ -25,14 +25,15 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -155,25 +156,14 @@ private fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding().navigationBarsPadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||||
modifier = Modifier
|
Spacer(Modifier.statusBarsPadding())
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(
|
|
||||||
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(text = stringResource(R.string.configuration), fontWeight = FontWeight.Bold)
|
||||||
text = stringResource(R.string.configuration),
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -185,17 +175,14 @@ private fun SettingsScreen(
|
|||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = null,
|
contentDescription = "Back",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = checkOnClick) {
|
IconButton(onClick = checkOnClick) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Check, contentDescription = "Check")
|
||||||
imageVector = Icons.Filled.Check,
|
|
||||||
contentDescription = "Check"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -209,13 +196,9 @@ private fun SettingsScreen(
|
|||||||
title = stringResource(R.string.restart_request),
|
title = stringResource(R.string.restart_request),
|
||||||
message = stringResource(R.string.config_restart),
|
message = stringResource(R.string.config_restart),
|
||||||
firstButtonText = stringResource(R.string.cancel),
|
firstButtonText = stringResource(R.string.cancel),
|
||||||
onFirstClicked = {
|
onFirstClicked = { navController.navigateUp() },
|
||||||
navController.navigateUp()
|
|
||||||
},
|
|
||||||
lastButtonText = stringResource(R.string.restart),
|
lastButtonText = stringResource(R.string.restart),
|
||||||
onLastClicked = {
|
onLastClicked = { onRestartApp() },
|
||||||
onRestartApp()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,9 +287,7 @@ private fun SettingsContent(
|
|||||||
val startAutomaticallyHelp = stringResource(R.string.start_automatically_help)
|
val startAutomaticallyHelp = stringResource(R.string.start_automatically_help)
|
||||||
val appearOnTopPermissionMessage = stringResource(R.string.appear_on_top_permission)
|
val appearOnTopPermissionMessage = stringResource(R.string.appear_on_top_permission)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -355,9 +336,7 @@ private fun SettingsContent(
|
|||||||
val addressFamilyTitle = stringResource(R.string.address_family)
|
val addressFamilyTitle = stringResource(R.string.address_family)
|
||||||
val addressFamilyHelp = stringResource(R.string.address_family_help)
|
val addressFamilyHelp = stringResource(R.string.address_family_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -372,9 +351,7 @@ private fun SettingsContent(
|
|||||||
},
|
},
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
val isDropDownExpanded = remember {
|
val isDropDownExpanded = remember { mutableStateOf(false) }
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
val familyNames = listOf("--", "IPv4", "IPv6")
|
val familyNames = listOf("--", "IPv4", "IPv6")
|
||||||
val familyValues = listOf("", "ipv4", "ipv6")
|
val familyValues = listOf("", "ipv4", "ipv6")
|
||||||
val itemPosition = remember { mutableIntStateOf(familyValues.indexOf(addressFamily)) }
|
val itemPosition = remember { mutableIntStateOf(familyValues.indexOf(addressFamily)) }
|
||||||
@@ -382,9 +359,7 @@ private fun SettingsContent(
|
|||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable { isDropDownExpanded.value = true }
|
||||||
isDropDownExpanded.value = true
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(text = familyNames[itemPosition.intValue])
|
Text(text = familyNames[itemPosition.intValue])
|
||||||
Icon(
|
Icon(
|
||||||
@@ -396,9 +371,8 @@ private fun SettingsContent(
|
|||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = isDropDownExpanded.value,
|
expanded = isDropDownExpanded.value,
|
||||||
onDismissRequest = {
|
onDismissRequest = { isDropDownExpanded.value = false }
|
||||||
isDropDownExpanded.value = false
|
) {
|
||||||
}) {
|
|
||||||
familyNames.forEachIndexed { index, family ->
|
familyNames.forEachIndexed { index, family ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = family) },
|
text = { Text(text = family) },
|
||||||
@@ -420,9 +394,7 @@ private fun SettingsContent(
|
|||||||
val listenAddressTitle = stringResource(R.string.listen_address)
|
val listenAddressTitle = stringResource(R.string.listen_address)
|
||||||
val listenAddressHelp = stringResource(R.string.listen_address_help)
|
val listenAddressHelp = stringResource(R.string.listen_address_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
) {
|
) {
|
||||||
@@ -430,9 +402,7 @@ private fun SettingsContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = listenAddress,
|
value = listenAddress,
|
||||||
placeholder = { Text(stringResource(R.string._0_0_0_0_5060)) },
|
placeholder = { Text(stringResource(R.string._0_0_0_0_5060)) },
|
||||||
onValueChange = {
|
onValueChange = { viewModel.listenAddress.value = it },
|
||||||
viewModel.listenAddress.value = it
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
@@ -453,18 +423,14 @@ private fun SettingsContent(
|
|||||||
val transportProtocolsTitle = stringResource(R.string.transport_protocols)
|
val transportProtocolsTitle = stringResource(R.string.transport_protocols)
|
||||||
val transportProtocolsHelp = stringResource(R.string.transport_protocols_help)
|
val transportProtocolsHelp = stringResource(R.string.transport_protocols_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
val transportProtocols by viewModel.transportProtocols.collectAsState()
|
val transportProtocols by viewModel.transportProtocols.collectAsState()
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = transportProtocols,
|
value = transportProtocols,
|
||||||
onValueChange = {
|
onValueChange = { viewModel.transportProtocols.value = it },
|
||||||
viewModel.transportProtocols.value = it
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
@@ -484,18 +450,14 @@ private fun SettingsContent(
|
|||||||
val dnsServersTitle = stringResource(R.string.dns_servers)
|
val dnsServersTitle = stringResource(R.string.dns_servers)
|
||||||
val dnsServersHelp = stringResource(R.string.dns_servers_help)
|
val dnsServersHelp = stringResource(R.string.dns_servers_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
val dnsServers by viewModel.dnsServers.collectAsState()
|
val dnsServers by viewModel.dnsServers.collectAsState()
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = dnsServers,
|
value = dnsServers,
|
||||||
onValueChange = {
|
onValueChange = { viewModel.dnsServers.value = it },
|
||||||
viewModel.dnsServers.value = it
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
@@ -555,9 +517,7 @@ private fun SettingsContent(
|
|||||||
val showAlertDialog = remember { mutableStateOf(false) }
|
val showAlertDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -635,9 +595,7 @@ private fun SettingsContent(
|
|||||||
val verifyServerTitle = stringResource(R.string.verify_server)
|
val verifyServerTitle = stringResource(R.string.verify_server)
|
||||||
val verifyServerHelp = stringResource(R.string.verify_server_help)
|
val verifyServerHelp = stringResource(R.string.verify_server_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -697,9 +655,7 @@ private fun SettingsContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -719,7 +675,7 @@ private fun SettingsContent(
|
|||||||
checked = caFile,
|
checked = caFile,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
viewModel.caFile.value = it
|
viewModel.caFile.value = it
|
||||||
if (it) {
|
if (it)
|
||||||
if (VERSION.SDK_INT < 29) {
|
if (VERSION.SDK_INT < 29) {
|
||||||
viewModel.caFile.value = false
|
viewModel.caFile.value = false
|
||||||
val permission = Manifest.permission.READ_EXTERNAL_STORAGE
|
val permission = Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
@@ -754,7 +710,6 @@ private fun SettingsContent(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
Utils.selectInputFile(caCertsRequest)
|
Utils.selectInputFile(caCertsRequest)
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
Utils.deleteFile(File(BaresipService.filesPath + "/ca_certs.crt"))
|
Utils.deleteFile(File(BaresipService.filesPath + "/ca_certs.crt"))
|
||||||
restart = true
|
restart = true
|
||||||
@@ -769,9 +724,7 @@ private fun SettingsContent(
|
|||||||
val userAgentTitle = stringResource(R.string.user_agent)
|
val userAgentTitle = stringResource(R.string.user_agent)
|
||||||
val userAgentHelp = stringResource(R.string.user_agent_help)
|
val userAgentHelp = stringResource(R.string.user_agent_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -799,9 +752,7 @@ private fun SettingsContent(
|
|||||||
val uniqueContactUriTitle = stringResource(R.string.unique_contact_uri)
|
val uniqueContactUriTitle = stringResource(R.string.unique_contact_uri)
|
||||||
val uniqueContactUriHelp = stringResource(R.string.unique_contact_uri_help)
|
val uniqueContactUriHelp = stringResource(R.string.unique_contact_uri_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -826,17 +777,13 @@ private fun SettingsContent(
|
|||||||
@Composable
|
@Composable
|
||||||
fun AudioSettings(navController: NavController) {
|
fun AudioSettings(navController: NavController) {
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(top = 12.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.audio_settings),
|
text = stringResource(R.string.audio_settings),
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f).clickable { navController.navigate("audio") },
|
||||||
.weight(1f)
|
|
||||||
.clickable { navController.navigate("audio") },
|
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight. Bold
|
fontWeight = FontWeight. Bold
|
||||||
)
|
)
|
||||||
@@ -848,9 +795,7 @@ private fun SettingsContent(
|
|||||||
val batteryOptimizationsTitle = stringResource(R.string.battery_optimizations)
|
val batteryOptimizationsTitle = stringResource(R.string.battery_optimizations)
|
||||||
val batteryOptimizationsHelp = stringResource(R.string.battery_optimizations_help)
|
val batteryOptimizationsHelp = stringResource(R.string.battery_optimizations_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -889,9 +834,7 @@ private fun SettingsContent(
|
|||||||
val darkThemeTitle = stringResource(R.string.dark_theme)
|
val darkThemeTitle = stringResource(R.string.dark_theme)
|
||||||
val darkThemeHelp = stringResource(R.string.dark_theme_help)
|
val darkThemeHelp = stringResource(R.string.dark_theme_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -918,9 +861,7 @@ private fun SettingsContent(
|
|||||||
val dynamicColorsTitle = stringResource(R.string.dynamic_colors)
|
val dynamicColorsTitle = stringResource(R.string.dynamic_colors)
|
||||||
val dynamicColorsHelp = stringResource(R.string.dynamic_colors_help)
|
val dynamicColorsHelp = stringResource(R.string.dynamic_colors_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -947,9 +888,7 @@ private fun SettingsContent(
|
|||||||
val colorBlindTitle = stringResource(R.string.colorblind)
|
val colorBlindTitle = stringResource(R.string.colorblind)
|
||||||
val colorBlindHelp = stringResource(R.string.colorblind_help)
|
val colorBlindHelp = stringResource(R.string.colorblind_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -976,9 +915,7 @@ private fun SettingsContent(
|
|||||||
val proximitySensingTitle = stringResource(R.string.proximity_sensing)
|
val proximitySensingTitle = stringResource(R.string.proximity_sensing)
|
||||||
val proximitySensingHelp = stringResource(R.string.proximity_sensing_help)
|
val proximitySensingHelp = stringResource(R.string.proximity_sensing_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -1007,9 +944,7 @@ private fun SettingsContent(
|
|||||||
val defaultPhoneAppHelp = stringResource(R.string.default_phone_app_help)
|
val defaultPhoneAppHelp = stringResource(R.string.default_phone_app_help)
|
||||||
val dialerRoleNotAvailableMessage = stringResource(R.string.dialer_role_not_available)
|
val dialerRoleNotAvailableMessage = stringResource(R.string.dialer_role_not_available)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -1041,14 +976,13 @@ private fun SettingsContent(
|
|||||||
val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
|
val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
|
||||||
viewModel.defaultDialer.value = isHeld
|
viewModel.defaultDialer.value = isHeld
|
||||||
if (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()
|
BaresipService.instance?.addMobileUserAgent()
|
||||||
} else {
|
else
|
||||||
requestPermissionLauncher.launch(Manifest.permission.READ_PHONE_NUMBERS)
|
requestPermissionLauncher.launch(Manifest.permission.READ_PHONE_NUMBERS)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BaresipService.instance?.addMobileUserAgent()
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
BaresipService.instance?.addMobileUserAgent()
|
||||||
}
|
}
|
||||||
Switch(
|
Switch(
|
||||||
checked = defaultDialer,
|
checked = defaultDialer,
|
||||||
@@ -1063,7 +997,8 @@ private fun SettingsContent(
|
|||||||
else
|
else
|
||||||
if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER))
|
if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER))
|
||||||
dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER))
|
dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER))
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
viewModel.defaultMessaging.value = false
|
viewModel.defaultMessaging.value = false
|
||||||
try {
|
try {
|
||||||
dialerRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
|
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 defaultMessagingAppHelp = stringResource(R.string.default_messaging_app_help)
|
||||||
val messagingRoleNotAvailableMessage = stringResource(R.string.messaging_role_not_available)
|
val messagingRoleNotAvailableMessage = stringResource(R.string.messaging_role_not_available)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -1122,13 +1055,13 @@ private fun SettingsContent(
|
|||||||
else
|
else
|
||||||
if (!roleManager.isRoleHeld(RoleManager.ROLE_SMS))
|
if (!roleManager.isRoleHeld(RoleManager.ROLE_SMS))
|
||||||
messagingRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS))
|
messagingRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS))
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
try {
|
try {
|
||||||
messagingRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
|
messagingRoleRequest.launch(Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"))
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
Log.e(TAG, "ActivityNotFound exception: ${e.message}")
|
Log.e(TAG, "ActivityNotFound exception: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1139,9 +1072,7 @@ private fun SettingsContent(
|
|||||||
val debugTitle = stringResource(R.string.debug)
|
val debugTitle = stringResource(R.string.debug)
|
||||||
val debugHelp = stringResource(R.string.debug_help)
|
val debugHelp = stringResource(R.string.debug_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -1168,9 +1099,7 @@ private fun SettingsContent(
|
|||||||
val sipTraceTitle = stringResource(R.string.sip_trace)
|
val sipTraceTitle = stringResource(R.string.sip_trace)
|
||||||
val sipTraceHelp = stringResource(R.string.sip_trace_help)
|
val sipTraceHelp = stringResource(R.string.sip_trace_help)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -1199,9 +1128,7 @@ private fun SettingsContent(
|
|||||||
val resetConfigAlert = stringResource(R.string.reset_config_alert)
|
val resetConfigAlert = stringResource(R.string.reset_config_alert)
|
||||||
val resetButtonText = stringResource(R.string.reset)
|
val resetButtonText = stringResource(R.string.reset)
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 10.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
@@ -1222,9 +1149,7 @@ private fun SettingsContent(
|
|||||||
dialogTitle.value = confirmationText
|
dialogTitle.value = confirmationText
|
||||||
dialogMessage.value = resetConfigAlert
|
dialogMessage.value = resetConfigAlert
|
||||||
firstButtonText.value = cancelButtonText
|
firstButtonText.value = cancelButtonText
|
||||||
onFirstClicked.value = {
|
onFirstClicked.value = { reset = false }
|
||||||
reset = false
|
|
||||||
}
|
|
||||||
lastButtonText.value = resetButtonText
|
lastButtonText.value = resetButtonText
|
||||||
onLastClicked.value = {
|
onLastClicked.value = {
|
||||||
Config.reset()
|
Config.reset()
|
||||||
@@ -1281,10 +1206,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
|||||||
val noticeTitle = ctx.getString(R.string.notice)
|
val noticeTitle = ctx.getString(R.string.notice)
|
||||||
|
|
||||||
if ((Config.variable("auto_start") == "yes") != viewModel.autoStart.value) {
|
if ((Config.variable("auto_start") == "yes") != viewModel.autoStart.value) {
|
||||||
Config.replaceVariable(
|
Config.replaceVariable("auto_start", if (viewModel.autoStart.value) "yes" else "no")
|
||||||
"auto_start",
|
|
||||||
if (viewModel.autoStart.value) "yes" else "no"
|
|
||||||
)
|
|
||||||
save = true
|
save = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,7 +1266,8 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
|||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Config.replaceVariable("dyn_dns", "yes")
|
Config.replaceVariable("dyn_dns", "yes")
|
||||||
Config.updateDnsServers(BaresipService.dnsServers)
|
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) {
|
if ((Config.variable("sip_verify_server") == "yes") != viewModel.verifyServer.value) {
|
||||||
Config.replaceVariable("sip_verify_server",
|
Config.replaceVariable("sip_verify_server", if (viewModel.verifyServer.value) "yes" else "no")
|
||||||
if (viewModel.verifyServer.value) "yes" else "no")
|
|
||||||
Api.config_verify_server_set(viewModel.verifyServer.value)
|
Api.config_verify_server_set(viewModel.verifyServer.value)
|
||||||
save = true
|
save = true
|
||||||
}
|
}
|
||||||
@@ -1377,8 +1299,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((Config.variable("sip_cuser_random") == "yes") != viewModel.uniqueContactUri.value) {
|
if ((Config.variable("sip_cuser_random") == "yes") != viewModel.uniqueContactUri.value) {
|
||||||
Config.replaceVariable("sip_cuser_random",
|
Config.replaceVariable("sip_cuser_random", if (viewModel.uniqueContactUri.value) "yes" else "no")
|
||||||
if (viewModel.uniqueContactUri.value) "yes" else "no")
|
|
||||||
save = true
|
save = true
|
||||||
restart = true
|
restart = true
|
||||||
}
|
}
|
||||||
@@ -1392,25 +1313,20 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
|||||||
Preferences(ctx).displayTheme = newDisplayTheme
|
Preferences(ctx).displayTheme = newDisplayTheme
|
||||||
BaresipService.darkTheme.value = darkTheme
|
BaresipService.darkTheme.value = darkTheme
|
||||||
AppCompatDelegate.setDefaultNightMode(newDisplayTheme)
|
AppCompatDelegate.setDefaultNightMode(newDisplayTheme)
|
||||||
Config.replaceVariable("dark_theme",
|
Config.replaceVariable("dark_theme", if (darkTheme) "yes" else "no")
|
||||||
if (darkTheme) "yes" else "no")
|
|
||||||
save = true
|
save = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val dynamicColors = viewModel.dynamicColors.value
|
val dynamicColors = viewModel.dynamicColors.value
|
||||||
if (BaresipService.dynamicColors.value != dynamicColors) {
|
if (BaresipService.dynamicColors.value != dynamicColors) {
|
||||||
BaresipService.dynamicColors.value = dynamicColors
|
BaresipService.dynamicColors.value = dynamicColors
|
||||||
Config.replaceVariable("dynamic_colors",
|
Config.replaceVariable("dynamic_colors", if (dynamicColors) "yes" else "no")
|
||||||
if (dynamicColors) "yes" else "no")
|
|
||||||
save = true
|
save = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val colorblind = viewModel.colorblind.value
|
val colorblind = viewModel.colorblind.value
|
||||||
if ((Config.variable("colorblind") == "yes") != colorblind) {
|
if ((Config.variable("colorblind") == "yes") != colorblind) {
|
||||||
Config.replaceVariable(
|
Config.replaceVariable("colorblind", if (colorblind) "yes" else "no")
|
||||||
"colorblind",
|
|
||||||
if (colorblind) "yes" else "no"
|
|
||||||
)
|
|
||||||
BaresipService.colorblind = colorblind
|
BaresipService.colorblind = colorblind
|
||||||
UserAgent.updateColorblindStatus()
|
UserAgent.updateColorblindStatus()
|
||||||
val baresipService = Intent(ctx, BaresipService::class.java)
|
val baresipService = Intent(ctx, BaresipService::class.java)
|
||||||
@@ -1421,10 +1337,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
|
|||||||
|
|
||||||
val proximitySensing = viewModel.proximitySensing.value
|
val proximitySensing = viewModel.proximitySensing.value
|
||||||
if ((Config.variable("proximity_sensing") == "yes") != proximitySensing) {
|
if ((Config.variable("proximity_sensing") == "yes") != proximitySensing) {
|
||||||
Config.replaceVariable(
|
Config.replaceVariable("proximity_sensing", if (proximitySensing) "yes" else "no")
|
||||||
"proximity_sensing",
|
|
||||||
if (proximitySensing) "yes" else "no"
|
|
||||||
)
|
|
||||||
BaresipService.proximitySensing = proximitySensing
|
BaresipService.proximitySensing = proximitySensing
|
||||||
save = true
|
save = true
|
||||||
}
|
}
|
||||||
@@ -1457,9 +1370,9 @@ private fun addMissingPorts(addressList: String): String {
|
|||||||
if (addressList == "") return ""
|
if (addressList == "") return ""
|
||||||
var result = ""
|
var result = ""
|
||||||
for (addr in addressList.split(","))
|
for (addr in addressList.split(","))
|
||||||
result = if (Utils.checkIpPort(addr)) {
|
result = if (Utils.checkIpPort(addr))
|
||||||
"$result,$addr"
|
"$result,$addr"
|
||||||
} else {
|
else {
|
||||||
if (Utils.checkIpV4(addr))
|
if (Utils.checkIpV4(addr))
|
||||||
"$result,$addr:53"
|
"$result,$addr:53"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user