Create MessageInformationDataFactory for reusability

This commit is contained in:
Benoit Marty 2019-06-18 12:25:50 +02:00
parent 273c8a19b8
commit a53e40e1ee
5 changed files with 121 additions and 76 deletions

View File

@ -29,6 +29,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.factory.*
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.util.MessageInformationDataFactory
import im.vector.riotredesign.features.home.room.list.RoomSummaryController import im.vector.riotredesign.features.home.room.list.RoomSummaryController
import im.vector.riotredesign.features.html.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@ -69,15 +70,22 @@ class HomeModule {
val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get())
val noticeEventFormatter = get<NoticeEventFormatter>(parameters = { parametersOf(fragment) }) val noticeEventFormatter = get<NoticeEventFormatter>(parameters = { parametersOf(fragment) })
val timelineMediaSizeProvider = TimelineMediaSizeProvider() val timelineMediaSizeProvider = TimelineMediaSizeProvider()
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, val messageInformationDataFactory = MessageInformationDataFactory(timelineDateFormatter, colorProvider)
timelineDateFormatter, eventHtmlRenderer, get(), get()) val messageItemFactory = MessageItemFactory(colorProvider,
timelineMediaSizeProvider,
eventHtmlRenderer,
get(),
messageInformationDataFactory,
get())

val encryptedItemFactory = EncryptedItemFactory(messageInformationDataFactory, colorProvider, get())


val timelineItemFactory = TimelineItemFactory( val timelineItemFactory = TimelineItemFactory(
messageItemFactory = messageItemFactory, messageItemFactory = messageItemFactory,
noticeItemFactory = NoticeItemFactory(noticeEventFormatter), noticeItemFactory = NoticeItemFactory(noticeEventFormatter),
defaultItemFactory = DefaultItemFactory(), defaultItemFactory = DefaultItemFactory(),
encryptionItemFactory = EncryptionItemFactory(get()), encryptionItemFactory = EncryptionItemFactory(get()),
encryptedItemFactory = EncryptedItemFactory(get()) encryptedItemFactory = encryptedItemFactory
) )
TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider) TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider)
} }

View File

@ -16,28 +16,28 @@


package im.vector.riotredesign.features.home.room.detail.timeline.factory package im.vector.riotredesign.features.home.room.detail.timeline.factory


import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableString
import android.text.style.StyleSpan
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName import im.vector.riotredesign.features.home.room.detail.timeline.util.MessageInformationDataFactory
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import me.gujun.android.span.span
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_


// This class handles timeline event who haven't been successfully decrypted // This class handles timeline events who haven't been successfully decrypted
class EncryptedItemFactory(private val stringProvider: StringProvider) { class EncryptedItemFactory(private val messageInformationDataFactory: MessageInformationDataFactory,
private val colorProvider: ColorProvider,
private val stringProvider: StringProvider) {

fun create(event: TimelineEvent, nextEvent: TimelineEvent?): VectorEpoxyModel<*>? {
event.root.eventId ?: return null


fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? {
return when { return when {
EventType.ENCRYPTED == timelineEvent.root.getClearType() -> { EventType.ENCRYPTED == event.root.getClearType() -> {
val cryptoError = timelineEvent.root.mCryptoError val cryptoError = event.root.mCryptoError
val errorDescription = val errorDescription =
if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) { if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
@ -46,22 +46,21 @@ class EncryptedItemFactory(private val stringProvider: StringProvider) {
} }


val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
val spannableStr = SpannableString(message) val spannableStr = span(message) {
spannableStr.setSpan(StyleSpan(Typeface.ITALIC), 0, message.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) textStyle = "italic"
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
}

// TODO This is not correct format for error, change it // TODO This is not correct format for error, change it
val informationData = MessageInformationData( val informationData = messageInformationDataFactory.create(event, nextEvent)
eventId = timelineEvent.root.eventId ?: "?",
senderId = timelineEvent.root.sender ?: "", return MessageTextItem_()
sendState = timelineEvent.sendState, .message(spannableStr)
avatarUrl = timelineEvent.senderAvatar(),
memberName = timelineEvent.senderName(),
showInformation = false
)
return NoticeItem_()
.noticeText(spannableStr)
.informationData(informationData) .informationData(informationData)

// TODO Handle click on this event
} }
else -> null else -> null
} }
} }
} }

View File

@ -25,7 +25,6 @@ import android.text.style.RelativeSizeSpan
import android.view.View import android.view.View
import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
@ -35,16 +34,14 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.linkify.VectorLinkify import im.vector.riotredesign.core.linkify.VectorLinkify
import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.DebouncedClickListener import im.vector.riotredesign.core.utils.DebouncedClickListener
import im.vector.riotredesign.features.home.getColorFromUserId
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.item.* import im.vector.riotredesign.features.home.room.detail.timeline.item.*
import im.vector.riotredesign.features.home.room.detail.timeline.util.MessageInformationDataFactory
import im.vector.riotredesign.features.html.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer import im.vector.riotredesign.features.media.VideoContentRenderer
@ -52,50 +49,18 @@ import me.gujun.android.span.span


class MessageItemFactory(private val colorProvider: ColorProvider, class MessageItemFactory(private val colorProvider: ColorProvider,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val timelineDateFormatter: TimelineDateFormatter,
private val htmlRenderer: EventHtmlRenderer, private val htmlRenderer: EventHtmlRenderer,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val messageInformationDataFactory: MessageInformationDataFactory,
private val emojiCompatFontProvider: EmojiCompatFontProvider) { private val emojiCompatFontProvider: EmojiCompatFontProvider) {


fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
callback: TimelineEventController.Callback? callback: TimelineEventController.Callback?
): VectorEpoxyModel<*>? { ): VectorEpoxyModel<*>? {
event.root.eventId ?: return null


val eventId = event.root.eventId ?: return null val informationData = messageInformationDataFactory.create(event, nextEvent)

val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false

val showInformation = addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName
|| nextEvent?.root?.getClearType() != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo

val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar
val memberName = event.senderName ?: event.root.sender ?: ""
val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.sender
?: ""))
}
val hasBeenEdited = event.annotations?.editSummary != null
val informationData = MessageInformationData(eventId = eventId,
senderId = event.root.sender ?: "",
sendState = event.sendState,
time = time,
avatarUrl = avatarUrl,
memberName = formattedMemberName,
showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
},
hasBeenEdited = hasBeenEdited
)


if (event.root.unsignedData?.redactedEvent != null) { if (event.root.unsignedData?.redactedEvent != null) {
//message is redacted //message is redacted
@ -117,13 +82,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData, informationData,
hasBeenEdited,
event.annotations?.editSummary, event.annotations?.editSummary,
callback) callback)
is MessageTextContent -> buildTextMessageItem(event.sendState, is MessageTextContent -> buildTextMessageItem(event.sendState,
messageContent, messageContent,
informationData, informationData,
hasBeenEdited,
event.annotations?.editSummary, event.annotations?.editSummary,
callback callback
) )
@ -266,7 +229,6 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
private fun buildTextMessageItem(sendState: SendState, private fun buildTextMessageItem(sendState: SendState,
messageContent: MessageTextContent, messageContent: MessageTextContent,
informationData: MessageInformationData, informationData: MessageInformationData,
hasBeenEdited: Boolean,
editSummary: EditAggregatedSummary?, editSummary: EditAggregatedSummary?,
callback: TimelineEventController.Callback?): MessageTextItem? { callback: TimelineEventController.Callback?): MessageTextItem? {


@ -278,7 +240,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,


return MessageTextItem_() return MessageTextItem_()
.apply { .apply {
if (hasBeenEdited) { if (informationData.hasBeenEdited) {
val spannable = annotateWithEdited(linkifiedBody, callback, informationData, editSummary) val spannable = annotateWithEdited(linkifiedBody, callback, informationData, editSummary)
message(spannable) message(spannable)
} else { } else {
@ -368,7 +330,6 @@ class MessageItemFactory(private val colorProvider: ColorProvider,


private fun buildEmoteMessageItem(messageContent: MessageEmoteContent, private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
informationData: MessageInformationData, informationData: MessageInformationData,
hasBeenEdited: Boolean,
editSummary: EditAggregatedSummary?, editSummary: EditAggregatedSummary?,
callback: TimelineEventController.Callback?): MessageTextItem? { callback: TimelineEventController.Callback?): MessageTextItem? {


@ -378,7 +339,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
} }
return MessageTextItem_() return MessageTextItem_()
.apply { .apply {
if (hasBeenEdited) { if (informationData.hasBeenEdited) {
val spannable = annotateWithEdited(message, callback, informationData, editSummary) val spannable = annotateWithEdited(message, callback, informationData, editSummary)
message(spannable) message(spannable)
} else { } else {

View File

@ -53,7 +53,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,


// Crypto // Crypto
EventType.ENCRYPTION -> encryptionItemFactory.create(event) EventType.ENCRYPTION -> encryptionItemFactory.create(event)
EventType.ENCRYPTED -> encryptedItemFactory.create(event) EventType.ENCRYPTED -> encryptedItemFactory.create(event, nextEvent)


// Unhandled event types (yet) // Unhandled event types (yet)
EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STATE_ROOM_THIRD_PARTY_INVITE,

View File

@ -0,0 +1,77 @@
/*
* 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.riotredesign.features.home.room.detail.timeline.util

import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.features.home.getColorFromUserId
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.ReactionInfoData
import me.gujun.android.span.span

/**
* This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline
*/
class MessageInformationDataFactory(private val timelineDateFormatter: TimelineDateFormatter,
private val colorProvider: ColorProvider) {

fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
// Non nullability has been tested before
val eventId = event.root.eventId!!

val date = event.root.localDateTime()

val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false
val showInformation =
addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName
|| (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo

val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar()
val memberName = event.senderName ?: event.root.sender ?: ""
val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.sender
?: ""))
}

val hasBeenEdited = event.annotations?.editSummary != null

return MessageInformationData(
eventId = eventId,
senderId = event.root.sender ?: "",
sendState = event.sendState,
time = time,
avatarUrl = avatarUrl,
memberName = formattedMemberName,
showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
},
hasBeenEdited = hasBeenEdited
)
}
}