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
interface MxCallDetail {
val sessionId: String
val callId: String
val isOutgoing: Boolean
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 incomingCall = mxCallFactory.createIncomingCall(
roomId = event.roomId,
senderId = event.senderId,
opponentUserId = event.senderId,
content = content
) ?: return
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.util.Optional
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.session.call.model.MxCallImpl
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
@ -29,21 +30,23 @@ import java.util.UUID
import javax.inject.Inject
internal class MxCallFactory @Inject constructor(
@SessionId private val sessionId: String,
@DeviceId private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor,
@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
return MxCallImpl(
sessionId = sessionId,
callId = content.callId,
isOutgoing = false,
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = senderId,
opponentUserId = opponentUserId,
isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory,
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(
sessionId = sessionId,
callId = UUID.randomUUID().toString(),
isOutgoing = true,
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = otherUserId,
opponentUserId = opponentUserId,
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor

View file

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

View file

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

View file

@ -25,9 +25,16 @@ import android.view.KeyEvent
import androidx.core.content.ContextCompat
import androidx.media.session.MediaButtonReceiver
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.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.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
/**
@ -39,6 +46,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
private lateinit var notificationUtils: NotificationUtils
private lateinit var callManager: WebRtcCallManager
private lateinit var avatarRenderer: AvatarRenderer
private lateinit var alertManager: PopupAlertManager
private var callRingPlayerIncoming: CallRingPlayerIncoming? = null
private var callRingPlayerOutgoing: CallRingPlayerOutgoing? = null
@ -64,6 +73,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
super.onCreate()
notificationUtils = vectorComponent().notificationUtils()
callManager = vectorComponent().webRtcCallManager()
avatarRenderer = vectorComponent().avatarRenderer()
alertManager = vectorComponent().alertManager()
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
@ -111,20 +122,20 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
callRingPlayerOutgoing?.start()
displayOutgoingRingingCallNotification(intent)
}
ACTION_ONGOING_CALL -> {
ACTION_ONGOING_CALL -> {
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
displayCallInProgressNotification(intent)
}
ACTION_NO_ACTIVE_CALL -> hideCallNotifications()
ACTION_CALL_CONNECTING -> {
ACTION_NO_ACTIVE_CALL -> hideCallNotifications()
ACTION_CALL_CONNECTING -> {
// lower notification priority
displayCallInProgressNotification(intent)
// stop ringing
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
}
ACTION_ONGOING_CALL_BG -> {
ACTION_ONGOING_CALL_BG -> {
// there is an ongoing call but call activity is in background
displayCallOnGoingInBackground(intent)
}
@ -154,56 +165,52 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
*/
private fun displayIncomingCallNotification(intent: Intent) {
Timber.v("## VOIP displayIncomingCallNotification $intent")
// the incoming call in progress is already displayed
// if (!TextUtils.isEmpty(mIncomingCallId)) {
// Timber.v("displayIncomingCallNotification : the incoming call in progress is already displayed")
// } else if (!TextUtils.isEmpty(mCallIdInProgress)) {
// Timber.v("displayIncomingCallNotification : a 'call in progress' notification is displayed")
// } else
// // if (null == webRtcPeerConnectionManager.currentCall)
// {
val callId = intent.getStringExtra(EXTRA_CALL_ID)
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
val call = callManager.getCallById(callId) ?: return
val isVideoCall = call.mxCall.isVideoCall
val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false)
val opponentMatrixItem = getOpponentMatrixItem(call)
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(
intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
callId ?: "")
mxCall = call.mxCall,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
fromBg = fromBg
)
startForeground(NOTIFICATION_ID, notification)
}
// mIncomingCallId = callId
// turn the screen on for 3 seconds
// if (Matrix.getInstance(VectorApp.getInstance())!!.pushManager.isScreenTurnedOn) {
// try {
// val pm = getSystemService<PowerManager>()!!
// val wl = pm.newWakeLock(
// 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 acceptIncomingCall(call: WebRtcCall){
val intent = VectorCallActivity.newIntent(
context = this,
mxCall = call.mxCall,
mode = VectorCallActivity.INCOMING_ACCEPT
)
startActivity(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")
val notification = notificationUtils.buildOutgoingRingingCallNotification(
intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
callId ?: "")
mxCall = call.mxCall,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
)
startForeground(NOTIFICATION_ID, notification)
}
@ -213,16 +220,14 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
private fun displayCallInProgressNotification(intent: Intent) {
Timber.v("## VOIP displayCallInProgressNotification")
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(
intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
intent.getStringExtra(EXTRA_MATRIX_ID) ?: "",
callId)
mxCall = call.mxCall,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
)
startForeground(NOTIFICATION_ID, notification)
// mCallIdInProgress = callId
}
@ -231,18 +236,15 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
*/
private fun displayCallOnGoingInBackground(intent: Intent) {
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(
isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false),
roomName = intent.getStringExtra(EXTRA_ROOM_NAME) ?: "",
roomId = intent.getStringExtra(EXTRA_ROOM_ID) ?: "",
matrixId = intent.getStringExtra(EXTRA_MATRIX_ID) ?: "",
callId = callId,
mxCall = call.mxCall,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
fromBg = true)
startForeground(NOTIFICATION_ID, notification)
// mCallIdInProgress = callId
}
@ -251,7 +253,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
*/
private fun hideCallNotifications() {
val notification = notificationUtils.buildCallEndedNotification()
alertManager.cancelAlert(INCOMING_CALL_ALERT_UID)
mediaSession?.isActive = false
// It's mandatory to startForeground to avoid crash
startForeground(NOTIFICATION_ID, notification)
@ -263,9 +265,14 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
connections[callConnection.callId] = callConnection
}
private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? {
return vectorComponent().currentSession().getUser(call.mxCall.opponentUserId)?.toMatrixItem()
}
companion object {
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_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"
@ -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_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_IS_IN_BG = "EXTRA_IS_IN_BG"
fun onIncomingCallRinging(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) {
callId: String,
isInBackground: Boolean) {
val intent = Intent(context, CallService::class.java)
.apply {
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_IS_IN_BG, isInBackground)
}
ContextCompat.startForegroundService(context, intent)
}
fun onOnGoingCallBackground(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) {
val intent = Intent(context, CallService::class.java)
.apply {
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)
}
@ -320,18 +309,10 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
}
fun onOutgoingCallRinging(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) {
val intent = Intent(context, CallService::class.java)
.apply {
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)
}
@ -339,18 +320,10 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
}
fun onPendingCall(context: Context,
isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String) {
val intent = Intent(context, CallService::class.java)
.apply {
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)
}
@ -362,7 +335,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
.apply {
action = ACTION_NO_ACTIVE_CALL
}
ContextCompat.startForegroundService(context, intent)
}
}

View file

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

View file

@ -339,12 +339,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
const val INCOMING_RINGING = "INCOMING_RINGING"
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 {
// what could be the best flags?
flags = Intent.FLAG_ACTIVITY_NEW_TASK
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 roomId = mxCall.roomId
private var peerConnection: PeerConnection? = null
private var localAudioSource: AudioSource? = null
@ -237,16 +238,9 @@ class WebRtcCall(val mxCall: MxCall,
mxCall
.takeIf { it.state is CallState.Connected }
?.let { mxCall ->
val session = sessionProvider.get()
val name = session?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.roomId
// Start background service with notification
CallService.onPendingCall(
context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?: "",
callId = mxCall.callId)
}
@ -307,15 +301,8 @@ class WebRtcCall(val mxCall: MxCall,
.takeIf { it.state is CallState.Connected }
?.let { mxCall ->
// Start background service with notification
val session = sessionProvider.get()
val name = session?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onOnGoingCallBackground(
context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?: "",
callId = mxCall.callId
)
}
@ -344,15 +331,8 @@ class WebRtcCall(val mxCall: MxCall,
val turnServerResponse = getTurnServer()
// Update service state
withContext(Dispatchers.Main) {
val session = sessionProvider.get()
val name = session?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.roomId
CallService.onPendingCall(
context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?: "",
callId = mxCall.callId
)
}

View file

@ -189,18 +189,12 @@ class WebRtcCallManager @Inject constructor(
createWebRtcCall(mxCall)
callAudioManager.startForCall(mxCall)
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onOutgoingCallRinging(
context = context.applicationContext,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = currentSession?.myUserId ?: "",
callId = mxCall.callId)
// 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) {
@ -264,15 +258,10 @@ class WebRtcCallManager @Inject constructor(
}
callAudioManager.startForCall(mxCall)
// Start background service with notification
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onIncomingCallRinging(
context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = currentSession?.myUserId ?: "",
callId = mxCall.callId
callId = mxCall.callId,
isInBackground = isInBackground
)
// 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
@ -294,14 +283,8 @@ class WebRtcCallManager @Inject constructor(
}
val mxCall = call.mxCall
// Update service state
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
?: mxCall.opponentUserId
CallService.onPendingCall(
context = context,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = currentSession?.myUserId ?: "",
callId = mxCall.callId
)
call.onCallAnswerReceived(callAnswerContent)

View file

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

View file

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

View file

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

View file

@ -72,7 +72,7 @@ class RoomDetailActivity :
}
// Simple filter
private var currentRoomId: String? = null
var currentRoomId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
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.call.SharedActiveCallViewModel
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.webrtc.WebRtcCallManager
import im.vector.app.features.command.Command
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.util.toImageRes
@ -311,7 +311,6 @@ class RoomDetailFragment @Inject constructor(
setupActiveCallView()
setupJumpToBottomView()
setupConfBannerView()
roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
}
@ -340,9 +339,9 @@ class RoomDetailFragment @Inject constructor(
}
when (mode) {
is SendMode.REGULAR -> renderRegularMode(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.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, 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.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
}
}
@ -352,33 +351,33 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.observeViewEvents {
when (it) {
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
is RoomDetailViewEvents.ShowE2EErrorMessage -> displayE2eError(it.withHeldCode)
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
is RoomDetailViewEvents.ShowE2EErrorMessage -> displayE2eError(it.withHeldCode)
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it)
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId)
RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show()
RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings()
is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item ->
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it)
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId)
RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show()
RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings()
is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item ->
navigator.openBigImageViewer(requireActivity(), it.view, item)
}
}.exhaustive
@ -525,14 +524,14 @@ class RoomDetailFragment @Inject constructor(
private fun handleShareData() {
when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> {
is SharedData.Text -> {
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
}
is SharedData.Attachments -> {
// open share edition
onContentAttachmentsReady(sharedData.attachmentData)
}
null -> Timber.v("No share data to process")
null -> Timber.v("No share data to process")
}.exhaustive
}
@ -657,8 +656,8 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) { state ->
// Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
1 -> false
2 -> state.isAllowedToStartWebRTCCall
1 -> false
2 -> state.isAllowedToStartWebRTCCall
else -> state.isAllowedToManageWidgets
}
setOf(R.id.voice_call, R.id.video_call).forEach {
@ -688,36 +687,36 @@ class RoomDetailFragment @Inject constructor(
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.invite -> {
R.id.invite -> {
navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
true
}
R.id.timeline_setting -> {
R.id.timeline_setting -> {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
true
}
R.id.resend_all -> {
R.id.resend_all -> {
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
true
}
R.id.open_matrix_apps -> {
R.id.open_matrix_apps -> {
roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations)
true
}
R.id.voice_call,
R.id.video_call -> {
R.id.video_call -> {
handleCallRequest(item)
true
}
R.id.hangup_call -> {
R.id.hangup_call -> {
roomDetailViewModel.handle(RoomDetailAction.EndCall)
true
}
R.id.search -> {
R.id.search -> {
handleSearchAction()
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 isVideoCall = item.itemId == R.id.video_call
when (roomSummary.joinedMembersCount) {
1 -> {
1 -> {
val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0
if (pendingInvite) {
// wait for other to join
@ -743,7 +742,7 @@ class RoomDetailFragment @Inject constructor(
showDialogWithMessage(getString(R.string.cannot_call_yourself))
}
}
2 -> {
2 -> {
val activeCall = sharedCallActionViewModel.activeCall.value
if (activeCall != null) {
// 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) {
is RoomDetailPendingAction.JumpToReadReceipt ->
roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
is RoomDetailPendingAction.MentionUser ->
is RoomDetailPendingAction.MentionUser ->
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm ->
is RoomDetailPendingAction.OpenOrCreateDm ->
roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
}.exhaustive
}
@ -1069,9 +1068,9 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) {
val showJumpToUnreadBanner = when (it.unreadState) {
UnreadState.Unknown,
UnreadState.HasNoUnread -> false
UnreadState.HasNoUnread -> false
is UnreadState.ReadMarkerNotLoaded -> true
is UnreadState.HasUnread -> {
is UnreadState.HasUnread -> {
if (it.canShowJumpToReadMarker) {
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
@ -1280,7 +1279,7 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, async())
vectorBaseActivity.finish()
}
is Fail -> {
is Fail -> {
vectorBaseActivity.hideWaitingView()
vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
}
@ -1289,19 +1288,19 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) {
is RoomDetailViewEvents.SlashCommandHandled -> {
is RoomDetailViewEvents.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
}
is RoomDetailViewEvents.SlashCommandError -> {
is RoomDetailViewEvents.SlashCommandError -> {
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))
}
is RoomDetailViewEvents.SlashCommandResultOk -> {
is RoomDetailViewEvents.SlashCommandResultOk -> {
updateComposerText("")
}
is RoomDetailViewEvents.SlashCommandResultError -> {
is RoomDetailViewEvents.SlashCommandResultError -> {
displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
}
is RoomDetailViewEvents.SlashCommandNotImplemented -> {
@ -1323,7 +1322,7 @@ class RoomDetailFragment @Inject constructor(
private fun displayE2eError(withHeldCode: WithHeldCode?) {
val msgId = when (withHeldCode) {
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.UNAVAILABLE -> R.string.crypto_error_withheld_generic
else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc
@ -1375,7 +1374,7 @@ class RoomDetailFragment @Inject constructor(
private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) {
when (val data = result.action) {
is RoomDetailAction.ReportContent -> {
is RoomDetailAction.ReportContent -> {
when {
data.spam -> {
AlertDialog.Builder(requireActivity())
@ -1412,7 +1411,7 @@ class RoomDetailFragment @Inject constructor(
}
}
}
is RoomDetailAction.RequestVerification -> {
is RoomDetailAction.RequestVerification -> {
Timber.v("## SAS RequestVerification action")
VerificationBottomSheet.withArgs(
roomDetailArgs.roomId,
@ -1427,7 +1426,7 @@ class RoomDetailFragment @Inject constructor(
data.transactionId
).show(parentFragmentManager, "REQ")
}
is RoomDetailAction.ResumeVerification -> {
is RoomDetailAction.ResumeVerification -> {
val otherUserId = data.otherUserId ?: return
VerificationBottomSheet().apply {
arguments = Bundle().apply {
@ -1571,11 +1570,11 @@ class RoomDetailFragment @Inject constructor(
is MessageVerificationRequestContent -> {
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
}
is MessageWithAttachmentContent -> {
is MessageWithAttachmentContent -> {
val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent)
roomDetailViewModel.handle(action)
}
is EncryptedEventContent -> {
is EncryptedEventContent -> {
roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
}
}
@ -1716,75 +1715,75 @@ class RoomDetailFragment @Inject constructor(
private fun handleActions(action: EventSharedAction) {
when (action) {
is EventSharedAction.OpenUserProfile -> {
is EventSharedAction.OpenUserProfile -> {
openRoomMemberProfile(action.userId)
}
is EventSharedAction.AddReaction -> {
is EventSharedAction.AddReaction -> {
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
}
is EventSharedAction.ViewReactions -> {
is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
}
is EventSharedAction.Copy -> {
is EventSharedAction.Copy -> {
// I need info about the current selected message :/
copyToClipboard(requireContext(), action.content, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
}
is EventSharedAction.Redact -> {
is EventSharedAction.Redact -> {
promptConfirmationToRedactEvent(action)
}
is EventSharedAction.Share -> {
is EventSharedAction.Share -> {
onShareActionClicked(action)
}
is EventSharedAction.Save -> {
is EventSharedAction.Save -> {
onSaveActionClicked(action)
}
is EventSharedAction.ViewEditHistory -> {
is EventSharedAction.ViewEditHistory -> {
onEditedDecorationClicked(action.messageInformationData)
}
is EventSharedAction.ViewSource -> {
is EventSharedAction.ViewSource -> {
JSonViewerDialog.newInstance(
action.content,
-1,
createJSonViewerStyleProvider(colorProvider)
).show(childFragmentManager, "JSON_VIEWER")
}
is EventSharedAction.ViewDecryptedSource -> {
is EventSharedAction.ViewDecryptedSource -> {
JSonViewerDialog.newInstance(
action.content,
-1,
createJSonViewerStyleProvider(colorProvider)
).show(childFragmentManager, "JSON_VIEWER")
}
is EventSharedAction.QuickReact -> {
is EventSharedAction.QuickReact -> {
// eventId,ClickedOn,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()))
}
is EventSharedAction.Quote -> {
is EventSharedAction.Quote -> {
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString()))
}
is EventSharedAction.Reply -> {
is EventSharedAction.Reply -> {
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString()))
}
is EventSharedAction.CopyPermalink -> {
is EventSharedAction.CopyPermalink -> {
val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId)
copyToClipboard(requireContext(), permalink, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
}
is EventSharedAction.Resend -> {
is EventSharedAction.Resend -> {
roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
}
is EventSharedAction.Remove -> {
is EventSharedAction.Remove -> {
roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
}
is EventSharedAction.Cancel -> {
is EventSharedAction.Cancel -> {
roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId))
}
is EventSharedAction.ReportContentSpam -> {
is EventSharedAction.ReportContentSpam -> {
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is spam", spam = true))
}
@ -1792,22 +1791,22 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
}
is EventSharedAction.ReportContentCustom -> {
is EventSharedAction.ReportContentCustom -> {
promptReasonToReportContent(action)
}
is EventSharedAction.IgnoreUser -> {
is EventSharedAction.IgnoreUser -> {
action.senderId?.let { askConfirmationToIgnoreUser(it) }
}
is EventSharedAction.OnUrlClicked -> {
is EventSharedAction.OnUrlClicked -> {
onUrlClicked(action.url, action.title)
}
is EventSharedAction.OnUrlLongClicked -> {
is EventSharedAction.OnUrlLongClicked -> {
onUrlLongClicked(action.url)
}
is EventSharedAction.ReRequestKey -> {
is EventSharedAction.ReRequestKey -> {
roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId))
}
is EventSharedAction.UseKeyBackup -> {
is EventSharedAction.UseKeyBackup -> {
context?.let {
startActivity(KeysBackupRestoreActivity.intent(it))
}
@ -1947,10 +1946,10 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) {
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
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.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.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.settings.VectorPreferences
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 javax.inject.Inject
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 matrixId the matrix id
* @param callId the call id.
* @param fromBg true if the app is in background when posting the notification
* @return the call notification.
*/
@SuppressLint("NewApi")
fun buildIncomingCallNotification(isVideo: Boolean,
otherUserId: String,
roomId: String,
callId: String): Notification {
fun buildIncomingCallNotification(mxCall: MxCall,
title: String,
fromBg: Boolean): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val builder = NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(otherUserId))
val notificationChannel = if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
val builder = NotificationCompat.Builder(context, notificationChannel)
.setContentTitle(ensureTitleNotEmpty(title))
.apply {
if (isVideo) {
if (mxCall.isVideoCall) {
setContentText(stringProvider.getString(R.string.incoming_video_call))
} else {
setContentText(stringProvider.getString(R.string.incoming_voice_call))
@ -300,15 +302,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentIntent = VectorCallActivity.newIntent(
context = context,
callId = callId,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mxCall = mxCall,
mode = VectorCallActivity.INCOMING_RINGING
).apply {
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)
@ -316,20 +314,16 @@ class NotificationUtils @Inject constructor(private val context: Context,
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(VectorCallActivity.newIntent(
context = context,
callId = callId,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mxCall = mxCall,
mode = VectorCallActivity.INCOMING_ACCEPT)
)
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction(
NotificationCompat.Action(
R.drawable.ic_call,
R.drawable.ic_call_answer,
// IconCompat.createWithResource(applicationContext, R.drawable.ic_call)
// .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)),
context.getString(R.string.call_notification_answer),
@ -339,7 +333,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.addAction(
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),
rejectCallPendingIntent)
)
@ -349,14 +343,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
return builder.build()
}
fun buildOutgoingRingingCallNotification(isVideo: Boolean,
otherUserId: String,
roomId: String,
callId: String): Notification {
fun buildOutgoingRingingCallNotification(mxCall: MxCall,
title: String): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(otherUserId))
.setContentTitle(ensureTitleNotEmpty(title))
.apply {
setContentText(stringProvider.getString(R.string.call_ring))
}
@ -367,18 +358,14 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentIntent = VectorCallActivity.newIntent(
context = context,
callId = callId,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mxCall = mxCall,
mode = null).apply {
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 rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction(
NotificationCompat.Action(
@ -402,16 +389,13 @@ class NotificationUtils @Inject constructor(private val context: Context,
* @return the call notification.
*/
@SuppressLint("NewApi")
fun buildPendingCallNotification(isVideo: Boolean,
roomName: String,
roomId: String,
matrixId: String,
callId: String,
fun buildPendingCallNotification(mxCall: MxCall,
title: String,
fromBg: Boolean = false): Notification {
val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(roomName))
.setContentTitle(ensureTitleNotEmpty(title))
.apply {
if (isVideo) {
if (mxCall.isVideoCall) {
setContentText(stringProvider.getString(R.string.video_call_in_progress))
} else {
setContentText(stringProvider.getString(R.string.call_in_progress))
@ -425,7 +409,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.setOngoing(true)
}
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction(
NotificationCompat.Action(
@ -436,8 +420,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
// TODO other userId
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
.addNextIntent(VectorCallActivity.newIntent(context, mxCall, null))
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
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
*/
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))
.setSmallIcon(R.drawable.ic_material_call_end_grey)
.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.Looper
import android.view.View
import android.widget.ImageView
import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener
import dagger.Lazy
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity
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.themes.ThemeUtils
import timber.log.Timber
@ -41,7 +38,7 @@ import javax.inject.Singleton
* Alerts are stacked and will be displayed sequentially
*/
@Singleton
class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<AvatarRenderer>) {
class PopupAlertManager @Inject constructor() {
private var weakCurrentActivity: WeakReference<Activity>? = null
private var currentAlerter: VectorAlert? = null
@ -191,17 +188,13 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
val noAnimation = !animate || isAnimationDisabled(activity)
alert.weakCurrentActivity = WeakReference(activity)
val alerter = if (alert is VerificationVectorAlert) Alerter.create(activity, R.layout.alerter_verification_layout)
else Alerter.create(activity)
val alerter = Alerter.create(activity, alert.layoutRes)
alerter.setTitle(alert.title)
.setText(alert.description)
.also { al ->
if (alert is VerificationVectorAlert) {
val tvCustomView = al.getLayoutContainer()
tvCustomView?.findViewById<ImageView>(R.id.ivUserAvatar)?.let { imageView ->
alert.matrixItem?.let { avatarRenderer.get().render(it, imageView) }
}
al.getLayoutContainer()?.also {
alert.viewBinder?.bind(it)
}
}
.apply {
@ -251,6 +244,8 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
.apply {
if (alert.colorInt != null) {
setBackgroundColorInt(alert.colorInt!!)
} else if (alert.colorAttribute != null) {
setBackgroundColorInt(ThemeUtils.getColor(activity, alert.colorAttribute!!))
} else {
setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color)
}

View file

@ -17,12 +17,18 @@
package im.vector.app.features.popup
import android.app.Activity
import android.view.View
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
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 java.lang.ref.WeakReference
interface VectorAlert {
val uid: String
val title: String
@ -47,22 +53,34 @@ interface VectorAlert {
actions.add(Button(title, action, autoClose))
}
var viewBinder: ViewBinder?
val layoutRes: Int
var colorRes: Int?
var colorInt: Int?
var colorAttribute: Int?
interface ViewBinder {
fun bind(view: View)
}
}
/**
* Dataclass to describe an important alert with actions.
*/
open class DefaultVectorAlert(override val uid: String,
override val 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 }
open class DefaultVectorAlert(
override val uid: String,
override val 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 },
) : VectorAlert {
// 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 */
override var expirationTimestamp: Long? = null
override fun addButton(title: String, action: Runnable, autoClose: Boolean) {
actions.add(VectorAlert.Button(title, action, autoClose))
}
@LayoutRes
override val layoutRes = R.layout.alerter_alert_default_layout
@ColorRes
override var colorRes: Int? = null
@ColorInt
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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomToolbar"
tools:visibility="visible" />
tools:visibility="gone" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/badgeBarrier"

View file

@ -1,7 +1,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:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.FrameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -200,4 +201,4 @@
<!-- app:layout_constraintEnd_toEndOf="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_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) -->
<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_dark">#22262E</color>
</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_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_alerter_background">@color/riotx_alerter_background_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_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_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_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_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_alerter_background">@color/riotx_alerter_background_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 -->