mirror of
https://github.com/element-hq/element-android
synced 2024-10-24 03:36:43 +03:00
Basic return to call Ux in Room detail
This commit is contained in:
parent
a1907aaddb
commit
c4b977c6e1
11 changed files with 274 additions and 33 deletions
|
@ -22,6 +22,7 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.core.platform.ConfigurationViewModel
|
import im.vector.riotx.core.platform.ConfigurationViewModel
|
||||||
|
import im.vector.riotx.features.call.SharedActiveCallViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||||
|
@ -85,6 +86,11 @@ interface ViewModelModule {
|
||||||
@ViewModelKey(ConfigurationViewModel::class)
|
@ViewModelKey(ConfigurationViewModel::class)
|
||||||
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(SharedActiveCallViewModel::class)
|
||||||
|
fun bindSharedActiveCallViewModel(viewModel: SharedActiveCallViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.core.ui.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
|
||||||
|
class ActiveCallView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : RelativeLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onTapToReturnToCall()
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
setupView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupView() {
|
||||||
|
inflate(context, R.layout.view_active_call_view, this)
|
||||||
|
setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
|
||||||
|
setOnClickListener { callback?.onTapToReturnToCall() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,12 +22,14 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import im.vector.matrix.android.api.session.call.CallState
|
import im.vector.matrix.android.api.session.call.CallState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import kotlinx.android.synthetic.main.fragment_call_controls.view.*
|
||||||
|
|
||||||
class CallControlsView @JvmOverloads constructor(
|
class CallControlsView @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
|
@ -82,6 +84,12 @@ class CallControlsView @JvmOverloads constructor(
|
||||||
interactionListener?.didTapToggleVideo()
|
interactionListener?.didTapToggleVideo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.iv_leftMiniControl)
|
||||||
|
fun returnToChat() {
|
||||||
|
interactionListener?.returnToChat()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateForState(state: VectorCallViewState) {
|
fun updateForState(state: VectorCallViewState) {
|
||||||
val callState = state.callState.invoke()
|
val callState = state.callState.invoke()
|
||||||
muteIcon.setImageResource(if (state.isAudioMuted) R.drawable.ic_microphone_off else R.drawable.ic_microphone_on)
|
muteIcon.setImageResource(if (state.isAudioMuted) R.drawable.ic_microphone_off else R.drawable.ic_microphone_on)
|
||||||
|
@ -106,6 +114,7 @@ class CallControlsView @JvmOverloads constructor(
|
||||||
CallState.CONNECTED -> {
|
CallState.CONNECTED -> {
|
||||||
ringingControls.isVisible = false
|
ringingControls.isVisible = false
|
||||||
connectedControls.isVisible = true
|
connectedControls.isVisible = true
|
||||||
|
iv_video_toggle.isInvisible = !state.isVideoCall
|
||||||
}
|
}
|
||||||
CallState.TERMINATED,
|
CallState.TERMINATED,
|
||||||
null -> {
|
null -> {
|
||||||
|
@ -121,5 +130,6 @@ class CallControlsView @JvmOverloads constructor(
|
||||||
fun didEndCall()
|
fun didEndCall()
|
||||||
fun didTapToggleMute()
|
fun didTapToggleMute()
|
||||||
fun didTapToggleVideo()
|
fun didTapToggleVideo()
|
||||||
|
fun returnToChat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.call
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import im.vector.matrix.android.api.session.call.MxCall
|
||||||
|
import im.vector.riotx.core.platform.VectorSharedAction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
sealed class CallActions : VectorSharedAction {
|
||||||
|
data class GoToCallActivity(val mxCall: MxCall) : CallActions()
|
||||||
|
data class ToggleVisibility(val visible: Boolean) : CallActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedActiveCallViewModel @Inject constructor(
|
||||||
|
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val activeCall: MutableLiveData<MxCall?> = MutableLiveData()
|
||||||
|
|
||||||
|
private val listener = object : WebRtcPeerConnectionManager.CurrentCallListener {
|
||||||
|
override fun onCurrentCallChange(call: MxCall?) {
|
||||||
|
activeCall.postValue(call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
activeCall.postValue(webRtcPeerConnectionManager.currentCall?.mxCall)
|
||||||
|
webRtcPeerConnectionManager.addCurrentCallListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
webRtcPeerConnectionManager.removeCurrentCallListener(listener)
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
}
|
|
@ -175,35 +175,26 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||||
callVideoGroup.isInvisible = true
|
callVideoGroup.isInvisible = true
|
||||||
callInfoGroup.isVisible = true
|
callInfoGroup.isVisible = true
|
||||||
callStatusText.setText(R.string.call_ring)
|
callStatusText.setText(R.string.call_ring)
|
||||||
state.otherUserMatrixItem.invoke()?.let {
|
configureCallInfo(state)
|
||||||
avatarRenderer.render(it, otherMemberAvatar)
|
|
||||||
participantNameText.text = it.getBestName()
|
|
||||||
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CallState.LOCAL_RINGING -> {
|
CallState.LOCAL_RINGING -> {
|
||||||
callVideoGroup.isInvisible = true
|
callVideoGroup.isInvisible = true
|
||||||
callInfoGroup.isVisible = true
|
callInfoGroup.isVisible = true
|
||||||
callStatusText.text = null
|
callStatusText.text = null
|
||||||
state.otherUserMatrixItem.invoke()?.let {
|
configureCallInfo(state)
|
||||||
avatarRenderer.render(it, otherMemberAvatar)
|
|
||||||
participantNameText.text = it.getBestName()
|
|
||||||
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CallState.ANSWERING -> {
|
CallState.ANSWERING -> {
|
||||||
callVideoGroup.isInvisible = true
|
callVideoGroup.isInvisible = true
|
||||||
callInfoGroup.isVisible = true
|
callInfoGroup.isVisible = true
|
||||||
callStatusText.setText(R.string.call_connecting)
|
callStatusText.setText(R.string.call_connecting)
|
||||||
state.otherUserMatrixItem.invoke()?.let {
|
configureCallInfo(state)
|
||||||
avatarRenderer.render(it, otherMemberAvatar)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
CallState.CONNECTING -> {
|
CallState.CONNECTING -> {
|
||||||
callVideoGroup.isInvisible = true
|
callVideoGroup.isInvisible = true
|
||||||
callInfoGroup.isVisible = true
|
callInfoGroup.isVisible = true
|
||||||
|
configureCallInfo(state)
|
||||||
callStatusText.setText(R.string.call_connecting)
|
callStatusText.setText(R.string.call_connecting)
|
||||||
}
|
}
|
||||||
CallState.CONNECTED -> {
|
CallState.CONNECTED -> {
|
||||||
|
@ -213,6 +204,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||||
} else {
|
} else {
|
||||||
callVideoGroup.isInvisible = true
|
callVideoGroup.isInvisible = true
|
||||||
callInfoGroup.isVisible = true
|
callInfoGroup.isVisible = true
|
||||||
|
configureCallInfo(state)
|
||||||
callStatusText.text = null
|
callStatusText.text = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,6 +216,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun configureCallInfo(state: VectorCallViewState) {
|
||||||
|
state.otherUserMatrixItem.invoke()?.let {
|
||||||
|
avatarRenderer.render(it, otherMemberAvatar)
|
||||||
|
participantNameText.text = it.getBestName()
|
||||||
|
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun configureCallViews() {
|
private fun configureCallViews() {
|
||||||
callControlsView.interactionListener = this
|
callControlsView.interactionListener = this
|
||||||
// if (callArgs.isVideoCall) {
|
// if (callArgs.isVideoCall) {
|
||||||
|
@ -337,4 +337,9 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||||
override fun didTapToggleVideo() {
|
override fun didTapToggleVideo() {
|
||||||
callViewModel.handle(VectorCallViewActions.ToggleVideo)
|
callViewModel.handle(VectorCallViewActions.ToggleVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun returnToChat() {
|
||||||
|
// TODO, what if the room is not in backstack??
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.call
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
import im.vector.matrix.android.api.session.call.CallState
|
import im.vector.matrix.android.api.session.call.CallState
|
||||||
import im.vector.matrix.android.api.session.call.CallsListener
|
import im.vector.matrix.android.api.session.call.CallsListener
|
||||||
import im.vector.matrix.android.api.session.call.EglUtils
|
import im.vector.matrix.android.api.session.call.EglUtils
|
||||||
|
@ -68,6 +69,19 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
private val sessionHolder: ActiveSessionHolder
|
private val sessionHolder: ActiveSessionHolder
|
||||||
) : CallsListener {
|
) : CallsListener {
|
||||||
|
|
||||||
|
interface CurrentCallListener {
|
||||||
|
fun onCurrentCallChange(call: MxCall?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val currentCallsListeners = emptyList<CurrentCallListener>().toMutableList()
|
||||||
|
fun addCurrentCallListener(listener: CurrentCallListener) {
|
||||||
|
currentCallsListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCurrentCallListener(listener: CurrentCallListener) {
|
||||||
|
currentCallsListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
data class CallContext(
|
data class CallContext(
|
||||||
val mxCall: MxCall,
|
val mxCall: MxCall,
|
||||||
|
|
||||||
|
@ -137,6 +151,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
var remoteSurfaceRenderer: WeakReference<SurfaceViewRenderer>? = null
|
var remoteSurfaceRenderer: WeakReference<SurfaceViewRenderer>? = null
|
||||||
|
|
||||||
var currentCall: CallContext? = null
|
var currentCall: CallContext? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
currentCallsListeners.forEach {
|
||||||
|
tryThis { it.onCurrentCallChange(value?.mxCall) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// TODO do this lazyly
|
// TODO do this lazyly
|
||||||
|
|
|
@ -42,6 +42,7 @@ import androidx.core.util.Pair
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -100,6 +101,7 @@ import im.vector.riotx.core.glide.GlideApp
|
||||||
import im.vector.riotx.core.intent.getMimeTypeFromUri
|
import im.vector.riotx.core.intent.getMimeTypeFromUri
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.ui.views.ActiveCallView
|
||||||
import im.vector.riotx.core.ui.views.JumpToReadMarkerView
|
import im.vector.riotx.core.ui.views.JumpToReadMarkerView
|
||||||
import im.vector.riotx.core.ui.views.NotificationAreaView
|
import im.vector.riotx.core.ui.views.NotificationAreaView
|
||||||
import im.vector.riotx.core.utils.Debouncer
|
import im.vector.riotx.core.utils.Debouncer
|
||||||
|
@ -127,6 +129,8 @@ import im.vector.riotx.features.attachments.ContactAttachment
|
||||||
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity
|
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity
|
||||||
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs
|
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs
|
||||||
import im.vector.riotx.features.attachments.toGroupedContentAttachmentData
|
import im.vector.riotx.features.attachments.toGroupedContentAttachmentData
|
||||||
|
import im.vector.riotx.features.call.SharedActiveCallViewModel
|
||||||
|
import im.vector.riotx.features.call.VectorCallActivity
|
||||||
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
||||||
|
@ -205,7 +209,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
AttachmentsHelper.Callback,
|
AttachmentsHelper.Callback,
|
||||||
RoomWidgetsBannerView.Callback {
|
RoomWidgetsBannerView.Callback,
|
||||||
|
ActiveCallView.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -245,6 +250,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
override fun getMenuRes() = R.menu.menu_timeline
|
override fun getMenuRes() = R.menu.menu_timeline
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||||
|
private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
|
||||||
|
|
||||||
private lateinit var layoutManager: LinearLayoutManager
|
private lateinit var layoutManager: LinearLayoutManager
|
||||||
private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager
|
private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager
|
||||||
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||||
|
@ -261,6 +268,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||||
|
sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
|
||||||
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
|
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
|
||||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||||
setupToolbar(roomToolbar)
|
setupToolbar(roomToolbar)
|
||||||
|
@ -269,6 +277,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
setupInviteView()
|
setupInviteView()
|
||||||
setupNotificationView()
|
setupNotificationView()
|
||||||
setupJumpToReadMarkerView()
|
setupJumpToReadMarkerView()
|
||||||
|
setupActiveCallView()
|
||||||
setupJumpToBottomView()
|
setupJumpToBottomView()
|
||||||
setupWidgetsBannerView()
|
setupWidgetsBannerView()
|
||||||
|
|
||||||
|
@ -283,6 +292,13 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
|
sharedCallActionViewModel
|
||||||
|
.activeCall
|
||||||
|
.observe(viewLifecycleOwner, Observer {
|
||||||
|
//TODO delay a bit if it's a new call to let call activity launch before ..
|
||||||
|
activeCallView.isVisible = it != null
|
||||||
|
})
|
||||||
|
|
||||||
roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) {
|
roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) {
|
||||||
renderTombstoneEventHandling(it)
|
renderTombstoneEventHandling(it)
|
||||||
}
|
}
|
||||||
|
@ -374,6 +390,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
timelineEventController.callback = null
|
timelineEventController.callback = null
|
||||||
timelineEventController.removeModelBuildListener(modelBuildListener)
|
timelineEventController.removeModelBuildListener(modelBuildListener)
|
||||||
|
activeCallView.callback = null
|
||||||
modelBuildListener = null
|
modelBuildListener = null
|
||||||
autoCompleter.clear()
|
autoCompleter.clear()
|
||||||
debouncer.cancelAll()
|
debouncer.cancelAll()
|
||||||
|
@ -412,6 +429,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
jumpToReadMarkerView.callback = this
|
jumpToReadMarkerView.callback = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupActiveCallView() {
|
||||||
|
activeCallView.callback = this
|
||||||
|
}
|
||||||
|
|
||||||
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
|
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
|
||||||
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
|
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
|
||||||
if (scrollPosition == null) {
|
if (scrollPosition == null) {
|
||||||
|
@ -483,8 +504,20 @@ class RoomDetailFragment @Inject constructor(
|
||||||
R.id.video_call -> {
|
R.id.video_call -> {
|
||||||
roomDetailViewModel.getOtherUserIds()?.firstOrNull()?.let {
|
roomDetailViewModel.getOtherUserIds()?.firstOrNull()?.let {
|
||||||
// TODO CALL We should check/ask for permission here first
|
// TODO CALL We should check/ask for permission here first
|
||||||
|
val activeCall = sharedCallActionViewModel.activeCall.value
|
||||||
|
if (activeCall != null) {
|
||||||
|
// resume existing if same room, if not prompt to kill and then restart new call?
|
||||||
|
if (activeCall.roomId == roomDetailArgs.roomId) {
|
||||||
|
onTapToReturnToCall()
|
||||||
|
} else {
|
||||||
|
// TODO might not work well, and should prompt
|
||||||
|
webRtcPeerConnectionManager.endCall()
|
||||||
webRtcPeerConnectionManager.startOutgoingCall(requireContext(), roomDetailArgs.roomId, it, item.itemId == R.id.video_call)
|
webRtcPeerConnectionManager.startOutgoingCall(requireContext(), roomDetailArgs.roomId, it, item.itemId == R.id.video_call)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
webRtcPeerConnectionManager.startOutgoingCall(requireContext(), roomDetailArgs.roomId, it, item.itemId == R.id.video_call)
|
||||||
|
}
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
@ -1479,4 +1512,21 @@ class RoomDetailFragment @Inject constructor(
|
||||||
RoomWidgetsBottomSheet.newInstance()
|
RoomWidgetsBottomSheet.newInstance()
|
||||||
.show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET")
|
.show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTapToReturnToCall() {
|
||||||
|
sharedCallActionViewModel.activeCall.value?.let { call ->
|
||||||
|
VectorCallActivity.newIntent(
|
||||||
|
requireContext(),
|
||||||
|
call.callId,
|
||||||
|
call.roomId,
|
||||||
|
call.otherUserId,
|
||||||
|
!call.isOutgoing,
|
||||||
|
call.isVideoCall,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
).let {
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
android:id="@+id/ringingControls"
|
android:id="@+id/ringingControls"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
tools:background="@color/password_strength_bar_ok"
|
tools:background="@color/password_strength_bar_ok"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@
|
||||||
android:id="@+id/connectedControls"
|
android:id="@+id/connectedControls"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:background="@color/password_strength_bar_low"
|
tools:background="@color/password_strength_bar_low"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
@ -66,11 +66,13 @@
|
||||||
android:layout_width="44dp"
|
android:layout_width="44dp"
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:layout_marginBottom="32dp"
|
android:layout_marginBottom="32dp"
|
||||||
|
android:background="@drawable/oval_positive"
|
||||||
|
android:backgroundTint="?attr/riotx_background"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="8dp"
|
android:padding="10dp"
|
||||||
android:src="@drawable/ic_home_bottom_chat"
|
android:src="@drawable/ic_home_bottom_chat"
|
||||||
android:tint="?attr/riotx_background"
|
android:tint="?attr/riotx_text_primary"
|
||||||
tools:ignore="MissingConstraints" />
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,9 +87,9 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:src="@drawable/ic_microphone_off"
|
android:src="@drawable/ic_microphone_off"
|
||||||
tools:src="@drawable/ic_microphone_on"
|
|
||||||
android:tint="?attr/riotx_text_primary"
|
android:tint="?attr/riotx_text_primary"
|
||||||
tools:ignore="MissingConstraints" />
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@drawable/ic_microphone_on" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_end_call"
|
android:id="@+id/iv_end_call"
|
||||||
|
|
|
@ -97,6 +97,12 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
|
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.ui.views.ActiveCallView
|
||||||
|
android:id="@+id/activeCallView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/syncStateView" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -105,7 +111,7 @@
|
||||||
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
|
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/syncStateView"
|
app:layout_constraintTop_toBottomOf="@id/activeCallView"
|
||||||
tools:listitem="@layout/item_timeline_event_base" />
|
tools:listitem="@layout/item_timeline_event_base" />
|
||||||
|
|
||||||
|
|
||||||
|
|
43
vector/src/main/res/layout/view_active_call_view.xml
Normal file
43
vector/src/main/res/layout/view_active_call_view.xml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?colorPrimary"
|
||||||
|
tools:parentTag="android.widget.RelativeLayout">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/activeCallInfo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toStartOf="@id/returnToCallButton"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:drawableStart="@drawable/ic_call"
|
||||||
|
android:drawableTint="@color/white"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:text="@string/active_call"
|
||||||
|
android:textColor="@color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/returnToCallButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/activeCallInfo"
|
||||||
|
android:layout_alignBottom="@+id/activeCallInfo"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:text="@string/return_to_call" />
|
||||||
|
|
||||||
|
</merge>
|
|
@ -362,6 +362,8 @@
|
||||||
<string name="incoming_voice_call">Incoming Voice Call</string>
|
<string name="incoming_voice_call">Incoming Voice Call</string>
|
||||||
<string name="call_in_progress">Call In Progress…</string>
|
<string name="call_in_progress">Call In Progress…</string>
|
||||||
<string name="video_call_in_progress">Video Call In Progress…</string>
|
<string name="video_call_in_progress">Video Call In Progress…</string>
|
||||||
|
<string name="active_call_with_duration">Active Call (%s)</string>
|
||||||
|
<string name="return_to_call">Return to call</string>
|
||||||
|
|
||||||
<string name="call_error_user_not_responding">The remote side failed to pick up.</string>
|
<string name="call_error_user_not_responding">The remote side failed to pick up.</string>
|
||||||
<string name="call_error_ice_failed">Media Connection Failed</string>
|
<string name="call_error_ice_failed">Media Connection Failed</string>
|
||||||
|
|
Loading…
Reference in a new issue