mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +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.multibindings.IntoMap
|
||||
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.KeysBackupRestoreFromPassphraseViewModel
|
||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
|
@ -85,6 +86,11 @@ interface ViewModelModule {
|
|||
@ViewModelKey(ConfigurationViewModel::class)
|
||||
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(SharedActiveCallViewModel::class)
|
||||
fun bindSharedActiveCallViewModel(viewModel: SharedActiveCallViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@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.LinearLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import butterknife.OnClick
|
||||
import im.vector.matrix.android.api.session.call.CallState
|
||||
import im.vector.riotx.R
|
||||
import kotlinx.android.synthetic.main.fragment_call_controls.view.*
|
||||
|
||||
class CallControlsView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
|
@ -82,6 +84,12 @@ class CallControlsView @JvmOverloads constructor(
|
|||
interactionListener?.didTapToggleVideo()
|
||||
}
|
||||
|
||||
@OnClick(R.id.iv_leftMiniControl)
|
||||
fun returnToChat() {
|
||||
interactionListener?.returnToChat()
|
||||
}
|
||||
|
||||
|
||||
fun updateForState(state: VectorCallViewState) {
|
||||
val callState = state.callState.invoke()
|
||||
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 -> {
|
||||
ringingControls.isVisible = false
|
||||
connectedControls.isVisible = true
|
||||
iv_video_toggle.isInvisible = !state.isVideoCall
|
||||
}
|
||||
CallState.TERMINATED,
|
||||
null -> {
|
||||
|
@ -121,5 +130,6 @@ class CallControlsView @JvmOverloads constructor(
|
|||
fun didEndCall()
|
||||
fun didTapToggleMute()
|
||||
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
|
||||
callInfoGroup.isVisible = true
|
||||
callStatusText.setText(R.string.call_ring)
|
||||
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)
|
||||
}
|
||||
configureCallInfo(state)
|
||||
}
|
||||
|
||||
CallState.LOCAL_RINGING -> {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
callStatusText.text = null
|
||||
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)
|
||||
}
|
||||
configureCallInfo(state)
|
||||
}
|
||||
|
||||
CallState.ANSWERING -> {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
callStatusText.setText(R.string.call_connecting)
|
||||
state.otherUserMatrixItem.invoke()?.let {
|
||||
avatarRenderer.render(it, otherMemberAvatar)
|
||||
}
|
||||
configureCallInfo(state)
|
||||
}
|
||||
CallState.CONNECTING -> {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
configureCallInfo(state)
|
||||
callStatusText.setText(R.string.call_connecting)
|
||||
}
|
||||
CallState.CONNECTED -> {
|
||||
|
@ -213,6 +204,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
} else {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
configureCallInfo(state)
|
||||
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() {
|
||||
callControlsView.interactionListener = this
|
||||
// if (callArgs.isVideoCall) {
|
||||
|
@ -337,4 +337,9 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
override fun didTapToggleVideo() {
|
||||
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 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.CallsListener
|
||||
import im.vector.matrix.android.api.session.call.EglUtils
|
||||
|
@ -68,6 +69,19 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
private val sessionHolder: ActiveSessionHolder
|
||||
) : 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(
|
||||
val mxCall: MxCall,
|
||||
|
||||
|
@ -137,6 +151,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
var remoteSurfaceRenderer: WeakReference<SurfaceViewRenderer>? = null
|
||||
|
||||
var currentCall: CallContext? = null
|
||||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCurrentCallChange(value?.mxCall) }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// TODO do this lazyly
|
||||
|
|
|
@ -42,6 +42,7 @@ import androidx.core.util.Pair
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.platform.VectorBaseFragment
|
||||
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.NotificationAreaView
|
||||
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.AttachmentsPreviewArgs
|
||||
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.command.Command
|
||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
||||
|
@ -205,7 +209,8 @@ class RoomDetailFragment @Inject constructor(
|
|||
JumpToReadMarkerView.Callback,
|
||||
AttachmentTypeSelectorView.Callback,
|
||||
AttachmentsHelper.Callback,
|
||||
RoomWidgetsBannerView.Callback {
|
||||
RoomWidgetsBannerView.Callback,
|
||||
ActiveCallView.Callback {
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -245,6 +250,8 @@ class RoomDetailFragment @Inject constructor(
|
|||
override fun getMenuRes() = R.menu.menu_timeline
|
||||
|
||||
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||
private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
|
||||
|
||||
private lateinit var layoutManager: LinearLayoutManager
|
||||
private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager
|
||||
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||
|
@ -261,6 +268,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||
sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
|
||||
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
|
||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||
setupToolbar(roomToolbar)
|
||||
|
@ -269,6 +277,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
setupInviteView()
|
||||
setupNotificationView()
|
||||
setupJumpToReadMarkerView()
|
||||
setupActiveCallView()
|
||||
setupJumpToBottomView()
|
||||
setupWidgetsBannerView()
|
||||
|
||||
|
@ -283,6 +292,13 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
.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")) {
|
||||
renderTombstoneEventHandling(it)
|
||||
}
|
||||
|
@ -374,6 +390,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
override fun onDestroyView() {
|
||||
timelineEventController.callback = null
|
||||
timelineEventController.removeModelBuildListener(modelBuildListener)
|
||||
activeCallView.callback = null
|
||||
modelBuildListener = null
|
||||
autoCompleter.clear()
|
||||
debouncer.cancelAll()
|
||||
|
@ -412,6 +429,10 @@ class RoomDetailFragment @Inject constructor(
|
|||
jumpToReadMarkerView.callback = this
|
||||
}
|
||||
|
||||
private fun setupActiveCallView() {
|
||||
activeCallView.callback = this
|
||||
}
|
||||
|
||||
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
|
||||
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
|
||||
if (scrollPosition == null) {
|
||||
|
@ -483,8 +504,20 @@ class RoomDetailFragment @Inject constructor(
|
|||
R.id.video_call -> {
|
||||
roomDetailViewModel.getOtherUserIds()?.firstOrNull()?.let {
|
||||
// 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)
|
||||
}
|
||||
} else {
|
||||
webRtcPeerConnectionManager.startOutgoingCall(requireContext(), roomDetailArgs.roomId, it, item.itemId == R.id.video_call)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
@ -1479,4 +1512,21 @@ class RoomDetailFragment @Inject constructor(
|
|||
RoomWidgetsBottomSheet.newInstance()
|
||||
.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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
tools:background="@color/password_strength_bar_ok"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
@ -55,8 +55,8 @@
|
|||
android:id="@+id/connectedControls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
tools:background="@color/password_strength_bar_low"
|
||||
tools:visibility="visible">
|
||||
|
@ -66,11 +66,13 @@
|
|||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:background="@drawable/oval_positive"
|
||||
android:backgroundTint="?attr/riotx_background"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_home_bottom_chat"
|
||||
android:tint="?attr/riotx_background"
|
||||
android:tint="?attr/riotx_text_primary"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
|
||||
|
@ -85,9 +87,9 @@
|
|||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_microphone_off"
|
||||
tools:src="@drawable/ic_microphone_on"
|
||||
android:tint="?attr/riotx_text_primary"
|
||||
tools:ignore="MissingConstraints" />
|
||||
tools:ignore="MissingConstraints"
|
||||
tools:src="@drawable/ic_microphone_on" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_end_call"
|
||||
|
|
|
@ -97,6 +97,12 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
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
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="0dp"
|
||||
|
@ -105,7 +111,7 @@
|
|||
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
|
||||
app:layout_constraintEnd_toEndOf="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" />
|
||||
|
||||
|
||||
|
|
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="call_in_progress">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_ice_failed">Media Connection Failed</string>
|
||||
|
|
Loading…
Reference in a new issue