Merge room creation events in one summary

This commit is contained in:
Valere 2020-04-08 14:39:57 +02:00
parent 68512e475f
commit 277f35a352
11 changed files with 454 additions and 106 deletions

View File

@ -0,0 +1,81 @@
/*
* 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.home.room.detail.timeline.factory
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem
import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem_
import javax.inject.Inject
class EncryptionItemFactory @Inject constructor(
private val messageItemAttributesFactory: MessageItemAttributesFactory,
private val messageColorProvider: MessageColorProvider,
private val stringProvider: StringProvider,
private val informationDataFactory: MessageInformationDataFactory,
private val avatarSizeProvider: AvatarSizeProvider) {
fun create(event: TimelineEvent,
highlight: Boolean,
callback: TimelineEventController.Callback?): StatusTileTimelineItem? {
val algorithm = event.root.getClearContent().toModel<EncryptionEventContent>()?.algorithm
val informationData = informationDataFactory.create(event, null)
val attributes = messageItemAttributesFactory.create(null, informationData, callback)
val isSafeAlgorithm = algorithm == MXCRYPTO_ALGORITHM_MEGOLM
val title: String
val description: String
val shield: StatusTileTimelineItem.ShieldUIState
if (isSafeAlgorithm) {
title = stringProvider.getString(R.string.encryption_enabled)
description = stringProvider.getString(R.string.encryption_enabled_tile_description)
shield = StatusTileTimelineItem.ShieldUIState.BLACK
} else {
title = stringProvider.getString(R.string.encryption_not_enabled)
description = stringProvider.getString(R.string.encryption_unknown_algorithm_tile_description)
shield = StatusTileTimelineItem.ShieldUIState.RED
}
return StatusTileTimelineItem_()
.attributes(
StatusTileTimelineItem.Attributes(
title = title,
description = description,
shieldUIState = shield,
informationData = informationData,
avatarRenderer = attributes.avatarRenderer,
messageColorProvider = messageColorProvider,
emojiTypeFace = attributes.emojiTypeFace,
itemClickListener = attributes.itemClickListener,
itemLongClickListener = attributes.itemLongClickListener,
reactionPillCallback = attributes.reactionPillCallback,
readReceiptsCallback = attributes.readReceiptsCallback
)
)
.highlighted(highlight)
.leftGuideline(avatarSizeProvider.leftGuideline)
}
}

View File

@ -17,7 +17,10 @@
package im.vector.riotx.features.home.room.detail.timeline.factory
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@ -26,10 +29,11 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.MergedTimelineE
import im.vector.riotx.features.home.room.detail.timeline.helper.canBeMerged
import im.vector.riotx.features.home.room.detail.timeline.helper.isRoomConfiguration
import im.vector.riotx.features.home.room.detail.timeline.helper.prevSameTypeEvents
import im.vector.riotx.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.riotx.features.home.room.detail.timeline.item.BasedMergedItem
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem
import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem_
import javax.inject.Inject
class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: ActiveSessionHolder,
@ -51,68 +55,8 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act
return if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE && event.isRoomConfiguration()) {
// It's the first item before room.create
// Collapse all room configuration events
var prevEvent = if (currentPosition > 0) items[currentPosition -1] else null
var tmpPos = currentPosition -1
val mergedEvents = ArrayList<TimelineEvent>().also { it.add(event) }
while(prevEvent != null && prevEvent.isRoomConfiguration()) {
mergedEvents.add(prevEvent)
tmpPos--
prevEvent = if (tmpPos >= 0) items[tmpPos] else null
}
if (mergedEvents.size > 2) {
var highlighted = false
val mergedData = ArrayList<BasedMergedItem.Data>(mergedEvents.size)
mergedEvents.reversed()
.forEach { mergedEvent ->
if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) {
highlighted = true
}
val senderAvatar = mergedEvent.senderAvatar
val senderName = mergedEvent.getDisambiguatedDisplayName()
val data = BasedMergedItem.Data(
userId = mergedEvent.root.senderId ?: "",
avatarUrl = senderAvatar,
memberName = senderName,
localId = mergedEvent.localId,
eventId = mergedEvent.root.eventId ?: ""
)
mergedData.add(data)
}
val mergedEventIds = mergedEvents.map { it.localId }
// We try to find if one of the item id were used as mergeItemCollapseStates key
// => handle case where paginating from mergeable events and we get more
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds)
} else {
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
val attributes = BasedMergedItem.Attributes(
isCollapsed = isCollapsed,
mergeData = mergedData,
avatarRenderer = avatarRenderer,
onCollapsedStateChanged = {
mergeItemCollapseStates[event.localId] = it
requestModelBuild()
},
readReceiptsCallback = callback
)
MergedHeaderItem_()
.id(mergeId)
.leftGuideline(avatarSizeProvider.leftGuideline)
.highlighted(isCollapsed && highlighted)
.attributes(attributes)
.also {
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
}
} else null
}
else if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) {
buildRoomCreationMergedSummary(currentPosition, items, event, eventIdToHighlight, requestModelBuild, callback)
} else if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) {
null
} else {
val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2)
@ -150,7 +94,7 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
val attributes = BasedMergedItem.Attributes(
val attributes = MergedHeaderItem.Attributes(
isCollapsed = isCollapsed,
mergeData = mergedData,
avatarRenderer = avatarRenderer,
@ -172,6 +116,76 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act
}
}
private fun buildRoomCreationMergedSummary(currentPosition: Int, items: List<TimelineEvent>, event: TimelineEvent, eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedRoomCreationItem_? {
var prevEvent = if (currentPosition > 0) items[currentPosition - 1] else null
var tmpPos = currentPosition - 1
val mergedEvents = ArrayList<TimelineEvent>().also { it.add(event) }
var hasEncryption = false
var encryptionAlgorithm: String? = null
while (prevEvent != null && prevEvent.isRoomConfiguration()) {
if (prevEvent.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION) {
hasEncryption = true
encryptionAlgorithm = prevEvent.root.getClearContent()?.toModel<EncryptionEventContent>()?.algorithm
}
mergedEvents.add(prevEvent)
tmpPos--
prevEvent = if (tmpPos >= 0) items[tmpPos] else null
}
return if (mergedEvents.size > 2) {
var highlighted = false
val mergedData = ArrayList<BasedMergedItem.Data>(mergedEvents.size)
mergedEvents.reversed()
.forEach { mergedEvent ->
if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) {
highlighted = true
}
val senderAvatar = mergedEvent.senderAvatar
val senderName = mergedEvent.getDisambiguatedDisplayName()
val data = BasedMergedItem.Data(
userId = mergedEvent.root.senderId ?: "",
avatarUrl = senderAvatar,
memberName = senderName,
localId = mergedEvent.localId,
eventId = mergedEvent.root.eventId ?: ""
)
mergedData.add(data)
}
val mergedEventIds = mergedEvents.map { it.localId }
// We try to find if one of the item id were used as mergeItemCollapseStates key
// => handle case where paginating from mergeable events and we get more
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds)
} else {
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
val attributes = MergedRoomCreationItem.Attributes(
isCollapsed = isCollapsed,
mergeData = mergedData,
avatarRenderer = avatarRenderer,
onCollapsedStateChanged = {
mergeItemCollapseStates[event.localId] = it
requestModelBuild()
},
hasEncryptionEvent = hasEncryption,
isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM,
readReceiptsCallback = callback
)
MergedRoomCreationItem_()
.id(mergeId)
.leftGuideline(avatarSizeProvider.leftGuideline)
.highlighted(isCollapsed && highlighted)
.attributes(attributes)
.also {
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
}
} else null
}
fun isCollapsed(localId: Long): Boolean {
return collapsedEventIds.contains(localId)
}

View File

@ -29,6 +29,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
private val encryptedItemFactory: EncryptedItemFactory,
private val noticeItemFactory: NoticeItemFactory,
private val defaultItemFactory: DefaultItemFactory,
private val encryptionItemFactory: EncryptionItemFactory,
private val roomCreateItemFactory: RoomCreateItemFactory,
private val verificationConclusionItemFactory: VerificationItemFactory,
private val userPreferencesProvider: UserPreferencesProvider) {
@ -57,8 +58,10 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.REACTION,
EventType.REDACTION,
EventType.STATE_ROOM_ENCRYPTION -> noticeItemFactory.create(event, highlight, callback)
EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback)
EventType.STATE_ROOM_ENCRYPTION -> {
encryptionItemFactory.create(event, highlight, callback)
}
// State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
// Crypto

View File

@ -25,15 +25,17 @@ import im.vector.matrix.android.api.session.room.model.message.MessageRelationCo
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationCancelContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.session.room.VerificationState
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem
import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem_
import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem
import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem_
import javax.inject.Inject
/**
@ -48,6 +50,7 @@ class VerificationItemFactory @Inject constructor(
private val avatarSizeProvider: AvatarSizeProvider,
private val noticeItemFactory: NoticeItemFactory,
private val userPreferencesProvider: UserPreferencesProvider,
private val stringProvider: StringProvider,
private val session: Session
) {
@ -88,12 +91,12 @@ class VerificationItemFactory @Inject constructor(
CancelCode.MismatchedKeys,
CancelCode.MismatchedSas -> {
// We should display these bad conclusions
return VerificationRequestConclusionItem_()
return StatusTileTimelineItem_()
.attributes(
VerificationRequestConclusionItem.Attributes(
toUserId = informationData.senderId,
toUserName = informationData.memberName.toString(),
isPositive = false,
StatusTileTimelineItem.Attributes(
title = stringProvider.getString(R.string.verification_conclusion_warning),
description = "${informationData.memberName.toString()} (${informationData.senderId})",
shieldUIState = StatusTileTimelineItem.ShieldUIState.RED,
informationData = informationData,
avatarRenderer = attributes.avatarRenderer,
messageColorProvider = messageColorProvider,
@ -121,12 +124,12 @@ class VerificationItemFactory @Inject constructor(
// We only display the done sent by the other user, the done send by me is ignored
return ignoredConclusion(event, highlight, callback)
}
return VerificationRequestConclusionItem_()
return StatusTileTimelineItem_()
.attributes(
VerificationRequestConclusionItem.Attributes(
toUserId = informationData.senderId,
toUserName = informationData.memberName.toString(),
isPositive = true,
StatusTileTimelineItem.Attributes(
title = stringProvider.getString(R.string.sas_verified),
description = "${informationData.memberName.toString()} (${informationData.senderId})",
shieldUIState = StatusTileTimelineItem.ShieldUIState.GREEN,
informationData = informationData,
avatarRenderer = attributes.avatarRenderer,
messageColorProvider = messageColorProvider,

View File

@ -16,16 +16,34 @@
package im.vector.riotx.features.home.room.detail.timeline.item
import android.view.View
import android.widget.TextView
import androidx.annotation.IdRes
import com.airbnb.epoxy.EpoxyAttribute
import androidx.core.view.isVisible
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
abstract class BasedMergedItem<H : BasedMergedItem.Holder> : BaseEventItem<H>() {
@EpoxyAttribute
lateinit var attributes: Attributes
abstract val attributes: Attributes
override fun bind(holder: H) {
super.bind(holder)
holder.expandView.setOnClickListener {
attributes.onCollapsedStateChanged(!attributes.isCollapsed)
}
if (attributes.isCollapsed) {
holder.separatorView.visibility = View.GONE
holder.expandView.setText(R.string.merged_events_expand)
} else {
holder.separatorView.visibility = View.VISIBLE
holder.expandView.setText(R.string.merged_events_collapse)
}
// No read receipt for this item
holder.readReceiptsView.isVisible = false
}
protected val distinctMergeData by lazy {
attributes.mergeData.distinctBy { it.userId }
@ -49,15 +67,16 @@ abstract class BasedMergedItem<H : BasedMergedItem.Holder> : BaseEventItem<H>()
fun Data.toMatrixItem() = MatrixItem.UserItem(userId, memberName, avatarUrl)
data class Attributes(
val isCollapsed: Boolean,
val mergeData: List<Data>,
val avatarRenderer: AvatarRenderer,
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
val onCollapsedStateChanged: (Boolean) -> Unit
)
interface Attributes {
val isCollapsed: Boolean
val mergeData: List<Data>
val avatarRenderer: AvatarRenderer
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback?
val onCollapsedStateChanged: (Boolean) -> Unit
}
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
//val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
val expandView by bind<TextView>(R.id.itemMergedExpandTextView)
val separatorView by bind<View>(R.id.itemMergedSeparatorView)
}
}

View File

@ -22,19 +22,22 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.children
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
abstract class MergedHeaderItem : BasedMergedItem<MergedHeaderItem.Holder>() {
override fun getViewType() = STUB_ID
@EpoxyAttribute
override lateinit var attributes: Attributes
override fun bind(holder: Holder) {
super.bind(holder)
holder.expandView.setOnClickListener {
attributes.onCollapsedStateChanged(!attributes.isCollapsed)
}
if (attributes.isCollapsed) {
val summary = holder.expandView.resources.getQuantityString(R.plurals.membership_changes, attributes.mergeData.size, attributes.mergeData.size)
holder.summaryView.text = summary
@ -49,26 +52,28 @@ abstract class MergedHeaderItem : BasedMergedItem<MergedHeaderItem.Holder>() {
view.visibility = View.GONE
}
}
holder.separatorView.visibility = View.GONE
holder.expandView.setText(R.string.merged_events_expand)
} else {
holder.avatarListView.visibility = View.INVISIBLE
holder.summaryView.visibility = View.GONE
holder.separatorView.visibility = View.VISIBLE
holder.expandView.setText(R.string.merged_events_collapse)
}
// No read receipt for this item
holder.readReceiptsView.isVisible = false
}
class Holder : BasedMergedItem.Holder(STUB_ID) {
val expandView by bind<TextView>(R.id.itemMergedExpandTextView)
val summaryView by bind<TextView>(R.id.itemMergedSummaryTextView)
val separatorView by bind<View>(R.id.itemMergedSeparatorView)
val avatarListView by bind<ViewGroup>(R.id.itemMergedAvatarListView)
}
companion object {
private const val STUB_ID = R.id.messageContentMergedHeaderStub
}
data class Attributes(
override val isCollapsed: Boolean,
override val mergeData: List<Data>,
override val avatarRenderer: AvatarRenderer,
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
override val onCollapsedStateChanged: (Boolean) -> Unit
) : BasedMergedItem.Attributes
}

View File

@ -0,0 +1,118 @@
/*
* 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.home.room.detail.timeline.item
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.marginLeft
import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import org.w3c.dom.Text
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.Holder>() {
@EpoxyAttribute
override lateinit var attributes: Attributes
override fun getViewType() = STUB_ID
override fun bind(holder: Holder) {
super.bind(holder)
if (attributes.isCollapsed) {
val data = distinctMergeData.firstOrNull()
val summary = holder.expandView.resources.getString(R.string.room_created_summary_item,
data?.memberName ?: data?.userId ?: "")
holder.summaryView.text = summary
holder.summaryView.visibility = View.VISIBLE
holder.avatarView.visibility = View.VISIBLE
if (data != null) {
holder.avatarView.visibility = View.VISIBLE
attributes.avatarRenderer.render(data.toMatrixItem(), holder.avatarView)
} else {
holder.avatarView.visibility = View.GONE
}
if (attributes.hasEncryptionEvent) {
holder.encryptionTile.isVisible = true
holder.encryptionTile.updateLayoutParams<RelativeLayout.LayoutParams> {
this.marginEnd = leftGuideline
}
if (attributes.isEncryptionAlgorithmSecure) {
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled)
holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_enabled_tile_description)
holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black),
null, null, null
)
} else {
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled)
holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description)
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning),
null, null, null
)
}
} else {
holder.encryptionTile.isVisible = false
}
} else {
holder.avatarView.visibility = View.INVISIBLE
holder.summaryView.visibility = View.GONE
holder.encryptionTile.isGone = true
}
// No read receipt for this item
holder.readReceiptsView.isVisible = false
}
class Holder : BasedMergedItem.Holder(STUB_ID) {
val summaryView by bind<TextView>(R.id.itemNoticeTextView)
val avatarView by bind<ImageView>(R.id.itemNoticeAvatarView)
val encryptionTile by bind<ViewGroup>(R.id.creationEncryptionTile)
val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
val e2eTitleDescriptionView by bind<TextView>(R.id.itemVerificationDoneDetailTextView)
}
companion object {
private const val STUB_ID = R.id.messageContentMergedCreationStub
}
data class Attributes(
override val isCollapsed: Boolean,
override val mergeData: List<Data>,
override val avatarRenderer: AvatarRenderer,
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
override val onCollapsedStateChanged: (Boolean) -> Unit,
val hasEncryptionEvent : Boolean,
val isEncryptionAlgorithmSecure: Boolean
) : BasedMergedItem.Attributes
}

View File

@ -31,7 +31,7 @@ import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<VerificationRequestConclusionItem.Holder>() {
abstract class StatusTileTimelineItem : AbsBaseMessageItem<StatusTileTimelineItem.Holder>() {
override val baseAttributes: AbsBaseMessageItem.Attributes
get() = attributes
@ -47,11 +47,16 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<Verificati
holder.endGuideline.updateLayoutParams<RelativeLayout.LayoutParams> {
this.marginEnd = leftGuideline
}
val title = if (attributes.isPositive) R.string.sas_verified else R.string.verification_conclusion_warning
holder.titleView.text = holder.view.context.getString(title)
holder.descriptionView.text = "${attributes.informationData.memberName} (${attributes.informationData.senderId})"
val startDrawable = if (attributes.isPositive) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning
holder.titleView.text = attributes.title
holder.descriptionView.text = attributes.description
val startDrawable = when (attributes.shieldUIState) {
ShieldUIState.GREEN -> R.drawable.ic_shield_trusted
ShieldUIState.BLACK -> R.drawable.ic_shield_black
ShieldUIState.RED -> R.drawable.ic_shield_warning
}
holder.titleView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, startDrawable),
null, null, null
@ -75,9 +80,9 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<Verificati
* This class holds all the common attributes for timeline items.
*/
data class Attributes(
val toUserId: String,
val toUserName: String,
val isPositive: Boolean,
val shieldUIState: ShieldUIState,
val title: CharSequence,
val description: CharSequence,
override val informationData: MessageInformationData,
override val avatarRenderer: AvatarRenderer,
override val messageColorProvider: MessageColorProvider,
@ -87,4 +92,10 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<Verificati
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
val emojiTypeFace: Typeface? = null
) : AbsBaseMessageItem.Attributes
enum class ShieldUIState {
BLACK,
RED,
GREEN
}
}

View File

@ -54,6 +54,13 @@
tools:layout_marginTop="160dp"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageContentMergedCreationStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_merged_room_creation_stub"
tools:layout_marginTop="160dp"
tools:visibility="visible" />
</FrameLayout>
<im.vector.riotx.core.ui.views.ReadReceiptsView

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/creationEncryptionTile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_gravity="center"
android:layout_marginTop="2dp"
android:layout_marginEnd="52dp"
android:layout_marginBottom="2dp"
android:background="@drawable/rounded_rect_shape_8"
android:padding="8dp">
<include layout="@layout/item_timeline_event_verification_done_stub" />
</FrameLayout>
<RelativeLayout
android:id="@+id/mergedSumContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/creationEncryptionTile">
<ImageView
android:id="@+id/itemNoticeAvatarView"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/itemNoticeTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_gravity="top"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:layout_toStartOf="@id/itemMergedExpandTextView"
android:layout_toEndOf="@id/itemNoticeAvatarView"
android:textColor="?riotx_text_secondary"
android:textSize="14sp"
android:textStyle="italic"
tools:text="@string/room_created_summary_item" />
<TextView
android:id="@+id/itemMergedExpandTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginTop="2dp"
android:paddingLeft="8dp"
android:paddingTop="4dp"
android:paddingRight="8dp"
android:paddingBottom="4dp"
android:text="@string/merged_events_expand"
android:textColor="?attr/colorAccent"
android:textSize="14sp"
android:textStyle="italic" />
</RelativeLayout>
<View
android:id="@+id/itemMergedSeparatorView"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/mergedSumContainer"
android:layout_marginTop="4dp"
android:background="?attr/riotx_header_panel_background" />
</RelativeLayout>

View File

@ -87,6 +87,13 @@
<string name="bootstrap_skip_text">Setting a Message Password lets you secure &amp; unlock encrypted messages and trust.\n\nIf you dont want to set a Message Password, generate a Message Key instead.</string>
<string name="bootstrap_skip_text_no_gen_key">Setting a Message Password lets you secure &amp; unlock encrypted messages and trust.</string>
<string name="encryption_enabled">Encryption enabled</string>
<string name="encryption_enabled_tile_description">Messages in this room are end-to-end encrypted. Learn more &amp; verify users in their profile.</string>
<string name="encryption_not_enabled">Encryption not enabled</string>
<string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string>
<string name="room_created_summary_item">%s created and configured the room.</string>
<!-- END Strings added by Valere -->