Added support for conference 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" -> {
|
||||||
@ -1104,6 +1104,8 @@ class BaresipService: Service() {
|
|||||||
call.onHoldCall = null
|
call.onHoldCall = null
|
||||||
}
|
}
|
||||||
call.remove()
|
call.remove()
|
||||||
|
if (call.conferenceCall && ua.calls().isEmpty())
|
||||||
|
Api.module_unload("mixminus")
|
||||||
val reason = ev[1]
|
val reason = ev[1]
|
||||||
val tone = ev[2]
|
val tone = ev[2]
|
||||||
if (tone == "busy") {
|
if (tone == "busy") {
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
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, initialStatus: String) {
|
||||||
|
|
||||||
|
var status: MutableState<String> = mutableStateOf(initialStatus)
|
||||||
|
|
||||||
var onhold = false
|
var onhold = false
|
||||||
var held = false
|
var held = false
|
||||||
|
var terminated = false
|
||||||
|
var conferenceCall = false
|
||||||
var onHoldCall: Call? = null
|
var onHoldCall: Call? = null
|
||||||
var newCall: Call? = null
|
var newCall: Call? = null
|
||||||
var rejected = false // Incoming rejected by user or outgoing fails but not due to 408 or 480
|
var rejected = false // Incoming rejected by user or outgoing fails but not due to 408 or 480
|
||||||
@ -15,8 +22,29 @@ 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(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove() {
|
fun remove() {
|
||||||
@ -107,8 +135,8 @@ 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)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -40,8 +40,9 @@ class UserAgent(val uap: Long) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns call of UA that was added last or NULL
|
||||||
fun currentCall(): Call? {
|
fun currentCall(): Call? {
|
||||||
for (c in BaresipService.calls)
|
for (c in BaresipService.calls.reversed())
|
||||||
if (c.ua == this)
|
if (c.ua == this)
|
||||||
return c
|
return c
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -1,106 +1,129 @@
|
|||||||
package com.tutpro.baresip
|
package com.tutpro.baresip
|
||||||
|
|
||||||
import android.app.Application
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.runtime.mutableStateOf
|
||||||
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.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
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.filled.Mic
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// Sealed class for type-safe navigation events
|
||||||
sealed class NavigationCommand {
|
sealed class NavigationCommand {
|
||||||
data class NavigateToChat(val aor: String, val peer: String?) : NavigationCommand()
|
object NavigateToHome : NavigationCommand()
|
||||||
data class NavigateToCalls(val aor: String) : NavigationCommand()
|
data class NavigateToCalls(val aor: String) : NavigationCommand()
|
||||||
object NavigateToHome: NavigationCommand()
|
data class NavigateToChat(val aor: String, val peerUri: String) : NavigationCommand()
|
||||||
// Add other navigation commands as needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AorPeer(val aor: String, val peer: String)
|
class ViewModel: ViewModel() {
|
||||||
|
|
||||||
class ViewModel(application: Application) : AndroidViewModel(application) {
|
// A map to store message drafts. Key is "aor:peerUri"
|
||||||
|
private val messageDrafts = mutableMapOf<String, String>()
|
||||||
private val _selectedAor = MutableStateFlow("")
|
|
||||||
val selectedAor: StateFlow<String> = _selectedAor.asStateFlow()
|
|
||||||
|
|
||||||
fun updateSelectedAor(newValue: String) {
|
|
||||||
_selectedAor.value = newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
private val _accountUpdate = MutableStateFlow(0)
|
|
||||||
val accountUpdate: StateFlow<Int> = _accountUpdate
|
|
||||||
|
|
||||||
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 {
|
fun getAorPeerMessage(aor: String, peerUri: String): String {
|
||||||
val key = AorPeer(aor, peerUri)
|
return messageDrafts["$aor:$peerUri"] ?: ""
|
||||||
return _aorPeerMessage.value.getOrDefault(key, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _showKeyboard = mutableIntStateOf(0)
|
fun updateAorPeerMessage(aor: String, peerUri: String, message: String) {
|
||||||
val showKeyboard: State<Int> = _showKeyboard
|
val key = "$aor:$peerUri"
|
||||||
|
if (message.isEmpty()) {
|
||||||
fun requestShowKeyboard() {
|
messageDrafts.remove(key)
|
||||||
_showKeyboard.intValue++
|
} else {
|
||||||
|
messageDrafts[key] = message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _hideKeyboard = mutableIntStateOf(0)
|
data class DialerState(
|
||||||
val hideKeyboard: State<Int> = _hideKeyboard
|
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 showCallConferenceButton: MutableState<Boolean> = mutableStateOf(true),
|
||||||
|
val callButtonsEnabled: MutableState<Boolean> = mutableStateOf(true),
|
||||||
|
val conferenceCall: MutableState<Boolean> = mutableStateOf(false),
|
||||||
|
)
|
||||||
|
|
||||||
fun requestHideKeyboard() {
|
val dialerState = DialerState()
|
||||||
_hideKeyboard.intValue++
|
|
||||||
}
|
private val _calls = MutableStateFlow<List<Call>>(emptyList())
|
||||||
|
val calls = _calls.asStateFlow()
|
||||||
|
|
||||||
|
private val _selectedAor = MutableStateFlow("")
|
||||||
|
val selectedAor = _selectedAor.asStateFlow()
|
||||||
|
|
||||||
|
private val _accountUpdate = MutableStateFlow(0)
|
||||||
|
val accountUpdate = _accountUpdate.asStateFlow()
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
|
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) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_navigationCommand.emit(NavigationCommand.NavigateToChat(aor, peerUri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCalls(calls: List<Call>) {
|
||||||
|
_calls.value = calls
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSelectedAor(aor: String) {
|
||||||
|
_selectedAor.value = aor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun triggerAccountUpdate() {
|
||||||
|
_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 += 1
|
||||||
fun selectCallRow(callRow: CallRow) {
|
|
||||||
_selectedCallRow.value = callRow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consumeSelectedCallRow(): CallRow? {
|
fun requestHideKeyboard() {
|
||||||
val callRow = _selectedCallRow.value
|
_hideKeyboard.value += 1
|
||||||
_selectedCallRow.value = null
|
|
||||||
return callRow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _navigationCommand = MutableSharedFlow<NavigationCommand>()
|
fun navigateToHome() {
|
||||||
val navigationCommand = _navigationCommand.asSharedFlow()
|
|
||||||
|
|
||||||
fun onNewMessageReceived(aor: String, peer: String) {
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_navigationCommand.emit(NavigationCommand.NavigateToChat(aor, peer))
|
_navigationCommand.emit(NavigationCommand.NavigateToHome)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,9 +133,4 @@ class ViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigateToHome() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
_navigationCommand.emit(NavigationCommand.NavigateToHome)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user