Started work on multiple calls

This commit is contained in:
Juha Heinanen
2026-01-12 01:02:21 +02:00
parent feecb7dcf0
commit 04656b4449
3 changed files with 136 additions and 172 deletions
@@ -1,5 +1,8 @@
package com.tutpro.baresip package com.tutpro.baresip
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
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, var status: String) {
@@ -15,6 +18,27 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
var referTo = "" var referTo = ""
var dumpfiles = arrayOf("", "") var dumpfiles = arrayOf("", "")
// UI state properties
val callUri: MutableState<String> = mutableStateOf("")
val callUriEnabled: MutableState<Boolean> = mutableStateOf(true)
val callUriLabel: MutableState<String> = mutableStateOf("")
val securityIconTint: MutableState<Int> = mutableIntStateOf(-1)
val showCallTimer: MutableState<Boolean> = mutableStateOf(false)
var callDuration: Int = 0
val showSuggestions: MutableState<Boolean> = mutableStateOf(false)
val showCallButton: MutableState<Boolean> = mutableStateOf(true)
val callButtonEnabled: MutableState<Boolean> = mutableStateOf(true)
val showCancelButton: MutableState<Boolean> = mutableStateOf(false)
val showAnswerRejectButtons: MutableState<Boolean> = mutableStateOf(false)
val showHangupButton: MutableState<Boolean> = mutableStateOf(false)
val showOnHoldNotice: MutableState<Boolean> = mutableStateOf(false)
val callOnHold: MutableState<Boolean> = mutableStateOf(false)
val transferButtonEnabled: MutableState<Boolean> = mutableStateOf(false)
val callTransfer: MutableState<Boolean> = mutableStateOf(false)
val dtmfText: MutableState<String> = mutableStateOf("")
val dtmfEnabled: MutableState<Boolean> = mutableStateOf(false)
val focusDtmf: MutableState<Boolean> = mutableStateOf(false)
fun add() { fun add() {
BaresipService.calls.add(0, this) BaresipService.calls.add(0, this)
} }
@@ -841,26 +841,6 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
} }
} }
private val callUri = mutableStateOf("")
private var callUriEnabled = mutableStateOf(true)
private val callUriLabel = mutableStateOf("")
private var securityIconTint = mutableIntStateOf(-1)
private val showCallTimer = mutableStateOf(false)
private var callDuration = 0
private val showSuggestions = mutableStateOf(false)
private val showCallButton = mutableStateOf(true)
private val callButtonEnabled = mutableStateOf(true)
private val showCancelButton = mutableStateOf(false)
private val showAnswerRejectButtons = mutableStateOf(false)
private val showHangupButton = mutableStateOf(false)
private val showOnHoldNotice = mutableStateOf(false)
private var callOnHold = mutableStateOf(false)
private val transferButtonEnabled = mutableStateOf(false)
private val callTransfer = mutableStateOf(false)
private var dtmfText = mutableStateOf("")
private val dtmfEnabled = mutableStateOf(false)
private val focusDtmf = mutableStateOf(false)
private val alertTitle = mutableStateOf("") private val alertTitle = mutableStateOf("")
private val alertMessage = mutableStateOf("") private val alertMessage = mutableStateOf("")
private val showAlert = mutableStateOf(false) private val showAlert = mutableStateOf(false)
@@ -2116,24 +2096,12 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
val call = showCall ?: ua.currentCall() val call = showCall ?: ua.currentCall()
if (call == null) { if (call == null) {
pullToRefreshEnabled.value = true pullToRefreshEnabled.value = true
if (ua.account.resumeUri != "") viewModel.dialerState.callUri.value = ua.account.resumeUri
callUri.value = ua.account.resumeUri viewModel.dialerState.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
else viewModel.dialerState.callUriEnabled.value = true
callUri.value = "" viewModel.dialerState.showCallButton.value = true
callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots) viewModel.dialerState.callButtonEnabled.value = true
callUriEnabled.value = true viewModel.dialerState.showSuggestions.value = false
showCallTimer.value = false
securityIconTint.intValue = -1
showHangupButton.value = false
callTransfer.value = false
dtmfText.value = ""
dtmfEnabled.value = false
focusDtmf.value = false
showCallButton.value = true
callButtonEnabled.value = true
showCancelButton.value = false
showAnswerRejectButtons.value = false
showOnHoldNotice.value = false
dialpadButtonEnabled.value = true dialpadButtonEnabled.value = true
if (BaresipService.isMicMuted) { if (BaresipService.isMicMuted) {
BaresipService.isMicMuted = false BaresipService.isMicMuted = false
@@ -2141,84 +2109,84 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
} }
} else { } else {
pullToRefreshEnabled.value = false pullToRefreshEnabled.value = false
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 != "connected") {
focusDtmf.value = false call.focusDtmf.value = false
dtmfEnabled.value = !call.held call.dtmfEnabled.value = !call.held
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
viewModel.requestHideKeyboard() viewModel.requestHideKeyboard()
}, 25) }, 25)
} }
else { else {
dtmfEnabled.value = true call.dtmfEnabled.value = true
focusDtmf.value = true call.focusDtmf.value = true
viewModel.requestShowKeyboard() viewModel.requestShowKeyboard()
} }
when (call.status) { when (call.status) {
"outgoing", "transferring", "answered" -> { "outgoing", "transferring", "answered" -> {
callUriLabel.value = if (call.status == "answered") call.callUriLabel.value = if (call.status == "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)
callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account) call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
showCallTimer.value = false call.showCallTimer.value = false
securityIconTint.intValue = -1 call.securityIconTint.value = -1
showCallButton.value = false call.showCallButton.value = false
showCancelButton.value = call.status == "outgoing" call.showCancelButton.value = call.status == "outgoing"
showHangupButton.value = !showCancelButton.value call.showHangupButton.value = !call.showCancelButton.value
showAnswerRejectButtons.value = false call.showAnswerRejectButtons.value = false
showOnHoldNotice.value = false call.showOnHoldNotice.value = false
dialpadButtonEnabled.value = false dialpadButtonEnabled.value = false
} }
"incoming" -> { "incoming" -> {
showCallTimer.value = false call.showCallTimer.value = false
securityIconTint.intValue = -1 call.securityIconTint.value = -1
val uri = call.diverterUri() val uri = call.diverterUri()
if (uri != "") { if (uri != "") {
callUriLabel.value = ctx.getString(R.string.diverted_by_dots) call.callUriLabel.value = ctx.getString(R.string.diverted_by_dots)
callUri.value = Utils.friendlyUri(ctx, uri, ua.account) call.callUri.value = Utils.friendlyUri(ctx, uri, ua.account)
} }
else { else {
callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots) call.callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots)
callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account) call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
} }
showCallButton.value = false call.showCallButton.value = false
showCancelButton.value = false call.showCancelButton.value = false
showHangupButton.value = false call.showHangupButton.value = false
showAnswerRejectButtons.value = true call.showAnswerRejectButtons.value = true
showOnHoldNotice.value = false call.showOnHoldNotice.value = false
dialpadButtonEnabled.value = false dialpadButtonEnabled.value = false
} }
"connected" -> { "connected" -> {
if (call.referTo != "") { if (call.referTo != "") {
callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots) call.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
callUri.value = Utils.friendlyUri(ctx, call.referTo, ua.account) call.callUri.value = Utils.friendlyUri(ctx, call.referTo, ua.account)
transferButtonEnabled.value = false call.transferButtonEnabled.value = false
} else { } else {
if (call.dir == "out") { if (call.dir == "out") {
callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots) call.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account) call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
} else { } else {
callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots) call.callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots)
callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account) call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
} }
transferButtonEnabled.value = true call.transferButtonEnabled.value = true
} }
callTransfer.value = call.onHoldCall != null call.callTransfer.value = call.onHoldCall != null
callDuration = call.duration() call.callDuration = call.duration()
showCallTimer.value = true call.showCallTimer.value = true
if (ua.account.mediaEnc == "") if (ua.account.mediaEnc == "")
securityIconTint.intValue = -1 call.securityIconTint.value = -1
else else
securityIconTint.intValue = call.security call.securityIconTint.value = call.security
showCallButton.value = false call.showCallButton.value = false
showCancelButton.value = false call.showCancelButton.value = false
showHangupButton.value = true call.showHangupButton.value = true
showAnswerRejectButtons.value = false call.showAnswerRejectButtons.value = false
callOnHold.value = call.onhold call.callOnHold.value = call.onhold
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
showOnHoldNotice.value = call.held call.showOnHoldNotice.value = call.held
}, 100) }, 100)
} }
} }
@@ -1,118 +1,90 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.app.Application import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
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 androidx.compose.runtime.State
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
sealed class NavigationCommand { class ViewModel: ViewModel() {
data class NavigateToChat(val aor: String, val peer: String?) : NavigationCommand()
data class NavigateToCalls(val aor: String) : NavigationCommand()
object NavigateToHome: NavigationCommand()
// Add other navigation commands as needed
}
data class AorPeer(val aor: String, val peer: String) data class DialerState(
val callUri: MutableState<String> = mutableStateOf(""),
val callUriEnabled: MutableState<Boolean> = mutableStateOf(true),
val callUriLabel: MutableState<String> = mutableStateOf(""),
val showSuggestions: MutableState<Boolean> = mutableStateOf(false),
val showCallButton: MutableState<Boolean> = mutableStateOf(true),
val callButtonEnabled: MutableState<Boolean> = mutableStateOf(true),
)
class ViewModel(application: Application) : AndroidViewModel(application) { val dialerState = DialerState()
private val _calls = MutableStateFlow<List<Call>>(emptyList())
val calls = _calls.asStateFlow()
private val _selectedAor = MutableStateFlow("") private val _selectedAor = MutableStateFlow("")
val selectedAor: StateFlow<String> = _selectedAor.asStateFlow() val selectedAor = _selectedAor.asStateFlow()
fun updateSelectedAor(newValue: String) {
_selectedAor.value = newValue
}
private val _accountUpdate = MutableStateFlow(0) private val _accountUpdate = MutableStateFlow(0)
val accountUpdate: StateFlow<Int> = _accountUpdate val accountUpdate = _accountUpdate.asStateFlow()
fun triggerAccountUpdate() {
_accountUpdate.value++
}
private val _aorPeerMessage = MutableStateFlow(mutableMapOf<AorPeer, String>())
fun updateAorPeerMessage(aor: String, peerUri: String, message: String) {
val key = AorPeer(aor, peerUri)
if (message == "")
_aorPeerMessage.value.remove(key)
else
_aorPeerMessage.value[key] = message
}
fun getAorPeerMessage(aor: String, peerUri: String): String {
val key = AorPeer(aor, peerUri)
return _aorPeerMessage.value.getOrDefault(key, "")
}
private val _showKeyboard = mutableIntStateOf(0)
val showKeyboard: State<Int> = _showKeyboard
fun requestShowKeyboard() {
_showKeyboard.intValue++
}
private val _hideKeyboard = mutableIntStateOf(0)
val hideKeyboard: State<Int> = _hideKeyboard
fun requestHideKeyboard() {
_hideKeyboard.intValue++
}
private val _micIcon = MutableStateFlow(Icons.Filled.Mic) private val _micIcon = MutableStateFlow(Icons.Filled.Mic)
val micIcon: StateFlow<ImageVector> = _micIcon.asStateFlow() val micIcon = _micIcon.asStateFlow()
private val _isDialpadVisible = MutableStateFlow(false)
val isDialpadVisible = _isDialpadVisible.asStateFlow()
private val _showKeyboard = MutableStateFlow(0)
val showKeyboard = _showKeyboard.asStateFlow()
private val _hideKeyboard = MutableStateFlow(0)
val hideKeyboard = _hideKeyboard.asStateFlow()
fun onNewMessageReceived(aor: String, peerUri: String) {
val acc = Account.ofAor(aor)
if (acc != null) {
acc.unreadMessages = true
triggerAccountUpdate()
}
}
fun updateCalls(calls: List<Call>) {
_calls.value = calls
}
fun updateSelectedAor(aor: String) {
_selectedAor.value = aor
}
fun triggerAccountUpdate() {
_accountUpdate.value = _accountUpdate.value + 1
}
fun updateMicIcon(icon: ImageVector) { fun updateMicIcon(icon: ImageVector) {
_micIcon.value = icon _micIcon.value = icon
} }
private val _isDialpadVisible = MutableStateFlow(false)
val isDialpadVisible: StateFlow<Boolean> = _isDialpadVisible
fun toggleDialpadVisibility() { fun toggleDialpadVisibility() {
_isDialpadVisible.value = !_isDialpadVisible.value _isDialpadVisible.value = !_isDialpadVisible.value
} }
private val _selectedCallRow = MutableStateFlow<CallRow?>(null) fun requestShowKeyboard() {
_showKeyboard.value = _showKeyboard.value + 1
fun selectCallRow(callRow: CallRow) {
_selectedCallRow.value = callRow
} }
fun consumeSelectedCallRow(): CallRow? { fun requestHideKeyboard() {
val callRow = _selectedCallRow.value _hideKeyboard.value = _hideKeyboard.value + 1
_selectedCallRow.value = null
return callRow
}
private val _navigationCommand = MutableSharedFlow<NavigationCommand>()
val navigationCommand = _navigationCommand.asSharedFlow()
fun onNewMessageReceived(aor: String, peer: String) {
viewModelScope.launch {
_navigationCommand.emit(NavigationCommand.NavigateToChat(aor, peer))
}
}
fun navigateToCalls(aor: String) {
viewModelScope.launch {
_navigationCommand.emit(NavigationCommand.NavigateToCalls(aor))
}
} }
fun navigateToHome() { fun navigateToHome() {
viewModelScope.launch { // This function can be used to navigate to the main screen
_navigationCommand.emit(NavigationCommand.NavigateToHome)
} }
fun navigateToCalls(aor: String) {
// This function can be used to navigate to the calls screen
} }
} }