Basic return to call Ux in Room detail

This commit is contained in:
Valere 2020-06-12 17:38:17 +02:00
parent a1907aaddb
commit c4b977c6e1
11 changed files with 274 additions and 33 deletions

View file

@ -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)

View file

@ -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() }
}
}

View file

@ -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()
} }
} }

View file

@ -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()
}
}

View file

@ -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()
}
} }

View file

@ -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

View file

@ -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)
}
}
}
} }

View file

@ -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"

View file

@ -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" />

View 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>

View file

@ -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>