VoIP: hold/resume fix negotiation and start adding UI
This commit is contained in:
parent
1a9b0265dc
commit
8f5a11493b
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.call
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidate
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.SdpType
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
interface MxCallDetail {
|
||||
@ -34,7 +35,7 @@ interface MxCallDetail {
|
||||
interface MxCall : MxCallDetail {
|
||||
|
||||
companion object {
|
||||
const val VOIP_PROTO_VERSION = 0
|
||||
const val VOIP_PROTO_VERSION = 1
|
||||
}
|
||||
|
||||
val ourPartyId: String
|
||||
@ -52,7 +53,7 @@ interface MxCall : MxCallDetail {
|
||||
/**
|
||||
* SDP negotiation for media pause, hold/resume, ICE restarts and voice/video call up/downgrading
|
||||
*/
|
||||
fun negotiate(sdpString: String)
|
||||
fun negotiate(sdpString: String, type: SdpType)
|
||||
|
||||
/**
|
||||
* This has to be sent by the caller's client once it has chosen an answer.
|
||||
|
@ -37,9 +37,9 @@ data class CallAnswerContent(
|
||||
*/
|
||||
@Json(name = "answer") val answer: Answer,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this messages adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0"
|
||||
@Json(name = "version") override val version: String?
|
||||
): CallSignallingContent {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
@ -38,7 +38,7 @@ data class CallCandidatesContent(
|
||||
*/
|
||||
@Json(name = "candidates") val candidates: List<CallCandidate> = emptyList(),
|
||||
/**
|
||||
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this messages adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0"
|
||||
@Json(name = "version") override val version: String?
|
||||
): CallSignallingContent
|
||||
|
@ -34,9 +34,9 @@ data class CallHangupContent(
|
||||
*/
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
@Json(name = "version") override val version: String?,
|
||||
/**
|
||||
* Optional error reason for the hangup. This should not be provided when the user naturally ends or rejects the call.
|
||||
* When there was an error in the call negotiation, this should be `ice_failed` for when ICE negotiation fails
|
||||
|
@ -37,9 +37,9 @@ data class CallInviteContent(
|
||||
*/
|
||||
@Json(name = "offer") val offer: Offer?,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
@Json(name = "version") override val version: String?,
|
||||
/**
|
||||
* Required. The time in milliseconds that the invite is valid for.
|
||||
* Once the invite age exceeds this value, clients should discard it.
|
||||
|
@ -43,9 +43,9 @@ data class CallNegotiateContent(
|
||||
@Json(name = "description") val description: Description? = null,
|
||||
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
@Json(name = "version") override val version: String?,
|
||||
|
||||
): CallSignallingContent {
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
@ -34,7 +34,7 @@ data class CallRejectContent(
|
||||
*/
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
@Json(name = "version") override val version: String?,
|
||||
):CallSignallingContent
|
||||
|
@ -38,7 +38,7 @@ data class CallSelectAnswerContent(
|
||||
@Json(name = "selected_party_id") val selectedPartyId: String? = null,
|
||||
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
* Required. The version of the VoIP specification this message adheres to.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
@Json(name = "version") override val version: String?,
|
||||
): CallSignallingContent
|
||||
|
@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent
|
||||
@ -163,13 +164,13 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleCallNegotiateEvent(event: Event) {
|
||||
val content = event.getClearContent().toModel<CallSelectAnswerContent>() ?: return
|
||||
val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return
|
||||
val call = content.getCall() ?: return
|
||||
if (call.ourPartyId == content.partyId) {
|
||||
// Ignore remote echo
|
||||
return
|
||||
}
|
||||
callListenersDispatcher.onCallSelectAnswerReceived(content)
|
||||
callListenersDispatcher.onCallNegotiateReceived(content)
|
||||
}
|
||||
|
||||
private fun handleCallSelectAnswerEvent(event: Event) {
|
||||
|
@ -97,7 +97,8 @@ internal class MxCallImpl(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
|
||||
offer = CallInviteContent.Offer(sdp = sdpString)
|
||||
offer = CallInviteContent.Offer(sdp = sdpString),
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
@ -107,7 +108,8 @@ internal class MxCallImpl(
|
||||
CallCandidatesContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
candidates = candidates
|
||||
candidates = candidates,
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
@ -139,7 +141,8 @@ internal class MxCallImpl(
|
||||
CallHangupContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
reason = reason ?: CallHangupContent.Reason.USER_HANGUP
|
||||
reason = reason ?: CallHangupContent.Reason.USER_HANGUP,
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
@ -153,19 +156,21 @@ internal class MxCallImpl(
|
||||
CallAnswerContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
answer = CallAnswerContent.Answer(sdp = sdpString)
|
||||
answer = CallAnswerContent.Answer(sdp = sdpString),
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
}
|
||||
|
||||
override fun negotiate(sdpString: String) {
|
||||
override fun negotiate(sdpString: String, type: SdpType) {
|
||||
Timber.v("## VOIP negotiate $callId")
|
||||
CallNegotiateContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
|
||||
description = CallNegotiateContent.Description(sdp = sdpString, type = SdpType.OFFER)
|
||||
description = CallNegotiateContent.Description(sdp = sdpString, type = type),
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_NEGOTIATE, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
@ -178,7 +183,8 @@ internal class MxCallImpl(
|
||||
CallSelectAnswerContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
selectedPartyId = opponentPartyId?.getOrNull()
|
||||
selectedPartyId = opponentPartyId?.getOrNull(),
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_SELECT_ANSWER, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
|
@ -53,6 +53,11 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
callControlsToggleHoldResume.clickableView.debouncedClicks {
|
||||
callViewModel.handle(VectorCallViewActions.ToggleHoldResume)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
callViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is VectorCallViewEvents.ShowSoundDeviceChooser -> {
|
||||
@ -71,15 +76,15 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
text = getString(R.string.sound_device_wireless_headset)
|
||||
textStyle = if (current == it) "bold" else "normal"
|
||||
}
|
||||
CallAudioManager.SoundDevice.PHONE -> span {
|
||||
CallAudioManager.SoundDevice.PHONE -> span {
|
||||
text = getString(R.string.sound_device_phone)
|
||||
textStyle = if (current == it) "bold" else "normal"
|
||||
}
|
||||
CallAudioManager.SoundDevice.SPEAKER -> span {
|
||||
CallAudioManager.SoundDevice.SPEAKER -> span {
|
||||
text = getString(R.string.sound_device_speaker)
|
||||
textStyle = if (current == it) "bold" else "normal"
|
||||
}
|
||||
CallAudioManager.SoundDevice.HEADSET -> span {
|
||||
CallAudioManager.SoundDevice.HEADSET -> span {
|
||||
text = getString(R.string.sound_device_headset)
|
||||
textStyle = if (current == it) "bold" else "normal"
|
||||
}
|
||||
@ -90,13 +95,13 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
d.cancel()
|
||||
when (soundDevices[n].toString()) {
|
||||
// TODO Make an adapter and handle multiple Bluetooth headsets. Also do not use translations.
|
||||
getString(R.string.sound_device_phone) -> {
|
||||
getString(R.string.sound_device_phone) -> {
|
||||
callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.PHONE))
|
||||
}
|
||||
getString(R.string.sound_device_speaker) -> {
|
||||
getString(R.string.sound_device_speaker) -> {
|
||||
callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.SPEAKER))
|
||||
}
|
||||
getString(R.string.sound_device_headset) -> {
|
||||
getString(R.string.sound_device_headset) -> {
|
||||
callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.HEADSET))
|
||||
}
|
||||
getString(R.string.sound_device_wireless_headset) -> {
|
||||
@ -111,9 +116,9 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
private fun renderState(state: VectorCallViewState) {
|
||||
callControlsSoundDevice.title = getString(R.string.call_select_sound_device)
|
||||
callControlsSoundDevice.subTitle = when (state.soundDevice) {
|
||||
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
|
||||
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
|
||||
CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset)
|
||||
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
|
||||
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
|
||||
CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset)
|
||||
CallAudioManager.SoundDevice.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset)
|
||||
}
|
||||
|
||||
@ -134,5 +139,14 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
} else {
|
||||
callControlsToggleSDHD.isVisible = false
|
||||
}
|
||||
if (state.isRemoteOnHold) {
|
||||
callControlsToggleHoldResume.title = getString(R.string.call_resume_action)
|
||||
callControlsToggleHoldResume.subTitle = null
|
||||
callControlsToggleHoldResume.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_call_resume_action)
|
||||
} else {
|
||||
callControlsToggleHoldResume.title = getString(R.string.call_hold_action)
|
||||
callControlsToggleHoldResume.subTitle = null
|
||||
callControlsToggleHoldResume.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_call_hold_action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,25 +20,27 @@ import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import butterknife.BindView
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ScreenComponent
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
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
|
||||
@ -57,11 +59,11 @@ 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
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.webrtc.EglBase
|
||||
import org.webrtc.RendererCommon
|
||||
import org.webrtc.SurfaceViewRenderer
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
@ -107,63 +109,16 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||
var surfaceRenderersAreInitialized = false
|
||||
|
||||
override fun doBeforeSetContentView() {
|
||||
// Set window styles for fullscreen-window size. Needs to be done before adding content.
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
hideSystemUI()
|
||||
setContentView(R.layout.activity_call)
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
systemUiVisibility = false
|
||||
// Enables regular immersive mode.
|
||||
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
||||
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
// Set the content to appear under the system bars so that the
|
||||
// content doesn't resize when the system bars hide and show.
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
// Hide the nav bar and status bar
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
|
||||
// Shows the system bars by removing all the flags
|
||||
// except for the ones that make the content appear under the system bars.
|
||||
private fun showSystemUI() {
|
||||
systemUiVisibility = true
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
}
|
||||
|
||||
private fun toggleUiSystemVisibility() {
|
||||
if (systemUiVisibility) {
|
||||
hideSystemUI()
|
||||
} else {
|
||||
showSystemUI()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
// Rehide when bottom sheet is dismissed
|
||||
if (hasFocus) {
|
||||
hideSystemUI()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// This will need to be refined
|
||||
ViewCompat.setOnApplyWindowInsetsListener(constraintLayout) { v, insets ->
|
||||
v.updatePadding(bottom = if (systemUiVisibility) insets.systemWindowInsetBottom else 0)
|
||||
insets
|
||||
}
|
||||
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
if (intent.hasExtra(MvRx.KEY_ARG)) {
|
||||
@ -179,12 +134,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||
turnScreenOnAndKeyguardOff()
|
||||
}
|
||||
|
||||
constraintLayout.clicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { toggleUiSystemVisibility() }
|
||||
.disposeOnDestroy()
|
||||
|
||||
configureCallViews()
|
||||
|
||||
callViewModel.subscribe(this) {
|
||||
@ -232,6 +181,9 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||
callControlsView.updateForState(state)
|
||||
val callState = state.callState.invoke()
|
||||
callConnectingProgress.isVisible = false
|
||||
callActionText.setOnClickListener(null)
|
||||
callActionText.isVisible = false
|
||||
smallIsHeldIcon.isVisible = false
|
||||
when (callState) {
|
||||
is CallState.Idle,
|
||||
is CallState.Dialing -> {
|
||||
@ -257,15 +209,33 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||
}
|
||||
is CallState.Connected -> {
|
||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
if (callArgs.isVideoCall) {
|
||||
callVideoGroup.isVisible = true
|
||||
callInfoGroup.isVisible = false
|
||||
pip_video_view.isVisible = !state.isVideoCaptureInError
|
||||
} else {
|
||||
if (state.isLocalOnHold) {
|
||||
smallIsHeldIcon.isVisible = true
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
configureCallInfo(state)
|
||||
if (state.isRemoteOnHold) {
|
||||
callActionText.setText(R.string.call_resume_action)
|
||||
callActionText.isVisible = true
|
||||
callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.ToggleHoldResume) }
|
||||
callStatusText.setText(R.string.call_held_by_you)
|
||||
} else {
|
||||
callActionText.isInvisible = true
|
||||
state.otherUserMatrixItem.invoke()?.let {
|
||||
callStatusText.text = getString(R.string.call_held_by_user, it.getBestName())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callStatusText.text = null
|
||||
if (callArgs.isVideoCall) {
|
||||
callVideoGroup.isVisible = true
|
||||
callInfoGroup.isVisible = false
|
||||
pip_video_view.isVisible = !state.isVideoCaptureInError
|
||||
} else {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
configureCallInfo(state)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This state is not final, if you change network, new candidates will be sent
|
||||
@ -288,9 +258,8 @@ 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)
|
||||
avatarRenderer.render(it, otherMemberAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
||||
object DeclineCall : VectorCallViewActions()
|
||||
object ToggleMute : VectorCallViewActions()
|
||||
object ToggleVideo : VectorCallViewActions()
|
||||
object ToggleHoldResume: VectorCallViewActions()
|
||||
data class ChangeAudioDevice(val device: CallAudioManager.SoundDevice) : VectorCallViewActions()
|
||||
object SwitchSoundDevice : VectorCallViewActions()
|
||||
object HeadSetButtonPressed : VectorCallViewActions()
|
||||
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import timber.log.Timber
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
@ -53,6 +54,15 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
|
||||
private val callListener = object : WebRtcCall.Listener {
|
||||
|
||||
override fun onHoldUnhold() {
|
||||
setState {
|
||||
copy(
|
||||
isLocalOnHold = call?.isLocalOnHold() ?: false,
|
||||
isRemoteOnHold = call?.remoteOnHold ?: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCaptureStateChanged() {
|
||||
setState {
|
||||
copy(
|
||||
@ -62,7 +72,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCameraChange() {
|
||||
override fun onCameraChanged() {
|
||||
setState {
|
||||
copy(
|
||||
canSwitchCamera = call?.canSwitchCamera() ?: false,
|
||||
@ -107,6 +117,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
||||
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
// we need to check the state
|
||||
if (call == null) {
|
||||
@ -202,6 +213,10 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
}
|
||||
Unit
|
||||
}
|
||||
VectorCallViewActions.ToggleHoldResume -> {
|
||||
val isRemoteOnHold = state.isRemoteOnHold
|
||||
call?.updateRemoteOnHold(!isRemoteOnHold)
|
||||
}
|
||||
is VectorCallViewActions.ChangeAudioDevice -> {
|
||||
callManager.callAudioManager.setCurrentSoundDevice(action.device)
|
||||
setState {
|
||||
|
@ -26,6 +26,8 @@ data class VectorCallViewState(
|
||||
val callId: String,
|
||||
val roomId: String,
|
||||
val isVideoCall: Boolean,
|
||||
val isRemoteOnHold: Boolean = false,
|
||||
val isLocalOnHold: Boolean = false,
|
||||
val isAudioMuted: Boolean = false,
|
||||
val isVideoEnabled: Boolean = true,
|
||||
val isVideoCaptureInError: Boolean = false,
|
||||
|
@ -42,7 +42,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
@ -93,7 +92,8 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
|
||||
interface Listener : MxCall.StateListener {
|
||||
fun onCaptureStateChanged() {}
|
||||
fun onCameraChange() {}
|
||||
fun onCameraChanged() {}
|
||||
fun onHoldUnhold() {}
|
||||
}
|
||||
|
||||
private val listeners = ArrayList<Listener>()
|
||||
@ -113,6 +113,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
private var localAudioTrack: AudioTrack? = null
|
||||
private var localVideoSource: VideoSource? = null
|
||||
private var localVideoTrack: VideoTrack? = null
|
||||
private var remoteAudioTrack: AudioTrack? = null
|
||||
private var remoteVideoTrack: VideoTrack? = null
|
||||
|
||||
// Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example
|
||||
@ -192,7 +193,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
// send offer to peer
|
||||
mxCall.offerSdp(sessionDescription.description)
|
||||
} else {
|
||||
mxCall.negotiate(sessionDescription.description)
|
||||
mxCall.negotiate(sessionDescription.description, SdpType.OFFER)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
// Need to handle error properly.
|
||||
@ -463,7 +464,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
?: null.also { cameraInUse = null }
|
||||
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChange() }
|
||||
tryOrNull { it.onCameraChanged() }
|
||||
}
|
||||
|
||||
if (camera != null) {
|
||||
@ -532,8 +533,10 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
private fun updateMuteStatus() {
|
||||
val micShouldBeMuted = micMuted || remoteOnHold
|
||||
localAudioTrack?.setEnabled(!micShouldBeMuted)
|
||||
remoteAudioTrack?.setEnabled(!remoteOnHold)
|
||||
val vidShouldBeMuted = videoMuted || remoteOnHold
|
||||
localVideoTrack?.setEnabled(!vidShouldBeMuted)
|
||||
remoteVideoTrack?.setEnabled(!remoteOnHold)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -608,7 +611,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
it.get()?.setMirror(isFrontCamera)
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChange() }
|
||||
tryOrNull { it.onCameraChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,6 +675,11 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
mxCall.hangUp()
|
||||
return@launch
|
||||
}
|
||||
if (stream.audioTracks.size == 1) {
|
||||
val remoteAudioTrack = stream.audioTracks.first()
|
||||
remoteAudioTrack.setEnabled(true)
|
||||
this@WebRtcCall.remoteAudioTrack = remoteAudioTrack
|
||||
}
|
||||
if (stream.videoTracks.size == 1) {
|
||||
val remoteVideoTrack = stream.videoTracks.first()
|
||||
remoteVideoTrack.setEnabled(true)
|
||||
@ -688,11 +696,12 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
.mapNotNull { it.get() }
|
||||
.forEach { remoteVideoTrack?.removeSink(it) }
|
||||
remoteVideoTrack = null
|
||||
remoteAudioTrack = null
|
||||
}
|
||||
}
|
||||
|
||||
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
||||
if(mxCall.state == CallState.Terminated){
|
||||
if (mxCall.state == CallState.Terminated) {
|
||||
return
|
||||
}
|
||||
mxCall.state = CallState.Terminated
|
||||
@ -767,16 +776,24 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
Timber.i("Ignoring colliding negotiate event because we're impolite")
|
||||
return@launch
|
||||
}
|
||||
val prevOnHold = isLocalOnHold()
|
||||
try {
|
||||
val sdp = SessionDescription(type.asWebRTC(), sdpText)
|
||||
peerConnection.awaitSetRemoteDescription(sdp)
|
||||
if (type == SdpType.OFFER) {
|
||||
createAnswer()
|
||||
mxCall.negotiate(sdpText)
|
||||
createAnswer()?.also {
|
||||
mxCall.negotiate(it.description, SdpType.ANSWER)
|
||||
}
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Failed to complete negotiation")
|
||||
}
|
||||
val nowOnHold = isLocalOnHold()
|
||||
if (prevOnHold != nowOnHold) {
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onHoldUnhold() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,19 +67,9 @@ class WebRtcCallManager @Inject constructor(
|
||||
|
||||
interface CurrentCallListener {
|
||||
fun onCurrentCallChange(call: WebRtcCall?)
|
||||
fun onCaptureStateChanged() {}
|
||||
fun onAudioDevicesChange() {}
|
||||
fun onCameraChange() {}
|
||||
}
|
||||
|
||||
var capturerIsInError = false
|
||||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryOrNull { it.onCaptureStateChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
private val currentCallsListeners = emptyList<CurrentCallListener>().toMutableList()
|
||||
fun addCurrentCallListener(listener: CurrentCallListener) {
|
||||
currentCallsListeners.add(listener)
|
||||
|
@ -121,6 +121,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||
fun getCachedDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem): Drawable {
|
||||
return buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
||||
.onlyRetrieveFromCache(true)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.submit()
|
||||
.get()
|
||||
}
|
||||
|
9
vector/src/main/res/drawable/ic_call_answer.xml
Normal file
9
vector/src/main/res/drawable/ic_call_answer.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M8.027,15.9613C9.168,17.1932 11.9148,19.3263 12.6635,19.7641C12.7078,19.79 12.7585,19.8201 12.8152,19.8538C13.9576,20.5329 17.5373,22.6609 20.1454,20.6694C22.1661,19.1266 21.5091,17.3909 20.8289,16.875C20.3633,16.5128 18.9914,15.5145 17.7006,14.6152C16.4331,13.7322 15.7268,14.4397 15.2492,14.918C15.2404,14.9268 15.2317,14.9355 15.2231,14.9442L14.2621,15.9051C14.0174,16.1498 13.6451,16.0605 13.2886,15.7804C12.0092,14.8061 11.0681,13.8659 10.5972,13.395L10.5933,13.391C10.1225,12.9202 9.1939,11.9908 8.2196,10.7114C7.9395,10.3548 7.8502,9.9826 8.0949,9.7379L9.0559,8.7769C9.0645,8.7683 9.0732,8.7596 9.082,8.7508C9.5603,8.2732 10.2678,7.5668 9.3848,6.2994C8.4855,5.0086 7.4872,3.6367 7.125,3.1711C6.6091,2.4909 4.8734,1.8339 3.3306,3.8546C1.3391,6.4627 3.4671,10.0424 4.1462,11.1848C4.1799,11.2415 4.2101,11.2922 4.2359,11.3365C4.6737,12.0851 6.7951,14.8203 8.027,15.9613Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
9
vector/src/main/res/drawable/ic_call_hangup.xml
Normal file
9
vector/src/main/res/drawable/ic_call_hangup.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12.0084,7.7565C10.3211,7.6916 6.8514,8.1295 6.0078,8.3513C5.9579,8.3645 5.9004,8.3791 5.8362,8.3955C4.541,8.7261 0.4827,9.7618 0.0442,13.0436C-0.2955,15.5862 1.4058,16.3558 2.2562,16.2386C2.8448,16.1648 4.5301,15.8983 6.0872,15.6189C7.6163,15.3446 7.6155,14.3359 7.615,13.6538C7.615,13.6413 7.615,13.6288 7.615,13.6165L7.615,12.2453C7.615,11.8961 7.9432,11.6942 8.3958,11.6396C9.9982,11.422 11.3359,11.4213 12.0055,11.4213L12.0112,11.4213C12.6807,11.4213 14.0018,11.422 15.6042,11.6396C16.0569,11.6942 16.385,11.8961 16.385,12.2453L16.385,13.6165C16.385,13.6289 16.385,13.6413 16.385,13.6538C16.3845,14.3359 16.3837,15.3446 17.9128,15.619C19.4699,15.8983 21.1552,16.1648 21.7438,16.2386C22.5942,16.3558 24.2955,15.5862 23.9558,13.0436C23.5173,9.7618 19.459,8.7261 18.1638,8.3955C18.0996,8.3791 18.0421,8.3645 17.9922,8.3513C17.1487,8.1295 13.6956,7.6916 12.0084,7.7565Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
7
vector/src/main/res/drawable/ic_call_hold_action.xml
Normal file
7
vector/src/main/res/drawable/ic_call_hold_action.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<vector android:height="24dp"
|
||||
android:viewportHeight="20"
|
||||
android:viewportWidth="20"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#C1C6CD" android:pathData="M10,0C4.48,0 0,4.48 0,10C0,15.52 4.48,20 10,20C15.52,20 20,15.52 20,10C20,4.48 15.52,0 10,0ZM8,14C7.45,14 7,13.55 7,13V7C7,6.45 7.45,6 8,6C8.55,6 9,6.45 9,7V13C9,13.55 8.55,14 8,14ZM12,14C11.45,14 11,13.55 11,13V7C11,6.45 11.45,6 12,6C12.55,6 13,6.45 13,7V13C13,13.55 12.55,14 12,14Z"/>
|
||||
</vector>
|
9
vector/src/main/res/drawable/ic_call_pip.xml
Normal file
9
vector/src/main/res/drawable/ic_call_pip.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,11H19V17H11V11ZM1,19V4.98C1,3.88 1.9,3 3,3H21C22.1,3 23,3.88 23,4.98V19C23,20.1 22.1,21 21,21H3C1.9,21 1,20.1 1,19ZM3,19.02H21V4.97H3V19.02Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
8
vector/src/main/res/drawable/ic_call_resume_action.xml
Normal file
8
vector/src/main/res/drawable/ic_call_resume_action.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#C1C6CD" android:pathData="M10,0C4.48,0 0,4.48 0,10C0,15.52 4.48,20 10,20C15.52,20 20,15.52 20,10C20,4.48 15.52,0 10,0ZM8,13.5V6.5C8,6.09 8.47,5.85 8.8,6.1L13.47,9.6C13.74,9.8 13.74,10.2 13.47,10.4L8.8,13.9C8.47,14.15 8,13.91 8,13.5Z"/>
|
||||
</vector>
|
||||
|
9
vector/src/main/res/drawable/ic_call_small_pause.xml
Normal file
9
vector/src/main/res/drawable/ic_call_small_pause.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M10,0C4.48,0 0,4.48 0,10C0,15.52 4.48,20 10,20C15.52,20 20,15.52 20,10C20,4.48 15.52,0 10,0ZM9,14H7V6H9V14ZM13,14H11V6H13V14Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
@ -1,12 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- tools:ignore is needed because lint thinks this can be replaced with a merge. Replacing this
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- tools:ignore is needed because lint thinks this can be replaced with a merge. Replacing this
|
||||
with a merge causes the fullscreen SurfaceView not to be centered. -->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?riotx_background"
|
||||
android:background="@color/bg_call_screen"
|
||||
tools:ignore="MergeRootFrame">
|
||||
|
||||
<org.webrtc.SurfaceViewRenderer
|
||||
@ -26,48 +29,44 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/participantNameText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:gravity="center"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/callTypeText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/callTypeText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="22sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/otherMemberAvatar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="@string/action_video_call" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/otherMemberAvatar"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:contentDescription="@string/avatar"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.3"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<ImageView
|
||||
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_constraintStart_toStartOf="@id/otherMemberAvatar"
|
||||
app:layout_constraintEnd_toEndOf="@id/otherMemberAvatar" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/participantNameText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/otherMemberAvatar"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/callStatusText"
|
||||
@ -78,18 +77,31 @@
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="22sp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/otherMemberAvatar"
|
||||
app:layout_constraintTop_toBottomOf="@id/participantNameText"
|
||||
tools:text="@string/call_connecting" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/callActionText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
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"
|
||||
tools:text="@string/call_resume_action" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/callConnectingProgress"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@ -102,7 +114,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
app:constraint_referenced_ids="participantNameText,callTypeText,otherMemberAvatar,callStatusText" />
|
||||
app:constraint_referenced_ids="participantNameText, otherMemberAvatar,callStatusText" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/callVideoGroup"
|
||||
|
@ -30,9 +30,18 @@
|
||||
android:id="@+id/callControlsToggleSDHD"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:actionTitle="@string/call_switch_camera"
|
||||
app:actionTitle="@string/call_format_turn_hd_on"
|
||||
app:leftIcon="@drawable/ic_hd"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:actionDescription="Front" />
|
||||
|
||||
<im.vector.app.core.ui.views.BottomSheetActionButton
|
||||
android:id="@+id/callControlsToggleHoldResume"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:actionTitle="Hold/resume"
|
||||
app:leftIcon="@drawable/ic_call_hold_action"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:actionDescription="" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -24,7 +24,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_share"
|
||||
tools:src="@drawable/ic_call_resume_action"
|
||||
tools:visibility="visible"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
@ -60,7 +60,7 @@
|
||||
app:layout_constraintStart_toStartOf="@+id/itemVerificationActionTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/itemVerificationActionTitle"
|
||||
tools:text="For maximum security, do this in person"
|
||||
tools:visibility="visible" />
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemVerificationActionIcon"
|
||||
|
@ -15,29 +15,29 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icr_accept_call"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/oval_positive"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/call_notification_answer"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="@color/white" />
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_call_answer"
|
||||
app:tint="@color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icr_end_call"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/oval_destructive"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/call_notification_reject"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call_end"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="@color/white" />
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_call_hangup"
|
||||
app:tint="@color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:layout_width="match_parent"
|
||||
@ -53,6 +53,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:visibility="gone"
|
||||
tools:background="@color/password_strength_bar_low"
|
||||
tools:layout_marginTop="120dp"
|
||||
@ -60,73 +62,69 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_leftMiniControl"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/oval_positive"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/a11y_open_chat"
|
||||
android:focusable="true"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_home_bottom_chat"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_call_pip"
|
||||
app:tint="@android:color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_mute_toggle"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/oval_positive"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_microphone_off"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:contentDescription="@string/a11y_mute_microphone"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
tools:src="@drawable/ic_microphone_on"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
tools:src="@drawable/ic_microphone_on" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_end_call"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/oval_destructive"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/call_notification_hangup"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call_end"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="@color/white" />
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_call_hangup"
|
||||
app:tint="@color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_video_toggle"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/oval_positive"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call_videocam_off_default"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:contentDescription="@string/a11y_stop_camera"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_more"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/oval_positive"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/settings"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_more_vertical"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
android:src="@drawable/ic_more_horizontal"
|
||||
app:tint="@android:color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:layout_width="match_parent"
|
||||
@ -163,8 +161,8 @@
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/iv_end_call"-->
|
||||
<!-- android:layout_width="64dp"-->
|
||||
<!-- android:layout_height="64dp"-->
|
||||
<!-- android:layout_width="56dp"-->
|
||||
<!-- android:layout_height="56dp"-->
|
||||
<!-- android:layout_marginBottom="32dp"-->
|
||||
<!-- android:background="@drawable/oval_destructive"-->
|
||||
<!-- android:clickable="true"-->
|
||||
|
@ -18,6 +18,7 @@
|
||||
<color name="riotx_positive_accent_alpha12">#1E0DBD8B</color>
|
||||
<color name="riotx_button_disabled_alpha12">#1E61708B</color>
|
||||
|
||||
<color name="bg_call_screen">#99000000</color>
|
||||
|
||||
<color name="riotx_notice">#FFFF4B55</color>
|
||||
<color name="riotx_notice_secondary">#FF61708B</color>
|
||||
@ -46,6 +47,7 @@
|
||||
'riotx_<name in the palette snake case>_<theme>'
|
||||
-->
|
||||
|
||||
|
||||
<attr name="riotx_background" format="color" />
|
||||
<color name="riotx_background_light">#FFFFFFFF</color>
|
||||
<color name="riotx_background_dark">#FF15191E</color>
|
||||
|
@ -402,6 +402,10 @@
|
||||
<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_resume_action">Resume</string>
|
||||
<string name="call_hold_action">Hold</string>
|
||||
<string name="call_held_by_user">%s held the call</string>
|
||||
<string name="call_held_by_you">You held the 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…
x
Reference in New Issue
Block a user