mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Merge room creation events in one summary
This commit is contained in:
parent
68512e475f
commit
277f35a352
11 changed files with 454 additions and 106 deletions
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -87,6 +87,13 @@
|
|||
<string name="bootstrap_skip_text">Setting a Message Password lets you secure & unlock encrypted messages and trust.\n\nIf you don’t 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 & 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 & 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 -->
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue