mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-01 19:56:47 +01:00
Timeline: refact epoxy attributes
This commit is contained in:
parent
51a4c93676
commit
b8ebe3570b
@ -32,11 +32,13 @@ import im.vector.riotx.core.epoxy.LoadingItem_
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.factory.TimelineItemFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.*
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_
|
||||
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.MessageInformationData
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||
import im.vector.riotx.features.media.ImageContentRenderer
|
||||
@ -47,7 +49,7 @@ import javax.inject.Inject
|
||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||
private val timelineItemFactory: TimelineItemFactory,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val mergedHeaderItemFactory: MergedHeaderItemFactory,
|
||||
@TimelineEventControllerHandler
|
||||
private val backgroundHandler: Handler
|
||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
||||
@ -89,8 +91,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
fun onUrlLongClicked(url: String): Boolean
|
||||
}
|
||||
|
||||
private val collapsedEventIds = linkedSetOf<Long>()
|
||||
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
||||
private val modelCache = arrayListOf<CacheItemData?>()
|
||||
|
||||
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
||||
@ -231,7 +231,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
}
|
||||
return modelCache
|
||||
.map {
|
||||
val eventModel = if (it == null || collapsedEventIds.contains(it.localId)) {
|
||||
val eventModel = if (it == null || mergedHeaderItemFactory.isCollapsed(it.localId)) {
|
||||
null
|
||||
} else {
|
||||
it.eventModel
|
||||
@ -255,7 +255,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
it.id(event.localId)
|
||||
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
|
||||
}
|
||||
val mergedHeaderModel = buildMergedHeaderItem(event, nextEvent, items, addDaySeparator, currentPosition)
|
||||
val mergedHeaderModel = mergedHeaderItemFactory.create(event, nextEvent, items, addDaySeparator, currentPosition, callback){
|
||||
requestModelBuild()
|
||||
}
|
||||
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date)
|
||||
|
||||
return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem)
|
||||
@ -270,53 +272,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Phase 3 Handle the case where the eventId we have to highlight is merged
|
||||
private fun buildMergedHeaderItem(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
items: List<TimelineEvent>,
|
||||
addDaySeparator: Boolean,
|
||||
currentPosition: Int): MergedHeaderItem? {
|
||||
return if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) {
|
||||
null
|
||||
} else {
|
||||
val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2)
|
||||
if (prevSameTypeEvents.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed()
|
||||
val mergedData = mergedEvents.map { mergedEvent ->
|
||||
val senderAvatar = mergedEvent.senderAvatar()
|
||||
val senderName = mergedEvent.senderName()
|
||||
MergedHeaderItem.Data(
|
||||
userId = mergedEvent.root.senderId ?: "",
|
||||
avatarUrl = senderAvatar,
|
||||
memberName = senderName ?: "",
|
||||
eventId = mergedEvent.localId
|
||||
)
|
||||
}
|
||||
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() }
|
||||
MergedHeaderItem(isCollapsed, mergeId, mergedData, avatarRenderer) {
|
||||
mergeItemCollapseStates[event.localId] = it
|
||||
requestModelBuild()
|
||||
}.also {
|
||||
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if added
|
||||
*/
|
||||
|
@ -17,11 +17,12 @@
|
||||
package im.vector.riotx.features.home.room.detail.timeline.factory
|
||||
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultItemFactory @Inject constructor(){
|
||||
class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider) {
|
||||
|
||||
fun create(event: TimelineEvent, highlight: Boolean, exception: Exception? = null): DefaultItem? {
|
||||
val text = if (exception == null) {
|
||||
@ -30,8 +31,10 @@ class DefaultItemFactory @Inject constructor(){
|
||||
"an exception occurred when rendering the event ${event.root.eventId}"
|
||||
}
|
||||
return DefaultItem_()
|
||||
.text(text)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.highlighted(highlight)
|
||||
.text(text)
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -23,7 +23,6 @@ import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
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.MessageTextItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||
@ -55,7 +54,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
||||
}
|
||||
|
||||
val message = stringProvider.getString(R.string.encrypted_message).takeIf { cryptoError == null }
|
||||
?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
||||
?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
||||
val spannableStr = span(message) {
|
||||
textStyle = "italic"
|
||||
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||
@ -66,11 +65,10 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
||||
val informationData = messageInformationDataFactory.create(event, nextEvent)
|
||||
val attributes = attributesFactory.create(null, informationData, callback)
|
||||
return MessageTextItem_()
|
||||
.highlighted(highlight)
|
||||
.attributes(attributes)
|
||||
.message(spannableStr)
|
||||
.highlighted(highlight)
|
||||
.urlClickCallback(callback)
|
||||
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2019 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.room.timeline.TimelineEvent
|
||||
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.helper.AvatarSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.canBeMerged
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.prevSameTypeEvents
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem_
|
||||
import javax.inject.Inject
|
||||
|
||||
class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: AvatarRenderer,
|
||||
private val avatarSizeProvider: AvatarSizeProvider) {
|
||||
|
||||
private val collapsedEventIds = linkedSetOf<Long>()
|
||||
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
items: List<TimelineEvent>,
|
||||
addDaySeparator: Boolean,
|
||||
currentPosition: Int,
|
||||
callback: TimelineEventController.Callback?,
|
||||
requestModelBuild: () -> Unit)
|
||||
: MergedHeaderItem? {
|
||||
|
||||
return if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) {
|
||||
null
|
||||
} else {
|
||||
val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2)
|
||||
if (prevSameTypeEvents.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed()
|
||||
val mergedData = mergedEvents.map { mergedEvent ->
|
||||
val senderAvatar = mergedEvent.senderAvatar()
|
||||
val senderName = mergedEvent.senderName()
|
||||
MergedHeaderItem.Data(
|
||||
userId = mergedEvent.root.senderId ?: "",
|
||||
avatarUrl = senderAvatar,
|
||||
memberName = senderName ?: "",
|
||||
eventId = mergedEvent.localId
|
||||
)
|
||||
}
|
||||
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 = MergedHeaderItem.Attributes(
|
||||
isCollapsed = isCollapsed,
|
||||
mergeData = mergedData,
|
||||
avatarRenderer = avatarRenderer,
|
||||
onCollapsedStateChanged = {
|
||||
mergeItemCollapseStates[event.localId] = it
|
||||
requestModelBuild()
|
||||
}
|
||||
)
|
||||
MergedHeaderItem_()
|
||||
.id(mergeId)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.also {
|
||||
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isCollapsed(localId: Long): Boolean {
|
||||
return collapsedEventIds.contains(localId)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -41,16 +41,15 @@ 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.internal.crypto.attachments.toElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.riotx.EmojiCompatFontProvider
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.linkify.VectorLinkify
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||
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.helper.ContentUploadStateTrackerBinder
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.*
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||
@ -70,7 +69,8 @@ class MessageItemFactory @Inject constructor(
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||
private val noticeItemFactory: NoticeItemFactory) {
|
||||
private val noticeItemFactory: NoticeItemFactory,
|
||||
private val avatarSizeProvider: AvatarSizeProvider) {
|
||||
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
@ -90,11 +90,11 @@ class MessageItemFactory @Inject constructor(
|
||||
|
||||
val messageContent: MessageContent =
|
||||
event.getLastMessageContent()
|
||||
?: //Malformed content, we should echo something on screen
|
||||
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||
?: //Malformed content, we should echo something on screen
|
||||
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||
|
||||
if (messageContent.relatesTo?.type == RelationType.REPLACE
|
||||
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||
) {
|
||||
// This is an edit event, we should it when debugging as a notice event
|
||||
return noticeItemFactory.create(event, highlight, callback)
|
||||
@ -105,15 +105,15 @@ class MessageItemFactory @Inject constructor(
|
||||
// val ev = all.toModel<Event>()
|
||||
return when (messageContent) {
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes)
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes)
|
||||
is MessageTextContent -> buildTextMessageItem(messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes)
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
@ -131,6 +131,7 @@ class MessageItemFactory @Inject constructor(
|
||||
return MessageFileItem_()
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.filename(messageContent.body)
|
||||
.iconRes(R.drawable.filetype_audio)
|
||||
.clickListener(
|
||||
@ -146,6 +147,7 @@ class MessageItemFactory @Inject constructor(
|
||||
attributes: AbsMessageItem.Attributes): MessageFileItem? {
|
||||
return MessageFileItem_()
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.highlighted(highlight)
|
||||
.filename(messageContent.body)
|
||||
.iconRes(R.drawable.filetype_attachment)
|
||||
@ -158,6 +160,7 @@ class MessageItemFactory @Inject constructor(
|
||||
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? {
|
||||
val text = "${messageContent.type} message events are not yet handled"
|
||||
return DefaultItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.text(text)
|
||||
.highlighted(highlight)
|
||||
}
|
||||
@ -182,6 +185,7 @@ class MessageItemFactory @Inject constructor(
|
||||
)
|
||||
return MessageImageVideoItem_()
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.playable(messageContent.info?.mimeType == "image/gif")
|
||||
@ -203,7 +207,7 @@ class MessageItemFactory @Inject constructor(
|
||||
val thumbnailData = ImageContentRenderer.Data(
|
||||
filename = messageContent.body,
|
||||
url = messageContent.videoInfo?.thumbnailFile?.url
|
||||
?: messageContent.videoInfo?.thumbnailUrl,
|
||||
?: messageContent.videoInfo?.thumbnailUrl,
|
||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||
height = messageContent.videoInfo?.height,
|
||||
maxHeight = maxHeight,
|
||||
@ -220,6 +224,7 @@ class MessageItemFactory @Inject constructor(
|
||||
)
|
||||
|
||||
return MessageImageVideoItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
@ -250,6 +255,7 @@ class MessageItemFactory @Inject constructor(
|
||||
message(linkifiedBody)
|
||||
}
|
||||
}
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.urlClickCallback(callback)
|
||||
@ -282,9 +288,9 @@ class MessageItemFactory @Inject constructor(
|
||||
//nop
|
||||
}
|
||||
},
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
|
||||
@ -303,6 +309,7 @@ class MessageItemFactory @Inject constructor(
|
||||
linkifyBody(formattedBody, callback)
|
||||
}
|
||||
return MessageTextItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.message(message)
|
||||
.highlighted(highlight)
|
||||
@ -328,6 +335,7 @@ class MessageItemFactory @Inject constructor(
|
||||
message(message)
|
||||
}
|
||||
}
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.urlClickCallback(callback)
|
||||
@ -336,6 +344,7 @@ class MessageItemFactory @Inject constructor(
|
||||
private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
|
||||
highlight: Boolean): RedactedMessageItem? {
|
||||
return RedactedMessageItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
}
|
||||
|
@ -20,28 +20,34 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
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.format.NoticeEventFormatter
|
||||
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.item.NoticeItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
|
||||
import javax.inject.Inject
|
||||
|
||||
class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val informationDataFactory: MessageInformationDataFactory) {
|
||||
class NoticeItemFactory @Inject constructor(
|
||||
private val eventFormatter: NoticeEventFormatter,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val informationDataFactory: MessageInformationDataFactory,
|
||||
private val avatarSizeProvider: AvatarSizeProvider
|
||||
) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?): NoticeItem? {
|
||||
val formattedText = eventFormatter.format(event) ?: return null
|
||||
val informationData = informationDataFactory.create(event, null)
|
||||
|
||||
val attributes = NoticeItem.Attributes(
|
||||
avatarRenderer = avatarRenderer,
|
||||
informationData = informationData,
|
||||
noticeText = formattedText,
|
||||
callback = callback
|
||||
)
|
||||
return NoticeItem_()
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.noticeText(formattedText)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.highlighted(highlight)
|
||||
.informationData(informationData)
|
||||
.baseCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.attributes(attributes)
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 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.helper
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
||||
import javax.inject.Inject
|
||||
|
||||
class AvatarSizeProvider @Inject constructor(private val context: AppCompatActivity) {
|
||||
|
||||
private val avatarStyle = AvatarStyle.SMALL
|
||||
|
||||
val leftGuideline: Int by lazy {
|
||||
dpToPx(avatarStyle.avatarSizeDP + 8, context)
|
||||
}
|
||||
|
||||
val avatarSize: Int by lazy {
|
||||
dpToPx(avatarStyle.avatarSizeDP, context)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
enum class AvatarStyle(val avatarSizeDP: Int) {
|
||||
BIG(50),
|
||||
MEDIUM(40),
|
||||
SMALL(30),
|
||||
NONE(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -31,10 +31,15 @@ import javax.inject.Inject
|
||||
class MessageItemAttributesFactory @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val emojiCompatFontProvider: EmojiCompatFontProvider) {
|
||||
|
||||
fun create(messageContent: MessageContent?, informationData: MessageInformationData, callback: TimelineEventController.Callback?): AbsMessageItem.Attributes {
|
||||
fun create(messageContent: MessageContent?,
|
||||
informationData: MessageInformationData,
|
||||
callback: TimelineEventController.Callback?): AbsMessageItem.Attributes {
|
||||
|
||||
return AbsMessageItem.Attributes(
|
||||
avatarSize = avatarSizeProvider.avatarSize,
|
||||
informationData = informationData,
|
||||
avatarRenderer = avatarRenderer,
|
||||
colorProvider = colorProvider,
|
||||
|
@ -81,9 +81,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
super.bind(holder)
|
||||
if (attributes.informationData.showInformation) {
|
||||
holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
|
||||
val size = dpToPx(avatarStyle.avatarSizeDP, holder.view.context)
|
||||
height = size
|
||||
width = size
|
||||
height = attributes.avatarSize
|
||||
width = attributes.avatarSize
|
||||
}
|
||||
holder.avatarImageView.visibility = View.VISIBLE
|
||||
holder.avatarImageView.setOnClickListener(_avatarClickListener)
|
||||
@ -162,10 +161,21 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
failureIndicator?.isVisible = attributes.informationData.sendState.hasFailed()
|
||||
}
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||
val readMarkerView by bind<ReadMarkerView>(R.id.readMarkerView)
|
||||
var reactionWrapper: ViewGroup? = null
|
||||
var reactionFlowHelper: Flow? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* This class holds all the common attributes for message items.
|
||||
* This class holds all the common attributes for timeline items.
|
||||
*/
|
||||
data class Attributes(
|
||||
val avatarSize: Int,
|
||||
val informationData: MessageInformationData,
|
||||
val avatarRenderer: AvatarRenderer,
|
||||
val colorProvider: ColorProvider,
|
||||
@ -178,14 +188,4 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
val emojiTypeFace: Typeface? = null
|
||||
)
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||
val readMarkerView by bind<ReadMarkerView>(R.id.readMarkerView)
|
||||
var reactionWrapper: ViewGroup? = null
|
||||
var reactionFlowHelper: Flow? = null
|
||||
}
|
||||
|
||||
}
|
@ -24,28 +24,23 @@ import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.platform.CheckableView
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import org.w3c.dom.Attr
|
||||
|
||||
/**
|
||||
* Children must override getViewType()
|
||||
*/
|
||||
abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>() {
|
||||
|
||||
var avatarStyle: AvatarStyle = AvatarStyle.SMALL
|
||||
|
||||
// To use for instance when opening a permalink with an eventId
|
||||
@EpoxyAttribute
|
||||
var highlighted: Boolean = false
|
||||
@EpoxyAttribute
|
||||
open var leftGuideline: Int = 0
|
||||
|
||||
override fun bind(holder: H) {
|
||||
super.bind(holder)
|
||||
//optimize?
|
||||
val px = dpToPx(avatarStyle.avatarSizeDP + 8, holder.view.context)
|
||||
holder.leftGuideline.setGuidelineBegin(px)
|
||||
|
||||
holder.leftGuideline.setGuidelineBegin(leftGuideline)
|
||||
holder.checkableBackground.isChecked = highlighted
|
||||
}
|
||||
|
||||
@ -63,13 +58,4 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
enum class AvatarStyle(val avatarSizeDP: Int) {
|
||||
BIG(50),
|
||||
MEDIUM(40),
|
||||
SMALL(30),
|
||||
NONE(0)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
|
@ -21,29 +21,20 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.children
|
||||
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 : BaseEventItem<MergedHeaderItem.Holder>() {
|
||||
|
||||
data class MergedHeaderItem(private val isCollapsed: Boolean,
|
||||
private val mergeId: String,
|
||||
private val mergeData: List<Data>,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val onCollapsedStateChanged: (Boolean) -> Unit
|
||||
) : BaseEventItem<MergedHeaderItem.Holder>() {
|
||||
@EpoxyAttribute
|
||||
lateinit var attributes: Attributes
|
||||
|
||||
private val distinctMergeData = mergeData.distinctBy { it.userId }
|
||||
|
||||
init {
|
||||
id(mergeId)
|
||||
}
|
||||
|
||||
override fun getDefaultLayout(): Int {
|
||||
return R.layout.item_timeline_event_base_noinfo
|
||||
}
|
||||
|
||||
override fun createNewHolder(): Holder {
|
||||
return Holder()
|
||||
private val distinctMergeData by lazy {
|
||||
attributes.mergeData.distinctBy { it.userId }
|
||||
}
|
||||
|
||||
override fun getViewType() = STUB_ID
|
||||
@ -51,10 +42,10 @@ data class MergedHeaderItem(private val isCollapsed: Boolean,
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.expandView.setOnClickListener {
|
||||
onCollapsedStateChanged(!isCollapsed)
|
||||
attributes.onCollapsedStateChanged(!attributes.isCollapsed)
|
||||
}
|
||||
if (isCollapsed) {
|
||||
val summary = holder.expandView.resources.getQuantityString(R.plurals.membership_changes, mergeData.size, mergeData.size)
|
||||
if (attributes.isCollapsed) {
|
||||
val summary = holder.expandView.resources.getQuantityString(R.plurals.membership_changes, attributes.mergeData.size, attributes.mergeData.size)
|
||||
holder.summaryView.text = summary
|
||||
holder.summaryView.visibility = View.VISIBLE
|
||||
holder.avatarListView.visibility = View.VISIBLE
|
||||
@ -62,7 +53,7 @@ data class MergedHeaderItem(private val isCollapsed: Boolean,
|
||||
val data = distinctMergeData.getOrNull(index)
|
||||
if (data != null && view is ImageView) {
|
||||
view.visibility = View.VISIBLE
|
||||
avatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view)
|
||||
attributes.avatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view)
|
||||
} else {
|
||||
view.visibility = View.GONE
|
||||
}
|
||||
@ -84,6 +75,13 @@ data class MergedHeaderItem(private val isCollapsed: Boolean,
|
||||
val avatarUrl: String?
|
||||
)
|
||||
|
||||
data class Attributes(
|
||||
val isCollapsed: Boolean,
|
||||
val mergeData: List<Data>,
|
||||
val avatarRenderer: AvatarRenderer,
|
||||
val onCollapsedStateChanged: (Boolean) -> Unit
|
||||
)
|
||||
|
||||
class Holder : BaseHolder(STUB_ID) {
|
||||
|
||||
val expandView by bind<TextView>(R.id.itemMergedExpandTextView)
|
||||
|
@ -32,47 +32,38 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
|
||||
abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
@EpoxyAttribute
|
||||
var noticeText: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var informationData: MessageInformationData
|
||||
|
||||
@EpoxyAttribute
|
||||
var baseCallback: TimelineEventController.BaseCallback? = null
|
||||
lateinit var attributes: Attributes
|
||||
|
||||
private var longClickListener = View.OnLongClickListener {
|
||||
return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true
|
||||
return@OnLongClickListener attributes.callback?.onEventLongClicked(attributes.informationData, null, it) == true
|
||||
}
|
||||
|
||||
@EpoxyAttribute
|
||||
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||
|
||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||
readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts)
|
||||
})
|
||||
|
||||
private val _readMarkerCallback = object : ReadMarkerView.Callback {
|
||||
override fun onReadMarkerDisplayed() {
|
||||
readReceiptsCallback?.onReadMarkerLongDisplayed(informationData)
|
||||
readReceiptsCallback?.onReadMarkerLongDisplayed(attributes.informationData)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.noticeTextView.text = noticeText
|
||||
avatarRenderer.render(
|
||||
informationData.avatarUrl,
|
||||
informationData.senderId,
|
||||
informationData.memberName?.toString()
|
||||
?: informationData.senderId,
|
||||
holder.noticeTextView.text = attributes.noticeText
|
||||
attributes.avatarRenderer.render(
|
||||
attributes.informationData.avatarUrl,
|
||||
attributes.informationData.senderId,
|
||||
attributes.informationData.memberName?.toString()
|
||||
?: attributes.informationData.senderId,
|
||||
holder.avatarImageView
|
||||
)
|
||||
holder.view.setOnLongClickListener(longClickListener)
|
||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||
holder.readMarkerView.bindView(informationData, _readMarkerCallback)
|
||||
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
|
||||
holder.readMarkerView.bindView(attributes.informationData, _readMarkerCallback)
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
@ -89,6 +80,13 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
val readMarkerView by bind<ReadMarkerView>(R.id.readMarkerView)
|
||||
}
|
||||
|
||||
data class Attributes(
|
||||
val avatarRenderer: AvatarRenderer,
|
||||
val informationData: MessageInformationData,
|
||||
val noticeText: CharSequence,
|
||||
val callback: TimelineEventController.BaseCallback? = null
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val STUB_ID = R.id.messageContentNoticeStub
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user