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'
//Alerter
implementation 'com.tapadoo.android:alerter:4.0.3'
implementation 'com.tapadoo.android:alerter:5.1.2'
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.NotificationUtils
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.session.SessionListener
import im.vector.riotx.features.settings.VectorPreferences
@ -77,6 +78,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
@Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var appStateHandler: AppStateHandler
@Inject lateinit var rxConfig: RxConfig
@Inject lateinit var popupAlertManager: PopupAlertManager
lateinit var vectorComponent: VectorComponent
private var fontThreadHandler: Handler? = null
@ -102,7 +104,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager))
val fontRequest = FontRequest(
"com.google.android.gms.fonts",
"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.NotificationUtils
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.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
@ -128,6 +129,8 @@ interface VectorComponent {
fun emojiDataSource(): EmojiDataSource
fun alertManager() : PopupAlertManager
@Component.Factory
interface Factory {
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.DevicesListResponse
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.VectorAlert
import timber.log.Timber
import java.text.DateFormat
import java.text.SimpleDateFormat
@ -54,7 +56,7 @@ import javax.inject.Singleton
*/
@Singleton
class KeyRequestHandler @Inject constructor(private val context: Context)
class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager)
: GossipingRequestListener,
VerificationService.Listener {
@ -118,9 +120,9 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
}
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?
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),
context.getString(R.string.key_share_request),
dialogText,
@ -210,7 +212,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
denyAllRequests(mappingKey)
})
PopupAlertManager.postVectorAlert(alert)
popupAlertManager.postVectorAlert(alert)
}
private fun denyAllRequests(mappingKey: String) {
@ -250,7 +252,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
&& it.requestId == request.requestId
}
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
PopupAlertManager.cancelAlert(alertMgrUniqueKey)
popupAlertManager.cancelAlert(alertMgrUniqueKey)
alertsToRequests.remove(keyForMap(userId, deviceId))
}
}
@ -261,7 +263,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
if (state == VerificationTxState.Verified) {
// ok it's verified, see if we have key request for that
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
@ -271,7 +273,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
// accept related requests
shareAllSessions(keyForMap(userId, deviceId))
PopupAlertManager.cancelAlert(alertManagerId(userId, deviceId))
popupAlertManager.cancelAlert(alertManagerId(userId, deviceId))
}
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 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.VerificationTransaction
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.core.platform.VectorBaseActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.popup.VerificationVectorAlert
import im.vector.riotx.features.themes.ThemeUtils
import javax.inject.Inject
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.
*/
@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
@ -58,7 +62,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
val name = session?.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId
val alert = PopupAlertManager.VectorAlert(
val alert = VerificationVectorAlert(
uid,
context.getString(R.string.sas_incoming_request_notif_title),
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 :/
activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let {
false.also {
PopupAlertManager.cancelAlert(uid)
popupAlertManager.cancelAlert(uid)
}
} ?: true
} else true
})
.apply {
matrixItem = session?.getUser(tx.otherUserId ?: "")?.toMatrixItem()
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
@ -99,11 +106,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
// 10mn expiration
expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
}
PopupAlertManager.postVectorAlert(alert)
popupAlertManager.postVectorAlert(alert)
}
is VerificationTxState.TerminalTxState -> {
// cancel related notification
PopupAlertManager.cancelAlert(uid)
popupAlertManager.cancelAlert(uid)
}
else -> Unit
}
@ -115,7 +122,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
val name = session?.getUser(pr.otherUserId)?.displayName
?: pr.otherUserId
val alert = PopupAlertManager.VectorAlert(
val alert = VerificationVectorAlert(
uniqueIdForVerificationRequest(pr),
context.getString(R.string.sas_incoming_request_notif_title),
"$name(${pr.otherUserId})",
@ -128,6 +135,9 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
} else true
})
.apply {
matrixItem = session?.getUser(pr.otherUserId)?.toMatrixItem()
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
val roomId = pr.roomId
@ -148,14 +158,14 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
// 5mn expiration
expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L)
}
PopupAlertManager.postVectorAlert(alert)
popupAlertManager.postVectorAlert(alert)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// If an incoming request is readied (by another device?) we should discard the alert
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.notifications.NotificationDrawerManager
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.settings.VectorPreferences
import im.vector.riotx.features.workers.signout.SignOutViewModel
@ -60,6 +62,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
@Inject lateinit var pushManager: PushersManager
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var popupAlertManager: PopupAlertManager
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
override fun onDrawerStateChanged(newState: Int) {
@ -149,8 +152,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
if (crossSigningEnabledOnAccount && myCrossSigningKeys?.isTrusted() == false) {
// We need to ask
sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true
PopupAlertManager.postVectorAlert(
PopupAlertManager.VectorAlert(
popupAlertManager.postVectorAlert(
VerificationVectorAlert(
uid = "completeSecurity",
title = getString(R.string.new_signin),
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.UnreadCounterBadgeView
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.popup.VectorAlert
import im.vector.riotx.features.workers.signout.SignOutViewModel
import kotlinx.android.synthetic.main.fragment_home_detail.*
import timber.log.Timber
@ -53,7 +54,8 @@ private const val INDEX_ROOMS = 2
class HomeDetailFragment @Inject constructor(
val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
private val avatarRenderer: AvatarRenderer
private val avatarRenderer: AvatarRenderer,
private val alertManager: PopupAlertManager
) : VectorBaseFragment(), KeysBackupBanner.Delegate {
private val unreadCounterBadgeViews = arrayListOf<UnreadCounterBadgeView>()
@ -89,19 +91,21 @@ class HomeDetailFragment @Inject constructor(
it.unknownSessions.invoke()?.let { unknownDevices ->
Timber.v("## Detector - ${unknownDevices.size} Unknown sessions")
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()) {
val newest = unknownDevices.first()
val newest = unknownDevices.first().second
val user = unknownDevices.first().first
val uid = "ND_${newest.deviceId}"
PopupAlertManager.cancelAlert(uid)
PopupAlertManager.postVectorAlert(
PopupAlertManager.VectorAlert(
alertManager.cancelAlert(uid)
alertManager.postVectorAlert(
VectorAlert(
uid = uid,
title = getString(R.string.new_session),
description = getString(R.string.new_session_review),
iconId = R.drawable.ic_shield_warning
).apply {
matrixItem = user
colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)

View File

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

View File

@ -22,12 +22,12 @@ import android.os.Bundle
import im.vector.riotx.features.popup.PopupAlertManager
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 onActivityResumed(activity: Activity) {
PopupAlertManager.onNewActivityDisplayed(activity)
popupAlertManager.onNewActivityDisplayed(activity)
}
override fun onActivityStarted(activity: Activity) {

View File

@ -20,20 +20,23 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import android.widget.ImageView
import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener
import dagger.Lazy
import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
import timber.log.Timber
import java.lang.ref.WeakReference
import javax.inject.Inject
import javax.inject.Singleton
/**
* Responsible of displaying important popup alerts on top of the screen.
* 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 currentAlerter: VectorAlert? = null
@ -160,9 +163,19 @@ object PopupAlertManager {
clearLightStatusBar()
alert.weakCurrentActivity = WeakReference(activity)
Alerter.create(activity)
.setTitle(alert.title)
val alerter = if (alert is VerificationVectorAlert) Alerter.create(activity, R.layout.alerter_verification_layout)
else Alerter.create(activity)
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) }
}
}
}
.apply {
if (!animate) {
setEnterAnimation(R.anim.anim_alerter_no_anim)
@ -226,37 +239,4 @@ object PopupAlertManager {
displayNextIfPossible()
}, 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>