From 8cb884f10ea6ae5506f885f697bdfc3f25d93264 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 May 2019 18:52:48 +0200 Subject: [PATCH 1/7] Support message edition --- .../room/model/EditAggregatedSummary.kt | 25 ++++++++ .../room/model/EventAnnotationsSummary.kt | 19 +++++- .../room/model/annotation/ReactionInfo.kt | 2 +- .../room/model/annotation/RelationContent.kt | 4 +- .../annotation/RelationDefaultContent.kt | 21 ++++++- .../session/room/model/message/FileInfo.kt | 2 +- .../room/model/message/MessageAudioContent.kt | 6 +- .../room/model/message/MessageContent.kt | 5 ++ .../model/message/MessageDefaultContent.kt | 6 +- .../room/model/message/MessageEmoteContent.kt | 6 +- .../room/model/message/MessageFileContent.kt | 6 +- .../room/model/message/MessageImageContent.kt | 6 +- .../model/message/MessageLocationContent.kt | 6 +- .../model/message/MessageNoticeContent.kt | 6 +- .../room/model/message/MessageTextContent.kt | 6 +- .../room/model/message/MessageVideoContent.kt | 6 +- .../mapper/EventAnnotationsSummaryMapper.kt | 8 +++ .../model/EditAggregatedSummaryEntity.kt | 33 +++++++++++ .../model/EventAnnotationsSummaryEntity.kt | 3 +- .../database/model/SessionRealmModule.kt | 3 +- .../android/internal/di/MoshiProvider.kt | 32 ++++------ .../room/EventRelationsAggregationUpdater.kt | 59 ++++++++++++++++++- .../timeline/factory/MessageItemFactory.kt | 35 +++++++++-- .../detail/timeline/item/AbsMessageItem.kt | 2 +- .../room/detail/timeline/item/BlankItem.kt | 27 +++++++++ .../timeline/item/MessageInformationData.kt | 3 +- .../item_timeline_event_base_noinfo.xml | 2 + 27 files changed, 289 insertions(+), 50 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EditAggregatedSummary.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EditAggregatedSummaryEntity.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/BlankItem.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EditAggregatedSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EditAggregatedSummary.kt new file mode 100644 index 00000000..636343a5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EditAggregatedSummary.kt @@ -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, + val lastEditTs: Long = 0 +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt index 15008439..18899f87 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt @@ -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 - data class EventAnnotationsSummary( var eventId: String, - var reactionsSummary: List + var reactionsSummary: List, + var editSummary: EditAggregatedSummary? ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionInfo.kt index 14444508..bf573744 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionInfo.kt @@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) 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, val key: String ) : RelationContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationContent.kt index ccdd10a0..d9e23b30 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationContent.kt @@ -1,6 +1,6 @@ package im.vector.matrix.android.api.session.room.model.annotation interface RelationContent { - val type: String - val eventId: String + val type: String? + val eventId: String? } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationDefaultContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationDefaultContent.kt index 1d00b1a4..7137e86a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationDefaultContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationDefaultContent.kt @@ -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 import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) data class RelationDefaultContent( - @Json(name = "rel_type") override val type: String, - @Json(name = "event_id") override val eventId: String + @Json(name = "rel_type") override val type: String?, + @Json(name = "event_id") override val eventId: String? ) : RelationContent diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt index 2a115a58..16dc4d23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class FileInfo( - @Json(name = "mimetype") val mimeType: String, + @Json(name = "mimetype") val mimeType: String?, @Json(name = "size") val size: Long = 0, @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, @Json(name = "thumbnail_url") val thumbnailUrl: String? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt index ffae8eef..f1df2b58 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageAudioContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt index 1d6880b0..efc9af06 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt @@ -16,8 +16,13 @@ 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 { val type: String val body: String + val relatesTo: RelationDefaultContent? + val newContent: Content? } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt index a924efe4..4fb96a83 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt @@ -18,9 +18,13 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageDefaultContent( @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt index bae9c96f..8053fde4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageEmoteContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt index 505aee7a..57039d64 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt @@ -18,6 +18,8 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageFileContent( @@ -25,5 +27,7 @@ data class MessageFileContent( @Json(name = "body") override val body: String, @Json(name = "filename") val filename: String? = 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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt index a3e2f661..394b9879 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageImageContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt index b17bfae1..d3a81a93 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageLocationContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: 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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt index 4db1f4a0..53adaf9b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageNoticeContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt index 084b5dde..7942c259 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageTextContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt index 319a9493..0ca7def1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt @@ -18,11 +18,15 @@ package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json 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) data class MessageVideoContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, @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 \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt index 38388950..2d1a7da1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -1,5 +1,6 @@ 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.ReactionAggregatedSummary import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity @@ -16,6 +17,13 @@ internal object EventAnnotationsSummaryMapper { it.firstTimestamp, it.sourceEvents.toList() ) + }, + editSummary = annotationsSummary.editSummary?.let { + EditAggregatedSummary( + ContentMapper.map(it.aggregatedContent), + it.sourceEvents.toList(), + it.lastEditTs + ) } ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EditAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EditAggregatedSummaryEntity.kt new file mode 100644 index 00000000..4b2a43e8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EditAggregatedSummaryEntity.kt @@ -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 = RealmList(), + var lastEditTs: Long = 0 +) : RealmObject() { + + companion object + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt index bd4c2077..4c6b26f0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt @@ -24,7 +24,8 @@ internal open class EventAnnotationsSummaryEntity( @PrimaryKey var eventId: String = "", var roomId: String? = null, - var reactionsSummary: RealmList = RealmList() + var reactionsSummary: RealmList = RealmList(), + var editSummary: EditAggregatedSummaryEntity? = null ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index dc3ad001..20ba8d44 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -35,6 +35,7 @@ import io.realm.annotations.RealmModule SyncEntity::class, UserEntity::class, EventAnnotationsSummaryEntity::class, - ReactionAggregatedSummaryEntity::class + ReactionAggregatedSummaryEntity::class, + EditAggregatedSummaryEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index 45eb7831..43d1c488 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -17,19 +17,7 @@ package im.vector.matrix.android.internal.di import com.squareup.moshi.Moshi -import im.vector.matrix.android.api.session.events.model.EventType -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.api.session.room.model.message.* import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.UserAccountData @@ -41,17 +29,17 @@ object MoshiProvider { private val moshi: Moshi = Moshi.Builder() .add(UriMoshiAdapter()) .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) - .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) - .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE) - .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE) - .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO) - .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE) - .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO) - .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) - .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) + .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) + .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE) + .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE) + .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO) + .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE) + .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO) + .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) + .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) ) .build() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index 85e7245d..3ec43a44 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -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.session.events.model.* 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.ReactionAggregatedSummaryEntity import im.vector.matrix.android.internal.database.query.create @@ -22,16 +25,66 @@ internal class EventRelationsAggregationUpdater(private val credentials: Credent handleReaction(event, roomId, realm) } EventType.MESSAGE -> { - event.unsignedData?.relations?.annotations?.let { + if (event.unsignedData?.relations?.annotations != null) { 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) { aggregation.chunk?.forEach { if (it.type == EventType.REACTION) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index b4374c0c..03a4f768 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -16,12 +16,16 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory +import android.graphics.Color import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.ForegroundColorSpan import android.view.View import androidx.annotation.ColorRes 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.message.* 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 ?: "")) } + val hasBeenEdited = event.annotations?.editSummary != null val informationData = MessageInformationData(eventId = eventId, senderId = event.root.sender ?: "", sendState = event.sendState, @@ -80,7 +85,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider, avatarUrl = avatarUrl, memberName = formattedMemberName, 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) { @@ -88,13 +94,21 @@ class MessageItemFactory(private val colorProvider: ColorProvider, 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 ev = all.toModel() return when (messageContent) { 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 MessageNoticeContent -> buildNoticeMessageItem(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, informationData: MessageInformationData, + hasBeenEdited: Boolean, callback: TimelineEventController.Callback?): MessageTextItem? { val bodyToUse = messageContent.formattedBody?.let { @@ -261,8 +276,20 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } ?: messageContent.body val linkifiedBody = linkifyBody(bodyToUse, callback) + 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) .reactionPillCallback(callback) .avatarClickListener( diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index 4e219fad..d560424d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -129,7 +129,7 @@ abstract class AbsMessageItem : BaseEventItem() { } } - open fun shouldShowReactionAtBottom() : Boolean { + open fun shouldShowReactionAtBottom(): Boolean { return true } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/BlankItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/BlankItem.kt new file mode 100644 index 00000000..39ab2c42 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/BlankItem.kt @@ -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() { + class BlankHolder : VectorEpoxyHolder() +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt index b0d25f9b..5cdd1f9c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -31,5 +31,6 @@ data class MessageInformationData( val memberName: CharSequence? = null, val showInformation: Boolean = true, /*List of reactions (emoji,count,isSelected)*/ - var orderedReactionList: List>? = null + var orderedReactionList: List>? = null, + var hasBeenEdited: Boolean = false ) : Parcelable \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index 9504e205..07b43f7b 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -32,6 +32,8 @@ android:id="@+id/messageContentBlankStub" style="@style/TimelineContentStubNoInfoLayoutParams" android:layout="@layout/item_timeline_event_blank_stub" + android:layout_width="0dp" + android:layout_height="0dp" tools:ignore="MissingConstraints" /> Date: Tue, 21 May 2019 09:50:10 +0200 Subject: [PATCH 2/7] Fix / Missing schema for realm --- .../matrix/android/session/room/timeline/TimelineTest.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt index 7390bc4e..e81e481d 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt @@ -21,9 +21,10 @@ import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.database.model.SessionRealmModule import im.vector.matrix.android.internal.session.room.EventRelationExtractor import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater -import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor +import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor @@ -49,7 +50,9 @@ internal class TimelineTest : InstrumentedTest { fun setup() { Timber.plant(Timber.DebugTree()) Realm.init(context()) - val testConfiguration = RealmConfiguration.Builder().name("test-realm").build() + val testConfiguration = RealmConfiguration.Builder().name("test-realm") + .modules(SessionRealmModule()).build() + Realm.deleteRealm(testConfiguration) monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build() RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID) From a5a9fa37503f96185996bb941792cd1fa84e0df3 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 May 2019 14:10:19 +0200 Subject: [PATCH 3/7] Color provider need to be aware of theme --- .../room/EventRelationsAggregationUpdater.kt | 22 +++++++++++++++++-- .../vector/riotredesign/core/di/AppModule.kt | 7 +----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index 3ec43a44..ce87a586 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -1,3 +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.matrix.android.internal.session.room import im.vector.matrix.android.api.auth.data.Credentials @@ -13,7 +28,11 @@ import im.vector.matrix.android.internal.database.query.where import io.realm.Realm import timber.log.Timber - +/** + * Acts as a listener of incoming messages in order to incrementally computes a summary of annotations. + * For reactions will build a EventAnnotationsSummaryEntity, ans for edits a EditAggregatedSummaryEntity. + * The summaries can then be extracted and added (as a decoration) to a TimelineEvent for final display. + */ internal class EventRelationsAggregationUpdater(private val credentials: Credentials) { fun update(realm: Realm, roomId: String, events: List?) { @@ -79,7 +98,6 @@ internal class EventRelationsAggregationUpdater(private val credentials: Credent } else { //ignore this event for the summary Timber.v("###REPLACE ignoring event for summary, it's to old ${eventId}") - } } diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 3fd69879..7fff0208 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -19,7 +19,6 @@ package im.vector.riotredesign.core.di import android.content.Context import android.content.Context.MODE_PRIVATE import im.vector.matrix.android.api.Matrix -import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.features.home.group.SelectedGroupStore @@ -40,11 +39,7 @@ class AppModule(private val context: Context) { single { StringProvider(context.resources) } - - single { - ColorProvider(context) - } - + single { context.getSharedPreferences("im.vector.riot", MODE_PRIVATE) } From 6f103101b69c518527ecac25f95f23e180e1a533 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 May 2019 14:12:18 +0200 Subject: [PATCH 4/7] Show edited annotation in timeline + simple edit history --- .../session/room/EventRelationExtractor.kt | 19 ++++++++++- .../core/resources/ColorProvider.kt | 15 ++++++++ .../riotredesign/features/home/HomeModule.kt | 4 ++- .../home/room/detail/RoomDetailActions.kt | 2 ++ .../home/room/detail/RoomDetailFragment.kt | 19 +++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 25 ++++++++++++++ .../timeline/TimelineEventController.kt | 2 ++ .../action/MessageActionsViewModel.kt | 3 +- .../timeline/factory/MessageItemFactory.kt | 34 +++++++++++++++++-- vector/src/main/res/values/strings_riotX.xml | 1 + 10 files changed, 118 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt index 6fc1cb86..e52f5e09 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt @@ -1,3 +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.matrix.android.internal.session.room import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary @@ -7,7 +22,9 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where import io.realm.Realm - +/** + * Fetches annotations (reactions, edits...) associated to a given eventEntity from the data layer. + */ internal class EventRelationExtractor { fun extractFrom(event: EventEntity, realm: Realm = event.realm): EventAnnotationsSummary? { diff --git a/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt b/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt index 4fd37212..cfc48891 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt @@ -19,8 +19,11 @@ package im.vector.riotredesign.core.resources import android.content.Context +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.core.content.ContextCompat +import im.vector.riotredesign.features.themes.ThemeUtils class ColorProvider(private val context: Context) { @@ -28,4 +31,16 @@ class ColorProvider(private val context: Context) { return ContextCompat.getColor(context, colorRes) } + /** + * Translates color attributes to colors + * + * @param c Context + * @param colorAttribute Color Attribute + * @return Requested Color + */ + @ColorInt + fun getColorFromAttribute(@AttrRes colorAttribute: Int): Int { + return ThemeUtils.getColor(context, colorAttribute) + } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 98666053..1474d2da 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -18,6 +18,7 @@ package im.vector.riotredesign.features.home import androidx.fragment.app.Fragment import im.vector.riotredesign.core.glide.GlideApp +import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandController import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserController @@ -58,7 +59,8 @@ class HomeModule { val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) val timelineDateFormatter = TimelineDateFormatter(get()) val timelineMediaSizeProvider = TimelineMediaSizeProvider() - val messageItemFactory = MessageItemFactory(get(), timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) + val colorProvider = ColorProvider(fragment.requireContext()) + val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory, roomNameItemFactory = RoomNameItemFactory(get()), diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index 10552231..7d6ffc04 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -17,6 +17,7 @@ package im.vector.riotredesign.features.home.room.detail import com.jaiselrahman.filepicker.model.MediaFile +import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -31,6 +32,7 @@ sealed class RoomDetailActions { data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() data class UpdateQuickReactAction(val targetEventId: String,val selectedReaction: String,val opposite: String) : RoomDetailActions() + data class ShowEditHistoryAction(val event: String,val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions() object AcceptInvite : RoomDetailActions() object RejectInvite : RoomDetailActions() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 72141e8a..0278607a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -53,6 +53,7 @@ import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -177,6 +178,12 @@ class RoomDetailFragment : textComposerViewModel.subscribe { renderTextComposerState(it) } roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) } + roomDetailViewModel.nonBlockingPopAlert.observe(this, Observer { liveEvent -> + liveEvent.getContentIfNotHandled()?.let { + val message = requireContext().getString(it.first, *it.second.toTypedArray()) + showSnackWithMessage(message, Snackbar.LENGTH_LONG) + } + }) actionViewModel.actionCommandEvent.observe(this, Observer { handleActions(it) }) @@ -514,6 +521,12 @@ class RoomDetailFragment : } } + override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) { + editAggregatedSummary?.also { + roomDetailViewModel.process(RoomDetailActions.ShowEditHistoryAction(informationData.eventId, it)) + } + + } // AutocompleteUserPresenter.Callback override fun onQueryUsers(query: CharSequence?) { @@ -641,6 +654,12 @@ class RoomDetailFragment : } } + fun showSnackWithMessage(message: String, duration: Int = Snackbar.LENGTH_SHORT) { + val snack = Snackbar.make(view!!, message, Snackbar.LENGTH_SHORT) + snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) + snack.show() + } + // VectorInviteView.Callback override fun onAcceptInvite() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 1c185add..44c2aa8f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.rx.rx +import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.features.command.CommandParser @@ -36,6 +37,8 @@ import im.vector.riotredesign.features.home.room.VisibleRoomStore import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get +import java.text.SimpleDateFormat +import java.util.* import java.util.concurrent.TimeUnit class RoomDetailViewModel(initialState: RoomDetailViewState, @@ -83,10 +86,16 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, is RoomDetailActions.RedactAction -> handleRedactEvent(action) is RoomDetailActions.UndoReaction -> handleUndoReact(action) is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action) } } + private val _nonBlockingPopAlert = MutableLiveData>>>() + val nonBlockingPopAlert: LiveData>>> + get() = _nonBlockingPopAlert + + private val _sendMessageResultLiveData = MutableLiveData>() val sendMessageResultLiveData: LiveData> get() = _sendMessageResultLiveData @@ -161,6 +170,22 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, } } + private fun handleShowEditHistoryReaction(action: RoomDetailActions.ShowEditHistoryAction) { + //TODO temporary implementation + val lastReplace = action.editAggregatedSummary.sourceEvents.lastOrNull()?.let { + room.getTimeLineEvent(it) + } ?: return + + val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault()) + _nonBlockingPopAlert.postValue(LiveEvent( + Pair(R.string.last_edited_info_message, listOf( + lastReplace.senderName ?: "?", + dateFormat.format(Date(lastReplace.root.originServerTs ?: 0))) + )) + ) + } + + private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 4674edd4..c14b863a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -25,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyModel 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.RoomMember import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.Timeline @@ -58,6 +59,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean fun onAvatarClicked(informationData: MessageInformationData) fun onMemberNameClicked(informationData: MessageInformationData) + fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) } interface ReactionPillCallback { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 74582fcf..a35199a0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -51,7 +51,8 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) return if (event != null) { - val messageContent: MessageContent? = event.root.content.toModel() + val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() + ?: event.root.content.toModel() val originTs = event.root.originServerTs MessageActionState( event.root.sender ?: "", diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 03a4f768..8b5ecf89 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -16,10 +16,12 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory -import android.graphics.Color import android.text.SpannableStringBuilder import android.text.Spanned +import android.text.TextPaint +import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan import android.view.View import androidx.annotation.ColorRes import im.vector.matrix.android.api.permalinks.MatrixLinkify @@ -27,6 +29,7 @@ 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 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.timeline.TimelineEvent @@ -108,7 +111,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider, // val ev = all.toModel() return when (messageContent) { is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback) - is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, callback) + is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, + event.annotations?.editSummary, + callback + ) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback) @@ -269,6 +275,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, private fun buildTextMessageItem(sendState: SendState, messageContent: MessageTextContent, informationData: MessageInformationData, hasBeenEdited: Boolean, + editSummary: EditAggregatedSummary?, callback: TimelineEventController.Callback?): MessageTextItem? { val bodyToUse = messageContent.formattedBody?.let { @@ -284,7 +291,28 @@ class MessageItemFactory(private val colorProvider: ColorProvider, 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) + val color = colorProvider.getColorFromAttribute(R.attr.vctr_list_header_secondary_text_color) + val editStart = spannable.indexOf(editedSuffix) + val editEnd = editStart + editedSuffix.length + spannable.setSpan( + ForegroundColorSpan(color), + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + + spannable.setSpan(RelativeSizeSpan(.9f), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + spannable.setSpan(object : ClickableSpan() { + override fun onClick(widget: View?) { + callback?.onEditedDecorationClicked(informationData, editSummary) + } + + override fun updateDrawState(ds: TextPaint?) { + //nop + } + }, + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) message(spannable) } else { message(linkifiedBody) diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 8a9e11d9..556bd50c 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -15,5 +15,6 @@ Event deleted by user Event moderated by room admin + Last edited by %s on %s \ No newline at end of file From d49007538b5a791d3bd574316ea0405ff70c486f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 May 2019 14:26:46 +0200 Subject: [PATCH 5/7] Fix / Annotate emote also --- .../timeline/factory/MessageItemFactory.kt | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 8b5ecf89..7a68937c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -110,8 +110,15 @@ class MessageItemFactory(private val colorProvider: ColorProvider, // val all = event.root.toContent() // val ev = all.toModel() return when (messageContent) { - is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback) - is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, + is MessageEmoteContent -> buildEmoteMessageItem(messageContent, + informationData, + hasBeenEdited, + event.annotations?.editSummary, + callback) + is MessageTextContent -> buildTextMessageItem(event.sendState, + messageContent, + informationData, + hasBeenEdited, event.annotations?.editSummary, callback ) @@ -287,32 +294,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return MessageTextItem_() .apply { if (hasBeenEdited) { - val spannable = SpannableStringBuilder() - spannable.append(linkifiedBody) - val editedSuffix = "(edited)" - spannable.append(" ").append(editedSuffix) - val color = colorProvider.getColorFromAttribute(R.attr.vctr_list_header_secondary_text_color) - val editStart = spannable.indexOf(editedSuffix) - val editEnd = editStart + editedSuffix.length - spannable.setSpan( - ForegroundColorSpan(color), - editStart, - editEnd, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) - - spannable.setSpan(RelativeSizeSpan(.9f), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) - spannable.setSpan(object : ClickableSpan() { - override fun onClick(widget: View?) { - callback?.onEditedDecorationClicked(informationData, editSummary) - } - - override fun updateDrawState(ds: TextPaint?) { - //nop - } - }, - editStart, - editEnd, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + val spannable = annotateWithEdited(linkifiedBody, callback, informationData, editSummary) message(spannable) } else { message(linkifiedBody) @@ -343,6 +325,36 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } } + private fun annotateWithEdited(linkifiedBody: CharSequence, callback: TimelineEventController.Callback?, informationData: MessageInformationData, editSummary: EditAggregatedSummary?): SpannableStringBuilder { + val spannable = SpannableStringBuilder() + spannable.append(linkifiedBody) + val editedSuffix = "(edited)" + spannable.append(" ").append(editedSuffix) + val color = colorProvider.getColorFromAttribute(R.attr.vctr_list_header_secondary_text_color) + val editStart = spannable.indexOf(editedSuffix) + val editEnd = editStart + editedSuffix.length + spannable.setSpan( + ForegroundColorSpan(color), + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + + spannable.setSpan(RelativeSizeSpan(.9f), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + spannable.setSpan(object : ClickableSpan() { + override fun onClick(widget: View?) { + callback?.onEditedDecorationClicked(informationData, editSummary) + } + + override fun updateDrawState(ds: TextPaint?) { + //nop + } + }, + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + return spannable + } + private fun buildNoticeMessageItem(messageContent: MessageNoticeContent, informationData: MessageInformationData, callback: TimelineEventController.Callback?): MessageTextItem? { @@ -377,6 +389,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } private fun buildEmoteMessageItem(messageContent: MessageEmoteContent, informationData: MessageInformationData, + hasBeenEdited: Boolean, + editSummary: EditAggregatedSummary?, callback: TimelineEventController.Callback?): MessageTextItem? { val message = messageContent.body.let { @@ -384,7 +398,14 @@ class MessageItemFactory(private val colorProvider: ColorProvider, linkifyBody(formattedBody, callback) } return MessageTextItem_() - .message(message) + .apply { + if (hasBeenEdited) { + val spannable = annotateWithEdited(message, callback, informationData, editSummary) + message(spannable) + } else { + message(message) + } + } .informationData(informationData) .reactionPillCallback(callback) .avatarClickListener( From b8c3bdbbf61cc783c0cdc0a11cdb66ae846d2699 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 May 2019 14:27:57 +0200 Subject: [PATCH 6/7] Cleaning --- .../main/java/im/vector/riotredesign/core/di/AppModule.kt | 2 +- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 7fff0208..9505f80e 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -39,7 +39,7 @@ class AppModule(private val context: Context) { single { StringProvider(context.resources) } - + single { context.getSharedPreferences("im.vector.riot", MODE_PRIVATE) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 7a68937c..11941423 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -325,7 +325,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } } - private fun annotateWithEdited(linkifiedBody: CharSequence, callback: TimelineEventController.Callback?, informationData: MessageInformationData, editSummary: EditAggregatedSummary?): SpannableStringBuilder { + private fun annotateWithEdited(linkifiedBody: CharSequence, + callback: TimelineEventController.Callback?, + informationData: MessageInformationData, + editSummary: EditAggregatedSummary?): SpannableStringBuilder { val spannable = SpannableStringBuilder() spannable.append(linkifiedBody) val editedSuffix = "(edited)" From 118a4392a252f289e0a7208ea059548ebe2401e5 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 May 2019 15:33:16 +0200 Subject: [PATCH 7/7] Fix / Support redaction of a m.replace event + refactoring --- .../room/EventRelationsAggregationUpdater.kt | 81 ++++++++++++++++++- .../internal/session/room/RoomModule.kt | 2 +- .../session/room/prune/PruneEventTask.kt | 58 ++++--------- 3 files changed, 93 insertions(+), 48 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index ce87a586..08698c08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -20,9 +20,8 @@ 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.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.ReactionAggregatedSummaryEntity +import im.vector.matrix.android.internal.database.mapper.EventMapper +import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.where import io.realm.Realm @@ -123,7 +122,7 @@ internal class EventRelationsAggregationUpdater(private val credentials: Credent } } - private fun handleReaction(event: Event, roomId: String, realm: Realm) { + fun handleReaction(event: Event, roomId: String, realm: Realm) { event.content.toModel()?.let { content -> //rel_type must be m.annotation if (RelationType.ANNOTATION == content.relatesTo?.type) { @@ -153,4 +152,78 @@ internal class EventRelationsAggregationUpdater(private val credentials: Credent } } } + + /** + * Called when an event is deleted + */ + fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) { + Timber.d("Handle redaction of m.replace") + val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventId).findFirst() + if (eventSummary == null) { + Timber.w("Redaction of a replace targeting an unknown event $relatedEventId") + return + } + val sourceEvents = eventSummary.editSummary?.sourceEvents + val sourceToDiscard = sourceEvents?.indexOf(redacted.eventId) + if (sourceToDiscard == null) { + Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard") + return + } + //Need to remove this event from the redaction list and compute new aggregation state + sourceEvents.removeAt(sourceToDiscard) + val previousEdit = sourceEvents.mapNotNull { EventEntity.where(realm, it).findFirst() }.sortedBy { it.originServerTs }.lastOrNull() + if (previousEdit == null) { + //revert to original + eventSummary.editSummary?.deleteFromRealm() + } else { + //I have the last event + ContentMapper.map(previousEdit.content)?.toModel()?.newContent?.let { newContent -> + eventSummary.editSummary?.lastEditTs = previousEdit.originServerTs + ?: System.currentTimeMillis() + eventSummary.editSummary?.aggregatedContent = ContentMapper.map(newContent) + } ?: run { + Timber.e("Failed to udate edited summary") + //TODO how to reccover that + } + + } + } + + fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) { + Timber.d("REDACTION of reaction ${eventToPrune.eventId}") + //delete a reaction, need to update the annotation summary if any + val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() + ?: return + val eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return + + val reactionkey = reactionContent.relatesTo.key + Timber.d("REMOVE reaction for key $reactionkey") + val summary = EventAnnotationsSummaryEntity.where(realm, eventThatWasReacted).findFirst() + if (summary != null) { + summary.reactionsSummary.where() + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionkey) + .findFirst()?.let { summary -> + Timber.d("Find summary for key with ${summary.sourceEvents.size} known reactions (count:${summary.count})") + Timber.d("Known reactions ${summary.sourceEvents.joinToString(",")}") + if (summary.sourceEvents.contains(eventToPrune.eventId)) { + Timber.d("REMOVE reaction for key $reactionkey") + summary.sourceEvents.remove(eventToPrune.eventId) + Timber.d("Known reactions after ${summary.sourceEvents.joinToString(",")}") + summary.count = summary.count - 1 + if (eventToPrune.sender == userId) { + //Was it a redact on my reaction? + summary.addedByMe = false + } + if (summary.count == 0) { + //delete! + summary.deleteFromRealm() + } + } else { + Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known") + } + } + } else { + Timber.e("## Cannot find summary for key $reactionkey") + } + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 8473f45e..952909ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -109,7 +109,7 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - DefaultPruneEventTask(get()) as PruneEventTask + DefaultPruneEventTask(get(),get()) as PruneEventTask } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index 82949b41..44c62a09 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -17,18 +17,14 @@ package im.vector.matrix.android.internal.session.room.prune import arrow.core.Try import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.UnsignedData -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent +import im.vector.matrix.android.api.session.events.model.* +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.mapper.EventMapper -import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm @@ -44,7 +40,9 @@ internal interface PruneEventTask : Task { } -internal class DefaultPruneEventTask(private val monarchy: Monarchy) : PruneEventTask { +internal class DefaultPruneEventTask( + private val monarchy: Monarchy, + private val eventRelationsAggregationUpdater: EventRelationsAggregationUpdater) : PruneEventTask { override fun execute(params: PruneEventTask.Params): Try { return monarchy.tryTransactionSync { realm -> @@ -72,52 +70,26 @@ internal class DefaultPruneEventTask(private val monarchy: Monarchy) : PruneEven Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData ?: UnsignedData(null, null) + + //was this event a m.replace + val contentModel = ContentMapper.map(eventToPrune.content)?.toModel() + if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { + eventRelationsAggregationUpdater.handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) + } + val modified = unsignedData.copy(redactedEvent = redactionEvent) eventToPrune.content = ContentMapper.map(emptyMap()) eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) } EventType.REACTION -> { - Timber.d("REDACTION of reaction ${eventToPrune.eventId}") - //delete a reaction, need to update the annotation summary if any - val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() - ?: return - val eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return - - val reactionkey = reactionContent.relatesTo.key - Timber.d("REMOVE reaction for key $reactionkey") - val summary = EventAnnotationsSummaryEntity.where(realm, eventThatWasReacted).findFirst() - if (summary != null) { - summary.reactionsSummary.where() - .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionkey) - .findFirst()?.let { summary -> - Timber.d("Find summary for key with ${summary.sourceEvents.size} known reactions (count:${summary.count})") - Timber.d("Known reactions ${summary.sourceEvents.joinToString(",")}") - if (summary.sourceEvents.contains(eventToPrune.eventId)) { - Timber.d("REMOVE reaction for key $reactionkey") - summary.sourceEvents.remove(eventToPrune.eventId) - Timber.d("Known reactions after ${summary.sourceEvents.joinToString(",")}") - summary.count = summary.count - 1 - if (eventToPrune.sender == userId) { - //Was it a redact on my reaction? - summary.addedByMe = false - } - if (summary.count == 0) { - //delete! - summary.deleteFromRealm() - } - } else { - Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known") - } - } - } else { - Timber.e("## Cannot find summary for key $reactionkey") - } + eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId) } } } } + private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) {