Custom alert design

This commit is contained in:
Valere 2020-03-24 14:56:57 +01:00
parent 22642e71a3
commit b56a41bec7
12 changed files with 266 additions and 69 deletions

View File

@ -323,7 +323,7 @@ dependencies {
implementation 'com.nulab-inc:zxcvbn:1.2.7' implementation 'com.nulab-inc:zxcvbn:1.2.7'
//Alerter //Alerter
implementation 'com.tapadoo.android:alerter:4.0.3' implementation 'com.tapadoo.android:alerter:5.1.2'
implementation 'com.otaliastudios:autocomplete:1.1.0' implementation 'com.otaliastudios:autocomplete:1.1.0'

View File

@ -48,6 +48,7 @@ import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.session.SessionListener
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
@ -77,6 +78,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
@Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var appStateHandler: AppStateHandler @Inject lateinit var appStateHandler: AppStateHandler
@Inject lateinit var rxConfig: RxConfig @Inject lateinit var rxConfig: RxConfig
@Inject lateinit var popupAlertManager: PopupAlertManager
lateinit var vectorComponent: VectorComponent lateinit var vectorComponent: VectorComponent
private var fontThreadHandler: Handler? = null private var fontThreadHandler: Handler? = null
@ -102,7 +104,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
BigImageViewer.initialize(GlideImageLoader.with(applicationContext)) BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks()) registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager))
val fontRequest = FontRequest( val fontRequest = FontRequest(
"com.google.android.gms.fonts", "com.google.android.gms.fonts",
"com.google.android.gms", "com.google.android.gms",

View File

@ -45,6 +45,7 @@ import im.vector.riotx.features.notifications.NotificationBroadcastReceiver
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.VectorFileLogger import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
@ -128,6 +129,8 @@ interface VectorComponent {
fun emojiDataSource(): EmojiDataSource fun emojiDataSource(): EmojiDataSource
fun alertManager() : PopupAlertManager
@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(@BindsInstance context: Context): VectorComponent fun create(@BindsInstance context: Context): VectorComponent

View File

@ -36,7 +36,9 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.popup.DefaultVectorAlert
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.popup.VectorAlert
import timber.log.Timber import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -54,7 +56,7 @@ import javax.inject.Singleton
*/ */
@Singleton @Singleton
class KeyRequestHandler @Inject constructor(private val context: Context) class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager)
: GossipingRequestListener, : GossipingRequestListener,
VerificationService.Listener { VerificationService.Listener {
@ -118,9 +120,9 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
} }
if (deviceInfo.isUnknown) { if (deviceInfo.isUnknown) {
session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(false, false), userId, deviceId) session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false), userId, deviceId)
deviceInfo.trustLevel = DeviceTrustLevel(false, false) deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
// can we get more info on this device? // can we get more info on this device?
session?.cryptoService()?.getDevicesList(object : MatrixCallback<DevicesListResponse> { session?.cryptoService()?.getDevicesList(object : MatrixCallback<DevicesListResponse> {
@ -188,7 +190,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
} }
} }
val alert = PopupAlertManager.VectorAlert( val alert = DefaultVectorAlert(
alertManagerId(userId, deviceId), alertManagerId(userId, deviceId),
context.getString(R.string.key_share_request), context.getString(R.string.key_share_request),
dialogText, dialogText,
@ -210,7 +212,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
denyAllRequests(mappingKey) denyAllRequests(mappingKey)
}) })
PopupAlertManager.postVectorAlert(alert) popupAlertManager.postVectorAlert(alert)
} }
private fun denyAllRequests(mappingKey: String) { private fun denyAllRequests(mappingKey: String) {
@ -250,7 +252,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
&& it.requestId == request.requestId && it.requestId == request.requestId
} }
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) { if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
PopupAlertManager.cancelAlert(alertMgrUniqueKey) popupAlertManager.cancelAlert(alertMgrUniqueKey)
alertsToRequests.remove(keyForMap(userId, deviceId)) alertsToRequests.remove(keyForMap(userId, deviceId))
} }
} }
@ -261,7 +263,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
if (state == VerificationTxState.Verified) { if (state == VerificationTxState.Verified) {
// ok it's verified, see if we have key request for that // ok it's verified, see if we have key request for that
shareAllSessions("${tx.otherDeviceId}${tx.otherUserId}") shareAllSessions("${tx.otherDeviceId}${tx.otherUserId}")
PopupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}") popupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}")
} }
} }
// should do it with QR tx also // should do it with QR tx also
@ -271,7 +273,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
override fun markedAsManuallyVerified(userId: String, deviceId: String) { override fun markedAsManuallyVerified(userId: String, deviceId: String) {
// accept related requests // accept related requests
shareAllSessions(keyForMap(userId, deviceId)) shareAllSessions(keyForMap(userId, deviceId))
PopupAlertManager.cancelAlert(alertManagerId(userId, deviceId)) popupAlertManager.cancelAlert(alertManagerId(userId, deviceId))
} }
private fun keyForMap(userId: String, deviceId: String) = "$deviceId$userId" private fun keyForMap(userId: String, deviceId: String) = "$deviceId$userId"

View File

@ -17,15 +17,17 @@ package im.vector.riotx.features.crypto.verification
import android.content.Context import android.content.Context
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.popup.VerificationVectorAlert
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -34,7 +36,9 @@ import javax.inject.Singleton
* Listens to the VerificationManager and add a new notification when an incoming request is detected. * Listens to the VerificationManager and add a new notification when an incoming request is detected.
*/ */
@Singleton @Singleton
class IncomingVerificationRequestHandler @Inject constructor(private val context: Context) : VerificationService.Listener { class IncomingVerificationRequestHandler @Inject constructor(
private val context: Context,
private val popupAlertManager: PopupAlertManager) : VerificationService.Listener {
private var session: Session? = null private var session: Session? = null
@ -58,7 +62,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
val name = session?.getUser(tx.otherUserId)?.displayName val name = session?.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId ?: tx.otherUserId
val alert = PopupAlertManager.VectorAlert( val alert = VerificationVectorAlert(
uid, uid,
context.getString(R.string.sas_incoming_request_notif_title), context.getString(R.string.sas_incoming_request_notif_title),
context.getString(R.string.sas_incoming_request_notif_content, name), context.getString(R.string.sas_incoming_request_notif_content, name),
@ -68,12 +72,15 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
// TODO a bit too hugly :/ // TODO a bit too hugly :/
activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let { activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let {
false.also { false.also {
PopupAlertManager.cancelAlert(uid) popupAlertManager.cancelAlert(uid)
} }
} ?: true } ?: true
} else true } else true
}) })
.apply { .apply {
matrixItem = session?.getUser(tx.otherUserId ?: "")?.toMatrixItem()
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId) it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
@ -99,11 +106,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
// 10mn expiration // 10mn expiration
expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
} }
PopupAlertManager.postVectorAlert(alert) popupAlertManager.postVectorAlert(alert)
} }
is VerificationTxState.TerminalTxState -> { is VerificationTxState.TerminalTxState -> {
// cancel related notification // cancel related notification
PopupAlertManager.cancelAlert(uid) popupAlertManager.cancelAlert(uid)
} }
else -> Unit else -> Unit
} }
@ -115,7 +122,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
val name = session?.getUser(pr.otherUserId)?.displayName val name = session?.getUser(pr.otherUserId)?.displayName
?: pr.otherUserId ?: pr.otherUserId
val alert = PopupAlertManager.VectorAlert( val alert = VerificationVectorAlert(
uniqueIdForVerificationRequest(pr), uniqueIdForVerificationRequest(pr),
context.getString(R.string.sas_incoming_request_notif_title), context.getString(R.string.sas_incoming_request_notif_title),
"$name(${pr.otherUserId})", "$name(${pr.otherUserId})",
@ -128,6 +135,9 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
} else true } else true
}) })
.apply { .apply {
matrixItem = session?.getUser(pr.otherUserId)?.toMatrixItem()
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
val roomId = pr.roomId val roomId = pr.roomId
@ -148,14 +158,14 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
// 5mn expiration // 5mn expiration
expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L) expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L)
} }
PopupAlertManager.postVectorAlert(alert) popupAlertManager.postVectorAlert(alert)
} }
} }
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// If an incoming request is readied (by another device?) we should discard the alert // If an incoming request is readied (by another device?) we should discard the alert
if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) { if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) {
PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr)) popupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr))
} }
} }

View File

@ -42,6 +42,8 @@ import im.vector.riotx.core.pushers.PushersManager
import im.vector.riotx.features.disclaimer.showDisclaimerDialog import im.vector.riotx.features.disclaimer.showDisclaimerDialog
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.popup.VectorAlert
import im.vector.riotx.features.popup.VerificationVectorAlert
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.workers.signout.SignOutViewModel import im.vector.riotx.features.workers.signout.SignOutViewModel
@ -60,6 +62,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
@Inject lateinit var pushManager: PushersManager @Inject lateinit var pushManager: PushersManager
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var popupAlertManager: PopupAlertManager
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
override fun onDrawerStateChanged(newState: Int) { override fun onDrawerStateChanged(newState: Int) {
@ -149,8 +152,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
if (crossSigningEnabledOnAccount && myCrossSigningKeys?.isTrusted() == false) { if (crossSigningEnabledOnAccount && myCrossSigningKeys?.isTrusted() == false) {
// We need to ask // We need to ask
sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
PopupAlertManager.postVectorAlert( popupAlertManager.postVectorAlert(
PopupAlertManager.VectorAlert( VerificationVectorAlert(
uid = "completeSecurity", uid = "completeSecurity",
title = getString(R.string.new_signin), title = getString(R.string.new_signin),
description = getString(R.string.complete_security), description = getString(R.string.complete_security),

View File

@ -42,6 +42,7 @@ import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.room.list.RoomListParams import im.vector.riotx.features.home.room.list.RoomListParams
import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.popup.VectorAlert
import im.vector.riotx.features.workers.signout.SignOutViewModel import im.vector.riotx.features.workers.signout.SignOutViewModel
import kotlinx.android.synthetic.main.fragment_home_detail.* import kotlinx.android.synthetic.main.fragment_home_detail.*
import timber.log.Timber import timber.log.Timber
@ -53,7 +54,8 @@ private const val INDEX_ROOMS = 2
class HomeDetailFragment @Inject constructor( class HomeDetailFragment @Inject constructor(
val homeDetailViewModelFactory: HomeDetailViewModel.Factory, val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer,
private val alertManager: PopupAlertManager
) : VectorBaseFragment(), KeysBackupBanner.Delegate { ) : VectorBaseFragment(), KeysBackupBanner.Delegate {
private val unreadCounterBadgeViews = arrayListOf<UnreadCounterBadgeView>() private val unreadCounterBadgeViews = arrayListOf<UnreadCounterBadgeView>()
@ -89,19 +91,21 @@ class HomeDetailFragment @Inject constructor(
it.unknownSessions.invoke()?.let { unknownDevices -> it.unknownSessions.invoke()?.let { unknownDevices ->
Timber.v("## Detector - ${unknownDevices.size} Unknown sessions") Timber.v("## Detector - ${unknownDevices.size} Unknown sessions")
unknownDevices.forEachIndexed { index, deviceInfo -> unknownDevices.forEachIndexed { index, deviceInfo ->
Timber.v("## Detector - #${index} deviceId:${deviceInfo.deviceId} lastSeenTs:${deviceInfo.lastSeenTs}") Timber.v("## Detector - #${index} deviceId:${deviceInfo.second.deviceId} lastSeenTs:${deviceInfo.second.lastSeenTs}")
} }
if (it.canCrossSign && unknownDevices.isNotEmpty()) { if (it.canCrossSign && unknownDevices.isNotEmpty()) {
val newest = unknownDevices.first() val newest = unknownDevices.first().second
val user = unknownDevices.first().first
val uid = "ND_${newest.deviceId}" val uid = "ND_${newest.deviceId}"
PopupAlertManager.cancelAlert(uid) alertManager.cancelAlert(uid)
PopupAlertManager.postVectorAlert( alertManager.postVectorAlert(
PopupAlertManager.VectorAlert( VectorAlert(
uid = uid, uid = uid,
title = getString(R.string.new_session), title = getString(R.string.new_session),
description = getString(R.string.new_session_review), description = getString(R.string.new_session_review),
iconId = R.drawable.ic_shield_warning iconId = R.drawable.ic_shield_warning
).apply { ).apply {
matrixItem = user
colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity) (weakCurrentActivity?.get() as? VectorBaseActivity)

View File

@ -22,7 +22,9 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.NoOpCancellable import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
@ -34,7 +36,8 @@ import im.vector.riotx.core.platform.VectorViewModel
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
data class UnknownDevicesState( data class UnknownDevicesState(
val unknownSessions: Async<List<DeviceInfo>?> = Uninitialized, val unknownSessions: Async<List<Pair<MatrixItem?,DeviceInfo>>?> = Uninitialized,
val verifiedSessions: Async<List<Pair<MatrixItem?,DeviceInfo>>?> = Uninitialized,
val canCrossSign: Boolean = false val canCrossSign: Boolean = false
) : MvRxState ) : MvRxState
@ -51,6 +54,9 @@ class UnknownDeviceDetectorSharedViewModel(session: Session, initialState: Unkno
resp.devices?.filter { info -> resp.devices?.filter { info ->
deviceList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not() ?: false deviceList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not() ?: false
}?.sortedByDescending { it.lastSeenTs } }?.sortedByDescending { it.lastSeenTs }
?.map {
session.getUser(it.user_id ?: "")?.toMatrixItem() to it
}
} }
.toObservable() .toObservable()
} }

View File

@ -22,12 +22,12 @@ import android.os.Bundle
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import javax.inject.Inject import javax.inject.Inject
class VectorActivityLifecycleCallbacks @Inject constructor() : Application.ActivityLifecycleCallbacks { class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager: PopupAlertManager) : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) { override fun onActivityPaused(activity: Activity) {
} }
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
PopupAlertManager.onNewActivityDisplayed(activity) popupAlertManager.onNewActivityDisplayed(activity)
} }
override fun onActivityStarted(activity: Activity) { override fun onActivityStarted(activity: Activity) {

View File

@ -20,20 +20,23 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import androidx.annotation.ColorInt import android.widget.ImageView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener import com.tapadoo.alerter.OnHideAlertListener
import dagger.Lazy
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import javax.inject.Inject
import javax.inject.Singleton
/** /**
* Responsible of displaying important popup alerts on top of the screen. * 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
*/ */
object PopupAlertManager { @Singleton
class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<AvatarRenderer>) {
private var weakCurrentActivity: WeakReference<Activity>? = null private var weakCurrentActivity: WeakReference<Activity>? = null
private var currentAlerter: VectorAlert? = null private var currentAlerter: VectorAlert? = null
@ -160,9 +163,19 @@ object PopupAlertManager {
clearLightStatusBar() clearLightStatusBar()
alert.weakCurrentActivity = WeakReference(activity) alert.weakCurrentActivity = WeakReference(activity)
Alerter.create(activity) val alerter = if (alert is VerificationVectorAlert) Alerter.create(activity, R.layout.alerter_verification_layout)
.setTitle(alert.title) else Alerter.create(activity)
alerter.setTitle(alert.title)
.setText(alert.description) .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) }
}
}
}
.apply { .apply {
if (!animate) { if (!animate) {
setEnterAnimation(R.anim.anim_alerter_no_anim) setEnterAnimation(R.anim.anim_alerter_no_anim)
@ -226,37 +239,4 @@ object PopupAlertManager {
displayNextIfPossible() displayNextIfPossible()
}, 500) }, 500)
} }
/**
* Dataclass to describe an important alert with actions.
*/
data class VectorAlert(val uid: String,
val title: String,
val description: String,
@DrawableRes val iconId: Int?,
val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) {
data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
// will be set by manager, and accessible by actions at runtime
var weakCurrentActivity: WeakReference<Activity>? = null
val actions = ArrayList<Button>()
var contentAction: Runnable? = null
var dismissedAction: Runnable? = null
/** If this timestamp is after current time, this alert will be skipped */
var expirationTimestamp: Long? = null
fun addButton(title: String, action: Runnable, autoClose: Boolean = true) {
actions.add(Button(title, action, autoClose))
}
@ColorRes
var colorRes: Int? = null
@ColorInt
var colorInt: Int? = null
}
} }

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.popup
import android.app.Activity
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import im.vector.matrix.android.api.util.MatrixItem
import java.lang.ref.WeakReference
interface VectorAlert {
val uid: String
val title: String
val description: String
val iconId: Int?
val shouldBeDisplayedIn: ((Activity) -> Boolean)?
data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
// will be set by manager, and accessible by actions at runtime
var weakCurrentActivity: WeakReference<Activity>?
val actions: MutableList<Button>
var contentAction: Runnable?
var dismissedAction: Runnable?
/** If this timestamp is after current time, this alert will be skipped */
var expirationTimestamp: Long?
fun addButton(title: String, action: Runnable, autoClose: Boolean = true) {
actions.add(Button(title, action, autoClose))
}
var colorRes: Int?
var colorInt: Int?
}
/**
* 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?,
override val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) : VectorAlert {
// will be set by manager, and accessible by actions at runtime
override var weakCurrentActivity: WeakReference<Activity>? = null
override val actions = ArrayList<VectorAlert.Button>()
override var contentAction: Runnable? = null
override var dismissedAction: Runnable? = null
/** 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))
}
@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?,
override val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null
) : DefaultVectorAlert(
uid, title, description, iconId, shouldBeDisplayedIn
) {
var matrixItem: MatrixItem? = null
}

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:background="@android:color/darker_gray"
tools:foreground="?android:attr/selectableItemBackground"
tools:style="@style/AlertStyle">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/ivUserAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/alerter_texts"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/ivIcon"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintCircle="@+id/ivUserAvatar"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="20dp"
tools:ignore="MissingConstraints"
android:src="@drawable/ic_shield_warning"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/alerter_texts"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivUserAvatar"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/alerter_padding_half"
android:layout_marginEnd="@dimen/alerter_padding_half"
android:paddingStart="@dimen/alerter_padding_small"
android:paddingLeft="@dimen/alerter_padding_small"
android:paddingEnd="@dimen/alerter_padding_small"
android:textAppearance="@style/AlertTextAppearance.Title"
android:visibility="gone"
tools:text="Title"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/alerter_padding_half"
android:layout_marginEnd="@dimen/alerter_padding_half"
android:paddingStart="@dimen/alerter_padding_small"
android:paddingLeft="@dimen/alerter_padding_small"
android:paddingTop="@dimen/alerter_padding_small"
android:paddingEnd="@dimen/alerter_padding_small"
android:paddingBottom="@dimen/alerter_padding_small"
android:textAppearance="@style/AlertTextAppearance.Text"
android:visibility="gone"
tools:text="Text"
tools:visibility="visible" />
</LinearLayout>
<!-- <FrameLayout-->
<!-- android:id="@+id/flRightIconContainer"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="center_vertical">-->
<!-- <androidx.appcompat.widget.AppCompatImageView-->
<!-- android:id="@+id/ivRightIcon"-->
<!-- android:layout_width="@dimen/alerter_alert_icn_size"-->
<!-- android:layout_height="@dimen/alerter_alert_icn_size"-->
<!-- android:maxWidth="@dimen/alerter_alert_icn_size"-->
<!-- android:maxHeight="@dimen/alerter_alert_icn_size"-->
<!-- android:visibility="gone"-->
<!-- app:srcCompat="@drawable/alerter_ic_notifications"-->
<!-- app:tint="@color/alert_default_icon_color"-->
<!-- tools:visibility="visible" />-->
<!-- </FrameLayout>-->
</androidx.constraintlayout.widget.ConstraintLayout>