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 c43ff438..3f7d27e3 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 @@ -20,6 +20,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest 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.session.room.EventRelationExtractor import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory @@ -58,8 +59,16 @@ internal class TimelineTest : InstrumentedTest { val paginationTask = FakePaginationTask(tokenChunkEventPersistor) val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor) val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID) - val timelineEventFactory = TimelineEventFactory(roomMemberExtractor) - return DefaultTimeline(ROOM_ID, initialEventId, monarchy.realmConfiguration, taskExecutor, getContextOfEventTask, timelineEventFactory, paginationTask, null) + val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor()) + return DefaultTimeline( + ROOM_ID, + initialEventId, + monarchy.realmConfiguration, + taskExecutor, + getContextOfEventTask, + timelineEventFactory, + paginationTask, + null) } @Test diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/AggregatedAnnotation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/AggregatedAnnotation.kt new file mode 100644 index 00000000..3f1c6199 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/AggregatedAnnotation.kt @@ -0,0 +1,42 @@ +/* + * 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.events.model + +import com.squareup.moshi.JsonClass + +/** + * + * { + * "chunk": [ + * { + * "type": "m.reaction", + * "key": "👍", + * "count": 3 + * } + * ], + * "limited": false, + * "count": 1 + * }, + * + */ + +@JsonClass(generateAdapter = true) +data class AggregatedAnnotation ( + override val limited: Boolean? = false, + override val count: Int? = 0, + val chunk: List? = null + +) : UnsignedRelationInfo \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/AggregatedRelations.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/AggregatedRelations.kt new file mode 100644 index 00000000..0f8d21f5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/AggregatedRelations.kt @@ -0,0 +1,53 @@ +/* + * 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.events.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * + * { + * "m.annotation": { + * "chunk": [ + * { + * "type": "m.reaction", + * "key": "👍", + * "count": 3 + * } + * ], + * "limited": false, + * "count": 1 + * }, + * "m.reference": { + * "chunk": [ + * { + * "type": "m.room.message", + * "event_id": "$some_event_id" + * } + * ], + * "limited": false, + * "count": 1 + * } + * } + * + */ + +@JsonClass(generateAdapter = true) +data class AggregatedRelations( + @Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null, + @Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/DefaultUnsignedRelationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/DefaultUnsignedRelationInfo.kt new file mode 100644 index 00000000..3e2df0aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/DefaultUnsignedRelationInfo.kt @@ -0,0 +1,26 @@ +/* + * 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.events.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class DefaultUnsignedRelationInfo( + override val limited: Boolean? = false, + override val count: Int? = 0, + val chunk: List>? = null + +) : UnsignedRelationInfo \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 4315fd59..f2b67ec7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -65,6 +65,11 @@ object EventType { const val CALL_ANSWER = "m.call.answer" const val CALL_HANGUP = "m.call.hangup" + // Relation Events + + const val REACTION = "m.reaction" + + private val STATE_EVENTS = listOf( STATE_ROOM_NAME, STATE_ROOM_TOPIC, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationChunkInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationChunkInfo.kt new file mode 100644 index 00000000..f4f1d866 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationChunkInfo.kt @@ -0,0 +1,35 @@ +/* + * 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.events.model + +import com.squareup.moshi.JsonClass + +/** + * + * { + * "type": "m.reaction", + * "key": "👍", + * "count": 3 + * } + * + */ + +@JsonClass(generateAdapter = true) +data class RelationChunkInfo( + val type: String, + val key: String, + val count: Int +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt new file mode 100644 index 00000000..56d4801c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt @@ -0,0 +1,31 @@ +/* + * 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.events.model + + +/** + * Constants defining known event relation types from Matrix specifications. + */ +object RelationType { + + /** Lets you define an event which annotates an existing event.*/ + const val ANNOTATION = "m.annotation" + /** Lets you define an event which replaces an existing event.*/ + const val REPLACE = "m.replace" + /** ets you define an event which references an existing event.*/ + const val REFERENCE = "m.reference" + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt index 6a2ea226..004495b5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt @@ -24,5 +24,6 @@ data class UnsignedData( @Json(name = "age") val age: Long?, @Json(name = "redacted_because") val redactedEvent: Event? = null, @Json(name = "transaction_id") val transactionId: String? = null, - @Json(name = "prev_content") val prevContent: Map? = null + @Json(name = "prev_content") val prevContent: Map? = null, + @Json(name = "m.relations") val relations: AggregatedRelations? ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedRelationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedRelationInfo.kt new file mode 100644 index 00000000..5b627b62 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedRelationInfo.kt @@ -0,0 +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.events.model + + +interface UnsignedRelationInfo { + val limited : Boolean? + val count: Int? +} \ No newline at end of file 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 new file mode 100644 index 00000000..15008439 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt @@ -0,0 +1,7 @@ +package im.vector.matrix.android.api.session.room.model + + +data class EventAnnotationsSummary( + var eventId: String, + var reactionsSummary: List +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReactionAggregatedSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReactionAggregatedSummary.kt new file mode 100644 index 00000000..a6948cd1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReactionAggregatedSummary.kt @@ -0,0 +1,9 @@ +package im.vector.matrix.android.api.session.room.model + +data class ReactionAggregatedSummary( + val key: String, // "👍" + val count: Int, // 8 + val addedByMe: Boolean, // true + val firstTimestamp: Long, // unix timestamp + val sourceEvents: List +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionContent.kt new file mode 100644 index 00000000..02d4164d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionContent.kt @@ -0,0 +1,9 @@ +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 ReactionContent( + @Json(name = "m.relates_to") val relatesTo: ReactionInfo? = null +) 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 new file mode 100644 index 00000000..14444508 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionInfo.kt @@ -0,0 +1,11 @@ +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 ReactionInfo( + @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 new file mode 100644 index 00000000..ccdd10a0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationContent.kt @@ -0,0 +1,6 @@ +package im.vector.matrix.android.api.session.room.model.annotation + +interface RelationContent { + 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 new file mode 100644 index 00000000..1d00b1a4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/RelationDefaultContent.kt @@ -0,0 +1,8 @@ +package im.vector.matrix.android.api.session.room.model.annotation + +import com.squareup.moshi.Json + +data class RelationDefaultContent( + @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/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 7c73d761..4e583346 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room.timeline 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.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.send.SendState @@ -32,7 +33,8 @@ data class TimelineEvent( val displayIndex: Int, val senderName: String?, val senderAvatar: String?, - val sendState: SendState + val sendState: SendState, + val annotations: EventAnnotationsSummary? = null ) { val metadata = HashMap() 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 new file mode 100644 index 00000000..38388950 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -0,0 +1,26 @@ +package im.vector.matrix.android.internal.database.mapper + +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 + +internal object EventAnnotationsSummaryMapper { + fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary { + return EventAnnotationsSummary( + eventId = annotationsSummary.eventId, + reactionsSummary = annotationsSummary.reactionsSummary.toList().map { + ReactionAggregatedSummary( + it.key, + it.count, + it.addedByMe, + it.firstTimestamp, + it.sourceEvents.toList() + ) + } + ) + } +} + +internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary { + return EventAnnotationsSummaryMapper.map(this) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index 3d83881a..a0af56b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -19,12 +19,15 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.di.MoshiProvider internal object EventMapper { fun map(event: Event, roomId: String): EventEntity { + val uds = if (event.unsignedData == null) null + else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData) val eventEntity = EventEntity() eventEntity.eventId = event.eventId ?: "" eventEntity.roomId = event.roomId ?: roomId @@ -37,10 +40,13 @@ internal object EventMapper { eventEntity.originServerTs = event.originServerTs eventEntity.redacts = event.redacts eventEntity.age = event.unsignedData?.age ?: event.originServerTs + eventEntity.unsignedData = uds return eventEntity } fun map(eventEntity: EventEntity): Event { + var ud = if (eventEntity.unsignedData.isNullOrBlank()) null + else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData) return Event( type = eventEntity.type, eventId = eventEntity.eventId, @@ -50,7 +56,7 @@ internal object EventMapper { sender = eventEntity.sender, stateKey = eventEntity.stateKey, roomId = eventEntity.roomId, - unsignedData = UnsignedData(eventEntity.age), + unsignedData = ud, redacts = eventEntity.redacts ) } 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 new file mode 100644 index 00000000..bd4c2077 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt @@ -0,0 +1,32 @@ +/* + * 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 +import io.realm.annotations.PrimaryKey + + +internal open class EventAnnotationsSummaryEntity( + @PrimaryKey + var eventId: String = "", + var roomId: String? = null, + var reactionsSummary: RealmList = RealmList() +) : 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/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index c2fa36c1..38f4f451 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -36,6 +36,7 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI var originServerTs: Long? = null, @Index var sender: String? = null, var age: Long? = 0, + var unsignedData: String? = null, var redacts: String? = null, @Index var stateIndex: Int = 0, @Index var displayIndex: Int = 0, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReactionAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReactionAggregatedSummaryEntity.kt new file mode 100644 index 00000000..93ec8bd7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReactionAggregatedSummaryEntity.kt @@ -0,0 +1,24 @@ +package im.vector.matrix.android.internal.database.model + +import io.realm.RealmList +import io.realm.RealmObject + +/** + * Aggregated Summary of a reaction. + */ +internal open class ReactionAggregatedSummaryEntity( + // The reaction String 😀 + var key: String = "", + // Number of time this reaction was selected + var count: Int = 0, + // Did the current user sent this reaction + var addedByMe: Boolean = false, + // The first time this reaction was added (for ordering purpose) + var firstTimestamp: Long = 0, + // 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() +) : 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 a5042634..dc3ad001 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 @@ -33,6 +33,8 @@ import io.realm.annotations.RealmModule RoomSummaryEntity::class, RoomTagEntity::class, SyncEntity::class, - UserEntity::class + UserEntity::class, + EventAnnotationsSummaryEntity::class, + ReactionAggregatedSummaryEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt new file mode 100644 index 00000000..b738af35 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -0,0 +1,27 @@ +package im.vector.matrix.android.internal.database.query + +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntityFields +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where + +internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { + val query = realm.where() + query.equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) + return query +} + +internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery { + val query = realm.where() + if (roomId != null) { + query.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) + } + return query +} + + +internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, eventId: String): EventAnnotationsSummaryEntity { + val obj = realm.createObject(EventAnnotationsSummaryEntity::class.java, eventId) + return obj +} \ No newline at end of file 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 becc0cfe..45eb7831 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,6 +17,8 @@ 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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index accb91ec..7981f931 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.session.filter.* import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.room.DefaultRoomService +import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.RoomAvatarResolver import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver @@ -102,6 +103,10 @@ internal class SessionModule(private val sessionParams: SessionParams) { RoomSummaryUpdater(get(), get(), get()) } + scope(DefaultSession.SCOPE) { + EventRelationsAggregationUpdater() + } + scope(DefaultSession.SCOPE) { DefaultRoomService(get(), get(), get(), get()) as RoomService } 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 new file mode 100644 index 00000000..6fc1cb86 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt @@ -0,0 +1,16 @@ +package im.vector.matrix.android.internal.session.room + +import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary +import im.vector.matrix.android.internal.database.mapper.asDomain +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.query.where +import io.realm.Realm + + +internal class EventRelationExtractor { + + fun extractFrom(event: EventEntity, realm: Realm = event.realm): EventAnnotationsSummary? { + return EventAnnotationsSummaryEntity.where(realm, event.eventId).findFirst()?.asDomain() + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..13c62865 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -0,0 +1,81 @@ +package im.vector.matrix.android.internal.session.room + +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.internal.database.model.EventAnnotationsSummaryEntity +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.where +import io.realm.Realm +import timber.log.Timber + + +internal class EventRelationsAggregationUpdater { + + fun update(realm: Realm, roomId: String, events: List?) { + events?.forEach { event -> + when (event.type) { + EventType.REACTION -> { + //we got a reaction!! + Timber.v("###REACTION in room $roomId") + handleReaction(event, roomId, realm) + } + EventType.MESSAGE -> { + event.unsignedData?.relations?.annotations?.let { + Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") + handleInitialAggregatedRelations(event, roomId, it, realm) + } + //TODO message edits + } + } + } + } + + private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) { + aggregation.chunk?.forEach { + if (it.type == EventType.REACTION) { + val eventId = event.eventId ?: "" + val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + if (existing == null) { + val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId) + eventSummary.roomId = roomId + val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) + sum.key = it.key + sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order? + sum.count = it.count + eventSummary.reactionsSummary.add(sum) + } else { + //TODO how to handle that + } + } + } + } + + private 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) { + val reaction = content.relatesTo.key + val eventId = content.relatesTo.eventId + val eventSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + ?: EventAnnotationsSummaryEntity.create(realm, eventId).apply { this.roomId = roomId } + + var sum = eventSummary.reactionsSummary.find { it.key == reaction } + if (sum == null) { + sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) + sum.key = reaction + sum.firstTimestamp = event.originServerTs ?: 0 + sum.count = 1 + eventSummary.reactionsSummary.add(sum) + } else { + //is this a known event (is possible? pagination?) + if (!sum.sourceEvents.contains(eventId)) { + sum.count += 1 + sum.sourceEvents.add(eventId) + } + } + + } + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 24e51355..7bf9d964 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -46,7 +46,7 @@ internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask, fun instantiate(roomId: String): Room { val roomMemberExtractor = SenderRoomMemberExtractor(roomId) - val timelineEventFactory = TimelineEventFactory(roomMemberExtractor) + val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor()) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask) val sendService = DefaultSendService(roomId, eventFactory, monarchy) val stateService = DefaultStateService(roomId, sendStateTask, taskExecutor) 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 973b7381..07e8c6c1 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 @@ -51,7 +51,7 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - TokenChunkEventPersistor(get()) + TokenChunkEventPersistor(get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 58274288..f7297f27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -27,29 +27,22 @@ 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.api.util.CancelableBag import im.vector.matrix.android.api.util.addTo -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.ChunkEntityFields -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.database.query.whereInRoom import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.Debouncer -import io.realm.OrderedCollectionChangeSet -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmConfiguration -import io.realm.RealmQuery -import io.realm.RealmResults -import io.realm.Sort +import io.realm.* import timber.log.Timber import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference import kotlin.collections.ArrayList +import kotlin.collections.HashMap private const val INITIAL_LOAD_SIZE = 20 @@ -92,19 +85,23 @@ internal class DefaultTimeline( private var nextDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN private val isLive = initialEventId == null private val builtEvents = Collections.synchronizedList(ArrayList()) + private val builtEventsIdMap = Collections.synchronizedMap(HashMap()) private val backwardsPaginationState = AtomicReference(PaginationState()) private val forwardsPaginationState = AtomicReference(PaginationState()) + private lateinit var eventRelations: RealmResults + private val eventsChangeListener = OrderedRealmCollectionChangeListener> { _, changeSet -> - if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL ) { + if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) { handleInitialLoad() } else { // If changeSet has deletion we are having a gap, so we clear everything - if(changeSet.deletionRanges.isNotEmpty()){ + if (changeSet.deletionRanges.isNotEmpty()) { prevDisplayIndex = DISPLAY_INDEX_UNKNOWN nextDisplayIndex = DISPLAY_INDEX_UNKNOWN builtEvents.clear() + builtEventsIdMap.clear() timelineEventFactory.clear() } changeSet.insertionRanges.forEach { range -> @@ -130,6 +127,38 @@ internal class DefaultTimeline( } } + private val relationsListener = OrderedRealmCollectionChangeListener> { collection, changeSet -> + + var hasChange = false + + (changeSet.insertions + changeSet.changes).forEach { + val eventRelations = collection[it] + if (eventRelations != null) { + builtEventsIdMap[eventRelations.eventId]?.let { builtIndex -> + //Update the relation of existing event + builtEvents[builtIndex]?.let { te -> + builtEvents[builtIndex] = te.copy(annotations = eventRelations.asDomain()) + hasChange = true + } + } + } + } + changeSet.deletions?.forEach { + val eventRelations = collection[it] + if (eventRelations != null) { + builtEventsIdMap[eventRelations.eventId]?.let { builtIndex -> + //Update the relation of existing event + builtEvents[builtIndex]?.let { te -> + builtEvents[builtIndex] = te.copy(annotations = null) + hasChange = true + } + } + } + } + if (hasChange) + postSnapshot() + } + // Public methods ****************************************************************************** override fun paginate(direction: Timeline.Direction, count: Int) { @@ -146,6 +175,7 @@ internal class DefaultTimeline( } } + override fun start() { if (isStarted.compareAndSet(false, true)) { Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") @@ -171,6 +201,10 @@ internal class DefaultTimeline( .also { it.addChangeListener(eventsChangeListener) } isReady.set(true) + + eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId) + .findAllAsync() + .also { it.addChangeListener(relationsListener) } } } } @@ -242,6 +276,7 @@ internal class DefaultTimeline( } else { updatePaginationState(direction) { it.copy(isPaginating = false, requestedCount = 0) } } + return !shouldFetchMore } @@ -266,14 +301,14 @@ internal class DefaultTimeline( private fun getPaginationState(direction: Timeline.Direction): PaginationState { return when (direction) { - Timeline.Direction.FORWARDS -> forwardsPaginationState.get() + Timeline.Direction.FORWARDS -> forwardsPaginationState.get() Timeline.Direction.BACKWARDS -> backwardsPaginationState.get() } } private fun updatePaginationState(direction: Timeline.Direction, update: (PaginationState) -> PaginationState) { val stateReference = when (direction) { - Timeline.Direction.FORWARDS -> forwardsPaginationState + Timeline.Direction.FORWARDS -> forwardsPaginationState Timeline.Direction.BACKWARDS -> backwardsPaginationState } val currentValue = stateReference.get() @@ -316,9 +351,9 @@ internal class DefaultTimeline( private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { val token = getTokenLive(direction) ?: return val params = PaginationTask.Params(roomId = roomId, - from = token, - direction = direction.toPaginationDirection(), - limit = limit) + from = token, + direction = direction.toPaginationDirection(), + limit = limit) Timber.v("Should fetch $limit items $direction") paginationTask.configureWith(params) @@ -384,6 +419,7 @@ internal class DefaultTimeline( val timelineEvent = timelineEventFactory.create(eventEntity) val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size builtEvents.add(position, timelineEvent) + builtEventsIdMap[eventEntity.eventId] = position } Timber.v("Built ${offsetResults.size} items from db") return offsetResults.size diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt index 7fe38391..8fd15c18 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt @@ -19,10 +19,13 @@ package im.vector.matrix.android.internal.session.room.timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.session.room.EventRelationExtractor import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import io.realm.Realm -internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomMemberExtractor) { +internal class TimelineEventFactory( + private val roomMemberExtractor: SenderRoomMemberExtractor, + private val relationExtractor: EventRelationExtractor) { private val cached = mutableMapOf() @@ -30,20 +33,22 @@ internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomM val sender = eventEntity.sender val cacheKey = sender + eventEntity.stateIndex val senderData = cached.getOrPut(cacheKey) { - val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity,realm) + val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm) SenderData(senderRoomMember?.displayName, senderRoomMember?.avatarUrl) } + val relations = relationExtractor.extractFrom(eventEntity, realm) return TimelineEvent( eventEntity.asDomain(), eventEntity.localId, eventEntity.displayIndex, senderData.senderName, senderData.senderAvatar, - eventEntity.sendState + eventEntity.sendState, + relations ) } - fun clear(){ + fun clear() { cached.clear() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 283b807c..ec85c558 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -24,12 +24,14 @@ import im.vector.matrix.android.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.helper.deleteOnCascade import im.vector.matrix.android.internal.database.helper.isUnlinked import im.vector.matrix.android.internal.database.helper.merge +import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.kotlin.createObject import timber.log.Timber @@ -37,7 +39,8 @@ import timber.log.Timber /** * Insert Chunk in DB, and eventually merge with existing chunk event */ -internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { +internal class TokenChunkEventPersistor(private val monarchy: Monarchy, + private val eventRelationsAggregationUpdater: EventRelationsAggregationUpdater) { /** *
@@ -147,6 +150,9 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {
                     } else {
                         Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
                         currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked())
+
+                        //Event
+                        eventRelationsAggregationUpdater.update(realm,roomId,receivedChunk.events.toList())
                         // Then we merge chunks if needed
                         if (currentChunk != prevChunk && prevChunk != null) {
                             currentChunk = handleMerge(roomEntity, direction, currentChunk, prevChunk)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt
index 24dff339..d5ec714c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt
@@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
 import im.vector.matrix.android.internal.database.query.find
 import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
 import im.vector.matrix.android.internal.database.query.where
+import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
 import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
 import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
 import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
@@ -45,7 +46,8 @@ import timber.log.Timber
 internal class RoomSyncHandler(private val monarchy: Monarchy,
                                private val readReceiptHandler: ReadReceiptHandler,
                                private val roomSummaryUpdater: RoomSummaryUpdater,
-                               private val roomTagHandler: RoomTagHandler) {
+                               private val roomTagHandler: RoomTagHandler,
+                               private val eventRelationsAggregationUpdater: EventRelationsAggregationUpdater) {
 
     sealed class HandlingStrategy {
         data class JOINED(val data: Map) : HandlingStrategy()
@@ -120,6 +122,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
             }
         }
         roomSummaryUpdater.update(realm, roomId, roomSync.summary, roomSync.unreadNotifications)
+        eventRelationsAggregationUpdater.update(realm,roomId,roomSync.timeline?.events)
 
         if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
             handleEphemeral(realm, roomId, roomSync.ephemeral)
@@ -174,6 +177,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
         lastChunk?.isLastForward = false
         chunkEntity.isLastForward = true
         chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS, stateIndexOffset)
+
+        //update eventAnnotationSummary here?
+
         return chunkEntity
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt
index d0f60405..d6014061 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt
@@ -40,7 +40,7 @@ internal class SyncModule {
         }
 
         scope(DefaultSession.SCOPE) {
-            RoomSyncHandler(get(), get(), get(), get())
+            RoomSyncHandler(get(), get(), get(), get(), get())
         }
 
         scope(DefaultSession.SCOPE) {
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 f8ba03de..8afdf200 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
@@ -71,7 +71,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
         val avatarUrl = event.senderAvatar
         val memberName = event.senderName ?: event.root.sender ?: ""
         val formattedMemberName = span(memberName) {
-            textColor = colorProvider.getColor(AvatarRenderer.getColorFromUserId(event.root.sender ?: ""))
+            textColor = colorProvider.getColor(AvatarRenderer.getColorFromUserId(event.root.sender
+                    ?: ""))
         }
         val informationData = MessageInformationData(eventId = eventId,
                 senderId = event.root.sender ?: "",
@@ -79,7 +80,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 time = time,
                 avatarUrl = avatarUrl,
                 memberName = formattedMemberName,
-                showInformation = showInformation)
+                showInformation = showInformation,
+                orderedReactionList = event.annotations?.reactionsSummary?.map { Triple(it.key, it.count, it.addedByMe) }
+        )
 
         //Test for reactions UX
         //informationData.orderedReactionList = listOf( Triple("👍",1,false), Triple("👎",2,false))