More work on multiple calls
This commit is contained in:
@@ -830,7 +830,7 @@ class BaresipService: Service() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
"call outgoing" -> {
|
"call outgoing" -> {
|
||||||
if (call!!.status == "transferring")
|
if (call!!.status.value == "transferring")
|
||||||
break
|
break
|
||||||
stopMediaPlayer()
|
stopMediaPlayer()
|
||||||
setCallVolume()
|
setCallVolume()
|
||||||
@@ -989,8 +989,8 @@ class BaresipService: Service() {
|
|||||||
}
|
}
|
||||||
"call answered" -> {
|
"call answered" -> {
|
||||||
stopMediaPlayer()
|
stopMediaPlayer()
|
||||||
if (call!!.status == "incoming")
|
if (call!!.status.value == "incoming")
|
||||||
call.status = "answered"
|
call.status.value = "answered"
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1002,7 +1002,7 @@ class BaresipService: Service() {
|
|||||||
Log.d(TAG, "AoR $aor call $callp established in mode ${am.mode}")
|
Log.d(TAG, "AoR $aor call $callp established in mode ${am.mode}")
|
||||||
if (am.mode != MODE_IN_COMMUNICATION)
|
if (am.mode != MODE_IN_COMMUNICATION)
|
||||||
am.mode = MODE_IN_COMMUNICATION
|
am.mode = MODE_IN_COMMUNICATION
|
||||||
call!!.status = "connected"
|
call!!.status.value = "connected"
|
||||||
call.onhold = false
|
call.onhold = false
|
||||||
if (ua.account.callHistory)
|
if (ua.account.callHistory)
|
||||||
call.startTime = GregorianCalendar()
|
call.startTime = GregorianCalendar()
|
||||||
@@ -1020,7 +1020,7 @@ class BaresipService: Service() {
|
|||||||
else
|
else
|
||||||
playRingBack()
|
playRingBack()
|
||||||
}
|
}
|
||||||
if (!isMainVisible || call.status != "connected")
|
if (!isMainVisible || call.status.value != "connected")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
"call verified", "call secure" -> {
|
"call verified", "call secure" -> {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import androidx.compose.runtime.mutableIntStateOf
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: String, var status: String) {
|
class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: String, initialStatus: String) {
|
||||||
|
|
||||||
|
var status: MutableState<String> = mutableStateOf(initialStatus)
|
||||||
|
|
||||||
var onhold = false
|
var onhold = false
|
||||||
var held = false
|
var held = false
|
||||||
@@ -132,7 +134,7 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
|||||||
|
|
||||||
fun call(status: String): Call? {
|
fun call(status: String): Call? {
|
||||||
for (c in BaresipService.calls.reversed())
|
for (c in BaresipService.calls.reversed())
|
||||||
if (c.status == status) return c
|
if (c.status.value == status) return c
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
package com.tutpro.baresip
|
package com.tutpro.baresip
|
||||||
|
|
||||||
import java.util.*
|
import java.util.GregorianCalendar
|
||||||
|
|
||||||
class CallRow(
|
data class CallRow(
|
||||||
val aor: String, val peerUri: String, val direction: Int, startTime: GregorianCalendar?,
|
val aor: String,
|
||||||
val stopTime: GregorianCalendar, val recording: Array<String>
|
val peerUri: String,
|
||||||
|
var direction: Int,
|
||||||
|
var startTime: GregorianCalendar?,
|
||||||
|
var stopTime: GregorianCalendar,
|
||||||
|
var recording: Array<String>
|
||||||
) {
|
) {
|
||||||
class Details(
|
data class Details(
|
||||||
val direction: Int, val startTime: GregorianCalendar?,
|
var direction: Int,
|
||||||
val stopTime: GregorianCalendar, val recording: Array<String>
|
var startTime: GregorianCalendar?,
|
||||||
|
var stopTime: GregorianCalendar,
|
||||||
|
var recording: Array<String>
|
||||||
)
|
)
|
||||||
|
val details = mutableListOf(Details(direction, startTime, stopTime, recording))
|
||||||
val details = ArrayList<Details>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
details.add(Details(direction, startTime, stopTime, recording))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,29 +252,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
navController = rememberNavController()
|
navController = rememberNavController()
|
||||||
|
|
||||||
LaunchedEffect(key1 = viewModel, key2 = navController) {
|
LaunchedEffect(key1 = viewModel) {
|
||||||
viewModel.navigationCommand.collect { command ->
|
viewModel.navigationCommand.collect { command ->
|
||||||
Log.d(TAG, "MainActivity: Received NavigationCommand: $command")
|
Log.d(TAG, "MainActivity: Received NavigationCommand: $command")
|
||||||
when (command) {
|
when (command) {
|
||||||
is NavigationCommand.NavigateToChat -> {
|
is NavigationCommand.NavigateToChat -> {
|
||||||
val route = "chat/${command.aor}/${command.peer}"
|
val route = "chat/${command.aor}/${command.peerUri}"
|
||||||
navController.navigate(route) {
|
navController.navigate(route)
|
||||||
launchSingleTop = true
|
|
||||||
popUpTo("main")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is NavigationCommand.NavigateToCalls -> {
|
is NavigationCommand.NavigateToCalls -> {
|
||||||
val route = "calls/${command.aor}"
|
val route = "calls/${command.aor}"
|
||||||
navController.navigate(route) {
|
navController.navigate(route)
|
||||||
launchSingleTop = true
|
|
||||||
popUpTo("main")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is NavigationCommand.NavigateToHome -> {
|
is NavigationCommand.NavigateToHome -> {
|
||||||
navController.navigate("main") {
|
navController.navigate("main")
|
||||||
launchSingleTop = true
|
|
||||||
popUpTo("main")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -212,12 +211,15 @@ private fun MainScreen(
|
|||||||
val ua = uas.value.find { it.account.aor == viewModel.selectedAor.value }
|
val ua = uas.value.find { it.account.aor == viewModel.selectedAor.value }
|
||||||
val call = ua?.currentCall()
|
val call = ua?.currentCall()
|
||||||
|
|
||||||
LaunchedEffect(viewModel.showKeyboard.value) {
|
val showKeyboard by viewModel.showKeyboard.collectAsState()
|
||||||
|
val hideKeyboard by viewModel.hideKeyboard.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(showKeyboard) {
|
||||||
if (viewModel.showKeyboard.value > 0)
|
if (viewModel.showKeyboard.value > 0)
|
||||||
keyboardController?.show()
|
keyboardController?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(viewModel.hideKeyboard.value) {
|
LaunchedEffect(hideKeyboard) {
|
||||||
if (viewModel.hideKeyboard.value > 0)
|
if (viewModel.hideKeyboard.value > 0)
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
}
|
}
|
||||||
@@ -385,10 +387,10 @@ private fun MainScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = call?.status, key2 = configuration.orientation) {
|
LaunchedEffect(key1 = call?.status, key2 = configuration.orientation) {
|
||||||
val isConnected = call != null && call.status == "connected" && !call.held
|
val isConnected = call != null && call.status.value == "connected" && !call.held
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
focusDtmf.value = true
|
call.focusDtmf.value = true
|
||||||
delay(300)
|
delay(300)
|
||||||
keyboardController?.show()
|
keyboardController?.show()
|
||||||
}
|
}
|
||||||
@@ -479,9 +481,7 @@ private fun MainScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
.fillMaxSize()
|
|
||||||
.imePadding(),
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(
|
||||||
@@ -853,6 +853,21 @@ private val negativeText = mutableStateOf("")
|
|||||||
private val onNegativeClicked = mutableStateOf({})
|
private val onNegativeClicked = mutableStateOf({})
|
||||||
private val showDialog = mutableStateOf(false)
|
private val showDialog = mutableStateOf(false)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CallCard(
|
||||||
|
ctx: Context,
|
||||||
|
viewModel: ViewModel,
|
||||||
|
call: Call?,
|
||||||
|
dialerState: ViewModel.DialerState?
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
CallUriRow(ctx, viewModel, call, dialerState)
|
||||||
|
CallRow(ctx, viewModel, call, dialerState)
|
||||||
|
if (call != null && call.showOnHoldNotice.value)
|
||||||
|
OnHoldNotice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainContent(navController: NavController, viewModel: ViewModel, contentPadding: PaddingValues) {
|
private fun MainContent(navController: NavController, viewModel: ViewModel, contentPadding: PaddingValues) {
|
||||||
@@ -863,6 +878,12 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
val swipeThreshold = 200
|
val swipeThreshold = 200
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
|
|
||||||
|
val calls by viewModel.calls.collectAsState()
|
||||||
|
val selectedAor by viewModel.selectedAor.collectAsState()
|
||||||
|
val filteredCalls = calls.filter { it.ua.account.aor == selectedAor }
|
||||||
|
|
||||||
|
val dialingOrRinging = filteredCalls.any { it.status.value == "outgoing" || it.status.value == "incoming" }
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
LaunchedEffect(isRefreshing) {
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
@@ -936,8 +957,7 @@ 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) {
|
||||||
@@ -962,10 +982,16 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
AccountSpinner(ctx, viewModel, navController)
|
AccountSpinner(ctx, viewModel, navController)
|
||||||
CallUriRow(ctx, viewModel)
|
|
||||||
CallRow(ctx, viewModel)
|
filteredCalls.forEach { call ->
|
||||||
if (showOnHoldNotice.value)
|
CallCard(ctx = ctx, viewModel = viewModel, call = call, dialerState = null)
|
||||||
OnHoldNotice()
|
}
|
||||||
|
|
||||||
|
// Only show the dialer if we are not in a transient state
|
||||||
|
if (!dialingOrRinging) {
|
||||||
|
CallCard(ctx = ctx, viewModel = viewModel, call = null, dialerState = viewModel.dialerState)
|
||||||
|
}
|
||||||
|
|
||||||
Indicator(
|
Indicator(
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
isRefreshing = isRefreshing,
|
isRefreshing = isRefreshing,
|
||||||
@@ -1142,7 +1168,14 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
private fun CallUriRow(
|
||||||
|
ctx: Context,
|
||||||
|
viewModel: ViewModel,
|
||||||
|
call: Call?,
|
||||||
|
dialerState: ViewModel.DialerState?
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isDialer = dialerState != null
|
||||||
|
|
||||||
val suggestions by remember { contactNames }
|
val suggestions by remember { contactNames }
|
||||||
var filteredSuggestions by remember { mutableStateOf(suggestions) }
|
var filteredSuggestions by remember { mutableStateOf(suggestions) }
|
||||||
@@ -1159,27 +1192,29 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = callUri.value,
|
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
|
||||||
readOnly = !callUriEnabled.value,
|
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (it != callUri.value) {
|
if (isDialer) {
|
||||||
callUri.value = it
|
if (it != dialerState.callUri.value) {
|
||||||
|
dialerState.callUri.value = it
|
||||||
filteredSuggestions = suggestions.filter { suggestion ->
|
filteredSuggestions = suggestions.filter { suggestion ->
|
||||||
it.length > 2 && suggestion.startsWith(it, ignoreCase = true)
|
it.length > 2 && suggestion.startsWith(it, ignoreCase = true)
|
||||||
}
|
}
|
||||||
showSuggestions.value = it.length > 2
|
dialerState.showSuggestions.value = it.length > 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (callUriEnabled.value && callUri.value.isNotEmpty())
|
if (isDialer && dialerState.callUriEnabled.value && dialerState.callUri.value.isNotEmpty())
|
||||||
Icon(Icons.Outlined.Clear,
|
Icon(Icons.Outlined.Clear,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
if (showSuggestions.value)
|
if (dialerState.showSuggestions.value)
|
||||||
showSuggestions.value = false
|
dialerState.showSuggestions.value = false
|
||||||
else
|
else
|
||||||
callUri.value = ""
|
dialerState.callUri.value = ""
|
||||||
},
|
},
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -1189,12 +1224,21 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
.padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp)
|
.padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp)
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
|
if (isDialer) {
|
||||||
val account = Account.ofAor(viewModel.selectedAor.value)
|
val account = Account.ofAor(viewModel.selectedAor.value)
|
||||||
if (account != null && account.numericKeypad)
|
if (account != null && account.numericKeypad)
|
||||||
if (!isDialpadVisible)
|
if (!isDialpadVisible)
|
||||||
viewModel.toggleDialpadVisibility()
|
viewModel.toggleDialpadVisibility()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = if (isDialer)
|
||||||
|
dialerState.callUriLabel.value
|
||||||
|
else
|
||||||
|
call!!.callUriLabel.value,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
},
|
},
|
||||||
label = { Text(text = callUriLabel.value, fontSize = 18.sp) },
|
|
||||||
textStyle = TextStyle(fontSize = 18.sp),
|
textStyle = TextStyle(fontSize = 18.sp),
|
||||||
keyboardOptions = if (isDialpadVisible)
|
keyboardOptions = if (isDialpadVisible)
|
||||||
KeyboardOptions(keyboardType = KeyboardType.Phone)
|
KeyboardOptions(keyboardType = KeyboardType.Phone)
|
||||||
@@ -1212,7 +1256,7 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
)
|
)
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
) {
|
) {
|
||||||
if (showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
if (isDialer && dialerState.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(max = 150.dp)) {
|
.heightIn(max = 150.dp)) {
|
||||||
@@ -1234,8 +1278,8 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
callUri.value = suggestion
|
dialerState.callUri.value = suggestion
|
||||||
showSuggestions.value = false
|
dialerState.showSuggestions.value = false
|
||||||
}
|
}
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
@@ -1252,24 +1296,24 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showCallTimer.value) {
|
if (call != null && call.showCallTimer.value) {
|
||||||
CallTimer(
|
CallTimer(
|
||||||
initialDurationSeconds = callDuration.toLong(),
|
initialDurationSeconds = call.callDuration.toLong(),
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
start = 6.dp,
|
start = 6.dp,
|
||||||
top = 6.dp,
|
top = 6.dp,
|
||||||
end = if (securityIconTint.intValue != -1) 6.dp else 0.dp
|
end = if (call.securityIconTint.value != -1) 6.dp else 0.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (securityIconTint.intValue != -1)
|
if (call != null && call.securityIconTint.value != -1)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 4.dp)
|
.padding(top = 4.dp)
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable {
|
.clickable {
|
||||||
when (securityIconTint.intValue) {
|
when (call.securityIconTint.value) {
|
||||||
R.color.colorTrafficRed -> {
|
R.color.colorTrafficRed -> {
|
||||||
alertTitle.value = ctx.getString(R.string.alert)
|
alertTitle.value = ctx.getString(R.string.alert)
|
||||||
alertMessage.value = ctx.getString(R.string.call_not_secure)
|
alertMessage.value = ctx.getString(R.string.call_not_secure)
|
||||||
@@ -1285,17 +1329,13 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
dialogMessage.value = ctx.getString(R.string.call_is_secure)
|
dialogMessage.value = ctx.getString(R.string.call_is_secure)
|
||||||
positiveText.value = ctx.getString(R.string.unverify)
|
positiveText.value = ctx.getString(R.string.unverify)
|
||||||
onPositiveClicked.value = {
|
onPositiveClicked.value = {
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0)
|
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0)
|
||||||
Log.e(
|
Log.e(
|
||||||
TAG,
|
TAG,
|
||||||
"Command 'zrtp_unverify ${call.zid}' failed"
|
"Command 'zrtp_unverify ${call.zid}' failed"
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
securityIconTint.intValue = R.color.colorTrafficYellow
|
call.securityIconTint.value = R.color.colorTrafficYellow
|
||||||
}
|
|
||||||
}
|
}
|
||||||
negativeText.value = ctx.getString(R.string.cancel)
|
negativeText.value = ctx.getString(R.string.cancel)
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
@@ -1305,18 +1345,17 @@ private fun CallUriRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (securityIconTint.intValue == R.color.colorTrafficRed)
|
imageVector = if (call.securityIconTint.value == R.color.colorTrafficRed)
|
||||||
Icons.Filled.LockOpen
|
Icons.Filled.LockOpen
|
||||||
else
|
else
|
||||||
Icons.Filled.Lock,
|
Icons.Filled.Lock,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(28.dp),
|
modifier = Modifier.size(28.dp),
|
||||||
tint = colorResource(securityIconTint.intValue)
|
tint = colorResource(call.securityIconTint.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallTimer(
|
private fun CallTimer(
|
||||||
initialDurationSeconds: Long,
|
initialDurationSeconds: Long,
|
||||||
@@ -1348,8 +1387,14 @@ private fun CallTimer(
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
private fun CallRow(
|
||||||
|
ctx: Context,
|
||||||
|
viewModel: ViewModel,
|
||||||
|
call: Call?,
|
||||||
|
dialerState: ViewModel.DialerState?
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isDialer = dialerState != null
|
||||||
val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()
|
val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()
|
||||||
|
|
||||||
Row( modifier = Modifier
|
Row( modifier = Modifier
|
||||||
@@ -1357,43 +1402,38 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Absolute.SpaceBetween
|
horizontalArrangement = Arrangement.Absolute.SpaceBetween
|
||||||
) {
|
) {
|
||||||
if (showCallButton.value)
|
if (isDialer) {
|
||||||
|
if (dialerState.showCallButton.value)
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
showSuggestions.value = false
|
dialerState.showSuggestions.value = false
|
||||||
callClick(ctx, viewModel)
|
callClick(ctx, viewModel, dialerState)
|
||||||
},
|
},
|
||||||
enabled=callButtonEnabled.value
|
enabled = dialerState.callButtonEnabled.value
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Call,
|
imageVector = Icons.Filled.Call,
|
||||||
modifier = Modifier.size(42.dp),
|
modifier = Modifier.size(42.dp),
|
||||||
tint = colorResource(if (callButtonEnabled.value)
|
tint = colorResource(if (dialerState.callButtonEnabled.value)
|
||||||
R.color.colorTrafficGreen
|
R.color.colorTrafficGreen
|
||||||
else
|
else
|
||||||
R.color.colorTrafficYellow),
|
R.color.colorTrafficYellow),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (showCancelButton.value) {
|
if (call!!.showCancelButton.value) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
showSuggestions.value = false
|
|
||||||
abandonAudioFocus(ctx)
|
abandonAudioFocus(ctx)
|
||||||
val ua: UserAgent = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
val callp = call.callp
|
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
"AoR ${ua.account.aor} canceling call $callp with ${callUri.value}"
|
"AoR ${call.ua.account.aor} canceling call ${call.callp} with ${call.callUri.value}"
|
||||||
)
|
)
|
||||||
Api.ua_hangup(ua.uap, callp, 487, "Request Terminated")
|
Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated")
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -1406,20 +1446,14 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showHangupButton.value) {
|
if (call.showHangupButton.value) {
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
abandonAudioFocus(ctx)
|
abandonAudioFocus(ctx)
|
||||||
val uaCalls = ua.calls()
|
Log.d(TAG, "AoR ${call.ua.account.aor} hanging up call ${call.callp} with ${call.callUri.value}")
|
||||||
if (uaCalls.isNotEmpty()) {
|
Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated")
|
||||||
val call = uaCalls.first()
|
|
||||||
val callp = call.callp
|
|
||||||
Log.d(TAG, "AoR ${ua.account.aor} hanging up call $callp with ${callUri.value}")
|
|
||||||
Api.ua_hangup(ua.uap, callp, 487, "Request Terminated")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -1433,32 +1467,27 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
val aor = ua.account.aor
|
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
if (call.onhold) {
|
if (call.onhold) {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
"AoR $aor resuming call ${call.callp} with ${callUri.value}"
|
"AoR ${call.ua.account.aor} resuming call ${call.callp} with ${call.callUri.value}"
|
||||||
)
|
)
|
||||||
call.resume()
|
call.resume()
|
||||||
call.onhold = false
|
call.onhold = false
|
||||||
} else {
|
} else {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
"AoR $aor holding call ${call.callp} with ${callUri.value}"
|
"AoR ${call.ua.account.aor} holding call ${call.callp} with ${call.callUri.value}"
|
||||||
)
|
)
|
||||||
call.hold()
|
call.hold()
|
||||||
call.onhold = true
|
call.onhold = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.PauseCircle,
|
imageVector = Icons.Outlined.PauseCircle,
|
||||||
modifier = Modifier.size(42.dp),
|
modifier = Modifier.size(42.dp),
|
||||||
tint = if (callOnHold.value)
|
tint = if (call.callOnHold.value)
|
||||||
MaterialTheme.colorScheme.error
|
MaterialTheme.colorScheme.error
|
||||||
else
|
else
|
||||||
MaterialTheme.colorScheme.secondary,
|
MaterialTheme.colorScheme.secondary,
|
||||||
@@ -1469,11 +1498,8 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
var showTransferDialog by remember { mutableStateOf(false) }
|
var showTransferDialog by remember { mutableStateOf(false) }
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
enabled = transferButtonEnabled.value,
|
enabled = call.transferButtonEnabled.value,
|
||||||
onClick = {
|
onClick = {
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
if (call.onHoldCall != null) {
|
if (call.onHoldCall != null) {
|
||||||
if (!call.executeTransfer()) {
|
if (!call.executeTransfer()) {
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
alertTitle.value = ctx.getString(R.string.notice)
|
||||||
@@ -1482,13 +1508,12 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
showTransferDialog = true
|
showTransferDialog = true
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.ArrowCircleRight,
|
imageVector = Icons.Outlined.ArrowCircleRight,
|
||||||
modifier = Modifier.size(42.dp),
|
modifier = Modifier.size(42.dp),
|
||||||
tint = if (callTransfer.value)
|
tint = if (call.callTransfer.value)
|
||||||
MaterialTheme.colorScheme.error
|
MaterialTheme.colorScheme.error
|
||||||
else
|
else
|
||||||
MaterialTheme.colorScheme.secondary,
|
MaterialTheme.colorScheme.secondary,
|
||||||
@@ -1500,9 +1525,6 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
|
|
||||||
val showDialog = remember { mutableStateOf(true) }
|
val showDialog = remember { mutableStateOf(true) }
|
||||||
val blindChecked = remember { mutableStateOf(true) }
|
val blindChecked = remember { mutableStateOf(true) }
|
||||||
val selectedAor: String by viewModel.selectedAor.collectAsState()
|
|
||||||
val ua = UserAgent.ofAor(selectedAor)!!
|
|
||||||
val call = ua.currentCall()
|
|
||||||
|
|
||||||
if (showDialog.value)
|
if (showDialog.value)
|
||||||
BasicAlertDialog(
|
BasicAlertDialog(
|
||||||
@@ -1537,7 +1559,6 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (it != transferUri) {
|
if (it != transferUri) {
|
||||||
transferUri = it
|
|
||||||
filteredSuggestions =
|
filteredSuggestions =
|
||||||
suggestions.filter { suggestion ->
|
suggestions.filter { suggestion ->
|
||||||
transferUri.length > 2 &&
|
transferUri.length > 2 &&
|
||||||
@@ -1546,7 +1567,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
ignoreCase = true
|
ignoreCase = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showSuggestions.value = transferUri.length > 2
|
call.showSuggestions.value = transferUri.length > 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
@@ -1555,8 +1576,8 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
Icons.Outlined.Clear,
|
Icons.Outlined.Clear,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
if (showSuggestions.value)
|
if (call.showSuggestions.value)
|
||||||
showSuggestions.value = false
|
call.showSuggestions.value = false
|
||||||
else
|
else
|
||||||
transferUri = ""
|
transferUri = ""
|
||||||
},
|
},
|
||||||
@@ -1585,7 +1606,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
)
|
)
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
) {
|
) {
|
||||||
if (showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(max = 150.dp)) {
|
.heightIn(max = 150.dp)) {
|
||||||
@@ -1608,7 +1629,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
transferUri = suggestion
|
transferUri = suggestion
|
||||||
showSuggestions.value = false
|
call.showSuggestions.value = false
|
||||||
}
|
}
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
@@ -1624,7 +1645,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (call != null && call.replaces())
|
if (call.replaces())
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
@@ -1677,7 +1698,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
showSuggestions.value = false
|
call.showSuggestions.value = false
|
||||||
var uriText = transferUri.trim()
|
var uriText = transferUri.trim()
|
||||||
if (uriText.isNotEmpty()) {
|
if (uriText.isNotEmpty()) {
|
||||||
val uris = Contact.contactUris(uriText)
|
val uris = Contact.contactUris(uriText)
|
||||||
@@ -1688,7 +1709,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
transfer(
|
transfer(
|
||||||
ctx,
|
ctx,
|
||||||
viewModel,
|
viewModel,
|
||||||
ua,
|
call.ua,
|
||||||
if (Utils.isTelNumber(uri)) "tel:$uri" else uri,
|
if (Utils.isTelNumber(uri)) "tel:$uri" else uri,
|
||||||
!blindChecked.value
|
!blindChecked.value
|
||||||
)
|
)
|
||||||
@@ -1701,7 +1722,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
transfer(
|
transfer(
|
||||||
ctx,
|
ctx,
|
||||||
viewModel,
|
viewModel,
|
||||||
ua,
|
call.ua,
|
||||||
if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText,
|
if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText,
|
||||||
!blindChecked.value
|
!blindChecked.value
|
||||||
)
|
)
|
||||||
@@ -1730,25 +1751,24 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val shouldRequestFocus by focusDtmf
|
val shouldRequestFocus by call.focusDtmf
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = dtmfText.value,
|
value = call.dtmfText.value,
|
||||||
onValueChange = { newText ->
|
onValueChange = { newText ->
|
||||||
if (newText.length > dtmfText.value.length) {
|
if (newText.length > call.dtmfText.value.length) {
|
||||||
val char = newText.last()
|
val char = newText.last()
|
||||||
if (char.isDigit() || char == '*' || char == '#') {
|
if (char.isDigit() || char == '*' || char == '#') {
|
||||||
Log.d(TAG, "Got DTMF digit '$char'")
|
Log.d(TAG, "Got DTMF digit '$char'")
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
call.sendDigit(char)
|
||||||
ua.currentCall()?.sendDigit(char)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dtmfText.value = newText
|
call.dtmfText.value = newText
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(80.dp)
|
.width(80.dp)
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
enabled = dtmfEnabled.value,
|
enabled = call.dtmfEnabled.value,
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
@@ -1759,11 +1779,11 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
OutlinedTextFieldDefaults.DecorationBox(
|
OutlinedTextFieldDefaults.DecorationBox(
|
||||||
value = dtmfText.value,
|
value = call.dtmfText.value,
|
||||||
visualTransformation = VisualTransformation.None,
|
visualTransformation = VisualTransformation.None,
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
enabled = dtmfEnabled.value,
|
enabled = call.dtmfEnabled.value,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
@@ -1790,17 +1810,15 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
LaunchedEffect(shouldRequestFocus) {
|
LaunchedEffect(shouldRequestFocus) {
|
||||||
if (shouldRequestFocus) {
|
if (shouldRequestFocus) {
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
focusDtmf.value = false
|
call.focusDtmf.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
val stats = call.stats("audio")
|
||||||
val call = ua.currentCall()
|
if (stats.isNotEmpty() && call.startTime != null) {
|
||||||
val stats = call?.stats("audio")
|
|
||||||
if (stats != null && call.startTime != null && stats != "") {
|
|
||||||
val parts = stats.split(",") as java.util.ArrayList
|
val parts = stats.split(",") as java.util.ArrayList
|
||||||
if (parts[2] == "0/0") {
|
if (parts[2] == "0/0") {
|
||||||
parts[2] = "?/?"
|
parts[2] = "?/?"
|
||||||
@@ -1814,8 +1832,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
alertTitle.value = ctx.getString(R.string.call_info)
|
alertTitle.value = ctx.getString(R.string.call_info)
|
||||||
alertMessage.value =
|
alertMessage.value =
|
||||||
"${String.format(ctx.getString(R.string.duration), duration)}\n" +
|
"${String.format(ctx.getString(R.string.duration), duration)}\n" +
|
||||||
"${ctx.getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/" +
|
"${ctx.getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/${rxCodec[0]} ch ${rxCodec[2]}\n" +
|
||||||
"${rxCodec[0]} ch ${rxCodec[2]}\n" +
|
|
||||||
"${String.format(ctx.getString(R.string.rate), parts[0])}\n" +
|
"${String.format(ctx.getString(R.string.rate), parts[0])}\n" +
|
||||||
"${String.format(ctx.getString(R.string.average_rate), parts[1])}\n" +
|
"${String.format(ctx.getString(R.string.average_rate), parts[1])}\n" +
|
||||||
"${ctx.getString(R.string.packets)}: ${parts[2]}\n" +
|
"${ctx.getString(R.string.packets)}: ${parts[2]}\n" +
|
||||||
@@ -1838,12 +1855,12 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAnswerRejectButtons.value) {
|
if (call.showAnswerRejectButtons.value) {
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
answer(ctx, viewModel)
|
answer(ctx, call)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -1859,7 +1876,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
reject(viewModel)
|
reject(call)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -1871,6 +1888,7 @@ private fun CallRow(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1894,12 +1912,11 @@ private fun spinToAor(viewModel: ViewModel, aor: String) {
|
|||||||
viewModel.triggerAccountUpdate()
|
viewModel.triggerAccountUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun callClick(ctx: Context, viewModel: ViewModel) {
|
private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel.DialerState?) {
|
||||||
if (viewModel.selectedAor.value != "") {
|
if (viewModel.selectedAor.value != "") {
|
||||||
if (Utils.checkPermissions(ctx, arrayOf(RECORD_AUDIO))) {
|
if (Utils.checkPermissions(ctx, arrayOf(RECORD_AUDIO))) {
|
||||||
if (Call.inCall())
|
if (dialerState != null) {
|
||||||
return
|
val uriText = dialerState.callUri.value.trim()
|
||||||
val uriText = callUri.value.trim()
|
|
||||||
if (uriText.isNotEmpty()) {
|
if (uriText.isNotEmpty()) {
|
||||||
val uris = Contact.contactUris(uriText)
|
val uris = Contact.contactUris(uriText)
|
||||||
if (uris.isEmpty())
|
if (uris.isEmpty())
|
||||||
@@ -1913,12 +1930,12 @@ private fun callClick(ctx: Context, viewModel: ViewModel) {
|
|||||||
}
|
}
|
||||||
showSelectItemDialog.value = true
|
showSelectItemDialog.value = true
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
||||||
val latestPeerUri = CallHistoryNew.aorLatestPeerUri(ua.account.aor)
|
val latestPeerUri = CallHistoryNew.aorLatestPeerUri(ua.account.aor)
|
||||||
if (latestPeerUri != null)
|
if (latestPeerUri != null)
|
||||||
callUri.value = Utils.friendlyUri(ctx, latestPeerUri, ua.account)
|
dialerState.callUri.value = Utils.friendlyUri(ctx, latestPeerUri, ua.account)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1953,7 +1970,7 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String) {
|
|||||||
else if (!BaresipService.requestAudioFocus(ctx))
|
else if (!BaresipService.requestAudioFocus(ctx))
|
||||||
Toast.makeText(ctx, R.string.audio_focus_denied, Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, R.string.audio_focus_denied, Toast.LENGTH_SHORT).show()
|
||||||
else {
|
else {
|
||||||
callButtonEnabled.value = false
|
viewModel.dialerState.callButtonEnabled.value = false
|
||||||
if (Build.VERSION.SDK_INT < 31) {
|
if (Build.VERSION.SDK_INT < 31) {
|
||||||
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
|
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
|
||||||
am.mode = AudioManager.MODE_IN_COMMUNICATION
|
am.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||||
@@ -1984,18 +2001,28 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun answer(ctx: Context, call: Call) {
|
||||||
|
Log.d(TAG, "AoR ${call.ua.account.aor} answering call from ${call.callUri.value}")
|
||||||
|
val intent = Intent(ctx, BaresipService::class.java)
|
||||||
|
intent.action = "Call Answer"
|
||||||
|
intent.putExtra("uap", call.ua.uap)
|
||||||
|
intent.putExtra("callp", call.callp)
|
||||||
|
ctx.startService(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reject(call: Call) {
|
||||||
|
Log.d(TAG, "AoR ${call.ua.account.aor} rejecting call ${call.callp} from ${call.callUri.value}")
|
||||||
|
call.rejected = true
|
||||||
|
Api.ua_hangup(call.ua.uap, call.callp, 486, "Busy Here")
|
||||||
|
}
|
||||||
|
|
||||||
private fun runCall(ctx: Context, viewModel: ViewModel, ua: UserAgent, uri: String) {
|
private fun runCall(ctx: Context, viewModel: ViewModel, ua: UserAgent, uri: String) {
|
||||||
callRunnable = Runnable {
|
callRunnable = Runnable {
|
||||||
callRunnable = null
|
callRunnable = null
|
||||||
if (!call(ctx, viewModel, ua, uri)) {
|
val newCall = call(ctx, viewModel, ua, uri)
|
||||||
|
if (newCall == null) {
|
||||||
BaresipService.abandonAudioFocus(ctx)
|
BaresipService.abandonAudioFocus(ctx)
|
||||||
showCallButton.value = true
|
viewModel.dialerState.callButtonEnabled.value = true
|
||||||
callButtonEnabled.value = true
|
|
||||||
showCancelButton.value = false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
showCallButton.value = false
|
|
||||||
showCancelButton.value = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callHandler.postDelayed(callRunnable!!, BaresipService.audioDelay)
|
callHandler.postDelayed(callRunnable!!, BaresipService.audioDelay)
|
||||||
@@ -2003,11 +2030,10 @@ private fun runCall(ctx: Context, viewModel: ViewModel, ua: UserAgent, uri: Stri
|
|||||||
|
|
||||||
private fun call(
|
private fun call(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
viewModel: ViewModel,
|
viewModel: ViewModel, ua: UserAgent,
|
||||||
ua: UserAgent,
|
|
||||||
uri: String,
|
uri: String,
|
||||||
onHoldCall: Call? = null
|
onHoldCall: Call? = null
|
||||||
): Boolean {
|
): Call? {
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
val callp = ua.callAlloc(0L, Api.VIDMODE_OFF)
|
val callp = ua.callAlloc(0L, Api.VIDMODE_OFF)
|
||||||
return if (callp != 0L) {
|
return if (callp != 0L) {
|
||||||
@@ -2019,7 +2045,7 @@ private fun call(
|
|||||||
onHoldCall.newCall = call
|
onHoldCall.newCall = call
|
||||||
if (call.connect(uri)) {
|
if (call.connect(uri)) {
|
||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
true
|
call
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "call_connect $callp failed")
|
Log.w(TAG, "call_connect $callp failed")
|
||||||
if (onHoldCall != null)
|
if (onHoldCall != null)
|
||||||
@@ -2027,35 +2053,11 @@ private fun call(
|
|||||||
call.remove()
|
call.remove()
|
||||||
call.destroy()
|
call.destroy()
|
||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
false
|
null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "callAlloc for ${ua.uap} to $uri failed")
|
Log.w(TAG, "callAlloc for ${ua.uap} to $uri failed")
|
||||||
false
|
null
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun answer(ctx: Context, viewModel: ViewModel) {
|
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
Log.d(TAG, "AoR ${ua.account.aor} answering call from ${callUri.value}")
|
|
||||||
val intent = Intent(ctx, BaresipService::class.java)
|
|
||||||
intent.action = "Call Answer"
|
|
||||||
intent.putExtra("uap", ua.uap)
|
|
||||||
intent.putExtra("callp", call.callp)
|
|
||||||
ctx.startService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reject(viewModel: ViewModel) {
|
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
val callp = call.callp
|
|
||||||
Log.d(TAG, "AoR ${ua.account.aor} rejecting call $callp from ${callUri.value}")
|
|
||||||
call.rejected = true
|
|
||||||
Api.ua_hangup(ua.uap, callp, 486, "Busy Here")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2108,10 +2110,11 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
|
|||||||
viewModel.updateMicIcon(Icons.Filled.Mic)
|
viewModel.updateMicIcon(Icons.Filled.Mic)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
viewModel.dialerState.callUri.value = ""
|
||||||
pullToRefreshEnabled.value = false
|
pullToRefreshEnabled.value = false
|
||||||
call.callUriEnabled.value = false
|
call.callUriEnabled.value = false
|
||||||
val isLandscape = ctx.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
val isLandscape = ctx.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
if (isLandscape || call.held || call.status != "connected") {
|
if (isLandscape || call.held || call.status.value != "connected") {
|
||||||
call.focusDtmf.value = false
|
call.focusDtmf.value = false
|
||||||
call.dtmfEnabled.value = !call.held
|
call.dtmfEnabled.value = !call.held
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
@@ -2123,9 +2126,9 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
|
|||||||
call.focusDtmf.value = true
|
call.focusDtmf.value = true
|
||||||
viewModel.requestShowKeyboard()
|
viewModel.requestShowKeyboard()
|
||||||
}
|
}
|
||||||
when (call.status) {
|
when (call.status.value) {
|
||||||
"outgoing", "transferring", "answered" -> {
|
"outgoing", "transferring", "answered" -> {
|
||||||
call.callUriLabel.value = if (call.status == "answered")
|
call.callUriLabel.value = if (call.status.value == "answered")
|
||||||
ctx.getString(R.string.incoming_call_from_dots)
|
ctx.getString(R.string.incoming_call_from_dots)
|
||||||
else
|
else
|
||||||
ctx.getString(R.string.outgoing_call_to_dots)
|
ctx.getString(R.string.outgoing_call_to_dots)
|
||||||
@@ -2133,7 +2136,7 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
|
|||||||
call.showCallTimer.value = false
|
call.showCallTimer.value = false
|
||||||
call.securityIconTint.value = -1
|
call.securityIconTint.value = -1
|
||||||
call.showCallButton.value = false
|
call.showCallButton.value = false
|
||||||
call.showCancelButton.value = call.status == "outgoing"
|
call.showCancelButton.value = call.status.value == "outgoing"
|
||||||
call.showHangupButton.value = !call.showCancelButton.value
|
call.showHangupButton.value = !call.showCancelButton.value
|
||||||
call.showAnswerRejectButtons.value = false
|
call.showAnswerRejectButtons.value = false
|
||||||
call.showOnHoldNotice.value = false
|
call.showOnHoldNotice.value = false
|
||||||
@@ -2248,7 +2251,8 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
if (!BaresipService.isMainVisible)
|
if (!BaresipService.isMainVisible)
|
||||||
viewModel.navigateToHome()
|
viewModel.navigateToHome()
|
||||||
spinToAor(viewModel, aor)
|
spinToAor(viewModel, aor)
|
||||||
showCall(ctx, viewModel, ua)
|
val callp = params[1] as Long
|
||||||
|
showCall(ctx, viewModel, ua, Call.ofCallp(callp))
|
||||||
}
|
}
|
||||||
"call redirect" -> {
|
"call redirect" -> {
|
||||||
val redirectUri = ev[1]
|
val redirectUri = ev[1]
|
||||||
@@ -2275,8 +2279,13 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
}
|
}
|
||||||
"call established" -> {
|
"call established" -> {
|
||||||
if (aor == viewModel.selectedAor.value) {
|
if (aor == viewModel.selectedAor.value) {
|
||||||
dtmfText.value = ""
|
viewModel.dialerState.callButtonEnabled.value = true // Re-enable dialer
|
||||||
showCall(ctx, viewModel, ua)
|
val callp = params[1] as Long
|
||||||
|
val call = Call.ofCallp(callp)
|
||||||
|
if (call != null) {
|
||||||
|
call.dtmfText.value = ""
|
||||||
|
}
|
||||||
|
showCall(ctx, viewModel, ua, call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"call update" -> {
|
"call update" -> {
|
||||||
@@ -2301,14 +2310,14 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
}
|
}
|
||||||
call.zid = ev[2]
|
call.zid = ev[2]
|
||||||
if (aor == viewModel.selectedAor.value)
|
if (aor == viewModel.selectedAor.value)
|
||||||
securityIconTint.intValue = call.security
|
call.securityIconTint.value = call.security
|
||||||
}
|
}
|
||||||
negativeText.value = ctx.getString(R.string.no)
|
negativeText.value = ctx.getString(R.string.no)
|
||||||
onNegativeClicked.value = {
|
onNegativeClicked.value = {
|
||||||
call.security = R.color.colorTrafficYellow
|
call.security = R.color.colorTrafficYellow
|
||||||
call.zid = ev[2]
|
call.zid = ev[2]
|
||||||
if (aor == viewModel.selectedAor.value)
|
if (aor == viewModel.selectedAor.value)
|
||||||
securityIconTint.intValue = R.color.colorTrafficYellow
|
call.securityIconTint.value = R.color.colorTrafficYellow
|
||||||
onNegativeClicked.value = {}
|
onNegativeClicked.value = {}
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
@@ -2321,7 +2330,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (aor == viewModel.selectedAor.value)
|
if (aor == viewModel.selectedAor.value)
|
||||||
securityIconTint.intValue = call.security
|
call.securityIconTint.value = call.security
|
||||||
}
|
}
|
||||||
"call transfer", "transfer show" -> {
|
"call transfer", "transfer show" -> {
|
||||||
if (!BaresipService.isMainVisible)
|
if (!BaresipService.isMainVisible)
|
||||||
@@ -2364,24 +2373,13 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
}
|
}
|
||||||
"call closed" -> {
|
"call closed" -> {
|
||||||
val call = ua.currentCall()
|
|
||||||
if (call != null) {
|
|
||||||
call.resume()
|
|
||||||
callDuration = call.duration()
|
|
||||||
showCallTimer.value = true
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
showCallTimer.value = false
|
|
||||||
securityIconTint.intValue = -1
|
|
||||||
}
|
|
||||||
if (aor == viewModel.selectedAor.value) {
|
if (aor == viewModel.selectedAor.value) {
|
||||||
|
viewModel.dialerState.callButtonEnabled.value = true
|
||||||
ua.account.resumeUri = ""
|
ua.account.resumeUri = ""
|
||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
if (acc.missedCalls)
|
if (acc.missedCalls)
|
||||||
viewModel.triggerAccountUpdate()
|
viewModel.triggerAccountUpdate()
|
||||||
}
|
}
|
||||||
//if (kgm.isDeviceLocked)
|
|
||||||
// this.setShowWhenLocked(false)
|
|
||||||
}
|
}
|
||||||
"message", "message show", "message reply" -> {
|
"message", "message show", "message reply" -> {
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
@@ -2404,6 +2402,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
else -> Log.e(TAG, "Unknown event '${ev[0]}'")
|
else -> Log.e(TAG, "Unknown event '${ev[0]}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.updateCalls(Call.calls().toList())
|
||||||
handleNextEvent()
|
handleNextEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2423,10 +2422,10 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
|
|||||||
Log.w(TAG, "handleIntent 'call' did not find ua $uap")
|
Log.w(TAG, "handleIntent 'call' did not find ua $uap")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
callUri.value = intent.getStringExtra("peer")!!
|
viewModel.dialerState.callUri.value = intent.getStringExtra("peer")!!
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
if (ev[0] == "call")
|
if (ev[0] == "call")
|
||||||
callClick(ctx, viewModel)
|
callClick(ctx, viewModel, viewModel.dialerState)
|
||||||
}
|
}
|
||||||
"call show", "call answer" -> {
|
"call show", "call answer" -> {
|
||||||
val callp = intent.getLongExtra("callp", 0L)
|
val callp = intent.getLongExtra("callp", 0L)
|
||||||
@@ -2438,7 +2437,7 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
|
|||||||
val ua = call.ua
|
val ua = call.ua
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
if (ev[0] == "call answer")
|
if (ev[0] == "call answer")
|
||||||
answer(ctx, viewModel)
|
answer(ctx, call)
|
||||||
else
|
else
|
||||||
BaresipService.postServiceEvent(ServiceEvent(
|
BaresipService.postServiceEvent(ServiceEvent(
|
||||||
"call incoming",
|
"call incoming",
|
||||||
@@ -2549,8 +2548,8 @@ fun callAction(ctx: Context, viewModel: ViewModel, uri: Uri?, action: String) {
|
|||||||
private fun redirect(ctx: Context, viewModel: ViewModel, ua: UserAgent, redirectUri: String) {
|
private fun redirect(ctx: Context, viewModel: ViewModel, ua: UserAgent, redirectUri: String) {
|
||||||
if (ua.account.aor != viewModel.selectedAor.value)
|
if (ua.account.aor != viewModel.selectedAor.value)
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
callUri.value = redirectUri
|
viewModel.dialerState.callUri.value = redirectUri
|
||||||
callClick(ctx, viewModel)
|
callClick(ctx, viewModel, viewModel.dialerState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun acceptTransfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, call: Call, uri: String) {
|
private fun acceptTransfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, call: Call, uri: String) {
|
||||||
|
|||||||
@@ -4,13 +4,40 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Mic
|
import androidx.compose.material.icons.filled.Mic
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// Sealed class for type-safe navigation events
|
||||||
|
sealed class NavigationCommand {
|
||||||
|
object NavigateToHome : NavigationCommand()
|
||||||
|
data class NavigateToCalls(val aor: String) : NavigationCommand()
|
||||||
|
data class NavigateToChat(val aor: String, val peerUri: String) : NavigationCommand()
|
||||||
|
}
|
||||||
|
|
||||||
class ViewModel: ViewModel() {
|
class ViewModel: ViewModel() {
|
||||||
|
|
||||||
|
// A map to store message drafts. Key is "aor:peerUri"
|
||||||
|
private val messageDrafts = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
fun getAorPeerMessage(aor: String, peerUri: String): String {
|
||||||
|
return messageDrafts["$aor:$peerUri"] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAorPeerMessage(aor: String, peerUri: String, message: String) {
|
||||||
|
val key = "$aor:$peerUri"
|
||||||
|
if (message.isEmpty()) {
|
||||||
|
messageDrafts.remove(key)
|
||||||
|
} else {
|
||||||
|
messageDrafts[key] = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class DialerState(
|
data class DialerState(
|
||||||
val callUri: MutableState<String> = mutableStateOf(""),
|
val callUri: MutableState<String> = mutableStateOf(""),
|
||||||
val callUriEnabled: MutableState<Boolean> = mutableStateOf(true),
|
val callUriEnabled: MutableState<Boolean> = mutableStateOf(true),
|
||||||
@@ -43,11 +70,24 @@ class ViewModel: ViewModel() {
|
|||||||
private val _hideKeyboard = MutableStateFlow(0)
|
private val _hideKeyboard = MutableStateFlow(0)
|
||||||
val hideKeyboard = _hideKeyboard.asStateFlow()
|
val hideKeyboard = _hideKeyboard.asStateFlow()
|
||||||
|
|
||||||
|
private val _navigationCommand = MutableSharedFlow<NavigationCommand>()
|
||||||
|
val navigationCommand = _navigationCommand.asSharedFlow()
|
||||||
|
|
||||||
|
private var _selectedCallRow: CallRow? = null
|
||||||
|
|
||||||
|
fun selectCallRow(callRow: CallRow) {
|
||||||
|
_selectedCallRow = callRow
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeSelectedCallRow(): CallRow? {
|
||||||
|
val callRow = _selectedCallRow
|
||||||
|
_selectedCallRow = null
|
||||||
|
return callRow
|
||||||
|
}
|
||||||
|
|
||||||
fun onNewMessageReceived(aor: String, peerUri: String) {
|
fun onNewMessageReceived(aor: String, peerUri: String) {
|
||||||
val acc = Account.ofAor(aor)
|
viewModelScope.launch {
|
||||||
if (acc != null) {
|
_navigationCommand.emit(NavigationCommand.NavigateToChat(aor, peerUri))
|
||||||
acc.unreadMessages = true
|
|
||||||
triggerAccountUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +100,7 @@ class ViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun triggerAccountUpdate() {
|
fun triggerAccountUpdate() {
|
||||||
_accountUpdate.value = _accountUpdate.value + 1
|
_accountUpdate.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMicIcon(icon: ImageVector) {
|
fun updateMicIcon(icon: ImageVector) {
|
||||||
@@ -72,19 +112,23 @@ class ViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun requestShowKeyboard() {
|
fun requestShowKeyboard() {
|
||||||
_showKeyboard.value = _showKeyboard.value + 1
|
_showKeyboard.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestHideKeyboard() {
|
fun requestHideKeyboard() {
|
||||||
_hideKeyboard.value = _hideKeyboard.value + 1
|
_hideKeyboard.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigateToHome() {
|
fun navigateToHome() {
|
||||||
// This function can be used to navigate to the main screen
|
viewModelScope.launch {
|
||||||
|
_navigationCommand.emit(NavigationCommand.NavigateToHome)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigateToCalls(aor: String) {
|
fun navigateToCalls(aor: String) {
|
||||||
// This function can be used to navigate to the calls screen
|
viewModelScope.launch {
|
||||||
|
_navigationCommand.emit(NavigationCommand.NavigateToCalls(aor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user