mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Call transfer: start branching consult first action
This commit is contained in:
parent
8eeae51cc6
commit
bd8e46c84f
@ -198,7 +198,14 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
}
|
||||
is CallState.Connected -> {
|
||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||
if(state.transfereeName.hasValue()){
|
||||
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, state.transfereeName.get())
|
||||
views.callActionText.isVisible = true
|
||||
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) }
|
||||
views.callStatusText.text = state.formattedDuration
|
||||
configureCallInfo(state)
|
||||
}
|
||||
else if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||
views.smallIsHeldIcon.isVisible = true
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
@ -247,7 +254,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
state.callInfo.otherUserItem?.let {
|
||||
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
|
||||
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter)
|
||||
views.participantNameText.text = it.getBestName()
|
||||
if(state.transfereeName.hasValue()) {
|
||||
views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName())
|
||||
}else {
|
||||
views.participantNameText.text = it.getBestName()
|
||||
}
|
||||
if (blurAvatar) {
|
||||
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter)
|
||||
} else {
|
||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.call
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.call.audio.CallAudioManager
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
|
||||
sealed class VectorCallViewActions : VectorViewModelAction {
|
||||
object EndCall : VectorCallViewActions()
|
||||
@ -34,4 +35,5 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
||||
object ToggleCamera : VectorCallViewActions()
|
||||
object ToggleHDSD : VectorCallViewActions()
|
||||
object InitiateCallTransfer : VectorCallViewActions()
|
||||
object TransferCall: VectorCallViewActions()
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
|
||||
class VectorCallViewModel @AssistedInject constructor(
|
||||
@ -109,15 +110,24 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
val transfereeName = computeTransfereeNameIfAny(call)
|
||||
setState {
|
||||
copy(
|
||||
callState = Success(callState),
|
||||
canOpponentBeTransferred = call.capabilities.supportCallTransfer()
|
||||
canOpponentBeTransferred = call.capabilities.supportCallTransfer(),
|
||||
transfereeName = transfereeName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeTransfereeNameIfAny(call: MxCall): Optional<String> {
|
||||
val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return Optional.empty()
|
||||
val transfereeRoom = session.getRoomSummary(transfereeCall.roomId)
|
||||
val transfereeName = transfereeRoom?.displayName ?: "Unknown person"
|
||||
return Optional.from(transfereeName)
|
||||
}
|
||||
|
||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
||||
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
@ -186,7 +196,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
||||
formattedDuration = webRtcCall.formattedDuration(),
|
||||
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD,
|
||||
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer()
|
||||
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(),
|
||||
transfereeName = computeTransfereeNameIfAny(webRtcCall.mxCall)
|
||||
)
|
||||
}
|
||||
updateOtherKnownCall(webRtcCall)
|
||||
@ -273,9 +284,20 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
VectorCallViewEvents.ShowCallTransferScreen
|
||||
)
|
||||
}
|
||||
VectorCallViewActions.TransferCall -> {
|
||||
handleCallTransfer()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleCallTransfer() {
|
||||
viewModelScope.launch {
|
||||
val currentCall = call ?: return@launch
|
||||
val transfereeCall = callManager.getTransfereeForCallId(currentCall.callId) ?: return@launch
|
||||
currentCall.transferToCall(transfereeCall)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: VectorCallViewState): VectorCallViewModel
|
||||
|
@ -22,6 +22,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.call.audio.CallAudioManager
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
data class VectorCallViewState(
|
||||
val callId: String,
|
||||
@ -41,7 +42,8 @@ data class VectorCallViewState(
|
||||
val otherKnownCallInfo: CallInfo? = null,
|
||||
val callInfo: CallInfo = CallInfo(callId),
|
||||
val formattedDuration: String = "",
|
||||
val canOpponentBeTransferred: Boolean = false
|
||||
val canOpponentBeTransferred: Boolean = false,
|
||||
val transfereeName: Optional<String> = Optional.empty()
|
||||
) : MvRxState {
|
||||
|
||||
data class CallInfo(
|
||||
|
@ -28,13 +28,16 @@ import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
|
||||
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
|
||||
private val dialPadLookup: DialPadLookup,
|
||||
callManager: WebRtcCallManager)
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val callManager: WebRtcCallManager)
|
||||
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
@ -83,9 +86,18 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_viewEvents.post(CallTransferViewEvents.Loading)
|
||||
call?.transferToUser(action.selectedUserId, null)
|
||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||
if (action.consultFirst) {
|
||||
val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId)
|
||||
callManager.startOutgoingCall(
|
||||
signalingRoomId = dmRoomId,
|
||||
otherUserId = action.selectedUserId,
|
||||
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
|
||||
transferee = call
|
||||
)
|
||||
} else {
|
||||
call?.transferToUser(action.selectedUserId, null)
|
||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
||||
}
|
||||
@ -97,8 +109,17 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
try {
|
||||
_viewEvents.post(CallTransferViewEvents.Loading)
|
||||
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
|
||||
call?.transferToUser(result.userId, result.roomId)
|
||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||
if (action.consultFirst) {
|
||||
callManager.startOutgoingCall(
|
||||
signalingRoomId = result.roomId,
|
||||
otherUserId = result.userId,
|
||||
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
|
||||
transferee = call
|
||||
)
|
||||
} else {
|
||||
call?.transferToUser(result.userId, result.roomId)
|
||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) {
|
||||
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) = withContext(dispatcher){
|
||||
mxCall.transfer(
|
||||
targetUserId = targetUserId,
|
||||
targetRoomId = targetRoomId,
|
||||
@ -297,21 +297,21 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
endCall(true, CallHangupContent.Reason.REPLACED)
|
||||
}
|
||||
|
||||
suspend fun transferToCall(transferTargetCall: WebRtcCall) {
|
||||
suspend fun transferToCall(transferTargetCall: WebRtcCall)= withContext(dispatcher) {
|
||||
val newCallId = CallIdGenerator.generate()
|
||||
transferTargetCall.mxCall.transfer(
|
||||
targetUserId = this.mxCall.opponentUserId,
|
||||
targetUserId = this@WebRtcCall.mxCall.opponentUserId,
|
||||
targetRoomId = null,
|
||||
createCallId = null,
|
||||
awaitCallId = newCallId
|
||||
)
|
||||
this.mxCall.transfer(
|
||||
transferTargetCall.mxCall.opponentUserId,
|
||||
this@WebRtcCall.mxCall.transfer(
|
||||
targetUserId = transferTargetCall.mxCall.opponentUserId,
|
||||
targetRoomId = null,
|
||||
createCallId = newCallId,
|
||||
awaitCallId = null
|
||||
)
|
||||
endCall(true, CallHangupContent.Reason.REPLACED)
|
||||
this@WebRtcCall.endCall(true, CallHangupContent.Reason.REPLACED)
|
||||
transferTargetCall.endCall(true, CallHangupContent.Reason.REPLACED)
|
||||
}
|
||||
|
||||
|
@ -137,6 +137,10 @@ class WebRtcCallManager @Inject constructor(
|
||||
private val advertisedCalls = HashSet<String>()
|
||||
private val callsByCallId = ConcurrentHashMap<String, WebRtcCall>()
|
||||
private val callsByRoomId = ConcurrentHashMap<String, MutableList<WebRtcCall>>()
|
||||
// Calls started as an attended transfer, ie. with the intention of transferring another
|
||||
// call with a different party to this one.
|
||||
// callId (target) -> call (transferee)
|
||||
private val transferees = ConcurrentHashMap<String, WebRtcCall>()
|
||||
|
||||
fun getCallById(callId: String): WebRtcCall? {
|
||||
return callsByCallId[callId]
|
||||
@ -146,6 +150,10 @@ class WebRtcCallManager @Inject constructor(
|
||||
return callsByRoomId[roomId] ?: emptyList()
|
||||
}
|
||||
|
||||
fun getTransfereeForCallId(callId: String): WebRtcCall? {
|
||||
return transferees[callId]
|
||||
}
|
||||
|
||||
fun getCurrentCall(): WebRtcCall? {
|
||||
return currentCall.get()
|
||||
}
|
||||
@ -219,6 +227,7 @@ class WebRtcCallManager @Inject constructor(
|
||||
}
|
||||
CallService.onCallTerminated(context, callId)
|
||||
callsByRoomId[webRtcCall.roomId]?.remove(webRtcCall)
|
||||
transferees.remove(callId)
|
||||
if (getCurrentCall()?.callId == callId) {
|
||||
val otherCall = getCalls().lastOrNull()
|
||||
currentCall.setAndNotify(otherCall)
|
||||
@ -245,7 +254,7 @@ class WebRtcCallManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) {
|
||||
fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) {
|
||||
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
|
||||
if (getCallsByRoomId(signalingRoomId).isNotEmpty()) {
|
||||
Timber.w("## VOIP you already have a call in this room")
|
||||
@ -263,7 +272,9 @@ class WebRtcCallManager @Inject constructor(
|
||||
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
|
||||
val webRtcCall = createWebRtcCall(mxCall)
|
||||
currentCall.setAndNotify(webRtcCall)
|
||||
|
||||
if(transferee != null){
|
||||
transferees[webRtcCall.callId] = transferee
|
||||
}
|
||||
CallService.onOutgoingCallRinging(
|
||||
context = context.applicationContext,
|
||||
callId = mxCall.callId)
|
||||
|
@ -52,7 +52,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:enabled="false"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
|
@ -3231,7 +3231,8 @@
|
||||
<string name="call_transfer_title">Transfer</string>
|
||||
<string name="call_transfer_failure">An error occurred while transferring call</string>
|
||||
<string name="call_transfer_users_tab_title">Users</string>
|
||||
|
||||
<string name="call_transfer_consulting_with">Consulting with %1$s</string>
|
||||
<string name="call_transfer_transfer_to_title">Transfer to %1$s</string>
|
||||
|
||||
<string name="re_authentication_activity_title">Re-Authentication Needed</string>
|
||||
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
|
||||
|
Loading…
Reference in New Issue
Block a user