Merge pull request #2515 from vector-im/feature/fga/voip_notif

Feature/fga/voip notif
This commit is contained in:
ganfra 2020-12-11 10:01:57 +01:00 committed by GitHub
commit afc3c1462e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 534 additions and 371 deletions

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

@ -35,7 +35,7 @@ internal class MxCallFactory @Inject constructor(
@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(
callId = content.callId,
@ -43,7 +43,7 @@ internal class MxCallFactory @Inject constructor(
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = senderId,
opponentUserId = opponentUserId,
isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor
@ -53,14 +53,14 @@ 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(
callId = UUID.randomUUID().toString(),
isOutgoing = true,
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = otherUserId,
opponentUserId = opponentUserId,
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor

View file

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

View file

@ -24,10 +24,20 @@ import android.support.v4.media.session.MediaSessionCompat
import android.view.KeyEvent
import androidx.core.content.ContextCompat
import androidx.media.session.MediaButtonReceiver
import com.airbnb.mvrx.MvRx
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.call.CallArgs
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.home.room.detail.RoomDetailActivity
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 +49,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 +76,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 +125,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)
}
@ -147,63 +161,61 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
/**
* Display a permanent notification when there is an incoming call.
*
* @param session the session
* @param isVideo true if this is a video call, false for voice call
* @param room the room
* @param callId the callId
*/
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")
val incomingCallAlert = IncomingCallAlert(INCOMING_CALL_ALERT_UID,
shouldBeDisplayedIn = { activity ->
if (activity is RoomDetailActivity) {
call.roomId != activity.currentRoomId
} else if (activity is VectorCallActivity) {
activity.intent.getParcelableExtra<CallArgs>(MvRx.KEY_ARG)?.callId != call.callId
} else true
}
).apply {
viewBinder = IncomingCallAlert.ViewBinder(
matrixItem = opponentMatrixItem,
avatarRenderer = avatarRenderer,
isVideoCall = isVideoCall,
onAccept = { showCallScreen(call, VectorCallActivity.INCOMING_ACCEPT) },
onReject = { call.endCall() }
)
dismissedAction = Runnable { call.endCall() }
contentAction = Runnable { showCallScreen(call, VectorCallActivity.INCOMING_RINGING) }
}
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 showCallScreen(call: WebRtcCall, mode: String) {
val intent = VectorCallActivity.newIntent(
context = this,
mxCall = call.mxCall,
mode = mode
)
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 +225,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 +241,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 +258,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 +270,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 +287,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 +314,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 +325,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 +340,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
.apply {
action = ACTION_NO_ACTIVE_CALL
}
ContextCompat.startForegroundService(context, intent)
}
}

View file

@ -18,10 +18,10 @@ 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
import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.ButterKnife
@ -33,7 +33,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 +56,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

@ -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,14 +189,8 @@ 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
@ -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,11 +18,11 @@ 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
import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -31,6 +31,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 +40,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 +62,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?.getBestName() ?: tx.otherUserId
val alert = VerificationVectorAlert(
uid,
context.getString(R.string.sas_incoming_request_notif_title),
@ -77,10 +78,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 +121,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?.getBestName() ?: pr.otherUserId
val alert = VerificationVectorAlert(
uniqueIdForVerificationRequest(pr),
@ -134,10 +135,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 +155,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

@ -51,7 +51,6 @@ import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
import im.vector.app.push.fcm.FcmHelper
@ -82,6 +81,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 +126,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 +145,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 +180,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 +251,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)
@ -282,9 +282,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
uid = "upgradeSecurity",
title = getString(titleRes),
description = getString(descRes),
iconId = R.drawable.ic_shield_warning,
matrixItem = userItem
iconId = R.drawable.ic_shield_warning
).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)
}
@ -535,14 +534,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
}
@ -667,8 +666,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 {
@ -698,36 +697,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)
}
}
@ -743,7 +742,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
@ -753,7 +752,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?
@ -934,9 +933,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
}
@ -1079,9 +1078,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()
@ -1290,7 +1289,7 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, async())
vectorBaseActivity.finish()
}
is Fail -> {
is Fail -> {
vectorBaseActivity.hideWaitingView()
vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
}
@ -1299,19 +1298,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 -> {
@ -1333,7 +1332,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
@ -1385,7 +1384,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())
@ -1422,7 +1421,7 @@ class RoomDetailFragment @Inject constructor(
}
}
}
is RoomDetailAction.RequestVerification -> {
is RoomDetailAction.RequestVerification -> {
Timber.v("## SAS RequestVerification action")
VerificationBottomSheet.withArgs(
roomDetailArgs.roomId,
@ -1437,7 +1436,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 {
@ -1581,11 +1580,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))
}
}
@ -1726,75 +1725,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))
}
@ -1802,22 +1801,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))
}
@ -1957,10 +1956,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

@ -30,6 +30,10 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.Build
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat
@ -52,6 +56,7 @@ 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 timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -269,19 +274,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 +305,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,32 +317,28 @@ 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,
// IconCompat.createWithResource(applicationContext, R.drawable.ic_call)
// .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)),
context.getString(R.string.call_notification_answer),
answerCallPendingIntent
)
IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
getActionText(R.string.call_notification_reject, R.color.riotx_notice),
rejectCallPendingIntent)
)
builder.addAction(
NotificationCompat.Action(
IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
context.getString(R.string.call_notification_reject),
rejectCallPendingIntent)
R.drawable.ic_call_answer,
// IconCompat.createWithResource(applicationContext, R.drawable.ic_call)
// .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)),
getActionText(R.string.call_notification_answer, R.color.riotx_positive_accent),
answerCallPendingIntent
)
)
builder.setFullScreenIntent(contentPendingIntent, true)
@ -349,14 +346,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,23 +361,19 @@ 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(
IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
context.getString(R.string.call_notification_hangup),
IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
getActionText(R.string.call_notification_hangup, R.color.riotx_notice),
rejectCallPendingIntent)
)
builder.setContentIntent(contentPendingIntent)
@ -402,16 +392,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,19 +412,18 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.setOngoing(true)
}
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
val rejectCallPendingIntent = buildRejectCallPendingIntent(mxCall.callId)
builder.addAction(
NotificationCompat.Action(
IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
context.getString(R.string.call_notification_hangup),
IconCompat.createWithResource(context, R.drawable.ic_call_hangup).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
getActionText(R.string.call_notification_hangup, R.color.riotx_notice),
rejectCallPendingIntent)
)
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 +448,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)
@ -883,6 +869,13 @@ class NotificationUtils @Inject constructor(private val context: Context,
|| setting == NotificationManager.INTERRUPTION_FILTER_ALARMS
}
private fun getActionText(@StringRes stringRes: Int, @ColorRes colorRes: Int): Spannable {
return SpannableString(context.getText(stringRes)).apply {
val foregroundColorSpan = ForegroundColorSpan(ContextCompat.getColor(context, colorRes))
setSpan(foregroundColorSpan, 0, length, 0)
}
}
private fun ensureTitleNotEmpty(title: String?): CharSequence {
if (title.isNullOrBlank()) {
return stringProvider.getString(R.string.app_name)

View file

@ -0,0 +1,63 @@
/*
* 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 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 priority = PopupAlertManager.INCOMING_CALL_PRIORITY
override val layoutRes = R.layout.alerter_incoming_call_layout
override var colorAttribute: Int? = R.attr.riotx_alerter_background
override val dismissOnClick: Boolean = false
override val isLight: Boolean = true
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,10 @@ 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
@ -38,19 +34,25 @@ import javax.inject.Singleton
/**
* Responsible of displaying important popup alerts on top of the screen.
* Alerts are stacked and will be displayed sequentially
* Alerts are stacked and will be displayed sequentially but sorted by priority.
* So if a new alert is posted with a higher priority than the current one it will show it instead and the current one
* will be back in the queue in first position.
*/
@Singleton
class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<AvatarRenderer>) {
class PopupAlertManager @Inject constructor() {
companion object {
const val INCOMING_CALL_PRIORITY = Int.MAX_VALUE
}
private var weakCurrentActivity: WeakReference<Activity>? = null
private var currentAlerter: VectorAlert? = null
private val alertFiFo = mutableListOf<VectorAlert>()
private val alertQueue = mutableListOf<VectorAlert>()
fun postVectorAlert(alert: VectorAlert) {
synchronized(alertFiFo) {
alertFiFo.add(alert)
synchronized(alertQueue) {
alertQueue.add(alert)
}
weakCurrentActivity?.get()?.runOnUiThread {
displayNextIfPossible()
@ -58,8 +60,8 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
}
fun cancelAlert(uid: String) {
synchronized(alertFiFo) {
alertFiFo.listIterator().apply {
synchronized(alertQueue) {
alertQueue.listIterator().apply {
while (this.hasNext()) {
val next = this.next()
if (next.uid == uid) {
@ -82,8 +84,8 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
* Cancel all alerts, after a sign out for instance
*/
fun cancelAll() {
synchronized(alertFiFo) {
alertFiFo.clear()
synchronized(alertQueue) {
alertQueue.clear()
}
// Cancel any displayed alert
@ -98,7 +100,9 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
if (currentAlerter != null) {
weakCurrentActivity?.get()?.let {
Alerter.clearCurrent(it)
setLightStatusBar()
if (currentAlerter?.isLight == false) {
setLightStatusBar()
}
}
}
weakCurrentActivity = WeakReference(activity)
@ -129,15 +133,21 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
}
private fun displayNextIfPossible() {
val currentActivity = weakCurrentActivity?.get()
if (Alerter.isShowing || currentActivity == null) {
// will retry later
return
}
val currentActivity = weakCurrentActivity?.get() ?: return
val next: VectorAlert?
synchronized(alertFiFo) {
next = alertFiFo.firstOrNull()
if (next != null) alertFiFo.remove(next)
synchronized(alertQueue) {
next = alertQueue.maxByOrNull { it.priority }
// If next alert with highest priority is higher than the current one, we should display it
// and add the current one to queue again.
if (next != null && next.priority > currentAlerter?.priority ?: Int.MIN_VALUE) {
alertQueue.remove(next)
currentAlerter?.also {
alertQueue.add(0, it)
}
} else {
// otherwise, we don't do anything
return
}
}
currentAlerter = next
next?.let {
@ -186,22 +196,19 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
}
private fun showAlert(alert: VectorAlert, activity: Activity, animate: Boolean = true) {
clearLightStatusBar()
if (!alert.isLight) {
clearLightStatusBar()
}
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 {
@ -213,7 +220,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
setIcon(it)
}
alert.actions.forEach { action ->
addButton(action.title, R.style.AlerterButton, View.OnClickListener {
addButton(action.title, R.style.AlerterButton) {
if (action.autoClose) {
currentIsDismissed()
Alerter.hide()
@ -223,21 +230,23 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
} catch (e: java.lang.Exception) {
Timber.e("## failed to perform action")
}
})
}
}
setOnClickListener(View.OnClickListener { _ ->
setOnClickListener { _ ->
alert.contentAction?.let {
currentIsDismissed()
Alerter.hide()
if (alert.dismissOnClick) {
currentIsDismissed()
Alerter.hide()
}
try {
it.run()
} catch (e: java.lang.Exception) {
Timber.e("## failed to perform action")
}
}
})
}
}
.setOnHideListener(OnHideAlertListener {
.setOnHideListener {
// called when dismissed on swipe
try {
alert.dismissedAction?.run()
@ -245,12 +254,14 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
Timber.e("## failed to perform action")
}
currentIsDismissed()
})
}
.enableSwipeToDismiss()
.enableInfiniteDuration(true)
.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)
}
@ -261,8 +272,9 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
private fun currentIsDismissed() {
// current alert has been hidden
setLightStatusBar()
if (currentAlerter?.isLight == false) {
setLightStatusBar()
}
currentAlerter = null
Handler(Looper.getMainLooper()).postDelayed({
displayNextIfPossible()

View file

@ -17,10 +17,13 @@
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 org.matrix.android.sdk.api.util.MatrixItem
import androidx.annotation.LayoutRes
import im.vector.app.R
import java.lang.ref.WeakReference
interface VectorAlert {
@ -28,6 +31,9 @@ interface VectorAlert {
val title: String
val description: String
val iconId: Int?
val priority: Int
val dismissOnClick: Boolean
val isLight: Boolean
val shouldBeDisplayedIn: ((Activity) -> Boolean)
data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
@ -47,22 +53,33 @@ 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 +93,23 @@ 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
override val dismissOnClick: Boolean = true
override val priority: Int = 0
override val isLight: Boolean = false
@ColorRes
override var colorRes: Int? = null
@ColorInt
override var colorInt: Int? = 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
)
@AttrRes
override var colorAttribute: Int? = null
override var viewBinder: VectorAlert.ViewBinder? = null
}

View file

@ -0,0 +1,50 @@
/*
* 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 -->