Call transfer: handle unknown person correctly

This commit is contained in:
ganfra 2021-05-28 14:28:32 +02:00
parent 8e8bc0055d
commit 34b012732e
4 changed files with 49 additions and 39 deletions

View File

@ -175,7 +175,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
when (callState) { when (callState) {
is CallState.Idle, is CallState.Idle,
is CallState.CreateOffer, is CallState.CreateOffer,
is CallState.Dialing -> { is CallState.Dialing -> {
views.callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
views.callStatusText.setText(R.string.call_ring) views.callStatusText.setText(R.string.call_ring)
@ -189,17 +189,22 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
configureCallInfo(state) configureCallInfo(state)
} }
is CallState.Answering -> { is CallState.Answering -> {
views.callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
views.callStatusText.setText(R.string.call_connecting) views.callStatusText.setText(R.string.call_connecting)
views.callConnectingProgress.isVisible = true views.callConnectingProgress.isVisible = true
configureCallInfo(state) configureCallInfo(state)
} }
is CallState.Connected -> { is CallState.Connected -> {
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
if (state.transfereeName.hasValue()) { if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) {
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, state.transfereeName.get()) val transfereeName = if (state.transferee is VectorCallViewState.TransfereeState.KnownTransferee) {
state.transferee.name
} else {
getString(R.string.call_transfer_unknown_person)
}
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, transfereeName)
views.callActionText.isVisible = true views.callActionText.isVisible = true
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) } views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) }
views.callStatusText.text = state.formattedDuration views.callStatusText.text = state.formattedDuration
@ -226,7 +231,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
if (callArgs.isVideoCall) { if (callArgs.isVideoCall) {
views.callVideoGroup.isVisible = true views.callVideoGroup.isVisible = true
views.callInfoGroup.isVisible = false views.callInfoGroup.isVisible = false
views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null
} else { } else {
views.callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
@ -241,10 +246,10 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callConnectingProgress.isVisible = true views.callConnectingProgress.isVisible = true
} }
} }
is CallState.Terminated -> { is CallState.Terminated -> {
finish() finish()
} }
null -> { null -> {
} }
} }
} }
@ -253,10 +258,10 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
state.callInfo.otherUserItem?.let { state.callInfo.otherUserItem?.let {
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen) val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter) avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter)
if (state.transfereeName.hasValue()) { if (state.transferee is VectorCallViewState.TransfereeState.NoTransferee) {
views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName())
} else {
views.participantNameText.text = it.getBestName() views.participantNameText.text = it.getBestName()
} else {
views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName())
} }
if (blurAvatar) { if (blurAvatar) {
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter) avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter)
@ -332,13 +337,13 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
private fun handleViewEvents(event: VectorCallViewEvents?) { private fun handleViewEvents(event: VectorCallViewEvents?) {
Timber.v("## VOIP handleViewEvents $event") Timber.v("## VOIP handleViewEvents $event")
when (event) { when (event) {
VectorCallViewEvents.DismissNoCall -> { VectorCallViewEvents.DismissNoCall -> {
finish() finish()
} }
is VectorCallViewEvents.ConnectionTimeout -> { is VectorCallViewEvents.ConnectionTimeout -> {
onErrorTimoutConnect(event.turn) onErrorTimoutConnect(event.turn)
} }
is VectorCallViewEvents.ShowDialPad -> { is VectorCallViewEvents.ShowDialPad -> {
CallDialPadBottomSheet.newInstance(false).apply { CallDialPadBottomSheet.newInstance(false).apply {
callback = dialPadCallback callback = dialPadCallback
}.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG) }.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG)
@ -346,7 +351,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
is VectorCallViewEvents.ShowCallTransferScreen -> { is VectorCallViewEvents.ShowCallTransferScreen -> {
navigator.openCallTransfer(this, callArgs.callId) navigator.openCallTransfer(this, callArgs.callId)
} }
null -> { null -> {
} }
} }
} }

View File

@ -23,8 +23,8 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
@ -39,7 +39,6 @@ import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall 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.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
import org.matrix.android.sdk.api.util.Optional
class VectorCallViewModel @AssistedInject constructor( class VectorCallViewModel @AssistedInject constructor(
@Assisted initialState: VectorCallViewState, @Assisted initialState: VectorCallViewState,
@ -109,22 +108,22 @@ class VectorCallViewModel @AssistedInject constructor(
} }
} }
} }
val transfereeName = computeTransfereeNameIfAny(call)
setState { setState {
copy( copy(
callState = Success(callState), callState = Success(callState),
canOpponentBeTransferred = call.capabilities.supportCallTransfer(), canOpponentBeTransferred = call.capabilities.supportCallTransfer(),
transfereeName = transfereeName transferee = computeTransfereeState(call)
) )
} }
} }
} }
private fun computeTransfereeNameIfAny(call: MxCall): Optional<String> { private fun computeTransfereeState(call: MxCall): VectorCallViewState.TransfereeState {
val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return Optional.empty() val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return VectorCallViewState.TransfereeState.NoTransferee
val transfereeRoom = session.getRoomSummary(transfereeCall.nativeRoomId) val transfereeRoom = session.getRoomSummary(transfereeCall.nativeRoomId)
val transfereeName = transfereeRoom?.displayName ?: "Unknown person" return transfereeRoom?.displayName?.let {
return Optional.from(transfereeName) VectorCallViewState.TransfereeState.KnownTransferee(it)
} ?: VectorCallViewState.TransfereeState.UnknownTransferee
} }
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
@ -176,7 +175,7 @@ class VectorCallViewModel @AssistedInject constructor(
} else { } else {
call = webRtcCall call = webRtcCall
callManager.addCurrentCallListener(currentCallListener) callManager.addCurrentCallListener(currentCallListener)
val item = webRtcCall.getOpponentAsMatrixItem(session) val item = webRtcCall.getOpponentAsMatrixItem(session)
webRtcCall.addListener(callListener) webRtcCall.addListener(callListener)
val currentSoundDevice = callManager.audioManager.selectedDevice val currentSoundDevice = callManager.audioManager.selectedDevice
if (currentSoundDevice == CallAudioManager.Device.PHONE) { if (currentSoundDevice == CallAudioManager.Device.PHONE) {
@ -196,7 +195,7 @@ class VectorCallViewModel @AssistedInject constructor(
formattedDuration = webRtcCall.formattedDuration(), formattedDuration = webRtcCall.formattedDuration(),
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD, isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD,
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(), canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(),
transfereeName = computeTransfereeNameIfAny(webRtcCall.mxCall) transferee = computeTransfereeState(webRtcCall.mxCall)
) )
} }
updateOtherKnownCall(webRtcCall) updateOtherKnownCall(webRtcCall)
@ -212,27 +211,27 @@ class VectorCallViewModel @AssistedInject constructor(
override fun handle(action: VectorCallViewActions) = withState { state -> override fun handle(action: VectorCallViewActions) = withState { state ->
when (action) { when (action) {
VectorCallViewActions.EndCall -> call?.endCall() VectorCallViewActions.EndCall -> call?.endCall()
VectorCallViewActions.AcceptCall -> { VectorCallViewActions.AcceptCall -> {
setState { setState {
copy(callState = Loading()) copy(callState = Loading())
} }
call?.acceptIncomingCall() call?.acceptIncomingCall()
} }
VectorCallViewActions.DeclineCall -> { VectorCallViewActions.DeclineCall -> {
setState { setState {
copy(callState = Loading()) copy(callState = Loading())
} }
call?.endCall() call?.endCall()
} }
VectorCallViewActions.ToggleMute -> { VectorCallViewActions.ToggleMute -> {
val muted = state.isAudioMuted val muted = state.isAudioMuted
call?.muteCall(!muted) call?.muteCall(!muted)
setState { setState {
copy(isAudioMuted = !muted) copy(isAudioMuted = !muted)
} }
} }
VectorCallViewActions.ToggleVideo -> { VectorCallViewActions.ToggleVideo -> {
if (state.isVideoCall) { if (state.isVideoCall) {
val videoEnabled = state.isVideoEnabled val videoEnabled = state.isVideoEnabled
call?.enableVideo(!videoEnabled) call?.enableVideo(!videoEnabled)
@ -242,14 +241,14 @@ class VectorCallViewModel @AssistedInject constructor(
} }
Unit Unit
} }
VectorCallViewActions.ToggleHoldResume -> { VectorCallViewActions.ToggleHoldResume -> {
val isRemoteOnHold = state.isRemoteOnHold val isRemoteOnHold = state.isRemoteOnHold
call?.updateRemoteOnHold(!isRemoteOnHold) call?.updateRemoteOnHold(!isRemoteOnHold)
} }
is VectorCallViewActions.ChangeAudioDevice -> { is VectorCallViewActions.ChangeAudioDevice -> {
callManager.audioManager.setAudioDevice(action.device) callManager.audioManager.setAudioDevice(action.device)
} }
VectorCallViewActions.SwitchSoundDevice -> { VectorCallViewActions.SwitchSoundDevice -> {
_viewEvents.post( _viewEvents.post(
VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device) VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device)
) )
@ -265,17 +264,17 @@ class VectorCallViewModel @AssistedInject constructor(
} }
Unit Unit
} }
VectorCallViewActions.ToggleCamera -> { VectorCallViewActions.ToggleCamera -> {
call?.switchCamera() call?.switchCamera()
} }
VectorCallViewActions.ToggleHDSD -> { VectorCallViewActions.ToggleHDSD -> {
if (!state.isVideoCall) return@withState if (!state.isVideoCall) return@withState
call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD) call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
} }
VectorCallViewActions.OpenDialPad -> { VectorCallViewActions.OpenDialPad -> {
_viewEvents.post(VectorCallViewEvents.ShowDialPad) _viewEvents.post(VectorCallViewEvents.ShowDialPad)
} }
is VectorCallViewActions.SendDtmfDigit -> { is VectorCallViewActions.SendDtmfDigit -> {
call?.sendDtmfDigit(action.digit) call?.sendDtmfDigit(action.digit)
} }
VectorCallViewActions.InitiateCallTransfer -> { VectorCallViewActions.InitiateCallTransfer -> {
@ -283,7 +282,7 @@ class VectorCallViewModel @AssistedInject constructor(
VectorCallViewEvents.ShowCallTransferScreen VectorCallViewEvents.ShowCallTransferScreen
) )
} }
VectorCallViewActions.TransferCall -> { VectorCallViewActions.TransferCall -> {
handleCallTransfer() handleCallTransfer()
} }
}.exhaustive }.exhaustive

View File

@ -22,7 +22,6 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.Optional
data class VectorCallViewState( data class VectorCallViewState(
val callId: String, val callId: String,
@ -43,9 +42,15 @@ data class VectorCallViewState(
val callInfo: CallInfo = CallInfo(callId), val callInfo: CallInfo = CallInfo(callId),
val formattedDuration: String = "", val formattedDuration: String = "",
val canOpponentBeTransferred: Boolean = false, val canOpponentBeTransferred: Boolean = false,
val transfereeName: Optional<String> = Optional.empty() val transferee: TransfereeState = TransfereeState.NoTransferee
) : MvRxState { ) : MvRxState {
sealed class TransfereeState {
object NoTransferee: TransfereeState()
data class KnownTransferee(val name:String): TransfereeState()
object UnknownTransferee: TransfereeState()
}
data class CallInfo( data class CallInfo(
val callId: String, val callId: String,
val otherUserItem: MatrixItem? = null val otherUserItem: MatrixItem? = null

View File

@ -3234,6 +3234,7 @@
<string name="call_transfer_users_tab_title">Users</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_consulting_with">Consulting with %1$s</string>
<string name="call_transfer_transfer_to_title">Transfer to %1$s</string> <string name="call_transfer_transfer_to_title">Transfer to %1$s</string>
<string name="call_transfer_unknown_person">Unknown person</string>
<string name="re_authentication_activity_title">Re-Authentication Needed</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 --> <!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->