Decorate timeline with e2e warning
This commit is contained in:
parent
57779c99c2
commit
20e5ebc88b
@ -46,6 +46,7 @@ data class RoomSummary constructor(
|
||||
val readMarkerId: String? = null,
|
||||
val userDrafts: List<UserDraft> = emptyList(),
|
||||
val isEncrypted: Boolean,
|
||||
val encryptionEventTs: Long?,
|
||||
val inviterId: String? = null,
|
||||
val typingRoomMemberIds: List<String> = emptyList(),
|
||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
|
@ -53,6 +53,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
encryptionEventTs = roomSummaryEntity.encryptionEventTs,
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||
|
@ -48,6 +48,7 @@ internal open class RoomSummaryEntity(
|
||||
// this is required for querying
|
||||
var flatAliases: String = "",
|
||||
var isEncrypted: Boolean = false,
|
||||
var encryptionEventTs: Long? = 0,
|
||||
var typingUserIds: RealmList<String> = RealmList(),
|
||||
var roomEncryptionTrustLevelStr: String? = null,
|
||||
var inviterId: String? = null
|
||||
|
@ -136,6 +136,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
roomSummaryEntity.aliases.addAll(roomAliases)
|
||||
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
||||
roomSummaryEntity.isEncrypted = encryptionEvent != null
|
||||
roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs ?: System.currentTimeMillis()
|
||||
roomSummaryEntity.typingUserIds.clear()
|
||||
roomSummaryEntity.typingUserIds.addAll(ephemeralResult?.typingUserIds.orEmpty())
|
||||
|
||||
|
@ -26,7 +26,6 @@ import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.AutoTransition
|
||||
@ -172,7 +171,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
|
||||
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
|
||||
else -> R.drawable.ic_shield_black
|
||||
}
|
||||
composerShieldImageView.setImageDrawable(ContextCompat.getDrawable(context, shieldRes))
|
||||
composerShieldImageView.setImageResource(shieldRes)
|
||||
} else {
|
||||
composerEditText.setHint(R.string.room_message_placeholder)
|
||||
composerShieldImageView.isVisible = false
|
||||
|
@ -29,6 +29,7 @@ import im.vector.riotx.core.epoxy.dividerItem
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.E2EDecoration
|
||||
import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||
import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
|
||||
import javax.inject.Inject
|
||||
@ -72,6 +73,29 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
when (state.informationData.e2eDecoration) {
|
||||
E2EDecoration.WARN_IN_CLEAR -> {
|
||||
bottomSheetSendStateItem {
|
||||
id("e2e_clear")
|
||||
showProgress(false)
|
||||
text(stringProvider.getString(R.string.unencrytped))
|
||||
drawableStart(R.drawable.ic_shield_warning_small)
|
||||
}
|
||||
}
|
||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
|
||||
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
|
||||
bottomSheetSendStateItem {
|
||||
id("e2e_unverified")
|
||||
showProgress(false)
|
||||
text(stringProvider.getString(R.string.encrypted_unverified))
|
||||
drawableStart(R.drawable.ic_shield_warning_small)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
// Quick reactions
|
||||
if (state.canReact() && state.quickStates is Success) {
|
||||
// Separator
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
package im.vector.riotx.features.home.room.detail.timeline.helper
|
||||
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
@ -26,11 +27,13 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.utils.getColorFromUserId
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.E2EDecoration
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.PollResponseData
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
||||
@ -72,6 +75,52 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId))
|
||||
}
|
||||
|
||||
val room = event.root.roomId?.let { session.getRoom(it) }
|
||||
val e2eDecoration: E2EDecoration
|
||||
val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true
|
||||
if (room?.isEncrypted() == true && isUserVerified) {
|
||||
val ts = room.roomSummary()?.encryptionEventTs ?: 0
|
||||
val eventTs = event.root.originServerTs ?: 0
|
||||
if (event.isEncrypted()) {
|
||||
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
|
||||
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
|
||||
e2eDecoration = E2EDecoration.NONE
|
||||
} else {
|
||||
val sendingDevice = event.root.content
|
||||
.toModel<EncryptedEventContent>()
|
||||
?.deviceId
|
||||
?.let { deviceId ->
|
||||
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
|
||||
}
|
||||
e2eDecoration = when {
|
||||
sendingDevice == null -> {
|
||||
// For now do not decorate this with warning
|
||||
// maybe it's a deleted session
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
sendingDevice.trustLevel == null -> {
|
||||
E2EDecoration.WARN_SENT_BY_UNKNOWN
|
||||
}
|
||||
sendingDevice.trustLevel?.isVerified().orFalse() -> {
|
||||
E2EDecoration.NONE
|
||||
}
|
||||
else -> {
|
||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (EventType.isStateEvent(event.root.type)) {
|
||||
// Do not warn for state event, they are always in clear
|
||||
e2eDecoration = E2EDecoration.NONE
|
||||
} else {
|
||||
// If event is in clear after the room enabled encryption we should warn
|
||||
e2eDecoration = if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e2eDecoration = E2EDecoration.NONE
|
||||
}
|
||||
return MessageInformationData(
|
||||
eventId = eventId,
|
||||
senderId = event.root.senderId ?: "",
|
||||
@ -111,7 +160,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||
?: VerificationState.REQUEST
|
||||
ReferencesInfoData(verificationState)
|
||||
},
|
||||
sentByMe = event.root.senderId == session.myUserId
|
||||
sentByMe = event.root.senderId == session.myUserId,
|
||||
e2eDecoration = e2eDecoration
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.riotx.R
|
||||
@ -92,6 +93,18 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
||||
holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||
}
|
||||
|
||||
when (baseAttributes.informationData.e2eDecoration) {
|
||||
E2EDecoration.NONE -> {
|
||||
holder.e2EDecorationView.isVisible = false
|
||||
}
|
||||
E2EDecoration.WARN_IN_CLEAR,
|
||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
|
||||
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
|
||||
holder.e2EDecorationView.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning))
|
||||
holder.e2EDecorationView.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
holder.view.setOnClickListener(baseAttributes.itemClickListener)
|
||||
holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||
}
|
||||
@ -110,6 +123,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
|
||||
val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
|
||||
val e2EDecorationView by bind<ImageView>(R.id.messageE2EDecoration)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +40,8 @@ data class MessageInformationData(
|
||||
val hasPendingEdits: Boolean = false,
|
||||
val readReceipts: List<ReadReceiptData> = emptyList(),
|
||||
val referencesInfoData: ReferencesInfoData? = null,
|
||||
val sentByMe : Boolean
|
||||
val sentByMe : Boolean,
|
||||
val e2eDecoration: E2EDecoration = E2EDecoration.NONE
|
||||
) : Parcelable {
|
||||
|
||||
val matrixItem: MatrixItem
|
||||
@ -75,4 +76,11 @@ data class PollResponseData(
|
||||
val isClosed: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
enum class E2EDecoration {
|
||||
NONE,
|
||||
WARN_IN_CLEAR,
|
||||
WARN_SENT_BY_UNVERIFIED,
|
||||
WARN_SENT_BY_UNKNOWN
|
||||
}
|
||||
|
||||
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||
|
@ -19,6 +19,8 @@ package im.vector.riotx.features.home.room.detail.timeline.item
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
@ -45,6 +47,18 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
|
||||
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
|
||||
holder.avatarImageView.onClick(attributes.avatarClickListener)
|
||||
|
||||
when (attributes.informationData.e2eDecoration) {
|
||||
E2EDecoration.NONE -> {
|
||||
holder.e2EDecorationView.isVisible = false
|
||||
}
|
||||
E2EDecoration.WARN_IN_CLEAR,
|
||||
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
|
||||
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
|
||||
holder.e2EDecorationView.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning))
|
||||
holder.e2EDecorationView.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEventIds(): List<String> {
|
||||
@ -56,6 +70,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
class Holder : BaseHolder(STUB_ID) {
|
||||
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
||||
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
||||
val e2EDecorationView by bind<ImageView>(R.id.messageE2EDecoration)
|
||||
}
|
||||
|
||||
data class Attributes(
|
||||
|
20
vector/src/main/res/drawable/ic_shield_warning_small.xml
Normal file
20
vector/src/main/res/drawable/ic_shield_warning_small.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="m12,21s9,-3.8 9,-9.5v-6.65l-9,-2.85 -9,2.85v6.65c0,5.7 9,9.5 9,9.5z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#ff4b55"
|
||||
android:strokeColor="#fff"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12.05,5.5L12.05,5.5A1.25,1.25 0,0 1,13.3 6.75L13.3,12.25A1.25,1.25 0,0 1,12.05 13.5L12.05,13.5A1.25,1.25 0,0 1,10.8 12.25L10.8,6.75A1.25,1.25 0,0 1,12.05 5.5z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M12.05,15L12.05,15A1.25,1.25 0,0 1,13.3 16.25L13.3,16.25A1.25,1.25 0,0 1,12.05 17.5L12.05,17.5A1.25,1.25 0,0 1,10.8 16.25L10.8,16.25A1.25,1.25 0,0 1,12.05 15z"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
@ -62,6 +62,18 @@
|
||||
android:layout_height="0dp"
|
||||
tools:layout_marginStart="52dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageE2EDecoration"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignTop="@id/viewStubContainer"
|
||||
android:layout_toStartOf="@id/viewStubContainer"
|
||||
android:visibility="gone"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_marginTop="7dp"
|
||||
tools:src="@drawable/ic_shield_warning"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/viewStubContainer"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -63,6 +63,18 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageE2EDecoration"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_toStartOf="@id/viewStubContainer"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/ic_shield_warning"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||
android:id="@+id/readReceiptsView"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -54,6 +54,18 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageE2EDecoration"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_alignTop="@id/viewStubContainer"
|
||||
android:layout_toStartOf="@id/viewStubContainer"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/ic_shield_warning"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageFailToSendIndicator"
|
||||
android:layout_width="14dp"
|
||||
|
@ -19,6 +19,9 @@
|
||||
<string name="enter_secret_storage_input_key">Select your Recovery Key, or input it manually by typing it or pasting from your clipboard</string>
|
||||
<string name="keys_backup_recovery_key_error_decrypt">Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key.</string>
|
||||
<string name="failed_to_access_secure_storage">Failed to access secure storage</string>
|
||||
|
||||
<string name="unencrytped">Unencrypted</string>
|
||||
<string name="encrypted_unverified">Encrypted by an unverified device</string>
|
||||
<!-- END Strings added by Valere -->
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user