Support message edition

This commit is contained in:
Valere 2019-05-20 18:52:48 +02:00
parent 2581bf69e0
commit 8cb884f10e
27 changed files with 289 additions and 50 deletions

View File

@ -0,0 +1,25 @@
/*
* 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.matrix.android.api.session.room.model

import im.vector.matrix.android.api.session.events.model.Content

data class EditAggregatedSummary(
val aggregatedContent: Content? = null,
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
val sourceEvents: List<String>,
val lastEditTs: Long = 0
)

View File

@ -1,7 +1,22 @@
/*
* 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.matrix.android.api.session.room.model package im.vector.matrix.android.api.session.room.model



data class EventAnnotationsSummary( data class EventAnnotationsSummary(
var eventId: String, var eventId: String,
var reactionsSummary: List<ReactionAggregatedSummary> var reactionsSummary: List<ReactionAggregatedSummary>,
var editSummary: EditAggregatedSummary?
) )

View File

@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class ReactionInfo( data class ReactionInfo(
@Json(name = "rel_type") override val type: String, @Json(name = "rel_type") override val type: String?,
@Json(name = "event_id") override val eventId: String, @Json(name = "event_id") override val eventId: String,
val key: String val key: String
) : RelationContent ) : RelationContent

View File

@ -1,6 +1,6 @@
package im.vector.matrix.android.api.session.room.model.annotation package im.vector.matrix.android.api.session.room.model.annotation


interface RelationContent { interface RelationContent {
val type: String val type: String?
val eventId: String val eventId: String?
} }

View File

@ -1,8 +1,25 @@
/*
* 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.matrix.android.api.session.room.model.annotation package im.vector.matrix.android.api.session.room.model.annotation


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass


@JsonClass(generateAdapter = true)
data class RelationDefaultContent( data class RelationDefaultContent(
@Json(name = "rel_type") override val type: String, @Json(name = "rel_type") override val type: String?,
@Json(name = "event_id") override val eventId: String @Json(name = "event_id") override val eventId: String?
) : RelationContent ) : RelationContent

View File

@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class FileInfo( data class FileInfo(
@Json(name = "mimetype") val mimeType: String, @Json(name = "mimetype") val mimeType: String?,
@Json(name = "size") val size: Long = 0, @Json(name = "size") val size: Long = 0,
@Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
@Json(name = "thumbnail_url") val thumbnailUrl: String? = null @Json(name = "thumbnail_url") val thumbnailUrl: String? = null

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageAudioContent( data class MessageAudioContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "info") val info: AudioInfo? = null, @Json(name = "info") val info: AudioInfo? = null,
@Json(name = "url") val url: String? = null @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -16,8 +16,13 @@


package im.vector.matrix.android.api.session.room.model.message package im.vector.matrix.android.api.session.room.model.message


import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent



interface MessageContent { interface MessageContent {
val type: String val type: String
val body: String val body: String
val relatesTo: RelationDefaultContent?
val newContent: Content?
} }

View File

@ -18,9 +18,13 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageDefaultContent( data class MessageDefaultContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String @Json(name = "body") override val body: String,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageEmoteContent( data class MessageEmoteContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "format") val format: String? = null, @Json(name = "format") val format: String? = null,
@Json(name = "formatted_body") val formattedBody: String? = null @Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,6 +18,8 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageFileContent( data class MessageFileContent(
@ -25,5 +27,7 @@ data class MessageFileContent(
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "filename") val filename: String? = null, @Json(name = "filename") val filename: String? = null,
@Json(name = "info") val info: FileInfo? = null, @Json(name = "info") val info: FileInfo? = null,
@Json(name = "url") val url: String? = null @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageImageContent( data class MessageImageContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "info") val info: ImageInfo? = null, @Json(name = "info") val info: ImageInfo? = null,
@Json(name = "url") val url: String? = null @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageLocationContent( data class MessageLocationContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "geo_uri") val geoUri: String, @Json(name = "geo_uri") val geoUri: String,
@Json(name = "info") val info: LocationInfo? = null @Json(name = "info") val info: LocationInfo? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageNoticeContent( data class MessageNoticeContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "format") val format: String? = null, @Json(name = "format") val format: String? = null,
@Json(name = "formatted_body") val formattedBody: String? = null @Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageTextContent( data class MessageTextContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "format") val format: String? = null, @Json(name = "format") val format: String? = null,
@Json(name = "formatted_body") val formattedBody: String? = null @Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.annotation.RelationDefaultContent


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageVideoContent( data class MessageVideoContent(
@Json(name = "msgtype") override val type: String, @Json(name = "msgtype") override val type: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "info") val info: VideoInfo? = null, @Json(name = "info") val info: VideoInfo? = null,
@Json(name = "url") val url: String? = null @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContent

View File

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper


import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
@ -16,6 +17,13 @@ internal object EventAnnotationsSummaryMapper {
it.firstTimestamp, it.firstTimestamp,
it.sourceEvents.toList() it.sourceEvents.toList()
) )
},
editSummary = annotationsSummary.editSummary?.let {
EditAggregatedSummary(
ContentMapper.map(it.aggregatedContent),
it.sourceEvents.toList(),
it.lastEditTs
)
} }
) )
} }

View File

@ -0,0 +1,33 @@
/*
* 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.matrix.android.internal.database.model

import io.realm.RealmList
import io.realm.RealmObject

/**
* Keep the latest state of edition of a message
*/
internal open class EditAggregatedSummaryEntity(
var aggregatedContent: String? = null,
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
var sourceEvents: RealmList<String> = RealmList(),
var lastEditTs: Long = 0
) : RealmObject() {

companion object

}

View File

@ -24,7 +24,8 @@ internal open class EventAnnotationsSummaryEntity(
@PrimaryKey @PrimaryKey
var eventId: String = "", var eventId: String = "",
var roomId: String? = null, var roomId: String? = null,
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList() var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
var editSummary: EditAggregatedSummaryEntity? = null
) : RealmObject() { ) : RealmObject() {


companion object companion object

View File

@ -35,6 +35,7 @@ import io.realm.annotations.RealmModule
SyncEntity::class, SyncEntity::class,
UserEntity::class, UserEntity::class,
EventAnnotationsSummaryEntity::class, EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class
]) ])
internal class SessionRealmModule internal class SessionRealmModule

View File

@ -17,19 +17,7 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di


import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.annotation.RelationContent
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageLocationContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import im.vector.matrix.android.internal.session.sync.model.UserAccountData import im.vector.matrix.android.internal.session.sync.model.UserAccountData
@ -41,17 +29,17 @@ object MoshiProvider {
private val moshi: Moshi = Moshi.Builder() private val moshi: Moshi = Moshi.Builder()
.add(UriMoshiAdapter()) .add(UriMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
) )
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
.registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE) .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)
.registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE) .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE)
.registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO) .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO)
.registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE) .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE)
.registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO) .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
) )
.build() .build()



View File

@ -3,6 +3,9 @@ package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.* import im.vector.matrix.android.api.session.events.model.*
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.create
@ -22,16 +25,66 @@ internal class EventRelationsAggregationUpdater(private val credentials: Credent
handleReaction(event, roomId, realm) handleReaction(event, roomId, realm)
} }
EventType.MESSAGE -> { EventType.MESSAGE -> {
event.unsignedData?.relations?.annotations?.let { if (event.unsignedData?.relations?.annotations != null) {
Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(event, roomId, it, realm) handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
} else {
val content: MessageContent? = event.content.toModel()
if (content?.relatesTo?.type == RelationType.REPLACE) {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
//A replace!
handleReplace(event, content, roomId, realm)
}
} }
//TODO message edits
} }
} }
} }
} }


private fun handleReplace(event: Event, content: MessageContent, roomId: String, realm: Realm) {
val eventId = event.eventId ?: return
val targetEventId = content.relatesTo?.eventId ?: return
val newContent = content.newContent ?: return
//ok, this is a replace
var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
if (existing == null) {
Timber.v("###REPLACE creating no relation summary for ${targetEventId}")
existing = EventAnnotationsSummaryEntity.create(realm, targetEventId)
existing.roomId = roomId
}

//we have it
val existingSummary = existing.editSummary
if (existingSummary == null) {
Timber.v("###REPLACE no edit summary for ${targetEventId}, creating one")
//create the edit summary
val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
editSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
editSummary.aggregatedContent = ContentMapper.map(newContent)
editSummary.sourceEvents.add(eventId)

existing.editSummary = editSummary
} else {
if (existingSummary.sourceEvents.contains(eventId)) {
//ignore this event, we already know it (??)
Timber.v("###REPLACE ignoring event for summary, it's known ${eventId}")
return
}
//This message has already been edited
if (event.originServerTs ?: 0 > existingSummary.lastEditTs ?: 0) {
Timber.v("###REPLACE Computing aggregated edit summary")
existingSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
existingSummary.aggregatedContent = ContentMapper.map(newContent)
existingSummary.sourceEvents.add(eventId)
} else {
//ignore this event for the summary
Timber.v("###REPLACE ignoring event for summary, it's to old ${eventId}")

}
}

}

private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) { private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
aggregation.chunk?.forEach { aggregation.chunk?.forEach {
if (it.type == EventType.REACTION) { if (it.type == EventType.REACTION) {

View File

@ -16,12 +16,16 @@


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


import android.graphics.Color
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.View import android.view.View
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
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.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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
@ -73,6 +77,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
textColor = colorProvider.getColor(AvatarRenderer.getColorFromUserId(event.root.sender textColor = colorProvider.getColor(AvatarRenderer.getColorFromUserId(event.root.sender
?: "")) ?: ""))
} }
val hasBeenEdited = event.annotations?.editSummary != null
val informationData = MessageInformationData(eventId = eventId, val informationData = MessageInformationData(eventId = eventId,
senderId = event.root.sender ?: "", senderId = event.root.sender ?: "",
sendState = event.sendState, sendState = event.sendState,
@ -80,7 +85,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
memberName = formattedMemberName, memberName = formattedMemberName,
showInformation = showInformation, showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map { Triple(it.key, it.count, it.addedByMe) } orderedReactionList = event.annotations?.reactionsSummary?.map { Triple(it.key, it.count, it.addedByMe) },
hasBeenEdited = hasBeenEdited
) )


if (event.root.unsignedData?.redactedEvent != null) { if (event.root.unsignedData?.redactedEvent != null) {
@ -88,13 +94,21 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
return buildRedactedItem(informationData, callback) return buildRedactedItem(informationData, callback)
} }


val messageContent: MessageContent = event.root.content.toModel() ?: return null val messageContent: MessageContent =
event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
?: return null


if (messageContent.relatesTo?.type == RelationType.REPLACE) {
//TODO blank item or ignore??
// ignore this event
return BlankItem_()
}
// val all = event.root.toContent() // val all = event.root.toContent()
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback) is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback)
is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, callback) is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, callback)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback)
@ -254,6 +268,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,


private fun buildTextMessageItem(sendState: SendState, messageContent: MessageTextContent, private fun buildTextMessageItem(sendState: SendState, messageContent: MessageTextContent,
informationData: MessageInformationData, informationData: MessageInformationData,
hasBeenEdited: Boolean,
callback: TimelineEventController.Callback?): MessageTextItem? { callback: TimelineEventController.Callback?): MessageTextItem? {


val bodyToUse = messageContent.formattedBody?.let { val bodyToUse = messageContent.formattedBody?.let {
@ -261,8 +276,20 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
} ?: messageContent.body } ?: messageContent.body


val linkifiedBody = linkifyBody(bodyToUse, callback) val linkifiedBody = linkifyBody(bodyToUse, callback)

return MessageTextItem_() return MessageTextItem_()
.message(linkifiedBody) .apply {
if (hasBeenEdited) {
val spannable = SpannableStringBuilder()
spannable.append(linkifiedBody)
val editedSuffix = "(edited)"
spannable.append(" ").append(editedSuffix)
spannable.setSpan(ForegroundColorSpan(Color.LTGRAY), spannable.indexOf(editedSuffix), spannable.indexOf(editedSuffix) + editedSuffix.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
message(spannable)
} else {
message(linkifiedBody)
}
}
.informationData(informationData) .informationData(informationData)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.avatarClickListener( .avatarClickListener(

View File

@ -129,7 +129,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
} }
} }


open fun shouldShowReactionAtBottom() : Boolean { open fun shouldShowReactionAtBottom(): Boolean {
return true return true
} }



View File

@ -0,0 +1,27 @@
/*
* 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.item

import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel


@EpoxyModelClass(layout = R.layout.item_timeline_event_blank_stub)
abstract class BlankItem : VectorEpoxyModel<BlankItem.BlankHolder>() {
class BlankHolder : VectorEpoxyHolder()
}

View File

@ -31,5 +31,6 @@ data class MessageInformationData(
val memberName: CharSequence? = null, val memberName: CharSequence? = null,
val showInformation: Boolean = true, val showInformation: Boolean = true,
/*List of reactions (emoji,count,isSelected)*/ /*List of reactions (emoji,count,isSelected)*/
var orderedReactionList: List<Triple<String,Int,Boolean>>? = null var orderedReactionList: List<Triple<String,Int,Boolean>>? = null,
var hasBeenEdited: Boolean = false
) : Parcelable ) : Parcelable

View File

@ -32,6 +32,8 @@
android:id="@+id/messageContentBlankStub" android:id="@+id/messageContentBlankStub"
style="@style/TimelineContentStubNoInfoLayoutParams" style="@style/TimelineContentStubNoInfoLayoutParams"
android:layout="@layout/item_timeline_event_blank_stub" android:layout="@layout/item_timeline_event_blank_stub"
android:layout_width="0dp"
android:layout_height="0dp"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />


<ViewStub <ViewStub