VoIP: add info on other call when switching

This commit is contained in:
ganfra 2020-12-22 12:05:36 +01:00
parent b1f492de58
commit a5736efc75
19 changed files with 220 additions and 117 deletions

View File

@ -22,7 +22,7 @@ import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import im.vector.app.core.platform.ConfigurationViewModel
import im.vector.app.features.call.SharedCurrentCallViewModel
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
@ -85,8 +85,8 @@ interface ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(SharedCurrentCallViewModel::class)
fun bindSharedActiveCallViewModel(viewModel: SharedCurrentCallViewModel): ViewModel
@ViewModelKey(SharedKnownCallsViewModel::class)
fun bindSharedActiveCallViewModel(viewModel: SharedKnownCallsViewModel): ViewModel
@Binds
@IntoMap

View File

@ -28,7 +28,7 @@ import org.webrtc.SurfaceViewRenderer
class ActiveCallViewHolder {
private var activeCallPiP: SurfaceViewRenderer? = null
private var activeCallView: ActiveCallView? = null
private var activeCallView: CurrentCallsView? = null
private var pipWrapper: CardView? = null
private var activeCall: WebRtcCall? = null
@ -70,7 +70,7 @@ class ActiveCallViewHolder {
}
}
fun bind(activeCallPiP: SurfaceViewRenderer, activeCallView: ActiveCallView, pipWrapper: CardView, interactionListener: ActiveCallView.Callback) {
fun bind(activeCallPiP: SurfaceViewRenderer, activeCallView: CurrentCallsView, pipWrapper: CardView, interactionListener: CurrentCallsView.Callback) {
this.activeCallPiP = activeCallPiP
this.activeCallView = activeCallView
this.pipWrapper = pipWrapper

View File

@ -22,10 +22,10 @@ import android.widget.RelativeLayout
import im.vector.app.R
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.themes.ThemeUtils
import kotlinx.android.synthetic.main.view_active_call_view.view.*
import kotlinx.android.synthetic.main.view_current_calls.view.*
import org.matrix.android.sdk.api.session.call.CallState
class ActiveCallView @JvmOverloads constructor(
class CurrentCallsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
@ -42,25 +42,32 @@ class ActiveCallView @JvmOverloads constructor(
}
private fun setupView() {
inflate(context, R.layout.view_active_call_view, this)
inflate(context, R.layout.view_current_calls, this)
setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
setOnClickListener { callback?.onTapToReturnToCall() }
}
fun render(calls: List<WebRtcCall>) {
if (calls.size == 1) {
activeCallInfo.setText(R.string.call_active_call)
} else if (calls.size == 2) {
val activeCall = calls.firstOrNull {
it.mxCall.state is CallState.Connected && !it.isLocalOnHold()
}
if (activeCall == null) {
activeCallInfo.setText(R.string.call_two_paused_calls)
val connectedCalls = calls.filter {
it.mxCall.state is CallState.Connected
}
val heldCalls = connectedCalls.filter {
it.isLocalOnHold() || it.remoteOnHold
}
if (connectedCalls.size == 1) {
if (heldCalls.size == 1) {
currentCallsInfo.setText(R.string.call_only_paused)
} else {
activeCallInfo.setText(R.string.call_one_active_one_paused_call)
currentCallsInfo.setText(R.string.call_only_active)
}
} else {
visibility = GONE
if (heldCalls.size > 1) {
currentCallsInfo.text = resources.getString(R.string.call_only_multiple_paused , heldCalls.size)
} else if (heldCalls.size == 1) {
currentCallsInfo.setText(R.string.call_active_and_single_paused)
} else {
currentCallsInfo.text = resources.getString(R.string.call_active_and_multiple_paused, "00:00", heldCalls.size)
}
}
}
}

View File

@ -23,43 +23,51 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.session.call.MxCall
import javax.inject.Inject
class SharedCurrentCallViewModel @Inject constructor(
class SharedKnownCallsViewModel @Inject constructor(
private val callManager: WebRtcCallManager
) : ViewModel() {
val currentCall: MutableLiveData<WebRtcCall?> = MutableLiveData()
val liveKnownCalls: MutableLiveData<List<WebRtcCall>> = MutableLiveData()
val callStateListener = object : WebRtcCall.Listener {
val callListener = object : WebRtcCall.Listener {
override fun onStateUpdate(call: MxCall) {
//post it-self
currentCall.postValue(currentCall.value)
liveKnownCalls.postValue(liveKnownCalls.value)
}
override fun onHoldUnhold() {
super.onHoldUnhold()
//post it-self
currentCall.postValue(currentCall.value)
liveKnownCalls.postValue(liveKnownCalls.value)
}
}
private val listener = object : WebRtcCallManager.CurrentCallListener {
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
override fun onCurrentCallChange(call: WebRtcCall?) {
currentCall.value?.mxCall?.removeListener(callStateListener)
currentCall.postValue(call)
call?.addListener(callStateListener)
val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls)
knownCalls.forEach {
it.removeListener(callListener)
it.addListener(callListener)
}
}
}
init {
currentCall.postValue(callManager.currentCall)
callManager.addCurrentCallListener(listener)
val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls)
callManager.addCurrentCallListener(currentCallListener)
knownCalls.forEach {
it.addListener(callListener)
}
}
override fun onCleared() {
currentCall.value?.removeListener(callStateListener)
callManager.removeCurrentCallListener(listener)
callManager.getCalls().forEach {
it.removeListener(callListener)
}
callManager.removeCurrentCallListener(currentCallListener)
super.onCleared()
}
}

View File

@ -34,10 +34,10 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.services.CallService
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.allGranted
@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.parcelize.Parcelize
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.MxCallDetail
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
@ -199,7 +200,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callStatusText.setText(R.string.call_held_by_you)
} else {
views.callActionText.isInvisible = true
state.otherUserMatrixItem.invoke()?.let {
state.callInfo.otherUserItem?.let {
views.callStatusText.text = getString(R.string.call_held_by_user, it.getBestName())
}
}
@ -208,7 +209,8 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
if (callArgs.isVideoCall) {
views.callVideoGroup.isVisible = true
views.callInfoGroup.isVisible = false
//views.pip_video_view.isVisible = !state.isVideoCaptureInError
views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null
configureCallInfo(state)
} else {
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
@ -235,7 +237,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
}
private fun configureCallInfo(state: VectorCallViewState, blurAvatar: Boolean = false) {
state.otherUserMatrixItem.invoke()?.let {
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()
@ -245,10 +247,26 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
avatarRenderer.render(it, views.otherMemberAvatar)
}
}
if (state.otherKnownCallInfo?.otherUserItem == null) {
views.otherKnownCallLayout.isVisible = false
} else {
val otherCall = callManager.getCallById(state.otherKnownCallInfo.callId)
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
avatarRenderer.renderBlur(state.otherKnownCallInfo.otherUserItem, views.otherKnownCallAvatarView, sampling = 20, rounded = false, colorFilter = colorFilter)
views.otherKnownCallLayout.isVisible = true
views.otherSmallIsHeldIcon.isVisible = otherCall?.let { it.isLocalOnHold() || it.remoteOnHold }.orFalse()
}
}
private fun configureCallViews() {
views.callControlsView.interactionListener = this
views.otherKnownCallAvatarView.setOnClickListener {
withState(callViewModel) {
val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState
startActivity(newIntent(this, otherCall.mxCall, null))
finish()
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {

View File

@ -20,7 +20,6 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
@ -117,6 +116,10 @@ class VectorCallViewModel @AssistedInject constructor(
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
override fun onCurrentCallChange(call: WebRtcCall?) {
updateOtherKnownCall(call)
}
override fun onAudioDevicesChange() {
val currentSoundDevice = callManager.callAudioManager.getCurrentSoundDevice()
if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) {
@ -134,6 +137,21 @@ class VectorCallViewModel @AssistedInject constructor(
}
}
private fun updateOtherKnownCall(currentCall: WebRtcCall?) {
if (currentCall == null) return
val otherCall = callManager.getCalls().firstOrNull {
it.callId != currentCall.callId && it.mxCall.state is CallState.Connected
}
setState {
if (otherCall == null) {
copy(otherKnownCallInfo = null)
} else {
val otherUserItem: MatrixItem? = session.getUser(otherCall.mxCall.opponentUserId)?.toMatrixItem()
copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem))
}
}
}
init {
val webRtcCall = callManager.getCallById(initialState.callId)
if (webRtcCall == null) {
@ -153,7 +171,7 @@ class VectorCallViewModel @AssistedInject constructor(
copy(
isVideoCall = webRtcCall.mxCall.isVideoCall,
callState = Success(webRtcCall.mxCall.state),
otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized,
callInfo = VectorCallViewState.CallInfo(callId, item),
soundDevice = currentSoundDevice,
isLocalOnHold = webRtcCall.isLocalOnHold(),
isRemoteOnHold = webRtcCall.remoteOnHold,
@ -163,6 +181,7 @@ class VectorCallViewModel @AssistedInject constructor(
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD
)
}
updateOtherKnownCall(webRtcCall)
}
}

View File

@ -36,10 +36,16 @@ data class VectorCallViewState(
val canSwitchCamera: Boolean = true,
val soundDevice: CallAudioManager.SoundDevice = CallAudioManager.SoundDevice.PHONE,
val availableSoundDevices: List<CallAudioManager.SoundDevice> = emptyList(),
val otherUserMatrixItem: Async<MatrixItem> = Uninitialized,
val callState: Async<CallState> = Uninitialized
val callState: Async<CallState> = Uninitialized,
val otherKnownCallInfo: CallInfo? = null,
val callInfo: CallInfo = CallInfo(callId)
) : MvRxState {
data class CallInfo(
val callId: String,
val otherUserItem: MatrixItem? = null
)
constructor(callArgs: CallArgs): this(
callId = callArgs.callId,
roomId = callArgs.roomId,

View File

@ -72,6 +72,7 @@ import org.webrtc.VideoSource
import org.webrtc.VideoTrack
import timber.log.Timber
import java.lang.ref.WeakReference
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
import javax.inject.Provider
import kotlin.coroutines.CoroutineContext
@ -97,7 +98,7 @@ class WebRtcCall(val mxCall: MxCall,
fun onHoldUnhold() {}
}
private val listeners = ArrayList<Listener>()
private val listeners = CopyOnWriteArrayList<Listener>()
fun addListener(listener: Listener) {
listeners.add(listener)
@ -277,7 +278,7 @@ class WebRtcCall(val mxCall: MxCall,
}
}
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) = synchronized(this) {
Timber.v("## VOIP detachRenderers")
// currentCall?.localMediaStream?.let { currentCall?.peerConnection?.removeStream(it) }
if (renderers.isNullOrEmpty()) {
@ -533,7 +534,7 @@ class WebRtcCall(val mxCall: MxCall,
* rather than 'sendonly')
* @returns true if the other party has put us on hold
*/
fun isLocalOnHold(): Boolean {
fun isLocalOnHold(): Boolean = synchronized(this) {
if (mxCall.state !is CallState.Connected) return false
var callOnHold = true
// We consider a call to be on hold only if *all* the tracks are on hold
@ -546,7 +547,7 @@ class WebRtcCall(val mxCall: MxCall,
return callOnHold
}
fun updateRemoteOnHold(onHold: Boolean) {
fun updateRemoteOnHold(onHold: Boolean) = synchronized(this){
if (remoteOnHold == onHold) return
remoteOnHold = onHold
if (!onHold) {
@ -563,21 +564,21 @@ class WebRtcCall(val mxCall: MxCall,
updateMuteStatus()
}
fun muteCall(muted: Boolean) {
fun muteCall(muted: Boolean) = synchronized(this) {
micMuted = muted
updateMuteStatus()
}
fun enableVideo(enabled: Boolean) {
fun enableVideo(enabled: Boolean) = synchronized(this) {
videoMuted = !enabled
updateMuteStatus()
}
fun canSwitchCamera(): Boolean {
fun canSwitchCamera(): Boolean = synchronized(this){
return availableCamera.size > 1
}
private fun getOppositeCameraIfAny(): CameraProxy? {
private fun getOppositeCameraIfAny(): CameraProxy? = synchronized(this){
val currentCamera = cameraInUse ?: return null
return if (currentCamera.type == CameraType.FRONT) {
availableCamera.firstOrNull { it.type == CameraType.BACK }
@ -586,7 +587,7 @@ class WebRtcCall(val mxCall: MxCall,
}
}
fun switchCamera() {
fun switchCamera() = synchronized(this){
Timber.v("## VOIP switchCamera")
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
val oppositeCamera = getOppositeCameraIfAny() ?: return
@ -629,15 +630,15 @@ class WebRtcCall(val mxCall: MxCall,
}
}
fun currentCameraType(): CameraType? {
fun currentCameraType(): CameraType? = synchronized(this){
return cameraInUse?.type
}
fun currentCaptureFormat(): CaptureFormat {
fun currentCaptureFormat(): CaptureFormat = synchronized(this) {
return currentCaptureFormat
}
private fun release() {
private fun release() {
mxCall.removeListener(this)
videoCapturer?.stopCapture()
videoCapturer?.dispose()
@ -689,7 +690,7 @@ class WebRtcCall(val mxCall: MxCall,
}
}
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) = synchronized(this) {
if (mxCall.state == CallState.Terminated) {
return
}
@ -702,6 +703,7 @@ class WebRtcCall(val mxCall: MxCall,
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
}
release()
listeners.clear()
onCallEnded(this)
if (originatedByMe) {
// send hang up event

View File

@ -46,7 +46,9 @@ import org.webrtc.DefaultVideoEncoderFactory
import org.webrtc.PeerConnectionFactory
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import javax.inject.Singleton
@ -68,7 +70,7 @@ class WebRtcCallManager @Inject constructor(
fun onAudioDevicesChange() {}
}
private val currentCallsListeners = emptyList<CurrentCallListener>().toMutableList()
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
fun addCurrentCallListener(listener: CurrentCallListener) {
currentCallsListeners.add(listener)
}
@ -101,13 +103,17 @@ class WebRtcCallManager @Inject constructor(
isInBackground = true
}
var currentCall: WebRtcCall? = null
private set(value) {
field = value
currentCallsListeners.forEach {
tryOrNull { it.onCurrentCallChange(value) }
}
/**
* The current call is the call we interacted with whatever his state (connected,resumed, held...)
* As soon as we interact with an other call, it replaces this one and put it on held if not already.
*/
var currentCall: AtomicReference<WebRtcCall?> = AtomicReference(null)
private fun AtomicReference<WebRtcCall?>.setAndNotify(newValue: WebRtcCall?) {
set(newValue)
currentCallsListeners.forEach {
tryOrNull { it.onCurrentCallChange(newValue) }
}
}
private val callsByCallId = ConcurrentHashMap<String, WebRtcCall>()
private val callsByRoomId = ConcurrentHashMap<String, MutableList<WebRtcCall>>()
@ -120,13 +126,17 @@ class WebRtcCallManager @Inject constructor(
return callsByRoomId[roomId] ?: emptyList()
}
fun getCurrentCall(): WebRtcCall? {
return currentCall.get()
}
fun getCalls(): List<WebRtcCall> {
return callsByCallId.values.toList()
}
fun headSetButtonTapped() {
Timber.v("## VOIP headSetButtonTapped")
val call = currentCall ?: return
val call = currentCall.get() ?: return
if (call.mxCall.state is CallState.LocalRinging) {
// accept call
call.acceptIncomingCall()
@ -168,10 +178,9 @@ class WebRtcCallManager @Inject constructor(
private fun onCallActive(call: WebRtcCall) {
Timber.v("## VOIP WebRtcPeerConnectionManager onCall active: ${call.mxCall.callId}")
if (currentCall != call) {
currentCall?.updateRemoteOnHold(onHold = true)
currentCall = call
}
val currentCall = currentCall.get().takeIf { it != call }
currentCall?.updateRemoteOnHold(onHold = true)
this.currentCall.setAndNotify(call)
}
private fun onCallEnded(call: WebRtcCall) {
@ -180,12 +189,13 @@ class WebRtcCallManager @Inject constructor(
callAudioManager.stop()
callsByCallId.remove(call.mxCall.callId)
callsByRoomId[call.mxCall.roomId]?.remove(call)
if (currentCall == call) {
currentCall = getCalls().lastOrNull()
if (currentCall.get() == call) {
val otherCall = getCalls().lastOrNull()
currentCall.setAndNotify(otherCall)
}
// This must be done in this thread
executor.execute {
if (currentCall == null) {
if (currentCall.get() == null) {
Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
peerConnectionFactory?.dispose()
peerConnectionFactory = null
@ -196,7 +206,7 @@ class WebRtcCallManager @Inject constructor(
fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) {
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
if (currentCall != null && currentCall?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
if (currentCall.get() != null && currentCall.get()?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
Timber.w("## VOIP cannot start outgoing call")
// Just ignore, maybe we could answer from other session?
return
@ -204,9 +214,10 @@ class WebRtcCallManager @Inject constructor(
executor.execute {
createPeerConnectionFactoryIfNeeded()
}
currentCall?.updateRemoteOnHold(onHold = true)
currentCall.get()?.updateRemoteOnHold(onHold = true)
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
currentCall = createWebRtcCall(mxCall)
val webRtcCall = createWebRtcCall(mxCall)
currentCall.setAndNotify(webRtcCall)
callAudioManager.startForCall(mxCall)
CallService.onOutgoingCallRinging(
@ -253,7 +264,7 @@ class WebRtcCallManager @Inject constructor(
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
Timber.v("## VOIP onWiredDeviceEvent $event")
currentCall ?: return
currentCall.get() ?: return
// sometimes we received un-wanted unplugged...
callAudioManager.wiredStateChange(event)
}
@ -265,7 +276,7 @@ class WebRtcCallManager @Inject constructor(
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
if (currentCall != null && currentCall?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
if (currentCall.get() != null && currentCall.get()?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
Timber.w("## VOIP receiving incoming call but cannot handle it")
// Just ignore, maybe we could answer from other session?
return

View File

@ -32,11 +32,11 @@ import im.vector.app.core.glide.GlideApp
import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.ui.views.ActiveCallView
import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.ActiveCallViewHolder
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.databinding.FragmentHomeDetailBinding
import im.vector.app.features.call.SharedCurrentCallViewModel
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.room.list.RoomListFragment
@ -69,7 +69,7 @@ class HomeDetailFragment @Inject constructor(
private val vectorPreferences: VectorPreferences
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
KeysBackupBanner.Delegate,
ActiveCallView.Callback,
CurrentCallsView.Callback,
ServerBackupStatusViewModel.Factory {
private val viewModel: HomeDetailViewModel by fragmentViewModel()
@ -77,7 +77,7 @@ class HomeDetailFragment @Inject constructor(
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel()
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private lateinit var sharedCallActionViewModel: SharedCurrentCallViewModel
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding {
return FragmentHomeDetailBinding.inflate(inflater, container, false)
@ -88,7 +88,7 @@ class HomeDetailFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
sharedCallActionViewModel = activityViewModelProvider.get(SharedCurrentCallViewModel::class.java)
sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
setupBottomNavigationView()
setupToolbar()
@ -126,9 +126,9 @@ class HomeDetailFragment @Inject constructor(
}
sharedCallActionViewModel
.currentCall
.liveKnownCalls
.observe(viewLifecycleOwner, {
activeCallViewHolder.updateCall(it, callManager.getCalls())
activeCallViewHolder.updateCall(callManager.getCurrentCall(), callManager.getCalls())
invalidateOptionsMenu()
})
}
@ -335,7 +335,7 @@ class HomeDetailFragment @Inject constructor(
}
override fun onTapToReturnToCall() {
sharedCallActionViewModel.currentCall.value?.let { call ->
callManager.getCurrentCall()?.let { call ->
VectorCallActivity.newIntent(
context = requireContext(),
callId = call.callId,

View File

@ -89,7 +89,7 @@ import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.ui.views.ActiveCallView
import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.ActiveCallViewHolder
import im.vector.app.core.ui.views.ActiveConferenceView
import im.vector.app.core.ui.views.JumpToReadMarkerView
@ -120,7 +120,7 @@ import im.vector.app.features.attachments.ContactAttachment
import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.app.features.attachments.toGroupedContentAttachmentData
import im.vector.app.features.call.SharedCurrentCallViewModel
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.conference.JitsiCallViewModel
import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -237,7 +237,7 @@ class RoomDetailFragment @Inject constructor(
AttachmentTypeSelectorView.Callback,
AttachmentsHelper.Callback,
GalleryOrCameraDialogHelper.Listener,
ActiveCallView.Callback {
CurrentCallsView.Callback {
companion object {
/**
@ -283,7 +283,7 @@ class RoomDetailFragment @Inject constructor(
override fun getMenuRes() = R.menu.menu_timeline
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
private lateinit var sharedCurrentCallViewModel: SharedCurrentCallViewModel
private lateinit var knownCallsViewModel: SharedKnownCallsViewModel
private lateinit var layoutManager: LinearLayoutManager
private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager
@ -300,7 +300,7 @@ class RoomDetailFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
sharedCurrentCallViewModel = activityViewModelProvider.get(SharedCurrentCallViewModel::class.java)
knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
keyboardStateUtils = KeyboardStateUtils(requireActivity())
setupToolbar(views.roomToolbar)
@ -325,10 +325,10 @@ class RoomDetailFragment @Inject constructor(
}
.disposeOnDestroyView()
sharedCurrentCallViewModel
.currentCall
knownCallsViewModel
.liveKnownCalls
.observe(viewLifecycleOwner, {
activeCallViewHolder.updateCall(it, callManager.getCalls())
activeCallViewHolder.updateCall(callManager.getCurrentCall(), it)
invalidateOptionsMenu()
})
@ -801,17 +801,14 @@ class RoomDetailFragment @Inject constructor(
}
}
2 -> {
val activeCall = sharedCurrentCallViewModel.currentCall.value
if (activeCall != null) {
val currentCall = callManager.getCurrentCall()
if (currentCall != null) {
// resume existing if same room, if not prompt to kill and then restart new call?
if (activeCall.roomId == roomDetailArgs.roomId) {
if (currentCall.mxCall.roomId == roomDetailArgs.roomId) {
onTapToReturnToCall()
}else {
safeStartCall(isVideoCall)
}
// else {
// TODO might not work well, and should prompt
// webRtcPeerConnectionManager.endCall()
// safeStartCall(it, isVideoCall)
// }
} else if (!state.isAllowedToStartWebRTCCall) {
showDialogWithMessage(getString(
if (state.isDm()) {
@ -2016,7 +2013,7 @@ class RoomDetailFragment @Inject constructor(
}
override fun onTapToReturnToCall() {
sharedCurrentCallViewModel.currentCall.value?.let { call ->
callManager.getCurrentCall()?.let { call ->
VectorCallActivity.newIntent(
context = requireContext(),
callId = call.callId,

View File

@ -113,4 +113,5 @@ interface Navigator {
options: ((MutableList<Pair<View, String>>) -> Unit)?)
fun openSearch(context: Context, roomId: String)
}

View File

@ -21,6 +21,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.TextView
import im.vector.app.R
import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@ -50,7 +51,7 @@ class IncomingCallAlert(uid: String,
view.findViewById<TextView>(R.id.incomingCallKindView).setText(callKind)
view.findViewById<TextView>(R.id.incomingCallNameView).text = matrixItem?.getBestName()
view.findViewById<ImageView>(R.id.incomingCallAvatar)?.let { imageView ->
matrixItem?.let { avatarRenderer.render(it, imageView) }
matrixItem?.let { avatarRenderer.render(it, imageView, GlideApp.with(view.context.applicationContext)) }
}
view.findViewById<ImageView>(R.id.incomingCallAcceptView).setOnClickListener {
onAccept()

View File

@ -21,6 +21,7 @@ import android.view.View
import android.widget.ImageView
import androidx.annotation.DrawableRes
import im.vector.app.R
import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@ -43,7 +44,7 @@ class VerificationVectorAlert(uid: String,
override fun bind(view: View) {
view.findViewById<ImageView>(R.id.ivUserAvatar)?.let { imageView ->
matrixItem?.let { avatarRenderer.render(it, imageView) }
matrixItem?.let { avatarRenderer.render(it, imageView, GlideApp.with(view.context.applicationContext)) }
}
}
}

View File

@ -14,10 +14,10 @@
<ImageView
android:id="@+id/bgCallView"
tools:src="@tools:sample/avatars"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
<org.webrtc.SurfaceViewRenderer
android:id="@+id/fullscreenRenderer"
@ -36,6 +36,35 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/otherKnownCallLayout"
android:layout_width="80dp"
android:layout_height="144dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
tools:visibility="visible"
android:background="@color/riotx_background_light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/otherKnownCallAvatarView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?attr/selectableItemBackground"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/otherSmallIsHeldIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:src="@drawable/ic_call_small_pause" />
</FrameLayout>
<ImageView
android:id="@+id/otherMemberAvatar"
android:layout_width="80dp"
@ -49,14 +78,14 @@
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/smallIsHeldIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:id="@+id/smallIsHeldIcon"
android:src="@drawable/ic_call_small_pause"
app:layout_constraintTop_toTopOf="@id/otherMemberAvatar"
app:layout_constraintBottom_toBottomOf="@id/otherMemberAvatar"
app:layout_constraintEnd_toEndOf="@id/otherMemberAvatar"
app:layout_constraintStart_toStartOf="@id/otherMemberAvatar"
app:layout_constraintEnd_toEndOf="@id/otherMemberAvatar" />
app:layout_constraintTop_toTopOf="@id/otherMemberAvatar" />
<TextView
@ -95,10 +124,10 @@
android:id="@+id/callActionText"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_margin="8dp"
android:gravity="center"
android:textColor="?attr/colorAccent"
android:textSize="14sp"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/callStatusText"

View File

@ -65,7 +65,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView" />
<im.vector.app.core.ui.views.ActiveCallView
<im.vector.app.core.ui.views.CurrentCallsView
android:id="@+id/activeCallView"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -96,7 +96,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
<im.vector.app.core.ui.views.ActiveCallView
<im.vector.app.core.ui.views.CurrentCallsView
android:id="@+id/activeCallView"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -9,11 +9,10 @@
tools:parentTag="android.widget.RelativeLayout">
<TextView
android:id="@+id/activeCallInfo"
android:id="@+id/currentCallsInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/returnToCallButton"
android:background="?attr/selectableItemBackground"
android:drawablePadding="10dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
@ -21,7 +20,7 @@
android:paddingEnd="16dp"
android:paddingBottom="12dp"
android:textSize="14sp"
android:text="@string/call_one_active_one_paused_call"
android:text="@string/call_active_and_single_paused"
android:textColor="@color/white"
app:drawableTint="@color/white"
app:drawableStartCompat="@drawable/ic_call_answer" />
@ -31,8 +30,8 @@
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/activeCallInfo"
android:layout_alignBottom="@+id/activeCallInfo"
android:layout_alignTop="@+id/currentCallsInfo"
android:layout_alignBottom="@+id/currentCallsInfo"
android:layout_alignParentEnd="true"
android:clickable="false"
android:focusable="false"

View File

@ -2775,8 +2775,12 @@
<string name="call_tile_ended">This call has ended</string>
<string name="call_tile_call_back">Call back</string>
<string name="call_active_call">Active call (%1$s)</string>
<string name="call_one_active_one_paused_call">1 active call (%1$s) · 1 paused call</string>
<string name="call_two_paused_calls">2 paused calls</string>
<string name="call_only_active">Active call (%1$s)</string>
<string name="call_only_paused">Paused call</string>
<string name="call_active_and_single_paused">1 active call (%1$s) · 1 paused call</string>
<string name="call_active_and_multiple_paused">1 active call (%1$s) · %2$d paused calls</string>
<string name="call_only_multiple_paused">%1$d paused calls</string>
</resources>