VoIP: start to show in-app notification

This commit is contained in:
ganfra 2020-12-09 11:17:49 +01:00
parent bf6f60c7e5
commit 76ed775f6f
27 changed files with 496 additions and 351 deletions

View File

@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
interface MxCallDetail { interface MxCallDetail {
val sessionId: String
val callId: String val callId: String
val isOutgoing: Boolean val isOutgoing: Boolean
val roomId: String val roomId: String

View File

@ -160,7 +160,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
val content = event.getClearContent().toModel<CallInviteContent>() ?: return val content = event.getClearContent().toModel<CallInviteContent>() ?: return
val incomingCall = mxCallFactory.createIncomingCall( val incomingCall = mxCallFactory.createIncomingCall(
roomId = event.roomId, roomId = event.roomId,
senderId = event.senderId, opponentUserId = event.senderId,
content = content content = content
) ?: return ) ?: return
activeCallHandler.addCall(incomingCall) activeCallHandler.addCall(incomingCall)

View File

@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
@ -29,21 +30,23 @@ import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
internal class MxCallFactory @Inject constructor( internal class MxCallFactory @Inject constructor(
@SessionId private val sessionId: String,
@DeviceId private val deviceId: String?, @DeviceId private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor, private val eventSenderProcessor: EventSenderProcessor,
@UserId private val userId: String @UserId private val userId: String
) { ) {
fun createIncomingCall(roomId: String, senderId: String, content: CallInviteContent): MxCall? { fun createIncomingCall(roomId: String, opponentUserId: String, content: CallInviteContent): MxCall? {
if (content.callId == null) return null if (content.callId == null) return null
return MxCallImpl( return MxCallImpl(
sessionId = sessionId,
callId = content.callId, callId = content.callId,
isOutgoing = false, isOutgoing = false,
roomId = roomId, roomId = roomId,
userId = userId, userId = userId,
ourPartyId = deviceId ?: "", ourPartyId = deviceId ?: "",
opponentUserId = senderId, opponentUserId = opponentUserId,
isVideoCall = content.isVideo(), isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor eventSenderProcessor = eventSenderProcessor
@ -53,14 +56,15 @@ internal class MxCallFactory @Inject constructor(
} }
} }
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall {
return MxCallImpl( return MxCallImpl(
sessionId = sessionId,
callId = UUID.randomUUID().toString(), callId = UUID.randomUUID().toString(),
isOutgoing = true, isOutgoing = true,
roomId = roomId, roomId = roomId,
userId = userId, userId = userId,
ourPartyId = deviceId ?: "", ourPartyId = deviceId ?: "",
opponentUserId = otherUserId, opponentUserId = opponentUserId,
isVideoCall = isVideoCall, isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor eventSenderProcessor = eventSenderProcessor

View File

@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import timber.log.Timber import timber.log.Timber
internal class MxCallImpl( internal class MxCallImpl(
override val sessionId: String,
override val callId: String, override val callId: String,
override val isOutgoing: Boolean, override val isOutgoing: Boolean,
override val roomId: String, override val roomId: String,

View File

@ -239,7 +239,7 @@ android {
productFlavors { productFlavors {
gplay { gplay {
dimension "store" dimension "store"
isDefault = true
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
resValue "bool", "isGplay", "true" resValue "bool", "isGplay", "true"

View File

@ -25,9 +25,16 @@ import android.view.KeyEvent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media.session.MediaButtonReceiver import androidx.media.session.MediaButtonReceiver
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.telecom.CallConnection import im.vector.app.features.call.telecom.CallConnection
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.popup.IncomingCallAlert
import im.vector.app.features.popup.PopupAlertManager
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber import timber.log.Timber
/** /**
@ -39,6 +46,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
private lateinit var notificationUtils: NotificationUtils private lateinit var notificationUtils: NotificationUtils
private lateinit var callManager: WebRtcCallManager private lateinit var callManager: WebRtcCallManager
private lateinit var avatarRenderer: AvatarRenderer
private lateinit var alertManager: PopupAlertManager
private var callRingPlayerIncoming: CallRingPlayerIncoming? = null private var callRingPlayerIncoming: CallRingPlayerIncoming? = null
private var callRingPlayerOutgoing: CallRingPlayerOutgoing? = null private var callRingPlayerOutgoing: CallRingPlayerOutgoing? = null
@ -64,6 +73,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
super.onCreate() super.onCreate()
notificationUtils = vectorComponent().notificationUtils() notificationUtils = vectorComponent().notificationUtils()
callManager = vectorComponent().webRtcCallManager() callManager = vectorComponent().webRtcCallManager()
avatarRenderer = vectorComponent().avatarRenderer()
alertManager = vectorComponent().alertManager()
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext) callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext) callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this) wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
@ -111,20 +122,20 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
callRingPlayerOutgoing?.start() callRingPlayerOutgoing?.start()
displayOutgoingRingingCallNotification(intent) displayOutgoingRingingCallNotification(intent)
} }
ACTION_ONGOING_CALL -> { ACTION_ONGOING_CALL -> {
callRingPlayerIncoming?.stop() callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop() callRingPlayerOutgoing?.stop()
displayCallInProgressNotification(intent) displayCallInProgressNotification(intent)
} }
ACTION_NO_ACTIVE_CALL -> hideCallNotifications() ACTION_NO_ACTIVE_CALL -> hideCallNotifications()
ACTION_CALL_CONNECTING -> { ACTION_CALL_CONNECTING -> {
// lower notification priority // lower notification priority
displayCallInProgressNotification(intent) displayCallInProgressNotification(intent)
// stop ringing // stop ringing
callRingPlayerIncoming?.stop() callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop() callRingPlayerOutgoing?.stop()
} }
ACTION_ONGOING_CALL_BG -> { ACTION_ONGOING_CALL_BG -> {
// there is an ongoing call but call activity is in background // there is an ongoing call but call activity is in background
displayCallOnGoingInBackground(intent) displayCallOnGoingInBackground(intent)
} }
@ -154,56 +165,52 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
*/ */
private fun displayIncomingCallNotification(intent: Intent) { private fun displayIncomingCallNotification(intent: Intent) {
Timber.v("## VOIP displayIncomingCallNotification $intent") Timber.v("## VOIP displayIncomingCallNotification $intent")
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
// the incoming call in progress is already displayed val call = callManager.getCallById(callId) ?: return
// if (!TextUtils.isEmpty(mIncomingCallId)) { val isVideoCall = call.mxCall.isVideoCall
// Timber.v("displayIncomingCallNotification : the incoming call in progress is already displayed") val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false)
// } else if (!TextUtils.isEmpty(mCallIdInProgress)) { val opponentMatrixItem = getOpponentMatrixItem(call)
// Timber.v("displayIncomingCallNotification : a 'call in progress' notification is displayed")
// } else
// // if (null == webRtcPeerConnectionManager.currentCall)
// {
val callId = intent.getStringExtra(EXTRA_CALL_ID)
Timber.v("displayIncomingCallNotification : display the dedicated notification") Timber.v("displayIncomingCallNotification : display the dedicated notification")
if (!fromBg) {
// Show in-app notification if app is in foreground.
val incomingCallAlert = IncomingCallAlert(INCOMING_CALL_ALERT_UID).apply {
viewBinder = IncomingCallAlert.ViewBinder(
matrixItem = opponentMatrixItem,
avatarRenderer = avatarRenderer,
isVideoCall = isVideoCall,
onAccept = { acceptIncomingCall(call) },
onReject = { call.endCall() }
)
dismissedAction = Runnable { call.endCall() }
}
alertManager.postVectorAlert(incomingCallAlert)
}
val notification = notificationUtils.buildIncomingCallNotification( val notification = notificationUtils.buildIncomingCallNotification(
intent.getBooleanExtra(EXTRA_IS_VIDEO, false), mxCall = call.mxCall,
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
intent.getStringExtra(EXTRA_ROOM_ID) ?: "", fromBg = fromBg
callId ?: "") )
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)
}
// mIncomingCallId = callId private fun acceptIncomingCall(call: WebRtcCall){
val intent = VectorCallActivity.newIntent(
// turn the screen on for 3 seconds context = this,
// if (Matrix.getInstance(VectorApp.getInstance())!!.pushManager.isScreenTurnedOn) { mxCall = call.mxCall,
// try { mode = VectorCallActivity.INCOMING_ACCEPT
// val pm = getSystemService<PowerManager>()!! )
// val wl = pm.newWakeLock( startActivity(intent)
// WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or PowerManager.ACQUIRE_CAUSES_WAKEUP,
// CallService::class.java.simpleName)
// wl.acquire(3000)
// wl.release()
// } catch (re: RuntimeException) {
// Timber.e(re, "displayIncomingCallNotification : failed to turn screen on ")
// }
//
// }
// }
// else {
// Timber.i("displayIncomingCallNotification : do not display the incoming call notification because there is a pending call")
// }
} }
private fun displayOutgoingRingingCallNotification(intent: Intent) { private fun displayOutgoingRingingCallNotification(intent: Intent) {
val callId = intent.getStringExtra(EXTRA_CALL_ID) val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
val call = callManager.getCallById(callId) ?: return
val opponentMatrixItem = getOpponentMatrixItem(call)
Timber.v("displayOutgoingCallNotification : display the dedicated notification") Timber.v("displayOutgoingCallNotification : display the dedicated notification")
val notification = notificationUtils.buildOutgoingRingingCallNotification( val notification = notificationUtils.buildOutgoingRingingCallNotification(
intent.getBooleanExtra(EXTRA_IS_VIDEO, false), mxCall = call.mxCall,
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
intent.getStringExtra(EXTRA_ROOM_ID) ?: "", )
callId ?: "")
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)
} }
@ -213,16 +220,14 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
private fun displayCallInProgressNotification(intent: Intent) { private fun displayCallInProgressNotification(intent: Intent) {
Timber.v("## VOIP displayCallInProgressNotification") Timber.v("## VOIP displayCallInProgressNotification")
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
val call = callManager.getCallById(callId) ?: return
val opponentMatrixItem = getOpponentMatrixItem(call)
alertManager.cancelAlert(INCOMING_CALL_ALERT_UID)
val notification = notificationUtils.buildPendingCallNotification( val notification = notificationUtils.buildPendingCallNotification(
intent.getBooleanExtra(EXTRA_IS_VIDEO, false), mxCall = call.mxCall,
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
intent.getStringExtra(EXTRA_ROOM_ID) ?: "", )
intent.getStringExtra(EXTRA_MATRIX_ID) ?: "",
callId)
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)
// mCallIdInProgress = callId // mCallIdInProgress = callId
} }
@ -231,18 +236,15 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
*/ */
private fun displayCallOnGoingInBackground(intent: Intent) { private fun displayCallOnGoingInBackground(intent: Intent) {
Timber.v("## VOIP displayCallInProgressNotification") Timber.v("## VOIP displayCallInProgressNotification")
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
val call = callManager.getCallById(callId) ?: return
val opponentMatrixItem = getOpponentMatrixItem(call)
val notification = notificationUtils.buildPendingCallNotification( val notification = notificationUtils.buildPendingCallNotification(
isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false), mxCall = call.mxCall,
roomName = intent.getStringExtra(EXTRA_ROOM_NAME) ?: "", title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
roomId = intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
matrixId = intent.getStringExtra(EXTRA_MATRIX_ID) ?: "",
callId = callId,
fromBg = true) fromBg = true)
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)
// mCallIdInProgress = callId // mCallIdInProgress = callId
} }
@ -251,7 +253,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
*/ */
private fun hideCallNotifications() { private fun hideCallNotifications() {
val notification = notificationUtils.buildCallEndedNotification() val notification = notificationUtils.buildCallEndedNotification()
alertManager.cancelAlert(INCOMING_CALL_ALERT_UID)
mediaSession?.isActive = false mediaSession?.isActive = false
// It's mandatory to startForeground to avoid crash // It's mandatory to startForeground to avoid crash
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)
@ -263,9 +265,14 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
connections[callConnection.callId] = callConnection connections[callConnection.callId] = callConnection
} }
private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? {
return vectorComponent().currentSession().getUser(call.mxCall.opponentUserId)?.toMatrixItem()
}
companion object { companion object {
private const val NOTIFICATION_ID = 6480 private const val NOTIFICATION_ID = 6480
private const val INCOMING_CALL_ALERT_UID = "INCOMING_CALL_ALERT_UID"
private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL" private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL"
private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL" private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL"
private const val ACTION_CALL_CONNECTING = "im.vector.app.core.services.CallService.ACTION_CALL_CONNECTING" private const val ACTION_CALL_CONNECTING = "im.vector.app.core.services.CallService.ACTION_CALL_CONNECTING"
@ -275,44 +282,26 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
// private const val ACTION_ACTIVITY_VISIBLE = "im.vector.app.core.services.CallService.ACTION_ACTIVITY_VISIBLE" // private const val ACTION_ACTIVITY_VISIBLE = "im.vector.app.core.services.CallService.ACTION_ACTIVITY_VISIBLE"
// private const val ACTION_STOP_RINGING = "im.vector.app.core.services.CallService.ACTION_STOP_RINGING" // private const val ACTION_STOP_RINGING = "im.vector.app.core.services.CallService.ACTION_STOP_RINGING"
private const val EXTRA_IS_VIDEO = "EXTRA_IS_VIDEO"
private const val EXTRA_ROOM_NAME = "EXTRA_ROOM_NAME"
private const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
private const val EXTRA_MATRIX_ID = "EXTRA_MATRIX_ID"
private const val EXTRA_CALL_ID = "EXTRA_CALL_ID" private const val EXTRA_CALL_ID = "EXTRA_CALL_ID"
private const val EXTRA_IS_IN_BG = "EXTRA_IS_IN_BG"
fun onIncomingCallRinging(context: Context, fun onIncomingCallRinging(context: Context,
isVideo: Boolean, callId: String,
roomName: String, isInBackground: Boolean) {
roomId: String,
matrixId: String,
callId: String) {
val intent = Intent(context, CallService::class.java) val intent = Intent(context, CallService::class.java)
.apply { .apply {
action = ACTION_INCOMING_RINGING_CALL action = ACTION_INCOMING_RINGING_CALL
putExtra(EXTRA_IS_VIDEO, isVideo)
putExtra(EXTRA_ROOM_NAME, roomName)
putExtra(EXTRA_ROOM_ID, roomId)
putExtra(EXTRA_MATRIX_ID, matrixId)
putExtra(EXTRA_CALL_ID, callId) putExtra(EXTRA_CALL_ID, callId)
putExtra(EXTRA_IS_IN_BG, isInBackground)
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
} }
fun onOnGoingCallBackground(context: Context, fun onOnGoingCallBackground(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) { callId: String) {
val intent = Intent(context, CallService::class.java) val intent = Intent(context, CallService::class.java)
.apply { .apply {
action = ACTION_ONGOING_CALL_BG action = ACTION_ONGOING_CALL_BG
putExtra(EXTRA_IS_VIDEO, isVideo)
putExtra(EXTRA_ROOM_NAME, roomName)
putExtra(EXTRA_ROOM_ID, roomId)
putExtra(EXTRA_MATRIX_ID, matrixId)
putExtra(EXTRA_CALL_ID, callId) putExtra(EXTRA_CALL_ID, callId)
} }
@ -320,18 +309,10 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
} }
fun onOutgoingCallRinging(context: Context, fun onOutgoingCallRinging(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) { callId: String) {
val intent = Intent(context, CallService::class.java) val intent = Intent(context, CallService::class.java)
.apply { .apply {
action = ACTION_OUTGOING_RINGING_CALL action = ACTION_OUTGOING_RINGING_CALL
putExtra(EXTRA_IS_VIDEO, isVideo)
putExtra(EXTRA_ROOM_NAME, roomName)
putExtra(EXTRA_ROOM_ID, roomId)
putExtra(EXTRA_MATRIX_ID, matrixId)
putExtra(EXTRA_CALL_ID, callId) putExtra(EXTRA_CALL_ID, callId)
} }
@ -339,18 +320,10 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
} }
fun onPendingCall(context: Context, fun onPendingCall(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) { callId: String) {
val intent = Intent(context, CallService::class.java) val intent = Intent(context, CallService::class.java)
.apply { .apply {
action = ACTION_ONGOING_CALL action = ACTION_ONGOING_CALL
putExtra(EXTRA_IS_VIDEO, isVideo)
putExtra(EXTRA_ROOM_NAME, roomName)
putExtra(EXTRA_ROOM_ID, roomId)
putExtra(EXTRA_MATRIX_ID, matrixId)
putExtra(EXTRA_CALL_ID, callId) putExtra(EXTRA_CALL_ID, callId)
} }
@ -362,7 +335,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
.apply { .apply {
action = ACTION_NO_ACTIVE_CALL action = ACTION_NO_ACTIVE_CALL
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
} }
} }

View File

@ -18,7 +18,9 @@ package im.vector.app.features.call
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
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
@ -33,7 +35,7 @@ import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
class CallControlsView @JvmOverloads constructor( class CallControlsView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) { ) : FrameLayout(context, attrs, defStyleAttr) {
var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null
@ -56,8 +58,7 @@ class CallControlsView @JvmOverloads constructor(
lateinit var videoToggleIcon: ImageView lateinit var videoToggleIcon: ImageView
init { init {
ConstraintLayout.inflate(context, R.layout.view_call_controls, this) View.inflate(context, R.layout.view_call_controls, this)
// layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
ButterKnife.bind(this) ButterKnife.bind(this)
} }

View File

@ -339,12 +339,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
const val INCOMING_RINGING = "INCOMING_RINGING" const val INCOMING_RINGING = "INCOMING_RINGING"
const val INCOMING_ACCEPT = "INCOMING_ACCEPT" const val INCOMING_ACCEPT = "INCOMING_ACCEPT"
fun newIntent(context: Context, mxCall: MxCallDetail): Intent { fun newIntent(context: Context, mxCall: MxCallDetail, mode: String?): Intent {
return Intent(context, VectorCallActivity::class.java).apply { return Intent(context, VectorCallActivity::class.java).apply {
// what could be the best flags? // what could be the best flags?
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.opponentUserId, !mxCall.isOutgoing, mxCall.isVideoCall)) putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.opponentUserId, !mxCall.isOutgoing, mxCall.isVideoCall))
putExtra(EXTRA_MODE, OUTGOING_CREATED) putExtra(EXTRA_MODE, mode)
} }
} }

View File

@ -107,6 +107,7 @@ class WebRtcCall(val mxCall: MxCall,
} }
val callId = mxCall.callId val callId = mxCall.callId
val roomId = mxCall.roomId
private var peerConnection: PeerConnection? = null private var peerConnection: PeerConnection? = null
private var localAudioSource: AudioSource? = null private var localAudioSource: AudioSource? = null
@ -237,16 +238,9 @@ class WebRtcCall(val mxCall: MxCall,
mxCall mxCall
.takeIf { it.state is CallState.Connected } .takeIf { it.state is CallState.Connected }
?.let { mxCall -> ?.let { mxCall ->
val session = sessionProvider.get()
val name = session?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.roomId
// Start background service with notification // Start background service with notification
CallService.onPendingCall( CallService.onPendingCall(
context = context, context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?: "",
callId = mxCall.callId) callId = mxCall.callId)
} }
@ -307,15 +301,8 @@ class WebRtcCall(val mxCall: MxCall,
.takeIf { it.state is CallState.Connected } .takeIf { it.state is CallState.Connected }
?.let { mxCall -> ?.let { mxCall ->
// Start background service with notification // Start background service with notification
val session = sessionProvider.get()
val name = session?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onOnGoingCallBackground( CallService.onOnGoingCallBackground(
context = context, context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?: "",
callId = mxCall.callId callId = mxCall.callId
) )
} }
@ -344,15 +331,8 @@ class WebRtcCall(val mxCall: MxCall,
val turnServerResponse = getTurnServer() val turnServerResponse = getTurnServer()
// Update service state // Update service state
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val session = sessionProvider.get()
val name = session?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.roomId
CallService.onPendingCall( CallService.onPendingCall(
context = context, context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?: "",
callId = mxCall.callId callId = mxCall.callId
) )
} }

View File

@ -189,18 +189,12 @@ class WebRtcCallManager @Inject constructor(
createWebRtcCall(mxCall) createWebRtcCall(mxCall)
callAudioManager.startForCall(mxCall) callAudioManager.startForCall(mxCall)
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onOutgoingCallRinging( CallService.onOutgoingCallRinging(
context = context.applicationContext, context = context.applicationContext,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = currentSession?.myUserId ?: "",
callId = mxCall.callId) callId = mxCall.callId)
// start the activity now // start the activity now
context.startActivity(VectorCallActivity.newIntent(context, mxCall)) context.startActivity(VectorCallActivity.newIntent(context, mxCall, VectorCallActivity.OUTGOING_CREATED))
} }
override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) { override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) {
@ -264,15 +258,10 @@ class WebRtcCallManager @Inject constructor(
} }
callAudioManager.startForCall(mxCall) callAudioManager.startForCall(mxCall)
// Start background service with notification // Start background service with notification
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onIncomingCallRinging( CallService.onIncomingCallRinging(
context = context, context = context,
isVideo = mxCall.isVideoCall, callId = mxCall.callId,
roomName = name, isInBackground = isInBackground
roomId = mxCall.roomId,
matrixId = currentSession?.myUserId ?: "",
callId = mxCall.callId
) )
// If this is received while in background, the app will not sync, // If this is received while in background, the app will not sync,
// and thus won't be able to received events. For example if the call is // and thus won't be able to received events. For example if the call is
@ -294,14 +283,8 @@ class WebRtcCallManager @Inject constructor(
} }
val mxCall = call.mxCall val mxCall = call.mxCall
// Update service state // Update service state
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onPendingCall( CallService.onPendingCall(
context = context, context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = currentSession?.myUserId ?: "",
callId = mxCall.callId callId = mxCall.callId
) )
call.onCallAnswerReceived(callAnswerContent) call.onCallAnswerReceived(callAnswerContent)

View File

@ -18,6 +18,7 @@ package im.vector.app.features.crypto.verification
import android.content.Context import android.content.Context
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.RoomDetailArgs
import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.PopupAlertManager
@ -31,6 +32,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton import javax.inject.Singleton
/** /**
@ -39,6 +41,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class IncomingVerificationRequestHandler @Inject constructor( class IncomingVerificationRequestHandler @Inject constructor(
private val context: Context, private val context: Context,
private var avatarRenderer: Provider<AvatarRenderer>,
private val popupAlertManager: PopupAlertManager) : VerificationService.Listener { private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
private var session: Session? = null private var session: Session? = null
@ -60,9 +63,8 @@ class IncomingVerificationRequestHandler @Inject constructor(
when (tx.state) { when (tx.state) {
is VerificationTxState.OnStarted -> { is VerificationTxState.OnStarted -> {
// Add a notification for every incoming request // Add a notification for every incoming request
val name = session?.getUser(tx.otherUserId)?.displayName val user = session?.getUser(tx.otherUserId)
?: tx.otherUserId val name = user?.displayName ?: tx.otherUserId
val alert = VerificationVectorAlert( val alert = VerificationVectorAlert(
uid, uid,
context.getString(R.string.sas_incoming_request_notif_title), context.getString(R.string.sas_incoming_request_notif_title),
@ -77,10 +79,10 @@ class IncomingVerificationRequestHandler @Inject constructor(
} }
} ?: true } ?: true
} else true } else true
}, }
matrixItem = session?.getUser(tx.otherUserId)?.toMatrixItem()
) )
.apply { .apply {
viewBinder = VerificationVectorAlert.ViewBinder(user?.toMatrixItem(), avatarRenderer.get())
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId) it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
@ -120,8 +122,8 @@ class IncomingVerificationRequestHandler @Inject constructor(
Timber.v("## SAS verificationRequestCreated ${pr.transactionId}") Timber.v("## SAS verificationRequestCreated ${pr.transactionId}")
// For incoming request we should prompt (if not in activity where this request apply) // For incoming request we should prompt (if not in activity where this request apply)
if (pr.isIncoming) { if (pr.isIncoming) {
val name = session?.getUser(pr.otherUserId)?.displayName val user = session?.getUser(pr.otherUserId)
?: pr.otherUserId val name = user?.displayName ?: pr.otherUserId
val alert = VerificationVectorAlert( val alert = VerificationVectorAlert(
uniqueIdForVerificationRequest(pr), uniqueIdForVerificationRequest(pr),
@ -134,10 +136,10 @@ class IncomingVerificationRequestHandler @Inject constructor(
it.roomId != pr.roomId it.roomId != pr.roomId
} ?: true } ?: true
} else true } else true
}, }
matrixItem = session?.getUser(pr.otherUserId)?.toMatrixItem()
) )
.apply { .apply {
viewBinder = VerificationVectorAlert.ViewBinder(user?.toMatrixItem(), avatarRenderer.get())
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
val roomId = pr.roomId val roomId = pr.roomId
@ -154,7 +156,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
pr.roomId ?: "" pr.roomId ?: ""
) )
} }
colorInt = ThemeUtils.getColor(context, R.attr.vctr_notice_secondary) colorAttribute = R.attr.vctr_notice_secondary
// 5mn expiration // 5mn expiration
expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L) expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L)
} }

View File

@ -82,6 +82,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
@Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory @Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var pushManager: PushersManager @Inject lateinit var pushManager: PushersManager
@ -126,9 +127,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
.observe() .observe()
.subscribe { sharedAction -> .subscribe { sharedAction ->
when (sharedAction) { when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> { is HomeActivitySharedAction.OpenGroup -> {
drawerLayout.closeDrawer(GravityCompat.START) drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
} }
@ -145,9 +146,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
homeActivityViewModel.observeViewEvents { homeActivityViewModel.observeViewEvents {
when (it) { when (it) {
is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
}.exhaustive }.exhaustive
} }
homeActivityViewModel.subscribe(this) { renderState(it) } homeActivityViewModel.subscribe(this) { renderState(it) }
@ -180,7 +181,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
private fun renderState(state: HomeActivityViewState) { private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) { when (val status = state.initialSyncProgressServiceStatus) {
is InitialSyncProgressService.Status.Idle -> { is InitialSyncProgressService.Status.Idle -> {
waiting_view.isVisible = false waiting_view.isVisible = false
} }
is InitialSyncProgressService.Status.Progressing -> { is InitialSyncProgressService.Status.Progressing -> {
@ -251,7 +252,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
it is HomeActivity it is HomeActivity
} }
).apply { ).apply {
colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary) colorAttribute = R.attr.vctr_notice_secondary
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
// action(it) // action(it)
@ -283,8 +284,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
title = getString(titleRes), title = getString(titleRes),
description = getString(descRes), description = getString(descRes),
iconId = R.drawable.ic_shield_warning, iconId = R.drawable.ic_shield_warning,
matrixItem = userItem
).apply { ).apply {
viewBinder = VerificationVectorAlert.ViewBinder(userItem, avatarRenderer)
colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent) colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {

View File

@ -151,9 +151,9 @@ class HomeDetailFragment @Inject constructor(
uid = uid, uid = uid,
title = getString(R.string.new_session), title = getString(R.string.new_session),
description = getString(R.string.verify_this_session, newest.displayName ?: newest.deviceId ?: ""), description = getString(R.string.verify_this_session, newest.displayName ?: newest.deviceId ?: ""),
iconId = R.drawable.ic_shield_warning, iconId = R.drawable.ic_shield_warning
matrixItem = user
).apply { ).apply {
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity) (weakCurrentActivity?.get() as? VectorBaseActivity)
@ -179,9 +179,9 @@ class HomeDetailFragment @Inject constructor(
uid = uid, uid = uid,
title = getString(R.string.review_logins), title = getString(R.string.review_logins),
description = getString(R.string.verify_other_sessions), description = getString(R.string.verify_other_sessions),
iconId = R.drawable.ic_shield_warning, iconId = R.drawable.ic_shield_warning
matrixItem = user
).apply { ).apply {
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {

View File

@ -72,7 +72,7 @@ class RoomDetailActivity :
} }
// Simple filter // Simple filter
private var currentRoomId: String? = null var currentRoomId: String? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@ -118,8 +118,8 @@ import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.app.features.attachments.toGroupedContentAttachmentData import im.vector.app.features.attachments.toGroupedContentAttachmentData
import im.vector.app.features.call.SharedActiveCallViewModel import im.vector.app.features.call.SharedActiveCallViewModel
import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.JitsiCallViewModel
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.command.Command import im.vector.app.features.command.Command
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.util.toImageRes import im.vector.app.features.crypto.util.toImageRes
@ -311,7 +311,6 @@ class RoomDetailFragment @Inject constructor(
setupActiveCallView() setupActiveCallView()
setupJumpToBottomView() setupJumpToBottomView()
setupConfBannerView() setupConfBannerView()
roomToolbarContentView.debouncedClicks { roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
} }
@ -340,9 +339,9 @@ class RoomDetailFragment @Inject constructor(
} }
when (mode) { when (mode) {
is SendMode.REGULAR -> renderRegularMode(mode.text) is SendMode.REGULAR -> renderRegularMode(mode.text)
is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
} }
} }
@ -352,33 +351,33 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.observeViewEvents { roomDetailViewModel.observeViewEvents {
when (it) { when (it) {
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
is RoomDetailViewEvents.ShowE2EErrorMessage -> displayE2eError(it.withHeldCode) is RoomDetailViewEvents.ShowE2EErrorMessage -> displayE2eError(it.withHeldCode)
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager() RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it) is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog() is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager() is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it) is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it)
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked() RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message) is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo) is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it) is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId) RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId)
RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show() RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show()
RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings() RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings()
is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item -> is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item ->
navigator.openBigImageViewer(requireActivity(), it.view, item) navigator.openBigImageViewer(requireActivity(), it.view, item)
} }
}.exhaustive }.exhaustive
@ -525,14 +524,14 @@ class RoomDetailFragment @Inject constructor(
private fun handleShareData() { private fun handleShareData() {
when (val sharedData = roomDetailArgs.sharedData) { when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> { is SharedData.Text -> {
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true)) roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
} }
is SharedData.Attachments -> { is SharedData.Attachments -> {
// open share edition // open share edition
onContentAttachmentsReady(sharedData.attachmentData) onContentAttachmentsReady(sharedData.attachmentData)
} }
null -> Timber.v("No share data to process") null -> Timber.v("No share data to process")
}.exhaustive }.exhaustive
} }
@ -657,8 +656,8 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) { state -> withState(roomDetailViewModel) { state ->
// Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions // Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) { val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
1 -> false 1 -> false
2 -> state.isAllowedToStartWebRTCCall 2 -> state.isAllowedToStartWebRTCCall
else -> state.isAllowedToManageWidgets else -> state.isAllowedToManageWidgets
} }
setOf(R.id.voice_call, R.id.video_call).forEach { setOf(R.id.voice_call, R.id.video_call).forEach {
@ -688,36 +687,36 @@ class RoomDetailFragment @Inject constructor(
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.invite -> { R.id.invite -> {
navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId) navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
true true
} }
R.id.timeline_setting -> { R.id.timeline_setting -> {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
true true
} }
R.id.resend_all -> { R.id.resend_all -> {
roomDetailViewModel.handle(RoomDetailAction.ResendAll) roomDetailViewModel.handle(RoomDetailAction.ResendAll)
true true
} }
R.id.open_matrix_apps -> { R.id.open_matrix_apps -> {
roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations) roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations)
true true
} }
R.id.voice_call, R.id.voice_call,
R.id.video_call -> { R.id.video_call -> {
handleCallRequest(item) handleCallRequest(item)
true true
} }
R.id.hangup_call -> { R.id.hangup_call -> {
roomDetailViewModel.handle(RoomDetailAction.EndCall) roomDetailViewModel.handle(RoomDetailAction.EndCall)
true true
} }
R.id.search -> { R.id.search -> {
handleSearchAction() handleSearchAction()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
@ -733,7 +732,7 @@ class RoomDetailFragment @Inject constructor(
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
val isVideoCall = item.itemId == R.id.video_call val isVideoCall = item.itemId == R.id.video_call
when (roomSummary.joinedMembersCount) { when (roomSummary.joinedMembersCount) {
1 -> { 1 -> {
val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0 val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0
if (pendingInvite) { if (pendingInvite) {
// wait for other to join // wait for other to join
@ -743,7 +742,7 @@ class RoomDetailFragment @Inject constructor(
showDialogWithMessage(getString(R.string.cannot_call_yourself)) showDialogWithMessage(getString(R.string.cannot_call_yourself))
} }
} }
2 -> { 2 -> {
val activeCall = sharedCallActionViewModel.activeCall.value val activeCall = sharedCallActionViewModel.activeCall.value
if (activeCall != null) { if (activeCall != null) {
// resume existing if same room, if not prompt to kill and then restart new call? // resume existing if same room, if not prompt to kill and then restart new call?
@ -924,9 +923,9 @@ class RoomDetailFragment @Inject constructor(
when (roomDetailPendingAction) { when (roomDetailPendingAction) {
is RoomDetailPendingAction.JumpToReadReceipt -> is RoomDetailPendingAction.JumpToReadReceipt ->
roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
is RoomDetailPendingAction.MentionUser -> is RoomDetailPendingAction.MentionUser ->
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm -> is RoomDetailPendingAction.OpenOrCreateDm ->
roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId)) roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
}.exhaustive }.exhaustive
} }
@ -1069,9 +1068,9 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) { withState(roomDetailViewModel) {
val showJumpToUnreadBanner = when (it.unreadState) { val showJumpToUnreadBanner = when (it.unreadState) {
UnreadState.Unknown, UnreadState.Unknown,
UnreadState.HasNoUnread -> false UnreadState.HasNoUnread -> false
is UnreadState.ReadMarkerNotLoaded -> true is UnreadState.ReadMarkerNotLoaded -> true
is UnreadState.HasUnread -> { is UnreadState.HasUnread -> {
if (it.canShowJumpToReadMarker) { if (it.canShowJumpToReadMarker) {
val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
@ -1280,7 +1279,7 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, async()) navigator.openRoom(vectorBaseActivity, async())
vectorBaseActivity.finish() vectorBaseActivity.finish()
} }
is Fail -> { is Fail -> {
vectorBaseActivity.hideWaitingView() vectorBaseActivity.hideWaitingView()
vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error)) vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
} }
@ -1289,19 +1288,19 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is RoomDetailViewEvents.SlashCommandHandled -> { is RoomDetailViewEvents.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
} }
is RoomDetailViewEvents.SlashCommandError -> { is RoomDetailViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
} }
is RoomDetailViewEvents.SlashCommandUnknown -> { is RoomDetailViewEvents.SlashCommandUnknown -> {
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
} }
is RoomDetailViewEvents.SlashCommandResultOk -> { is RoomDetailViewEvents.SlashCommandResultOk -> {
updateComposerText("") updateComposerText("")
} }
is RoomDetailViewEvents.SlashCommandResultError -> { is RoomDetailViewEvents.SlashCommandResultError -> {
displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
} }
is RoomDetailViewEvents.SlashCommandNotImplemented -> { is RoomDetailViewEvents.SlashCommandNotImplemented -> {
@ -1323,7 +1322,7 @@ class RoomDetailFragment @Inject constructor(
private fun displayE2eError(withHeldCode: WithHeldCode?) { private fun displayE2eError(withHeldCode: WithHeldCode?) {
val msgId = when (withHeldCode) { val msgId = when (withHeldCode) {
WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted
WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified
WithHeldCode.UNAUTHORISED, WithHeldCode.UNAUTHORISED,
WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic
else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc
@ -1375,7 +1374,7 @@ class RoomDetailFragment @Inject constructor(
private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) { private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) {
when (val data = result.action) { when (val data = result.action) {
is RoomDetailAction.ReportContent -> { is RoomDetailAction.ReportContent -> {
when { when {
data.spam -> { data.spam -> {
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
@ -1412,7 +1411,7 @@ class RoomDetailFragment @Inject constructor(
} }
} }
} }
is RoomDetailAction.RequestVerification -> { is RoomDetailAction.RequestVerification -> {
Timber.v("## SAS RequestVerification action") Timber.v("## SAS RequestVerification action")
VerificationBottomSheet.withArgs( VerificationBottomSheet.withArgs(
roomDetailArgs.roomId, roomDetailArgs.roomId,
@ -1427,7 +1426,7 @@ class RoomDetailFragment @Inject constructor(
data.transactionId data.transactionId
).show(parentFragmentManager, "REQ") ).show(parentFragmentManager, "REQ")
} }
is RoomDetailAction.ResumeVerification -> { is RoomDetailAction.ResumeVerification -> {
val otherUserId = data.otherUserId ?: return val otherUserId = data.otherUserId ?: return
VerificationBottomSheet().apply { VerificationBottomSheet().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
@ -1571,11 +1570,11 @@ class RoomDetailFragment @Inject constructor(
is MessageVerificationRequestContent -> { is MessageVerificationRequestContent -> {
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
} }
is MessageWithAttachmentContent -> { is MessageWithAttachmentContent -> {
val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent) val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent)
roomDetailViewModel.handle(action) roomDetailViewModel.handle(action)
} }
is EncryptedEventContent -> { is EncryptedEventContent -> {
roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
} }
} }
@ -1716,75 +1715,75 @@ class RoomDetailFragment @Inject constructor(
private fun handleActions(action: EventSharedAction) { private fun handleActions(action: EventSharedAction) {
when (action) { when (action) {
is EventSharedAction.OpenUserProfile -> { is EventSharedAction.OpenUserProfile -> {
openRoomMemberProfile(action.userId) openRoomMemberProfile(action.userId)
} }
is EventSharedAction.AddReaction -> { is EventSharedAction.AddReaction -> {
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId)) emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
} }
is EventSharedAction.ViewReactions -> { is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
} }
is EventSharedAction.Copy -> { is EventSharedAction.Copy -> {
// I need info about the current selected message :/ // I need info about the current selected message :/
copyToClipboard(requireContext(), action.content, false) copyToClipboard(requireContext(), action.content, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
} }
is EventSharedAction.Redact -> { is EventSharedAction.Redact -> {
promptConfirmationToRedactEvent(action) promptConfirmationToRedactEvent(action)
} }
is EventSharedAction.Share -> { is EventSharedAction.Share -> {
onShareActionClicked(action) onShareActionClicked(action)
} }
is EventSharedAction.Save -> { is EventSharedAction.Save -> {
onSaveActionClicked(action) onSaveActionClicked(action)
} }
is EventSharedAction.ViewEditHistory -> { is EventSharedAction.ViewEditHistory -> {
onEditedDecorationClicked(action.messageInformationData) onEditedDecorationClicked(action.messageInformationData)
} }
is EventSharedAction.ViewSource -> { is EventSharedAction.ViewSource -> {
JSonViewerDialog.newInstance( JSonViewerDialog.newInstance(
action.content, action.content,
-1, -1,
createJSonViewerStyleProvider(colorProvider) createJSonViewerStyleProvider(colorProvider)
).show(childFragmentManager, "JSON_VIEWER") ).show(childFragmentManager, "JSON_VIEWER")
} }
is EventSharedAction.ViewDecryptedSource -> { is EventSharedAction.ViewDecryptedSource -> {
JSonViewerDialog.newInstance( JSonViewerDialog.newInstance(
action.content, action.content,
-1, -1,
createJSonViewerStyleProvider(colorProvider) createJSonViewerStyleProvider(colorProvider)
).show(childFragmentManager, "JSON_VIEWER") ).show(childFragmentManager, "JSON_VIEWER")
} }
is EventSharedAction.QuickReact -> { is EventSharedAction.QuickReact -> {
// eventId,ClickedOn,Add // eventId,ClickedOn,Add
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
} }
is EventSharedAction.Edit -> { is EventSharedAction.Edit -> {
roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString()))
} }
is EventSharedAction.Quote -> { is EventSharedAction.Quote -> {
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString()))
} }
is EventSharedAction.Reply -> { is EventSharedAction.Reply -> {
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString()))
} }
is EventSharedAction.CopyPermalink -> { is EventSharedAction.CopyPermalink -> {
val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId)
copyToClipboard(requireContext(), permalink, false) copyToClipboard(requireContext(), permalink, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
} }
is EventSharedAction.Resend -> { is EventSharedAction.Resend -> {
roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
} }
is EventSharedAction.Remove -> { is EventSharedAction.Remove -> {
roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
} }
is EventSharedAction.Cancel -> { is EventSharedAction.Cancel -> {
roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId))
} }
is EventSharedAction.ReportContentSpam -> { is EventSharedAction.ReportContentSpam -> {
roomDetailViewModel.handle(RoomDetailAction.ReportContent( roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is spam", spam = true)) action.eventId, action.senderId, "This message is spam", spam = true))
} }
@ -1792,22 +1791,22 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.ReportContent( roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
} }
is EventSharedAction.ReportContentCustom -> { is EventSharedAction.ReportContentCustom -> {
promptReasonToReportContent(action) promptReasonToReportContent(action)
} }
is EventSharedAction.IgnoreUser -> { is EventSharedAction.IgnoreUser -> {
action.senderId?.let { askConfirmationToIgnoreUser(it) } action.senderId?.let { askConfirmationToIgnoreUser(it) }
} }
is EventSharedAction.OnUrlClicked -> { is EventSharedAction.OnUrlClicked -> {
onUrlClicked(action.url, action.title) onUrlClicked(action.url, action.title)
} }
is EventSharedAction.OnUrlLongClicked -> { is EventSharedAction.OnUrlLongClicked -> {
onUrlLongClicked(action.url) onUrlLongClicked(action.url)
} }
is EventSharedAction.ReRequestKey -> { is EventSharedAction.ReRequestKey -> {
roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId)) roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId))
} }
is EventSharedAction.UseKeyBackup -> { is EventSharedAction.UseKeyBackup -> {
context?.let { context?.let {
startActivity(KeysBackupRestoreActivity.intent(it)) startActivity(KeysBackupRestoreActivity.intent(it))
} }
@ -1947,10 +1946,10 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) { when (type) {
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher) AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher)
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.exhaustive }.exhaustive

View File

@ -52,6 +52,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.RoomDetailArgs
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.util.MatrixItem
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -269,19 +271,19 @@ class NotificationUtils @Inject constructor(private val context: Context,
* @param roomName the room name in which the call is pending. * @param roomName the room name in which the call is pending.
* @param matrixId the matrix id * @param matrixId the matrix id
* @param callId the call id. * @param callId the call id.
* @param fromBg true if the app is in background when posting the notification
* @return the call notification. * @return the call notification.
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun buildIncomingCallNotification(isVideo: Boolean, fun buildIncomingCallNotification(mxCall: MxCall,
otherUserId: String, title: String,
roomId: String, fromBg: Boolean): Notification {
callId: String): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val notificationChannel = if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
val builder = NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID) val builder = NotificationCompat.Builder(context, notificationChannel)
.setContentTitle(ensureTitleNotEmpty(otherUserId)) .setContentTitle(ensureTitleNotEmpty(title))
.apply { .apply {
if (isVideo) { if (mxCall.isVideoCall) {
setContentText(stringProvider.getString(R.string.incoming_video_call)) setContentText(stringProvider.getString(R.string.incoming_video_call))
} else { } else {
setContentText(stringProvider.getString(R.string.incoming_voice_call)) setContentText(stringProvider.getString(R.string.incoming_voice_call))
@ -300,15 +302,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentIntent = VectorCallActivity.newIntent( val contentIntent = VectorCallActivity.newIntent(
context = context, context = context,
callId = callId, mxCall = mxCall,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mode = VectorCallActivity.INCOMING_RINGING mode = VectorCallActivity.INCOMING_RINGING
).apply { ).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
data = Uri.parse("foobar://$callId") data = Uri.parse("foobar://${mxCall.callId}")
} }
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
@ -316,20 +314,16 @@ class NotificationUtils @Inject constructor(private val context: Context,
.addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(VectorCallActivity.newIntent( .addNextIntent(VectorCallActivity.newIntent(
context = context, context = context,
callId = callId, mxCall = mxCall,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mode = VectorCallActivity.INCOMING_ACCEPT) mode = VectorCallActivity.INCOMING_ACCEPT)
) )
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId) val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
R.drawable.ic_call, R.drawable.ic_call_answer,
// IconCompat.createWithResource(applicationContext, R.drawable.ic_call) // IconCompat.createWithResource(applicationContext, R.drawable.ic_call)
// .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)), // .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)),
context.getString(R.string.call_notification_answer), context.getString(R.string.call_notification_answer),
@ -339,7 +333,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)), IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
context.getString(R.string.call_notification_reject), context.getString(R.string.call_notification_reject),
rejectCallPendingIntent) rejectCallPendingIntent)
) )
@ -349,14 +343,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
return builder.build() return builder.build()
} }
fun buildOutgoingRingingCallNotification(isVideo: Boolean, fun buildOutgoingRingingCallNotification(mxCall: MxCall,
otherUserId: String, title: String): Notification {
roomId: String,
callId: String): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(otherUserId)) .setContentTitle(ensureTitleNotEmpty(title))
.apply { .apply {
setContentText(stringProvider.getString(R.string.call_ring)) setContentText(stringProvider.getString(R.string.call_ring))
} }
@ -367,18 +358,14 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentIntent = VectorCallActivity.newIntent( val contentIntent = VectorCallActivity.newIntent(
context = context, context = context,
callId = callId, mxCall = mxCall,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mode = null).apply { mode = null).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
data = Uri.parse("foobar://$callId") data = Uri.parse("foobar://$mxCall.callId")
} }
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId) val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
@ -402,16 +389,13 @@ class NotificationUtils @Inject constructor(private val context: Context,
* @return the call notification. * @return the call notification.
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun buildPendingCallNotification(isVideo: Boolean, fun buildPendingCallNotification(mxCall: MxCall,
roomName: String, title: String,
roomId: String,
matrixId: String,
callId: String,
fromBg: Boolean = false): Notification { fromBg: Boolean = false): Notification {
val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID) val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(roomName)) .setContentTitle(ensureTitleNotEmpty(title))
.apply { .apply {
if (isVideo) { if (mxCall.isVideoCall) {
setContentText(stringProvider.getString(R.string.video_call_in_progress)) setContentText(stringProvider.getString(R.string.video_call_in_progress))
} else { } else {
setContentText(stringProvider.getString(R.string.call_in_progress)) setContentText(stringProvider.getString(R.string.call_in_progress))
@ -425,7 +409,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.setOngoing(true) builder.setOngoing(true)
} }
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId) val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction( builder.addAction(
NotificationCompat.Action( NotificationCompat.Action(
@ -436,8 +420,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentPendingIntent = TaskStackBuilder.create(context) val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
// TODO other userId .addNextIntent(VectorCallActivity.newIntent(context, mxCall, null))
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(contentPendingIntent) builder.setContentIntent(contentPendingIntent)
@ -462,7 +445,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
* Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended * Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended
*/ */
fun buildCallEndedNotification(): Notification { fun buildCallEndedNotification(): Notification {
return NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID) return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(stringProvider.getString(R.string.call_ended)) .setContentTitle(stringProvider.getString(R.string.call_ended))
.setSmallIcon(R.drawable.ic_material_call_end_grey) .setSmallIcon(R.drawable.ic_material_call_end_grey)
.setCategory(NotificationCompat.CATEGORY_CALL) .setCategory(NotificationCompat.CATEGORY_CALL)

View File

@ -0,0 +1,61 @@
/*
* 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.app.features.popup
import android.app.Activity
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import im.vector.app.R
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
class IncomingCallAlert(uid: String,
override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true }
) : DefaultVectorAlert(uid, "", "", 0, shouldBeDisplayedIn) {
override val layoutRes = R.layout.alerter_incoming_call_layout
override var colorAttribute: Int? = R.attr.riotx_alerter_background
class ViewBinder(private val matrixItem: MatrixItem?,
private val avatarRenderer: AvatarRenderer,
private val isVideoCall: Boolean,
private val onAccept: () -> Unit,
private val onReject: () -> Unit)
: VectorAlert.ViewBinder {
override fun bind(view: View) {
val callKind = if (isVideoCall) {
R.string.action_video_call
} else {
R.string.action_voice_call
}
view.findViewById<TextView>(R.id.incomingCallKindView).setText(callKind)
view.findViewById<TextView>(R.id.incomingCallNameView).text = matrixItem?.getBestName()
view.findViewById<ImageView>(R.id.incomingCallAvatar)?.let { imageView ->
matrixItem?.let { avatarRenderer.render(it, imageView) }
}
view.findViewById<ImageView>(R.id.incomingCallAcceptView).setOnClickListener {
onAccept()
}
view.findViewById<ImageView>(R.id.incomingCallRejectView).setOnClickListener {
onReject()
}
}
}
}

View File

@ -21,14 +21,11 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import android.widget.ImageView
import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener import com.tapadoo.alerter.OnHideAlertListener
import dagger.Lazy
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.isAnimationDisabled import im.vector.app.core.utils.isAnimationDisabled
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinActivity
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber import timber.log.Timber
@ -41,7 +38,7 @@ import javax.inject.Singleton
* Alerts are stacked and will be displayed sequentially * Alerts are stacked and will be displayed sequentially
*/ */
@Singleton @Singleton
class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<AvatarRenderer>) { class PopupAlertManager @Inject constructor() {
private var weakCurrentActivity: WeakReference<Activity>? = null private var weakCurrentActivity: WeakReference<Activity>? = null
private var currentAlerter: VectorAlert? = null private var currentAlerter: VectorAlert? = null
@ -191,17 +188,13 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
val noAnimation = !animate || isAnimationDisabled(activity) val noAnimation = !animate || isAnimationDisabled(activity)
alert.weakCurrentActivity = WeakReference(activity) alert.weakCurrentActivity = WeakReference(activity)
val alerter = if (alert is VerificationVectorAlert) Alerter.create(activity, R.layout.alerter_verification_layout) val alerter = Alerter.create(activity, alert.layoutRes)
else Alerter.create(activity)
alerter.setTitle(alert.title) alerter.setTitle(alert.title)
.setText(alert.description) .setText(alert.description)
.also { al -> .also { al ->
if (alert is VerificationVectorAlert) { al.getLayoutContainer()?.also {
val tvCustomView = al.getLayoutContainer() alert.viewBinder?.bind(it)
tvCustomView?.findViewById<ImageView>(R.id.ivUserAvatar)?.let { imageView ->
alert.matrixItem?.let { avatarRenderer.get().render(it, imageView) }
}
} }
} }
.apply { .apply {
@ -251,6 +244,8 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
.apply { .apply {
if (alert.colorInt != null) { if (alert.colorInt != null) {
setBackgroundColorInt(alert.colorInt!!) setBackgroundColorInt(alert.colorInt!!)
} else if (alert.colorAttribute != null) {
setBackgroundColorInt(ThemeUtils.getColor(activity, alert.colorAttribute!!))
} else { } else {
setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color) setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color)
} }

View File

@ -17,12 +17,18 @@
package im.vector.app.features.popup package im.vector.app.features.popup
import android.app.Activity import android.app.Activity
import android.view.View
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import im.vector.app.R
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
interface VectorAlert { interface VectorAlert {
val uid: String val uid: String
val title: String val title: String
@ -47,22 +53,34 @@ interface VectorAlert {
actions.add(Button(title, action, autoClose)) actions.add(Button(title, action, autoClose))
} }
var viewBinder: ViewBinder?
val layoutRes: Int
var colorRes: Int? var colorRes: Int?
var colorInt: Int? var colorInt: Int?
var colorAttribute: Int?
interface ViewBinder {
fun bind(view: View)
}
} }
/** /**
* Dataclass to describe an important alert with actions. * Dataclass to describe an important alert with actions.
*/ */
open class DefaultVectorAlert(override val uid: String, open class DefaultVectorAlert(
override val title: String, override val uid: String,
override val description: String, override val title: String,
@DrawableRes override val iconId: Int?, override val description: String,
/** @DrawableRes override val iconId: Int?,
* Alert are displayed by default, but let this lambda return false to prevent displaying /**
*/ * Alert are displayed by default, but let this lambda return false to prevent displaying
override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true } */
override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true },
) : VectorAlert { ) : VectorAlert {
// will be set by manager, and accessible by actions at runtime // will be set by manager, and accessible by actions at runtime
@ -76,26 +94,19 @@ open class DefaultVectorAlert(override val uid: String,
/** If this timestamp is after current time, this alert will be skipped */ /** If this timestamp is after current time, this alert will be skipped */
override var expirationTimestamp: Long? = null override var expirationTimestamp: Long? = null
override fun addButton(title: String, action: Runnable, autoClose: Boolean) { @LayoutRes
actions.add(VectorAlert.Button(title, action, autoClose)) override val layoutRes = R.layout.alerter_alert_default_layout
}
@ColorRes @ColorRes
override var colorRes: Int? = null override var colorRes: Int? = null
@ColorInt @ColorInt
override var colorInt: Int? = null override var colorInt: Int? = null
@AttrRes
override var colorAttribute: Int? = null
override var viewBinder: VectorAlert.ViewBinder? = null
} }
class VerificationVectorAlert(uid: String,
title: String,
override val description: String,
@DrawableRes override val iconId: Int?,
/**
* Alert are displayed by default, but let this lambda return false to prevent displaying
*/
override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true },
val matrixItem: MatrixItem?
) : DefaultVectorAlert(
uid, title, description, iconId, shouldBeDisplayedIn
)

View File

@ -0,0 +1,59 @@
/*
* 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.app.features.popup
import android.app.Activity
import android.view.View
import android.widget.ImageView
import androidx.annotation.DrawableRes
import im.vector.app.R
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
class VerificationVectorAlert(uid: String,
title: String,
override val description: String,
@DrawableRes override val iconId: Int?,
/**
* Alert are displayed by default, but let this lambda return false to prevent displaying
*/
override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true }
) : DefaultVectorAlert(
uid, title, description, iconId, shouldBeDisplayedIn
) {
override val layoutRes = R.layout.alerter_verification_layout
class ViewBinder(private val matrixItem: MatrixItem?,
private val avatarRenderer: AvatarRenderer)
: VectorAlert.ViewBinder {
override fun bind(view: View) {
view.findViewById<ImageView>(R.id.ivUserAvatar)?.let { imageView ->
matrixItem?.let { avatarRenderer.render(it, imageView) }
}
}
}
}

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
xmlns:tools="http://schemas.android.com/tools"
tools:style="@style/AlertStyle"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/incomingCallAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/call_notification_answer"
android:layout_margin="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/incomingCallNameView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/incomingCallRejectView"
app:layout_constraintStart_toEndOf="@id/incomingCallAvatar"
app:layout_constraintTop_toTopOf="@id/incomingCallAvatar"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/incomingCallKindView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
android:maxLines="1"
app:layout_constraintEnd_toStartOf="@+id/incomingCallRejectView"
app:layout_constraintStart_toStartOf="@id/incomingCallNameView"
app:layout_constraintTop_toBottomOf="@id/incomingCallNameView"
tools:text="@string/action_voice_call" />
<ImageView
android:id="@+id/incomingCallAcceptView"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/oval_positive"
android:clickable="true"
android:contentDescription="@string/call_notification_answer"
android:focusable="true"
android:layout_marginEnd="12dp"
android:padding="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_call_answer" />
<ImageView
android:id="@+id/incomingCallRejectView"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/oval_destructive"
android:clickable="true"
android:contentDescription="@string/call_notification_reject"
android:focusable="true"
android:padding="8dp"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/incomingCallAcceptView"
android:src="@drawable/ic_call_hangup" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -179,7 +179,7 @@
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/roomToolbar" app:layout_constraintTop_toBottomOf="@+id/roomToolbar"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/badgeBarrier" android:id="@+id/badgeBarrier"

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.FrameLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -200,4 +201,4 @@
<!-- app:layout_constraintEnd_toEndOf="parent"--> <!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent" />--> <!-- app:layout_constraintTop_toTopOf="parent" />-->
</FrameLayout> </merge>

View File

@ -238,9 +238,16 @@
<color name="riotx_reaction_background_on_dark">#4011BC8A</color> <color name="riotx_reaction_background_on_dark">#4011BC8A</color>
<color name="riotx_reaction_background_on_black">#4011BC8A</color> <color name="riotx_reaction_background_on_black">#4011BC8A</color>
<attr name="riotx_alerter_background" format="color" />
<color name="riotx_alerter_background_light">#FFF3F8FD</color>
<color name="riotx_alerter_background_dark">#FF282C35</color>
<color name="riotx_alerter_background_black">#FF282C35</color>
<!-- (color from RiotWeb) --> <!-- (color from RiotWeb) -->
<attr name="riotx_keys_backup_banner_accent_color" format="color" /> <attr name="riotx_keys_backup_banner_accent_color" format="color" />
<color name="riotx_keys_backup_banner_accent_color_light">#FFF8E3</color> <color name="riotx_keys_backup_banner_accent_color_light">#FFF8E3</color>
<color name="riotx_keys_backup_banner_accent_color_dark">#22262E</color> <color name="riotx_keys_backup_banner_accent_color_dark">#22262E</color>
</resources> </resources>

View File

@ -38,6 +38,7 @@
<item name="riotx_room_active_widgets_banner_text">@color/riotx_room_active_widgets_banner_text_black</item> <item name="riotx_room_active_widgets_banner_text">@color/riotx_room_active_widgets_banner_text_black</item>
<item name="riotx_reaction_background_off">@color/riotx_reaction_background_off_black</item> <item name="riotx_reaction_background_off">@color/riotx_reaction_background_off_black</item>
<item name="riotx_reaction_background_on">@color/riotx_reaction_background_on_black</item> <item name="riotx_reaction_background_on">@color/riotx_reaction_background_on_black</item>
<item name="riotx_alerter_background">@color/riotx_alerter_background_black</item>
<item name="riotx_bottom_nav_icon_color">@color/riotx_bottom_nav_icon_color_black</item> <item name="riotx_bottom_nav_icon_color">@color/riotx_bottom_nav_icon_color_black</item>

View File

@ -36,7 +36,7 @@
<item name="riotx_room_active_widgets_banner_text">@color/riotx_room_active_widgets_banner_text_dark</item> <item name="riotx_room_active_widgets_banner_text">@color/riotx_room_active_widgets_banner_text_dark</item>
<item name="riotx_reaction_background_off">@color/riotx_reaction_background_off_dark</item> <item name="riotx_reaction_background_off">@color/riotx_reaction_background_off_dark</item>
<item name="riotx_reaction_background_on">@color/riotx_reaction_background_on_dark</item> <item name="riotx_reaction_background_on">@color/riotx_reaction_background_on_dark</item>
<item name="riotx_alerter_background">@color/riotx_alerter_background_dark</item>
<item name="riotx_bottom_nav_icon_color">@color/riotx_bottom_nav_icon_color_dark</item> <item name="riotx_bottom_nav_icon_color">@color/riotx_bottom_nav_icon_color_dark</item>
<item name="riotx_keys_backup_banner_accent_color">@color/riotx_keys_backup_banner_accent_color_dark</item> <item name="riotx_keys_backup_banner_accent_color">@color/riotx_keys_backup_banner_accent_color_dark</item>

View File

@ -37,7 +37,7 @@
<item name="riotx_room_active_widgets_banner_text">@color/riotx_room_active_widgets_banner_text_light</item> <item name="riotx_room_active_widgets_banner_text">@color/riotx_room_active_widgets_banner_text_light</item>
<item name="riotx_reaction_background_off">@color/riotx_reaction_background_off_light</item> <item name="riotx_reaction_background_off">@color/riotx_reaction_background_off_light</item>
<item name="riotx_reaction_background_on">@color/riotx_reaction_background_on_light</item> <item name="riotx_reaction_background_on">@color/riotx_reaction_background_on_light</item>
<item name="riotx_alerter_background">@color/riotx_alerter_background_light</item>
<item name="riotx_bottom_nav_icon_color">@color/riotx_bottom_nav_icon_color_light</item> <item name="riotx_bottom_nav_icon_color">@color/riotx_bottom_nav_icon_color_light</item>
<!-- Material color: Note: this block should be the same in all theme because it references only common colors and ?riotx attributes --> <!-- Material color: Note: this block should be the same in all theme because it references only common colors and ?riotx attributes -->