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.helper.TimelineDateFormatter
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.html.EventHtmlRenderer
import org.koin.core.parameter.parametersOf
@ -69,15 +70,22 @@ class HomeModule {
val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get())
val noticeEventFormatter = get<NoticeEventFormatter>(parameters = { parametersOf(fragment) })
val timelineMediaSizeProvider = TimelineMediaSizeProvider()
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider,
timelineDateFormatter, eventHtmlRenderer, get(), get())
val messageInformationDataFactory = MessageInformationDataFactory(timelineDateFormatter, colorProvider)
val messageItemFactory = MessageItemFactory(colorProvider,
timelineMediaSizeProvider,
eventHtmlRenderer,
get(),
messageInformationDataFactory,
get())

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

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

View File

@ -16,28 +16,28 @@

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.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R
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.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotredesign.features.home.room.detail.timeline.util.MessageInformationDataFactory
import me.gujun.android.span.span

// This class handles timeline event who haven't been successfully decrypted
class EncryptedItemFactory(private val stringProvider: StringProvider) {
// This class handles timeline events who haven't been successfully decrypted
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 {
EventType.ENCRYPTED == timelineEvent.root.getClearType() -> {
val cryptoError = timelineEvent.root.mCryptoError
EventType.ENCRYPTED == event.root.getClearType() -> {
val cryptoError = event.root.mCryptoError
val errorDescription =
if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
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 spannableStr = SpannableString(message)
spannableStr.setSpan(StyleSpan(Typeface.ITALIC), 0, message.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
val spannableStr = span(message) {
textStyle = "italic"
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
}

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

return MessageTextItem_()
.message(spannableStr)
.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 im.vector.matrix.android.api.permalinks.MatrixLinkify
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.toModel
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.R
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.resources.ColorProvider
import im.vector.riotredesign.core.resources.StringProvider
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.helper.TimelineDateFormatter
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.util.MessageInformationDataFactory
import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer
@ -52,50 +49,18 @@ import me.gujun.android.span.span

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

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

val eventId = event.root.eventId ?: return null

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
)
val informationData = messageInformationDataFactory.create(event, nextEvent)

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

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

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

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

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

View File

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

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

// Unhandled event types (yet)
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
)
}
}