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