From 00f316ba5d61a95be825891c385de877c1f2af2f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 26 Dec 2019 19:51:03 +0100 Subject: [PATCH 01/26] Room members: introduce RoomMemberEntity to be able to query. Still work to do. --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 8 +-- .../session/room/members/MembershipService.kt | 7 +- .../api/session/room/model/Membership.kt | 9 +++ .../api/session/room/model/RoomMember.kt | 23 ++----- .../session/room/model/RoomMemberContent.kt | 38 +++++++++++ .../matrix/android/api/util/MatrixItem.kt | 2 + .../database/helper/RoomEntityHelper.kt | 3 +- .../helper/TimelineEventEntityHelper.kt | 5 +- .../database/mapper/RoomMemberMapper.kt | 36 +++++++++++ .../database/model/RoomMemberEntity.kt | 43 +++++++++++++ .../database/model/RoomSummaryEntity.kt | 1 + .../database/model/SessionRealmModule.kt | 3 +- .../database/query/RoomMemberEntityQueries.kt | 36 +++++++++++ .../session/room/RoomAvatarResolver.kt | 12 ++-- .../session/room/RoomSummaryUpdater.kt | 6 +- .../membership/DefaultMembershipService.kt | 18 +++--- .../room/membership/LoadRoomMembersTask.kt | 7 +- .../membership/RoomDisplayNameResolver.kt | 49 +++++++------- .../membership/RoomMemberEntityFactory.kt | 36 +++++++++++ .../room/membership/RoomMemberEventHandler.kt | 47 ++++++++++++++ .../session/room/membership/RoomMembers.kt | 64 +++++++------------ .../room/timeline/TokenChunkEventPersistor.kt | 7 +- .../internal/session/sync/RoomSyncHandler.kt | 12 ++-- .../sync/UserAccountDataSyncHandler.kt | 5 +- .../session/user/UserEntityFactory.kt | 23 ++----- .../AutocompleteMemberController.kt} | 10 +-- .../AutocompleteMemberPresenter.kt} | 22 +++---- .../home/room/detail/RoomDetailFragment.kt | 23 +++---- .../detail/composer/TextComposerViewModel.kt | 16 ++--- .../detail/composer/TextComposerViewState.kt | 3 +- .../timeline/format/NoticeEventFormatter.kt | 8 +-- .../notifications/NotifiableEventResolver.kt | 3 +- 32 files changed, 393 insertions(+), 192 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt rename vector/src/main/java/im/vector/riotx/features/autocomplete/{user/AutocompleteUserController.kt => member/AutocompleteMemberController.kt} (80%) rename vector/src/main/java/im/vector/riotx/features/autocomplete/{user/AutocompleteUserPresenter.kt => member/AutocompleteMemberPresenter.kt} (66%) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index e5ebc536ff..bf4e924cf0 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -17,9 +17,7 @@ package im.vector.matrix.rx import im.vector.matrix.android.api.session.room.Room -import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary -import im.vector.matrix.android.api.session.room.model.ReadReceipt -import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.notification.RoomNotificationState import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -33,8 +31,8 @@ class RxRoom(private val room: Room) { return room.getRoomSummaryLive().asObservable() } - fun liveRoomMemberIds(): Observable> { - return room.getRoomMemberIdsLive().asObservable() + fun liveRoomMembers(memberships: List): Observable> { + return room.getRoomMembersLive(memberships).asObservable() } fun liveAnnotationSummary(eventId: String): Observable> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 34af2cf572..12f0378af7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room.members import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.util.Cancelable @@ -41,11 +42,11 @@ interface MembershipService { fun getRoomMember(userId: String): RoomMember? /** - * Return all the roomMembers ids of the room - * + * Return all the roomMembers of the room filtered by memberships + * @param memberships list of accepted memberships * @return a [LiveData] of roomMember list. */ - fun getRoomMemberIdsLive(): LiveData> + fun getRoomMembersLive(memberships: List): LiveData> fun getNumberOfJoinedMembers(): Int diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt index 1894effc7a..4d35d3b4dd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt @@ -43,4 +43,13 @@ enum class Membership(val value: String) { fun isLeft(): Boolean { return this == KNOCK || this == LEAVE || this == BAN } + + companion object { + fun activeMemberships(): List { + return listOf(INVITE, JOIN) + } + } + } + + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt index 6a4d8e3c94..994c27be4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt @@ -16,23 +16,12 @@ package im.vector.matrix.android.api.session.room.model -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import im.vector.matrix.android.api.session.events.model.UnsignedData - /** - * Class representing the EventType.STATE_ROOM_MEMBER state event content + * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content */ -@JsonClass(generateAdapter = true) data class RoomMember( - @Json(name = "membership") val membership: Membership, - @Json(name = "reason") val reason: String? = null, - @Json(name = "displayname") val displayName: String? = null, - @Json(name = "avatar_url") val avatarUrl: String? = null, - @Json(name = "is_direct") val isDirect: Boolean = false, - @Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null, - @Json(name = "unsigned") val unsignedData: UnsignedData? = null -) { - val safeReason - get() = reason?.takeIf { it.isNotBlank() } -} + val membership: Membership, + val userId: String, + val displayName: String? = null, + val avatarUrl: String? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberContent.kt new file mode 100644 index 0000000000..deeeb8ba52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberContent.kt @@ -0,0 +1,38 @@ +/* + * 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 com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.UnsignedData + +/** + * Class representing the EventType.STATE_ROOM_MEMBER state event content + */ +@JsonClass(generateAdapter = true) +data class RoomMemberContent( + @Json(name = "membership") val membership: Membership, + @Json(name = "reason") val reason: String? = null, + @Json(name = "displayname") val displayName: String? = null, + @Json(name = "avatar_url") val avatarUrl: String? = null, + @Json(name = "is_direct") val isDirect: Boolean = false, + @Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null, + @Json(name = "unsigned") val unsignedData: UnsignedData? = null +) { + val safeReason + get() = reason?.takeIf { it.isNotBlank() } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt index 4c8082b77e..d6ef522f41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.user.model.User @@ -146,3 +147,4 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl) fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl) +fun RoomMember.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 948af2af96..19c4715faa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -60,7 +60,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) { this.sendState = SendState.UNSENT } val roomMembers = RoomMembers(realm, roomId) - val myUser = roomMembers.get(senderId) + val myUser = roomMembers.getLastRoomMember(senderId) val localId = TimelineEventEntity.nextId(realm) val timelineEventEntity = TimelineEventEntity(localId).also { it.root = eventEntity @@ -69,7 +69,6 @@ internal fun RoomEntity.addSendingEvent(event: Event) { it.senderName = myUser?.displayName it.senderAvatar = myUser?.avatarUrl it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName) - it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst() } sendingTimelineEvents.add(0, timelineEventEntity) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt index 36ed2f7edf..c5e2aef910 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.next @@ -64,7 +65,7 @@ internal fun TimelineEventEntity.updateSenderData() { senderRoomMemberPrevContent = senderMembershipEvent?.prevContent } - ContentMapper.map(senderRoomMemberContent).toModel()?.also { + ContentMapper.map(senderRoomMemberContent).toModel()?.also { this.senderAvatar = it.avatarUrl this.senderName = it.displayName this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) @@ -72,7 +73,7 @@ internal fun TimelineEventEntity.updateSenderData() { // We try to fallback on prev content if we got a room member state events with null fields if (root?.type == EventType.STATE_ROOM_MEMBER) { - ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also { + ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also { if (this.senderAvatar == null && it.avatarUrl != null) { this.senderAvatar = it.avatarUrl } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberMapper.kt new file mode 100644 index 0000000000..a458c5e506 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberMapper.kt @@ -0,0 +1,36 @@ +/* + * 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.mapper + +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.internal.database.model.RoomMemberEntity + +internal object RoomMemberMapper { + + fun map(roomMemberEntity: RoomMemberEntity): RoomMember { + return RoomMember( + userId = roomMemberEntity.userId, + avatarUrl = roomMemberEntity.avatarUrl, + displayName = roomMemberEntity.displayName, + membership = roomMemberEntity.membership + ) + } +} + +internal fun RoomMemberEntity.asDomain(): RoomMember { + return RoomMemberMapper.map(this) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt new file mode 100644 index 0000000000..c532857fe1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt @@ -0,0 +1,43 @@ +/* + * 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 im.vector.matrix.android.api.session.room.model.Membership +import io.realm.RealmObject +import io.realm.annotations.Index +import io.realm.annotations.PrimaryKey + +internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "", + @Index var userId: String = "", + @Index var roomId: String = "", + var displayName: String = "", + var avatarUrl: String = "", + var reason: String? = null, + var isDirect: Boolean = false +) : RealmObject() { + + private var membershipStr: String = Membership.NONE.name + var membership: Membership + get() { + return Membership.valueOf(membershipStr) + } + set(value) { + membershipStr = value.name + } + + companion object +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index 406c8700b6..2fa892d874 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -42,6 +42,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, var canonicalAlias: String? = null, var aliases: RealmList = RealmList(), + // this is required for querying var flatAliases: String = "" ) : RealmObject() { 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 6059d3faf7..07ff1df005 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 @@ -49,6 +49,7 @@ import io.realm.annotations.RealmModule ReadMarkerEntity::class, UserDraftsEntity::class, DraftEntity::class, - HomeServerCapabilitiesEntity::class + HomeServerCapabilitiesEntity::class, + RoomMemberEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt new file mode 100644 index 0000000000..3eb1da87a1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt @@ -0,0 +1,36 @@ +/* + * 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.query + +import im.vector.matrix.android.internal.database.model.RoomMemberEntity +import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields +import im.vector.matrix.android.internal.database.model.UserEntity +import im.vector.matrix.android.internal.database.model.UserEntityFields +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where + +internal fun RoomMemberEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery { + val query = realm + .where() + .equalTo(RoomMemberEntityFields.ROOM_ID, roomId) + + if (userId != null) { + query.equalTo(RoomMemberEntityFields.USER_ID, userId) + } + return query +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt index c9d5aeb6bb..acfb57bd46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.internal.database.mapper.ContentMapper 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.RoomMemberEntityFields import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId @@ -47,19 +48,16 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona return@doWithRealm } val roomMembers = RoomMembers(realm, roomId) - val members = roomMembers.queryRoomMembersEvent().findAll() + val members = roomMembers.queryActiveRoomMembersEvent().findAll() // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) if (members.size == 1) { - res = members.firstOrNull()?.toRoomMember()?.avatarUrl + res = members.firstOrNull()?.avatarUrl } else if (members.size == 2) { - val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, userId).findFirst() - res = firstOtherMember?.toRoomMember()?.avatarUrl + val firstOtherMember = members.where().notEqualTo(RoomMemberEntityFields.USER_ID, userId).findFirst() + res = firstOtherMember?.avatarUrl } } return res } - private fun EventEntity?.toRoomMember(): RoomMember? { - return ContentMapper.map(this?.content).toModel() - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index e9f33a547b..536484de5a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -24,8 +24,8 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.database.model.* 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.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.* @@ -113,10 +113,10 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId if (updateMembers) { val otherRoomMembers = RoomMembers(realm, roomId) .queryRoomMembersEvent() - .notEqualTo(EventEntityFields.STATE_KEY, userId) + .notEqualTo(RoomMemberEntityFields.USER_ID, userId) .findAll() .asSequence() - .map { it.stateKey } + .map { it.userId } roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index 00c1c2c4ca..2b89f86d3e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -21,7 +21,6 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomMember @@ -33,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRo import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.fetchCopied +import io.realm.Realm internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String, private val monarchy: Monarchy, @@ -58,29 +58,27 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr } override fun getRoomMember(userId: String): RoomMember? { - val eventEntity = monarchy.fetchCopied { - RoomMembers(it, roomId).queryRoomMemberEvent(userId).findFirst() + val roomMemberEntity = monarchy.fetchCopied { + RoomMembers(it, roomId).getLastRoomMember(userId) } - return eventEntity?.asDomain()?.content.toModel() + return roomMemberEntity?.asDomain() } - override fun getRoomMemberIdsLive(): LiveData> { + override fun getRoomMembersLive(memberships: List): LiveData> { return monarchy.findAllMappedWithChanges( { RoomMembers(it, roomId).queryRoomMembersEvent() }, { - it.stateKey!! + it.asDomain() } ) } override fun getNumberOfJoinedMembers(): Int { - var result = 0 - monarchy.runTransactionSync { - result = RoomMembers(it, roomId).getNumberOfJoinedMembers() + return Realm.getInstance(monarchy.realmConfiguration).use { + RoomMembers(it, roomId).getNumberOfJoinedMembers() } - return result } override fun invite(userId: String, reason: String?, callback: MatrixCallback): Cancelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index 7d9332ee84..6affd120a8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -44,7 +44,8 @@ internal interface LoadRoomMembersTask : Task internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI, private val monarchy: Monarchy, private val syncTokenStore: SyncTokenStore, - private val roomSummaryUpdater: RoomSummaryUpdater + private val roomSummaryUpdater: RoomSummaryUpdater, + private val roomMemberEventHandler: RoomMemberEventHandler ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { @@ -66,9 +67,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP for (roomMemberEvent in response.roomMemberEvents) { roomEntity.addStateEvent(roomMemberEvent) - UserEntityFactory.createOrNull(roomMemberEvent)?.also { - realm.insertOrUpdate(it) - } + roomMemberEventHandler.handle(realm, roomId, roomMemberEvent) } roomEntity.chunks.flatMap { it.timelineEvents }.forEach { it.updateSenderData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt index 21270308ed..9382fbc54a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -23,9 +23,10 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.database.model.* 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.model.RoomMemberEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where @@ -75,43 +76,46 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: } val roomMembers = RoomMembers(realm, roomId) - val loadedMembers = roomMembers.queryRoomMembersEvent().findAll() + val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll() if (roomEntity?.membership == Membership.INVITE) { - val inviteMeEvent = roomMembers.queryRoomMemberEvent(userId).findFirst() + val inviteMeEvent = roomMembers.getLastStateEvent(userId) val inviterId = inviteMeEvent?.sender name = if (inviterId != null) { - val inviterMemberEvent = loadedMembers.where() - .equalTo(EventEntityFields.STATE_KEY, inviterId) + activeMembers.where() + .equalTo(RoomMemberEntityFields.USER_ID, inviterId) .findFirst() - inviterMemberEvent?.toRoomMember()?.displayName + ?.displayName } else { context.getString(R.string.room_displayname_room_invite) } } else if (roomEntity?.membership == Membership.JOIN) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - val otherMembersSubset: List = if (roomSummary?.heroes?.isNotEmpty() == true) { - roomSummary.heroes.mapNotNull { - roomMembers.getStateEvent(it) + val otherMembersSubset: List = if (roomSummary?.heroes?.isNotEmpty() == true) { + roomSummary.heroes.mapNotNull { userId -> + roomMembers.getLastRoomMember(userId)?.takeIf { + it.membership == Membership.INVITE || it.membership == Membership.JOIN + } } } else { - loadedMembers.where() - .notEqualTo(EventEntityFields.STATE_KEY, userId) + activeMembers.where() + .notEqualTo(RoomMemberEntityFields.USER_ID, userId) .limit(3) .findAll() + .createSnapshot() } - val otherMembersCount = roomMembers.getNumberOfMembers() - 1 + val otherMembersCount = otherMembersSubset.count() name = when (otherMembersCount) { 0 -> context.getString(R.string.room_displayname_empty_room) 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) 2 -> context.getString(R.string.room_displayname_two_members, - resolveRoomMemberName(otherMembersSubset[0], roomMembers), - resolveRoomMemberName(otherMembersSubset[1], roomMembers) + resolveRoomMemberName(otherMembersSubset[0], roomMembers), + resolveRoomMemberName(otherMembersSubset[1], roomMembers) ) else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members, - roomMembers.getNumberOfJoinedMembers() - 1, - resolveRoomMemberName(otherMembersSubset[0], roomMembers), - roomMembers.getNumberOfJoinedMembers() - 1) + roomMembers.getNumberOfJoinedMembers() - 1, + resolveRoomMemberName(otherMembersSubset[0], roomMembers), + roomMembers.getNumberOfJoinedMembers() - 1) } } return@doWithRealm @@ -119,19 +123,14 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: return name ?: roomId } - private fun resolveRoomMemberName(eventEntity: EventEntity?, + private fun resolveRoomMemberName(roomMember: RoomMemberEntity?, roomMembers: RoomMembers): String? { - if (eventEntity == null) return null - val roomMember = eventEntity.toRoomMember() ?: return null + if (roomMember == null) return null val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName) return if (isUnique) { roomMember.displayName } else { - "${roomMember.displayName} (${eventEntity.stateKey})" + "${roomMember.displayName} (${roomMember.userId})" } } - - private fun EventEntity?.toRoomMember(): RoomMember? { - return ContentMapper.map(this?.content).toModel() - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt new file mode 100644 index 0000000000..933431b77f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt @@ -0,0 +1,36 @@ +/* + * 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.membership + +import im.vector.matrix.android.api.session.room.model.RoomMemberContent +import im.vector.matrix.android.internal.database.model.RoomMemberEntity + +internal object RoomMemberEntityFactory { + + fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberEntity { + val primaryKey = "${roomId}_${userId}" + return RoomMemberEntity( + primaryKey = primaryKey, + userId = userId, + roomId = roomId, + displayName = roomMember.displayName ?: "", + avatarUrl = roomMember.avatarUrl ?: "" + ).apply { + membership = roomMember.membership + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt new file mode 100644 index 0000000000..73c4c8cdc1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt @@ -0,0 +1,47 @@ +/* + * 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.membership + +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.toModel +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomMemberContent +import im.vector.matrix.android.internal.session.user.UserEntityFactory +import io.realm.Realm +import javax.inject.Inject + +internal class RoomMemberEventHandler @Inject constructor() { + + fun handle(realm: Realm, roomId: String, event: Event): Boolean { + if (event.type != EventType.STATE_ROOM_MEMBER) { + return false + } + val roomMember = event.content.toModel() ?: return false + val userId = event.stateKey ?: return false + val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember) + realm.insertOrUpdate(roomMemberEntity) + if (roomMember.membership == Membership.JOIN || roomMember.membership == Membership.INVITE) { + val userEntity = UserEntityFactory.create(userId, roomMember) + realm.insertOrUpdate(userEntity) + } + return true + } + + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt index 9fba1d8f02..ca397a0608 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt @@ -21,8 +21,9 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.* 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.RoomMemberEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.where import io.realm.Realm @@ -42,19 +43,18 @@ internal class RoomMembers(private val realm: Realm, RoomSummaryEntity.where(realm, roomId).findFirst() } - fun getStateEvent(userId: String): EventEntity? { + fun getLastStateEvent(userId: String): EventEntity? { return EventEntity .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) .equalTo(EventEntityFields.STATE_KEY, userId) + .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) .findFirst() } - fun get(userId: String): RoomMember? { - return getStateEvent(userId) - ?.let { - it.asDomain().content?.toModel() - } + fun getLastRoomMember(userId: String): RoomMemberEntity? { + return RoomMemberEntity + .where(realm, roomId, userId) + .findFirst() } fun isUniqueDisplayName(displayName: String?): Boolean { @@ -69,36 +69,35 @@ internal class RoomMembers(private val realm: Realm, .size == 1 } - fun queryRoomMembersEvent(): RealmQuery { - return EventEntity - .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) - .isNotNull(EventEntityFields.STATE_KEY) - .distinct(EventEntityFields.STATE_KEY) - .isNotNull(EventEntityFields.CONTENT) + fun queryRoomMembersEvent(): RealmQuery { + return RoomMemberEntity.where(realm, roomId) } - fun queryJoinedRoomMembersEvent(): RealmQuery { - return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"join\"") + fun queryJoinedRoomMembersEvent(): RealmQuery { + return queryRoomMembersEvent().equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } - fun queryInvitedRoomMembersEvent(): RealmQuery { - return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"invite\"") + fun queryInvitedRoomMembersEvent(): RealmQuery { + return queryRoomMembersEvent().equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name) } - fun queryRoomMemberEvent(userId: String): RealmQuery { + fun queryActiveRoomMembersEvent(): RealmQuery { return queryRoomMembersEvent() - .equalTo(EventEntityFields.STATE_KEY, userId) + .beginGroup() + .equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name) + .or() + .equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) + .endGroup() } fun getNumberOfJoinedMembers(): Int { return roomSummary?.joinedMembersCount - ?: queryJoinedRoomMembersEvent().findAll().size + ?: queryJoinedRoomMembersEvent().findAll().size } fun getNumberOfInvitedMembers(): Int { return roomSummary?.invitedMembersCount - ?: queryInvitedRoomMembersEvent().findAll().size + ?: queryInvitedRoomMembersEvent().findAll().size } fun getNumberOfMembers(): Int { @@ -111,7 +110,7 @@ internal class RoomMembers(private val realm: Realm, * @return a roomMember id list of joined or invited members. */ fun getActiveRoomMemberIds(): List { - return getRoomMemberIdsFiltered { it.membership == Membership.JOIN || it.membership == Membership.INVITE } + return queryActiveRoomMembersEvent().findAll().map { it.userId } } /** @@ -120,21 +119,6 @@ internal class RoomMembers(private val realm: Realm, * @return a roomMember id list of joined members. */ fun getJoinedRoomMemberIds(): List { - return getRoomMemberIdsFiltered { it.membership == Membership.JOIN } - } - - /* ========================================================================================== - * Private - * ========================================================================================== */ - - private fun getRoomMemberIdsFiltered(predicate: (RoomMember) -> Boolean): List { - return RoomMembers(realm, roomId) - .queryRoomMembersEvent() - .findAll() - .map { it.asDomain() } - .associateBy { it.stateKey!! } - .filterValues { predicate(it.content.toModel()!!) } - .keys - .toList() + return queryJoinedRoomMembersEvent().findAll().map { it.userId } } } 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 7030509bfc..3727d1f5b0 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 @@ -28,6 +28,7 @@ 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.membership.RoomMemberEventHandler import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.kotlin.createObject @@ -154,9 +155,6 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy for (event in receivedChunk.events) { event.eventId?.also { eventIds.add(it) } currentChunk.add(roomId, event, direction, isUnlinked = currentChunk.isUnlinked()) - UserEntityFactory.createOrNull(event)?.also { - realm.insertOrUpdate(it) - } } // Then we merge chunks if needed if (currentChunk != prevChunk && prevChunk != null) { @@ -175,9 +173,6 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy roomEntity.addOrUpdate(currentChunk) for (stateEvent in receivedChunk.stateEvents) { roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked()) - UserEntityFactory.createOrNull(stateEvent)?.also { - realm.insertOrUpdate(it) - } } currentChunk.updateSenderDataFor(eventIds) } 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 a080a5158e..ea59cc1fd2 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 @@ -33,6 +33,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.mapWithProgress import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater +import im.vector.matrix.android.internal.session.room.membership.RoomMemberEventHandler import im.vector.matrix.android.internal.session.room.read.FullyReadContent import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.model.* @@ -46,7 +47,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private val roomSummaryUpdater: RoomSummaryUpdater, private val roomTagHandler: RoomTagHandler, private val roomFullyReadHandler: RoomFullyReadHandler, - private val cryptoService: DefaultCryptoService) { + private val cryptoService: DefaultCryptoService, + private val roomMemberEventHandler: RoomMemberEventHandler) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -119,9 +121,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) // Give info to crypto module cryptoService.onStateEvent(roomId, event) - UserEntityFactory.createOrNull(event)?.also { - realm.insertOrUpdate(it) - } + roomMemberEventHandler.handle(realm, roomId, event) } } if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { @@ -206,9 +206,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle Timber.v("Can't find corresponding local echo for tx:$it") } } - UserEntityFactory.createOrNull(event)?.also { - realm.insertOrUpdate(it) - } + roomMemberEventHandler.handle(realm, roomEntity.roomId, event) } chunkEntity.updateSenderDataFor(eventIds) return chunkEntity diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 35988e6c6f..571c9b4c27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.api.pushrules.RuleSetKey import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.* @@ -69,9 +70,9 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc var hasUpdate = false monarchy.doWithRealm { realm -> invites.forEach { (roomId, _) -> - val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(userId) + val myUserStateEvent = RoomMembers(realm, roomId).getLastStateEvent(userId) val inviterId = myUserStateEvent?.sender - val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() } + val myUserRoomMember: RoomMemberContent? = myUserStateEvent?.let { it.asDomain().content?.toModel() } val isDirect = myUserRoomMember?.isDirect if (inviterId != null && inviterId != userId && isDirect == true) { directChats diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt index 2ded32b7db..f931db1cff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt @@ -16,27 +16,16 @@ package im.vector.matrix.android.internal.session.user -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.toModel -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.internal.database.model.UserEntity internal object UserEntityFactory { - fun createOrNull(event: Event): UserEntity? { - if (event.type != EventType.STATE_ROOM_MEMBER) { - return null - } - val roomMember = event.content.toModel() ?: return null - // We only use JOIN and INVITED memberships to create User data - if (roomMember.membership != Membership.JOIN && roomMember.membership != Membership.INVITE) { - return null - } - return UserEntity(event.stateKey ?: "", - roomMember.displayName ?: "", - roomMember.avatarUrl ?: "" + fun create(userId: String, roomMember: RoomMemberContent): UserEntity { + return UserEntity( + userId = userId, + displayName = roomMember.displayName ?: "", + avatarUrl = roomMember.avatarUrl ?: "" ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberController.kt similarity index 80% rename from vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt rename to vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberController.kt index 53a87fe27a..1c8dc99196 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberController.kt @@ -14,23 +14,23 @@ * limitations under the License. */ -package im.vector.riotx.features.autocomplete.user +package im.vector.riotx.features.autocomplete.member import com.airbnb.epoxy.TypedEpoxyController -import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.features.autocomplete.AutocompleteClickListener import im.vector.riotx.features.autocomplete.autocompleteMatrixItem import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject -class AutocompleteUserController @Inject constructor() : TypedEpoxyController>() { +class AutocompleteMemberController @Inject constructor() : TypedEpoxyController>() { - var listener: AutocompleteClickListener? = null + var listener: AutocompleteClickListener? = null @Inject lateinit var avatarRenderer: AvatarRenderer - override fun buildModels(data: List?) { + override fun buildModels(data: List?) { if (data.isNullOrEmpty()) { return } diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt similarity index 66% rename from vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt rename to vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt index 01dceb5399..b39d1d9b44 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt @@ -14,20 +14,20 @@ * limitations under the License. */ -package im.vector.riotx.features.autocomplete.user +package im.vector.riotx.features.autocomplete.member import android.content.Context import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.Async import com.airbnb.mvrx.Success import com.otaliastudios.autocomplete.RecyclerViewPresenter -import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.riotx.features.autocomplete.AutocompleteClickListener import javax.inject.Inject -class AutocompleteUserPresenter @Inject constructor(context: Context, - private val controller: AutocompleteUserController -) : RecyclerViewPresenter(context), AutocompleteClickListener { +class AutocompleteMemberPresenter @Inject constructor(context: Context, + private val controller: AutocompleteMemberController +) : RecyclerViewPresenter(context), AutocompleteClickListener { var callback: Callback? = null @@ -41,21 +41,21 @@ class AutocompleteUserPresenter @Inject constructor(context: Context, return controller.adapter } - override fun onItemClick(t: User) { + override fun onItemClick(t: RoomMember) { dispatchClick(t) } override fun onQuery(query: CharSequence?) { - callback?.onQueryUsers(query) + callback?.onQueryMembers(query) } - fun render(users: Async>) { - if (users is Success) { - controller.setData(users()) + fun render(members: Async>) { + if (members is Success) { + controller.setData(members()) } } interface Callback { - fun onQueryUsers(query: CharSequence?) + fun onQueryMembers(query: CharSequence?) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 334445870c..eff9df9d18 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -61,6 +61,7 @@ import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState @@ -88,7 +89,7 @@ import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresente import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter -import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter +import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter import im.vector.riotx.features.command.Command import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.getColorFromUserId @@ -144,7 +145,7 @@ class RoomDetailFragment @Inject constructor( private val timelineEventController: TimelineEventController, private val commandAutocompletePolicy: CommandAutocompletePolicy, private val autocompleteCommandPresenter: AutocompleteCommandPresenter, - private val autocompleteUserPresenter: AutocompleteUserPresenter, + private val autocompleteMemberPresenter: AutocompleteMemberPresenter, private val autocompleteRoomPresenter: AutocompleteRoomPresenter, private val autocompleteGroupPresenter: AutocompleteGroupPresenter, private val permalinkHandler: PermalinkHandler, @@ -156,7 +157,7 @@ class RoomDetailFragment @Inject constructor( ) : VectorBaseFragment(), TimelineEventController.Callback, - AutocompleteUserPresenter.Callback, + AutocompleteMemberPresenter.Callback, AutocompleteRoomPresenter.Callback, AutocompleteGroupPresenter.Callback, VectorInviteView.Callback, @@ -693,14 +694,14 @@ class RoomDetailFragment @Inject constructor( }) .build() - autocompleteUserPresenter.callback = this - Autocomplete.on(composerLayout.composerEditText) + autocompleteMemberPresenter.callback = this + Autocomplete.on(composerLayout.composerEditText) .with(CharPolicy('@', true)) - .with(autocompleteUserPresenter) + .with(autocompleteMemberPresenter) .with(elevation) .with(backgroundDrawable) - .with(object : AutocompleteCallback { - override fun onPopupItemClicked(editable: Editable, item: User): Boolean { + .with(object : AutocompleteCallback { + override fun onPopupItemClicked(editable: Editable, item: RoomMember): Boolean { // Detect last '@' and remove it var startIndex = editable.lastIndexOf("@") if (startIndex == -1) { @@ -834,7 +835,7 @@ class RoomDetailFragment @Inject constructor( } private fun renderTextComposerState(state: TextComposerViewState) { - autocompleteUserPresenter.render(state.asyncUsers) + autocompleteMemberPresenter.render(state.asyncMembers) autocompleteRoomPresenter.render(state.asyncRooms) autocompleteGroupPresenter.render(state.asyncGroups) } @@ -1163,9 +1164,9 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState) } - // AutocompleteUserPresenter.Callback + // AutocompleteMemberPresenter.Callback - override fun onQueryUsers(query: CharSequence?) { + override fun onQueryMembers(query: CharSequence?) { textComposerViewModel.handle(TextComposerAction.QueryUsers(query)) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt index f7ec78c6c4..25f92d4ae2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt @@ -25,6 +25,8 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx @@ -90,17 +92,15 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeUsersQuery() { - Observable.combineLatest, Option, List>( - room.rx().liveRoomMemberIds(), + Observable.combineLatest, Option, List>( + room.rx().liveRoomMembers(Membership.activeMemberships()), usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS), - BiFunction { roomMemberIds, query -> - val users = roomMemberIds.mapNotNull { session.getUser(it) } - + BiFunction { roomMembers, query -> val filter = query.orNull() if (filter.isNullOrBlank()) { - users + roomMembers } else { - users.filter { + roomMembers.filter { it.displayName?.contains(filter, ignoreCase = true) ?: false } } @@ -108,7 +108,7 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: } ).execute { async -> copy( - asyncUsers = async + asyncMembers = async ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt index e863970afe..0320818f67 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt @@ -20,12 +20,13 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.features.home.room.detail.RoomDetailArgs data class TextComposerViewState(val roomId: String, - val asyncUsers: Async> = Uninitialized, + val asyncMembers: Async> = Uninitialized, val asyncRooms: Async> = Uninitialized, val asyncGroups: Async> = Uninitialized ) : MvRxState { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 9cb045c01e..a201890912 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -128,8 +128,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active } private fun formatRoomMemberEvent(event: Event, senderName: String?): String? { - val eventContent: RoomMember? = event.getClearContent().toModel() - val prevEventContent: RoomMember? = event.prevContent.toModel() + val eventContent: RoomMemberContent? = event.getClearContent().toModel() + val prevEventContent: RoomMemberContent? = event.prevContent.toModel() val isMembershipEvent = prevEventContent?.membership != eventContent?.membership return if (isMembershipEvent) { buildMembershipNotice(event, senderName, eventContent, prevEventContent) @@ -166,7 +166,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active ?: sp.getString(R.string.notice_room_canonical_alias_unset, senderName) } - private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String { + private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String { val displayText = StringBuilder() // Check display name has been changed if (eventContent?.displayName != prevEventContent?.displayName) { @@ -198,7 +198,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active return displayText.toString() } - private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { + private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String? { val senderDisplayName = senderName ?: event.senderId ?: "" val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: "" return when (eventContent?.membership) { diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index e38e7d548a..ef620a7df3 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getEditedEventId import im.vector.matrix.android.api.session.room.timeline.getLastMessageBody @@ -163,7 +164,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St } private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? { - val content = event.content?.toModel() ?: return null + val content = event.content?.toModel() ?: return null val roomId = event.roomId ?: return null val dName = event.senderId?.let { session.getUser(it)?.displayName } if (Membership.INVITE == content.membership) { From 833a5a37a25147d52e1e39522238ff8bdb9b8a74 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 27 Dec 2019 10:24:58 +0100 Subject: [PATCH 02/26] Pill: fix blink and clean files --- .../android/api/session/room/model/Membership.kt | 3 --- .../database/helper/TimelineEventEntityHelper.kt | 1 - .../database/query/RoomMemberEntityQueries.kt | 2 -- .../internal/session/room/RoomAvatarResolver.kt | 3 --- .../room/membership/LoadRoomMembersTask.kt | 1 - .../room/membership/RoomMemberEntityFactory.kt | 2 +- .../room/membership/RoomMemberEventHandler.kt | 3 --- .../session/room/membership/RoomMembers.kt | 3 --- .../room/timeline/TokenChunkEventPersistor.kt | 2 -- .../internal/session/sync/RoomSyncHandler.kt | 1 - .../session/sync/UserAccountDataSyncHandler.kt | 1 - .../vector/riotx/core/epoxy/VectorEpoxyModel.kt | 11 +++++++++++ .../bottomsheet/BottomSheetMessagePreviewItem.kt | 2 +- .../vector/riotx/features/home/AvatarRenderer.kt | 8 ++++++++ .../home/room/detail/RoomDetailFragment.kt | 1 - .../detail/composer/TextComposerViewModel.kt | 1 - .../detail/composer/TextComposerViewState.kt | 1 - .../room/detail/timeline/item/BaseEventItem.kt | 1 + .../room/detail/timeline/item/MessageTextItem.kt | 2 +- .../detail/timeline/tools/EventRenderingTools.kt | 9 +++------ .../vector/riotx/features/html/PillImageSpan.kt | 16 +++++++++------- .../notifications/NotifiableEventResolver.kt | 1 - 22 files changed, 35 insertions(+), 40 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt index 4d35d3b4dd..93eb54fbd3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt @@ -49,7 +49,4 @@ enum class Membership(val value: String) { return listOf(INVITE, JOIN) } } - } - - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt index c5e2aef910..51775f9e9d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.model.* diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt index 3eb1da87a1..2ddade0048 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomMemberEntityQueries.kt @@ -18,8 +18,6 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.internal.database.model.RoomMemberEntity import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields -import im.vector.matrix.android.internal.database.model.UserEntity -import im.vector.matrix.android.internal.database.model.UserEntityFields import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt index acfb57bd46..0bb2dc0f27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt @@ -20,10 +20,8 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomAvatarContent -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.internal.database.mapper.ContentMapper 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.RoomMemberEntityFields import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where @@ -59,5 +57,4 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona } return res } - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index 6affd120a8..64cbcb3527 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -26,7 +26,6 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.sync.SyncTokenStore -import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt index 933431b77f..51df244401 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.internal.database.model.RoomMemberEntity internal object RoomMemberEntityFactory { fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberEntity { - val primaryKey = "${roomId}_${userId}" + val primaryKey = "${roomId}_$userId" return RoomMemberEntity( primaryKey = primaryKey, userId = userId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt index 73c4c8cdc1..16d6aacbc9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt @@ -20,7 +20,6 @@ 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.toModel import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.internal.session.user.UserEntityFactory import io.realm.Realm @@ -42,6 +41,4 @@ internal class RoomMemberEventHandler @Inject constructor() { } return true } - - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt index ca397a0608..f2dd733978 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt @@ -17,10 +17,7 @@ package im.vector.matrix.android.internal.session.room.membership import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomMemberEntity 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 3727d1f5b0..df52e9a33a 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 @@ -28,8 +28,6 @@ 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.membership.RoomMemberEventHandler -import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.kotlin.createObject import timber.log.Timber 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 ea59cc1fd2..f4efa291b8 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 @@ -37,7 +37,6 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMemberEvent import im.vector.matrix.android.internal.session.room.read.FullyReadContent import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.model.* -import im.vector.matrix.android.internal.session.user.UserEntityFactory import io.realm.Realm import io.realm.kotlin.createObject import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 571c9b4c27..9bc8c86be5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -20,7 +20,6 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.api.pushrules.RuleSetKey import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.mapper.asDomain diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/VectorEpoxyModel.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/VectorEpoxyModel.kt index 667ccb1bd0..654e4c605a 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/VectorEpoxyModel.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/VectorEpoxyModel.kt @@ -18,14 +18,25 @@ package im.vector.riotx.core.epoxy import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.VisibilityState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren /** * EpoxyModelWithHolder which can listen to visibility state change */ abstract class VectorEpoxyModel : EpoxyModelWithHolder() { + protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null + override fun unbind(holder: H) { + coroutineScope.coroutineContext.cancelChildren() + super.unbind(holder) + } + override fun onVisibilityStateChanged(visibilityState: Int, view: H) { onModelVisibilityStateChangedListener?.onVisibilityStateChanged(visibilityState) super.onVisibilityStateChanged(visibilityState, view) diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index 7b79ce8549..e5ffd5f350 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -51,7 +51,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel() { holder.messageView.setOnClickListener(attributes.itemClickListener) holder.messageView.setOnLongClickListener(attributes.itemLongClickListener) if (searchForPills) { - message?.findPillsAndProcess { it.bind(holder.messageView) } + message?.findPillsAndProcess(coroutineScope) { it.bind(holder.messageView) } } val textFuture = PrecomputedTextCompat.getTextFuture( message ?: "", diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt index 492248985e..043763fd8e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt @@ -25,14 +25,11 @@ import im.vector.riotx.core.linkify.VectorLinkify import im.vector.riotx.core.utils.isValidUrl import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.html.PillImageSpan -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import me.saket.bettermovementmethod.BetterLinkMovementMethod -fun CharSequence.findPillsAndProcess(processBlock: (PillImageSpan) -> Unit) { - GlobalScope.launch(Dispatchers.Main) { +fun CharSequence.findPillsAndProcess(scope: CoroutineScope, processBlock: (PillImageSpan) -> Unit) { + scope.launch(Dispatchers.Main) { withContext(Dispatchers.IO) { toSpannable().let { spannable -> spannable.getSpans(0, spannable.length, PillImageSpan::class.java) diff --git a/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt index a609541a62..3d3dcbea94 100644 --- a/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt +++ b/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt @@ -88,25 +88,27 @@ class PillImageSpan(private val glideRequests: GlideRequests, } internal fun updateAvatarDrawable(drawable: Drawable?) { - pillDrawable.apply { - chipIcon = drawable - } - tv?.get()?.apply { - invalidate() - } + pillDrawable.chipIcon = drawable + tv?.get()?.invalidate() } // Private methods ***************************************************************************** private fun createChipDrawable(): ChipDrawable { val textPadding = context.resources.getDimension(R.dimen.pill_text_padding) + val icon = try { + avatarRenderer.getCachedDrawable(glideRequests, matrixItem) + } catch (exception: Exception) { + avatarRenderer.getPlaceholderDrawable(context, matrixItem) + } + return ChipDrawable.createFromResource(context, R.xml.pill_view).apply { text = matrixItem.getBestName() textEndPadding = textPadding textStartPadding = textPadding setChipMinHeightResource(R.dimen.pill_min_height) setChipIconSizeResource(R.dimen.pill_avatar_size) - chipIcon = avatarRenderer.getPlaceholderDrawable(context, matrixItem) + chipIcon = icon setBounds(0, 0, intrinsicWidth, intrinsicHeight) } } diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index ef620a7df3..11d770adc4 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -23,7 +23,6 @@ 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.toModel import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getEditedEventId From 037bf45884210a4675a2f801af1d7bfd6adb0f6d Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 27 Dec 2019 17:09:57 +0100 Subject: [PATCH 03/26] Sync: use foreground service on every android version --- .../riotx/core/services/VectorSyncService.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index 31b56c4e47..b6b8fbf06a 100644 --- a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -45,15 +45,13 @@ class VectorSyncService : SyncService() { } override fun onStart(isInitialSync: Boolean) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notificationSubtitleRes = if (isInitialSync) { - R.string.notification_initial_sync - } else { - R.string.notification_listening_for_events - } - val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false) - startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) + val notificationSubtitleRes = if (isInitialSync) { + R.string.notification_initial_sync + } else { + R.string.notification_listening_for_events } + val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false) + startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) } override fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) { From 8109262cbb2f0286ce2c9b562e28bbc7c62274e9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 27 Dec 2019 17:16:30 +0100 Subject: [PATCH 04/26] Home: fix double tab selection --- .../riotx/features/home/HomeDetailFragment.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index b9d3e3c95e..2913ce80fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -66,6 +66,11 @@ class HomeDetailFragment @Inject constructor( setupToolbar() setupKeysBackupBanner() + withState(viewModel) { + // Update the navigation view if needed (for when we restore the tabs) + bottomNavigationView.selectedItemId = it.displayMode.toMenuId() + } + viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary -> onGroupChange(groupSummary.orNull()) } @@ -127,7 +132,6 @@ class HomeDetailFragment @Inject constructor( private fun setupBottomNavigationView() { bottomNavigationView.setOnNavigationItemSelectedListener { val displayMode = when (it.itemId) { - R.id.bottom_action_home -> RoomListDisplayMode.HOME R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS else -> RoomListDisplayMode.HOME @@ -149,12 +153,6 @@ class HomeDetailFragment @Inject constructor( private fun switchDisplayMode(displayMode: RoomListDisplayMode) { groupToolbarTitleView.setText(displayMode.titleRes) updateSelectedFragment(displayMode) - // Update the navigation view (for when we restore the tabs) - bottomNavigationView.selectedItemId = when (displayMode) { - RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people - RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms - else -> R.id.bottom_action_home - } } private fun updateSelectedFragment(displayMode: RoomListDisplayMode) { @@ -194,4 +192,11 @@ class HomeDetailFragment @Inject constructor( unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms)) syncStateView.render(it.syncState) } + + private fun RoomListDisplayMode.toMenuId() = when (this) { + RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people + RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms + else -> R.id.bottom_action_home + } + } From 92f4288d3e6615858672bdc7eebb98b365d0d8b5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 27 Dec 2019 17:16:44 +0100 Subject: [PATCH 05/26] Realm: update realm lib version --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ab5f122dbc..799a619748 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:5.12.0" + classpath "io.realm:realm-gradle-plugin:6.0.2" } } From 679417332145f4bd29e2119a2245eab758cdecbb Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 27 Dec 2019 18:54:07 +0100 Subject: [PATCH 06/26] Room detail: fix crash with banner --- .../riotx/features/home/room/detail/RoomDetailFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index c0ae1ec252..fa483d675c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -563,7 +563,7 @@ class RoomDetailFragment @Inject constructor( } } } - jumpToReadMarkerView.isVisible = showJumpToUnreadBanner + jumpToReadMarkerView?.isVisible = showJumpToUnreadBanner } } } From cba7e460ebb9a8a030286bfe25a77fff05e5ad2d Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 27 Dec 2019 18:54:28 +0100 Subject: [PATCH 07/26] Action bottom sheet: fix deprecated constraints --- vector/src/main/res/layout/item_bottom_sheet_action.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/item_bottom_sheet_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml index 0ad7a211da..db01db0a2f 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_action.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml @@ -38,7 +38,7 @@ From 6ad914154a107e6d2e4297cec7a69801f29b2179 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 30 Dec 2019 10:46:25 +0100 Subject: [PATCH 08/26] Update some libs --- vector/build.gradle | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index c8d474088f..410e478ee2 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -216,8 +216,8 @@ android { dependencies { - def epoxy_version = '3.8.0' - def fragment_version = '1.2.0-rc01' + def epoxy_version = '3.9.0' + def fragment_version = '1.2.0-rc04' def arrow_version = "0.8.2" def coroutines_version = "1.3.2" def markwon_version = '4.1.2' @@ -225,7 +225,7 @@ dependencies { def glide_version = '4.10.0' def moshi_version = '1.8.0' def daggerVersion = '2.24' - def autofill_version = "1.0.0-rc01" + def autofill_version = "1.0.0" implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") @@ -239,8 +239,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation "androidx.fragment:fragment:$fragment_version" implementation "androidx.fragment:fragment-ktx:$fragment_version" - //Do not use beta2 at the moment, as it breaks things - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' implementation 'androidx.core:core-ktx:1.1.0' implementation "org.threeten:threetenbp:1.4.0:no-tzdb" @@ -276,10 +275,10 @@ dependencies { implementation 'com.airbnb.android:mvrx:1.3.0' // Work - implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01" + implementation "androidx.work:work-runtime-ktx:2.3.0-beta02" // Paging - implementation "androidx.paging:paging-runtime-ktx:2.1.0" + implementation "androidx.paging:paging-runtime-ktx:2.1.1" // Functional Programming implementation "io.arrow-kt:arrow-core:$arrow_version" @@ -289,7 +288,7 @@ dependencies { // UI implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - implementation 'com.google.android.material:material:1.1.0-beta01' + implementation 'com.google.android.material:material:1.2.0-alpha03' implementation 'me.gujun.android:span:1.7' implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:html:$markwon_version" From 03fd474aa8e4bd3ae911030ee7e072cd984ceac7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 30 Dec 2019 19:53:36 +0100 Subject: [PATCH 09/26] Member events: try to cache (WIP) --- .../session/room/timeline/ChunkEntityTest.kt | 17 ++- .../session/room/timeline/RoomDataHelper.kt | 22 --- .../database/helper/ChunkEntityHelper.kt | 70 ++++----- .../database/helper/RoomMembersCache.kt | 135 ++++++++++++++++++ .../helper/TimelineEventEntityHelper.kt | 26 ++-- .../database/model/TimelineEventEntity.kt | 2 +- .../query/TimelineEventEntityQueries.kt | 2 +- .../room/timeline/TokenChunkEventPersistor.kt | 8 +- .../internal/session/sync/RoomSyncHandler.kt | 10 +- 9 files changed, 204 insertions(+), 88 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt index 592086b0ec..5cca9f6696 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt @@ -19,7 +19,11 @@ package im.vector.matrix.android.session.room.timeline import androidx.test.ext.junit.runners.AndroidJUnit4 import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.internal.database.helper.* +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.internal.database.helper.add +import im.vector.matrix.android.internal.database.helper.isUnlinked +import im.vector.matrix.android.internal.database.helper.lastStateIndex +import im.vector.matrix.android.internal.database.helper.merge import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.SessionRealmModule import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection @@ -197,4 +201,15 @@ internal class ChunkEntityTest : InstrumentedTest { chunk1.nextToken shouldEqual nextToken } } + + private fun ChunkEntity.addAll(roomId: String, + events: List, + direction: PaginationDirection, + stateIndexOffset: Int = 0, + // Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk) + isUnlinked: Boolean = false) { + events.forEach { event -> + add(roomId, event, direction, stateIndexOffset, isUnlinked) + } + } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt index 8a8ee11854..dd4daee9cd 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.session.room.timeline -import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType @@ -25,12 +24,6 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomMember 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.internal.database.helper.addAll -import im.vector.matrix.android.internal.database.helper.addOrUpdate -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection -import io.realm.kotlin.createObject import kotlin.random.Random object RoomDataHelper { @@ -73,19 +66,4 @@ object RoomDataHelper { val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent() return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember) } - - fun fakeInitialSync(monarchy: Monarchy, roomId: String) { - monarchy.runTransactionSync { realm -> - val roomEntity = realm.createObject(roomId) - roomEntity.membership = Membership.JOIN - val eventList = createFakeListOfEvents(10) - val chunkEntity = realm.createObject().apply { - nextToken = null - prevToken = Random.nextLong(System.currentTimeMillis()).toString() - isLastForward = true - } - chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS) - roomEntity.addOrUpdate(chunkEntity) - } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index f05fa01444..f60c37512c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.Sort +import io.realm.kotlin.createObject // By default if a chunk is empty we consider it unlinked internal fun ChunkEntity.isUnlinked(): Boolean { @@ -65,38 +66,22 @@ internal fun ChunkEntity.merge(roomId: String, this.isLastBackward = chunkToMerge.isLastBackward eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } - val events = eventsToMerge.mapNotNull { it.root?.asDomain() } - val eventIds = ArrayList() - events.forEach { event -> - add(roomId, event, direction, isUnlinked = isUnlinked) - if (event.eventId != null) { - eventIds.add(event.eventId) - } - } - updateSenderDataFor(eventIds) + val timelineEvents = eventsToMerge + .mapNotNull { + val event = it.root?.asDomain() ?: return@mapNotNull null + add(roomId, event, direction, isUnlinked = isUnlinked) + } + updateSenderDataFor(timelineEvents) } -internal fun ChunkEntity.addAll(roomId: String, - events: List, - direction: PaginationDirection, - stateIndexOffset: Int = 0, - // Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk) - isUnlinked: Boolean = false) { - assertIsManaged() - val eventIds = ArrayList() - events.forEach { event -> - add(roomId, event, direction, stateIndexOffset, isUnlinked) - if (event.eventId != null) { - eventIds.add(event.eventId) - } - } - updateSenderDataFor(eventIds) -} - -internal fun ChunkEntity.updateSenderDataFor(eventIds: List) { - for (eventId in eventIds) { - val timelineEventEntity = timelineEvents.find(eventId) ?: continue - timelineEventEntity.updateSenderData() +internal fun ChunkEntity.updateSenderDataFor(events: List) { + val cache = RoomMembersCache() + events.forEach { + val result = cache.get(it) + it.isUniqueDisplayName = result.isUniqueDisplayName + it.senderAvatar = result.senderAvatar + it.senderName = result.senderName + it.senderMembershipEventId = result.senderMembershipEventId } } @@ -104,10 +89,10 @@ internal fun ChunkEntity.add(roomId: String, event: Event, direction: PaginationDirection, stateIndexOffset: Int = 0, - isUnlinked: Boolean = false) { + isUnlinked: Boolean = false): TimelineEventEntity? { assertIsManaged() if (event.eventId != null && timelineEvents.find(event.eventId) != null) { - return + return null } var currentDisplayIndex = lastDisplayIndex(direction, 0) if (direction == PaginationDirection.FORWARDS) { @@ -134,7 +119,9 @@ internal fun ChunkEntity.add(roomId: String, val senderId = event.senderId ?: "" val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() - ?: ReadReceiptsSummaryEntity(eventId, roomId) + ?: realm.createObject(eventId).apply { + this.roomId = roomId + } // Update RR for the sender of a new message with a dummy one @@ -151,13 +138,15 @@ internal fun ChunkEntity.add(roomId: String, } } - val eventEntity = TimelineEventEntity(localId).also { - it.root = event.toEntity(roomId).apply { - this.stateIndex = currentStateIndex - this.isUnlinked = isUnlinked - this.displayIndex = currentDisplayIndex - this.sendState = SendState.SYNCED - } + val rootEvent = event.toEntity(roomId).apply { + this.stateIndex = currentStateIndex + this.isUnlinked = isUnlinked + this.displayIndex = currentDisplayIndex + this.sendState = SendState.SYNCED + } + val eventEntity = realm.createObject().also { + it.localId = localId + it.root = realm.copyToRealm(rootEvent) it.eventId = eventId it.roomId = roomId it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() @@ -165,6 +154,7 @@ internal fun ChunkEntity.add(roomId: String, } val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size timelineEvents.add(position, eventEntity) + return eventEntity } internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt new file mode 100644 index 0000000000..fb2c367e64 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt @@ -0,0 +1,135 @@ +/* + * 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.helper + +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.RoomMemberContent +import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.database.model.* +import im.vector.matrix.android.internal.database.query.next +import im.vector.matrix.android.internal.database.query.prev +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.session.room.membership.RoomMembers +import io.realm.RealmList +import io.realm.RealmQuery +import timber.log.Timber + +/** + * This is an internal cache to avoid querying all the time the room member events + */ +internal class RoomMembersCache { + + internal data class Key( + val roomId: String, + val stateIndex: Int, + val senderId: String + ) + + internal class Value( + var senderAvatar: String? = null, + var senderName: String? = null, + var isUniqueDisplayName: Boolean = false, + var senderMembershipEventId: String? = null + ) + + private val values = HashMap() + + fun get(timelineEventEntity: TimelineEventEntity): Value { + val key = Key( + roomId = timelineEventEntity.roomId, + stateIndex = timelineEventEntity.root?.stateIndex ?: 0, + senderId = timelineEventEntity.root?.sender ?: "" + ) + val result: Value + val start = System.currentTimeMillis() + result = values.getOrPut(key) { + doQueryAndBuildValue(timelineEventEntity) + } + val end = System.currentTimeMillis() + Timber.v("Get value took: ${end - start} millis") + return result + } + + private fun doQueryAndBuildValue(timelineEventEntity: TimelineEventEntity): Value { + return timelineEventEntity.computeValue() + } + + private fun RealmList.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery { + return where() + .equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender) + .equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER) + .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked) + } + + private fun TimelineEventEntity.computeValue(): Value { + assertIsManaged() + val result = Value() + + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result + val stateIndex = root?.stateIndex ?: return result + val senderId = root?.sender ?: return result + val chunkEntity = chunk?.firstOrNull() ?: return result + val isUnlinked = chunkEntity.isUnlinked() + var senderMembershipEvent: EventEntity? + var senderRoomMemberContent: String? + var senderRoomMemberPrevContent: String? + + if (stateIndex <= 0) { + senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root + senderRoomMemberContent = senderMembershipEvent?.prevContent + senderRoomMemberPrevContent = senderMembershipEvent?.content + } else { + senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root + senderRoomMemberContent = senderMembershipEvent?.content + senderRoomMemberPrevContent = senderMembershipEvent?.prevContent + } + + // We fallback to untimelinedStateEvents if we can't find membership events in timeline + if (senderMembershipEvent == null) { + senderMembershipEvent = roomEntity.untimelinedStateEvents + .where() + .equalTo(EventEntityFields.STATE_KEY, senderId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) + .prev(since = stateIndex) + senderRoomMemberContent = senderMembershipEvent?.content + senderRoomMemberPrevContent = senderMembershipEvent?.prevContent + } + + ContentMapper.map(senderRoomMemberContent).toModel()?.also { + result.senderAvatar = it.avatarUrl + result.senderName = it.displayName + result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) + } + // We try to fallback on prev content if we got a room member state events with null fields + if (root?.type == EventType.STATE_ROOM_MEMBER) { + ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also { + if (result.senderAvatar == null && it.avatarUrl != null) { + result.senderAvatar = it.avatarUrl + } + if (result.senderName == null && it.displayName != null) { + result.senderName = it.displayName + result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) + } + } + } + result.senderMembershipEventId = senderMembershipEvent?.eventId + return result + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt index 51775f9e9d..c7bedaee21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt @@ -40,20 +40,18 @@ internal fun TimelineEventEntity.updateSenderData() { var senderMembershipEvent: EventEntity? var senderRoomMemberContent: String? var senderRoomMemberPrevContent: String? - when { - stateIndex <= 0 -> { - senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root - senderRoomMemberContent = senderMembershipEvent?.prevContent - senderRoomMemberPrevContent = senderMembershipEvent?.content - } - else -> { - senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root - senderRoomMemberContent = senderMembershipEvent?.content - senderRoomMemberPrevContent = senderMembershipEvent?.prevContent - } + + if (stateIndex <= 0) { + senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root + senderRoomMemberContent = senderMembershipEvent?.prevContent + senderRoomMemberPrevContent = senderMembershipEvent?.content + } else { + senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root + senderRoomMemberContent = senderMembershipEvent?.content + senderRoomMemberPrevContent = senderMembershipEvent?.prevContent } - // We fallback to untimelinedStateEvents if we can't find membership events in timeline +// We fallback to untimelinedStateEvents if we can't find membership events in timeline if (senderMembershipEvent == null) { senderMembershipEvent = roomEntity.untimelinedStateEvents .where() @@ -70,7 +68,7 @@ internal fun TimelineEventEntity.updateSenderData() { this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) } - // We try to fallback on prev content if we got a room member state events with null fields +// We try to fallback on prev content if we got a room member state events with null fields if (root?.type == EventType.STATE_ROOM_MEMBER) { ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also { if (this.senderAvatar == null && it.avatarUrl != null) { @@ -82,7 +80,7 @@ internal fun TimelineEventEntity.updateSenderData() { } } } - this.senderMembershipEvent = senderMembershipEvent + this.senderMembershipEventId = senderMembershipEvent?.eventId } internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index 235910b1ea..22f4b9c506 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -29,7 +29,7 @@ internal open class TimelineEventEntity(var localId: Long = 0, var senderName: String? = null, var isUniqueDisplayName: Boolean = false, var senderAvatar: String? = null, - var senderMembershipEvent: EventEntity? = null, + var senderMembershipEventId: String? = null, var readReceipts: ReadReceiptsSummaryEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 3bd035c0b1..221e8ccb46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -54,7 +54,7 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List { return realm.where() - .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT.EVENT_ID, senderMembershipEventId) + .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId) .findAll() } 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 df52e9a33a..342a40052c 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 @@ -149,10 +149,8 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy currentChunk.isLastBackward = true } else if (!shouldSkip) { Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") - val eventIds = ArrayList(receivedChunk.events.size) - for (event in receivedChunk.events) { - event.eventId?.also { eventIds.add(it) } - currentChunk.add(roomId, event, direction, isUnlinked = currentChunk.isUnlinked()) + val timelineEvents = receivedChunk.events.mapNotNull { + currentChunk.add(roomId, it, direction, isUnlinked = currentChunk.isUnlinked()) } // Then we merge chunks if needed if (currentChunk != prevChunk && prevChunk != null) { @@ -172,7 +170,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy for (stateEvent in receivedChunk.stateEvents) { roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked()) } - currentChunk.updateSenderDataFor(eventIds) + currentChunk.updateSenderDataFor(timelineEvents) } } return if (receivedChunk.events.isEmpty()) { 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 f4efa291b8..f3c0cd9511 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 @@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity 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.model.TimelineEventEntity 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 @@ -189,10 +190,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle lastChunk?.isLastForward = false chunkEntity.isLastForward = true - val eventIds = ArrayList(eventList.size) + val timelineEvents = ArrayList(eventList.size) for (event in eventList) { - event.eventId?.also { eventIds.add(it) } - chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset) + chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)?.also { + timelineEvents.add(it) + } // Give info to crypto module cryptoService.onLiveEvent(roomEntity.roomId, event) // Try to remove local echo @@ -207,7 +209,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } roomMemberEventHandler.handle(realm, roomEntity.roomId, event) } - chunkEntity.updateSenderDataFor(eventIds) + chunkEntity.updateSenderDataFor(timelineEvents) return chunkEntity } From 8156b754c173774ecbfdd28dbdabe6ec4d80f840 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 30 Dec 2019 19:54:39 +0100 Subject: [PATCH 10/26] RecyclerView: introduce view pool --- .../main/java/im/vector/riotx/core/di/ScreenComponent.kt | 3 ++- .../src/main/java/im/vector/riotx/core/di/ScreenModule.kt | 6 ++++++ .../java/im/vector/riotx/core/extensions/RecyclerView.kt | 6 +++++- .../riotx/features/home/room/list/RoomListFragment.kt | 6 ++++-- .../room/list/actions/RoomListQuickActionsBottomSheet.kt | 3 ++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index e0b14af9d0..aec3372098 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -63,7 +63,8 @@ import im.vector.riotx.features.ui.UiStateRepository ViewModelModule::class, FragmentModule::class, HomeModule::class, - RoomListModule::class + RoomListModule::class, + ScreenModule::class ] ) @ScreenScope diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenModule.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenModule.kt index 1073a59f7c..56fac34f1e 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenModule.kt @@ -17,6 +17,7 @@ package im.vector.riotx.core.di import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView import dagger.Module import dagger.Provides import im.vector.riotx.core.glide.GlideApp @@ -27,4 +28,9 @@ object ScreenModule { @Provides @JvmStatic fun providesGlideRequests(context: AppCompatActivity) = GlideApp.with(context) + + @Provides + @JvmStatic + @ScreenScope + fun providesSharedViewPool() = RecyclerView.RecycledViewPool() } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt index 003045af51..3d247e149c 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -26,9 +26,13 @@ import com.airbnb.epoxy.EpoxyController */ fun RecyclerView.configureWith(epoxyController: EpoxyController, itemAnimator: RecyclerView.ItemAnimator? = null, + viewPool: RecyclerView.RecycledViewPool? = null, showDivider: Boolean = false, hasFixedSize: Boolean = true) { - layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply { + recycleChildrenOnDetach = viewPool != null + } + setRecycledViewPool(viewPool) itemAnimator?.let { this.itemAnimator = it } if (showDivider) { addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index e272c1423f..122b95aa52 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -59,7 +59,8 @@ data class RoomListParams( class RoomListFragment @Inject constructor( private val roomController: RoomSummaryController, val roomListViewModelFactory: RoomListViewModel.Factory, - private val notificationDrawerManager: NotificationDrawerManager + private val notificationDrawerManager: NotificationDrawerManager, + private val sharedViewPool: RecyclerView.RecycledViewPool ) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener { @@ -95,7 +96,6 @@ class RoomListFragment @Inject constructor( setupCreateRoomButton() setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) - roomListViewModel.subscribe { renderState(it) } roomListViewModel.viewEvents .observe() @@ -193,6 +193,8 @@ class RoomListFragment @Inject constructor( val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() roomListView.layoutManager = layoutManager roomListView.itemAnimator = RoomListAnimator() + roomListView.setRecycledViewPool(sharedViewPool) + layoutManager.recycleChildrenOnDetach = true roomController.listener = this modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } roomController.addModelBuildListener(modelBuildListener) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index 60a26c8151..5fc33ffbe9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -46,6 +46,7 @@ data class RoomListActionsArgs( class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomListQuickActionsEpoxyController.Listener { private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel + @Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool @Inject lateinit var roomListActionsViewModelFactory: RoomListQuickActionsViewModel.Factory @Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController @Inject lateinit var navigator: Navigator @@ -70,7 +71,7 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) - recyclerView.configureWith(roomListActionsEpoxyController, hasFixedSize = false) + recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false) // Disable item animation recyclerView.itemAnimator = null roomListActionsEpoxyController.listener = this From 787908287c46c6576075e35c5ff30187ae4f32b3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 31 Dec 2019 08:07:32 +0100 Subject: [PATCH 11/26] Member events: cache all over the session --- .../database/helper/ChunkEntityHelper.kt | 16 +--- .../helper/TimelineEventEntityHelper.kt | 74 +------------------ ...Cache.kt => TimelineEventSenderVisitor.kt} | 41 ++++++---- .../room/membership/LoadRoomMembersTask.kt | 8 +- .../session/room/prune/PruneEventTask.kt | 18 +++-- .../room/timeline/TokenChunkEventPersistor.kt | 11 ++- .../internal/session/sync/RoomSyncHandler.kt | 5 +- 7 files changed, 55 insertions(+), 118 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/{RoomMembersCache.kt => TimelineEventSenderVisitor.kt} (84%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index f60c37512c..90236a4f98 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -47,7 +47,7 @@ internal fun ChunkEntity.deleteOnCascade() { internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, - direction: PaginationDirection) { + direction: PaginationDirection): List { assertIsManaged() val isChunkToMergeUnlinked = chunkToMerge.isUnlinked() val isCurrentChunkUnlinked = this.isUnlinked() @@ -66,23 +66,11 @@ internal fun ChunkEntity.merge(roomId: String, this.isLastBackward = chunkToMerge.isLastBackward eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } - val timelineEvents = eventsToMerge + return eventsToMerge .mapNotNull { val event = it.root?.asDomain() ?: return@mapNotNull null add(roomId, event, direction, isUnlinked = isUnlinked) } - updateSenderDataFor(timelineEvents) -} - -internal fun ChunkEntity.updateSenderDataFor(events: List) { - val cache = RoomMembersCache() - events.forEach { - val result = cache.get(it) - it.isUniqueDisplayName = result.isUniqueDisplayName - it.senderAvatar = result.senderAvatar - it.senderName = result.senderName - it.senderMembershipEventId = result.senderMembershipEventId - } } internal fun ChunkEntity.add(roomId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt index c7bedaee21..0bf02aa92f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt @@ -16,72 +16,9 @@ package im.vector.matrix.android.internal.database.helper -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMemberContent -import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.query.next -import im.vector.matrix.android.internal.database.query.prev -import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.extensions.assertIsManaged -import im.vector.matrix.android.internal.session.room.membership.RoomMembers +import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import io.realm.Realm -import io.realm.RealmList -import io.realm.RealmQuery - -internal fun TimelineEventEntity.updateSenderData() { - assertIsManaged() - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return - val stateIndex = root?.stateIndex ?: return - val senderId = root?.sender ?: return - val chunkEntity = chunk?.firstOrNull() ?: return - val isUnlinked = chunkEntity.isUnlinked() - var senderMembershipEvent: EventEntity? - var senderRoomMemberContent: String? - var senderRoomMemberPrevContent: String? - - if (stateIndex <= 0) { - senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root - senderRoomMemberContent = senderMembershipEvent?.prevContent - senderRoomMemberPrevContent = senderMembershipEvent?.content - } else { - senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root - senderRoomMemberContent = senderMembershipEvent?.content - senderRoomMemberPrevContent = senderMembershipEvent?.prevContent - } - -// We fallback to untimelinedStateEvents if we can't find membership events in timeline - if (senderMembershipEvent == null) { - senderMembershipEvent = roomEntity.untimelinedStateEvents - .where() - .equalTo(EventEntityFields.STATE_KEY, senderId) - .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) - .prev(since = stateIndex) - senderRoomMemberContent = senderMembershipEvent?.content - senderRoomMemberPrevContent = senderMembershipEvent?.prevContent - } - - ContentMapper.map(senderRoomMemberContent).toModel()?.also { - this.senderAvatar = it.avatarUrl - this.senderName = it.displayName - this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) - } - -// We try to fallback on prev content if we got a room member state events with null fields - if (root?.type == EventType.STATE_ROOM_MEMBER) { - ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also { - if (this.senderAvatar == null && it.avatarUrl != null) { - this.senderAvatar = it.avatarUrl - } - if (this.senderName == null && it.displayName != null) { - this.senderName = it.displayName - this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) - } - } - } - this.senderMembershipEventId = senderMembershipEvent?.eventId -} internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID) @@ -91,10 +28,3 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { currentIdNum.toLong() + 1 } } - -private fun RealmList.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery { - return where() - .equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender) - .equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER) - .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked) -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt similarity index 84% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt index fb2c367e64..2e8b02d6aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt @@ -25,15 +25,17 @@ import im.vector.matrix.android.internal.database.query.next import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.membership.RoomMembers import io.realm.RealmList import io.realm.RealmQuery -import timber.log.Timber +import javax.inject.Inject /** * This is an internal cache to avoid querying all the time the room member events */ -internal class RoomMembersCache { +@SessionScope +internal class TimelineEventSenderVisitor @Inject constructor() { internal data class Key( val roomId: String, @@ -50,24 +52,34 @@ internal class RoomMembersCache { private val values = HashMap() - fun get(timelineEventEntity: TimelineEventEntity): Value { + fun clear() { + values.clear() + } + + fun clear(roomId: String, senderId: String) { + val keysToRemove = values.keys.filter { it.senderId == senderId && it.roomId == roomId } + keysToRemove.forEach { + values.remove(it) + } + } + + fun visit(timelineEventEntities: List) = timelineEventEntities.forEach { visit(it) } + + fun visit(timelineEventEntity: TimelineEventEntity) { val key = Key( roomId = timelineEventEntity.roomId, stateIndex = timelineEventEntity.root?.stateIndex ?: 0, senderId = timelineEventEntity.root?.sender ?: "" ) - val result: Value - val start = System.currentTimeMillis() - result = values.getOrPut(key) { - doQueryAndBuildValue(timelineEventEntity) + val result = values.getOrPut(key) { + timelineEventEntity.computeValue() + } + timelineEventEntity.apply { + this.isUniqueDisplayName = result.isUniqueDisplayName + this.senderAvatar = result.senderAvatar + this.senderName = result.senderName + this.senderMembershipEventId = result.senderMembershipEventId } - val end = System.currentTimeMillis() - Timber.v("Get value took: ${end - start} millis") - return result - } - - private fun doQueryAndBuildValue(timelineEventEntity: TimelineEventEntity): Value { - return timelineEventEntity.computeValue() } private fun RealmList.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery { @@ -80,7 +92,6 @@ internal class RoomMembersCache { private fun TimelineEventEntity.computeValue(): Value { assertIsManaged() val result = Value() - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result val stateIndex = root?.stateIndex ?: return result val senderId = root?.sender ?: return result diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index 64cbcb3527..dd91875f98 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -18,8 +18,8 @@ package im.vector.matrix.android.internal.session.room.membership import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor import im.vector.matrix.android.internal.database.helper.addStateEvent -import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest @@ -44,7 +44,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP private val monarchy: Monarchy, private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, - private val roomMemberEventHandler: RoomMemberEventHandler + private val roomMemberEventHandler: RoomMemberEventHandler, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { @@ -68,8 +69,9 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP roomEntity.addStateEvent(roomMemberEvent) roomMemberEventHandler.handle(realm, roomId, roomMemberEvent) } + timelineEventSenderVisitor.clear() roomEntity.chunks.flatMap { it.timelineEvents }.forEach { - it.updateSenderData() + timelineEventSenderVisitor.visit(it) } roomEntity.areAllMembersLoaded = true roomSummaryUpdater.update(realm, roomId, updateMembers = true) 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 de3eb1eab2..8228136f10 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 @@ -20,7 +20,7 @@ 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.LocalEcho import im.vector.matrix.android.api.session.events.model.UnsignedData -import im.vector.matrix.android.internal.database.helper.updateSenderData +import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor 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.EventEntity @@ -41,7 +41,8 @@ internal interface PruneEventTask : Task { ) } -internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask { +internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor) : PruneEventTask { override suspend fun execute(params: PruneEventTask.Params) { monarchy.awaitTransaction { realm -> @@ -65,12 +66,14 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() ?: return - val allowedKeys = computeAllowedKeys(eventToPrune.type) + val typeToPrune = eventToPrune.type + val stateKey = eventToPrune.stateKey + val allowedKeys = computeAllowedKeys(typeToPrune) if (allowedKeys.isNotEmpty()) { val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) } eventToPrune.content = ContentMapper.map(prunedContent) } else { - when (eventToPrune.type) { + when (typeToPrune) { EventType.ENCRYPTED, EventType.MESSAGE -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") @@ -94,11 +97,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M // } } } - if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) { + if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) { + timelineEventSenderVisitor.clear(roomId = eventToPrune.roomId, senderId = stateKey) val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId) - for (timelineEvent in timelineEventsToUpdate) { - timelineEvent.updateSenderData() - } + timelineEventSenderVisitor.visit(timelineEventsToUpdate) } } 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 342a40052c..9773eff507 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 @@ -36,7 +36,8 @@ import javax.inject.Inject /** * Insert Chunk in DB, and eventually merge with existing chunk event */ -internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy) { +internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor) { /** *
@@ -170,7 +171,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
                         for (stateEvent in receivedChunk.stateEvents) {
                             roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
                         }
-                        currentChunk.updateSenderDataFor(timelineEvents)
+                        timelineEventSenderVisitor.visit(timelineEvents)
                     }
                 }
         return if (receivedChunk.events.isEmpty()) {
@@ -191,11 +192,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
         // We always merge the bottom chunk into top chunk, so we are always merging backwards
         Timber.v("Merge ${currentChunk.prevToken} | ${currentChunk.nextToken} with ${otherChunk.prevToken} | ${otherChunk.nextToken}")
         return if (direction == PaginationDirection.BACKWARDS && !otherChunk.isLastForward) {
-            currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
+            val events = currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
+            timelineEventSenderVisitor.visit(events)
             roomEntity.deleteOnCascade(otherChunk)
             currentChunk
         } else {
-            otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
+            val events = otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
+            timelineEventSenderVisitor.visit(events)
             roomEntity.deleteOnCascade(currentChunk)
             otherChunk
         }
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 f3c0cd9511..647635343e 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
@@ -48,7 +48,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                                    private val roomTagHandler: RoomTagHandler,
                                                    private val roomFullyReadHandler: RoomFullyReadHandler,
                                                    private val cryptoService: DefaultCryptoService,
-                                                   private val roomMemberEventHandler: RoomMemberEventHandler) {
+                                                   private val roomMemberEventHandler: RoomMemberEventHandler,
+                                                   private val timelineEventSenderVisitor: TimelineEventSenderVisitor) {
 
     sealed class HandlingStrategy {
         data class JOINED(val data: Map) : HandlingStrategy()
@@ -209,7 +210,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             }
             roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
         }
-        chunkEntity.updateSenderDataFor(timelineEvents)
+        timelineEventSenderVisitor.visit(timelineEvents)
         return chunkEntity
     }
 

From e32d242e382ee01472a7b34900c2621e9b74b4ab Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 31 Dec 2019 12:57:45 +0100
Subject: [PATCH 12/26] Timeline: remove use of isUnlinked method as it slows
 down the insertion a lot

---
 .../database/helper/ChunkEntityHelper.kt      | 23 ++++++-------------
 .../helper/TimelineEventSenderVisitor.kt      |  6 +++--
 .../internal/database/model/ChunkEntity.kt    |  3 ++-
 .../database/query/ChunkEntityQueries.kt      |  8 ++++++-
 .../room/timeline/TokenChunkEventPersistor.kt | 12 ++++------
 .../internal/session/sync/RoomSyncHandler.kt  |  1 +
 .../riotx/features/home/HomeDetailFragment.kt |  1 -
 7 files changed, 25 insertions(+), 29 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
index 90236a4f98..4060e21102 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
@@ -30,15 +30,6 @@ import im.vector.matrix.android.internal.session.room.timeline.PaginationDirecti
 import io.realm.Sort
 import io.realm.kotlin.createObject
 
-// By default if a chunk is empty we consider it unlinked
-internal fun ChunkEntity.isUnlinked(): Boolean {
-    assertIsManaged()
-    return timelineEvents.where()
-            .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
-            .findAll()
-            .isEmpty()
-}
-
 internal fun ChunkEntity.deleteOnCascade() {
     assertIsManaged()
     this.timelineEvents.deleteAllFromRealm()
@@ -49,9 +40,8 @@ internal fun ChunkEntity.merge(roomId: String,
                                chunkToMerge: ChunkEntity,
                                direction: PaginationDirection): List {
     assertIsManaged()
-    val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
-    val isCurrentChunkUnlinked = this.isUnlinked()
-    val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
+    val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
+    val isCurrentChunkUnlinked = isUnlinked
 
     if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
         this.timelineEvents.forEach { it.root?.isUnlinked = false }
@@ -69,15 +59,15 @@ internal fun ChunkEntity.merge(roomId: String,
     return eventsToMerge
             .mapNotNull {
                 val event = it.root?.asDomain() ?: return@mapNotNull null
-                add(roomId, event, direction, isUnlinked = isUnlinked)
+                add(roomId, event, direction)
             }
 }
 
 internal fun ChunkEntity.add(roomId: String,
                              event: Event,
                              direction: PaginationDirection,
-                             stateIndexOffset: Int = 0,
-                             isUnlinked: Boolean = false): TimelineEventEntity? {
+                             stateIndexOffset: Int = 0
+): TimelineEventEntity? {
     assertIsManaged()
     if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
         return null
@@ -102,6 +92,7 @@ internal fun ChunkEntity.add(roomId: String,
         }
     }
 
+    val isUnlinked = isUnlinked
     val localId = TimelineEventEntity.nextId(realm)
     val eventId = event.eventId ?: ""
     val senderId = event.senderId ?: ""
@@ -128,9 +119,9 @@ internal fun ChunkEntity.add(roomId: String,
 
     val rootEvent = event.toEntity(roomId).apply {
         this.stateIndex = currentStateIndex
-        this.isUnlinked = isUnlinked
         this.displayIndex = currentDisplayIndex
         this.sendState = SendState.SYNCED
+        this.isUnlinked = isUnlinked
     }
     val eventEntity = realm.createObject().also {
         it.localId = localId
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt
index 2e8b02d6aa..983de3a50f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt
@@ -66,6 +66,9 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
     fun visit(timelineEventEntities: List) = timelineEventEntities.forEach { visit(it) }
 
     fun visit(timelineEventEntity: TimelineEventEntity) {
+        if (!timelineEventEntity.isValid) {
+            return
+        }
         val key = Key(
                 roomId = timelineEventEntity.roomId,
                 stateIndex = timelineEventEntity.root?.stateIndex ?: 0,
@@ -96,7 +99,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
         val stateIndex = root?.stateIndex ?: return result
         val senderId = root?.sender ?: return result
         val chunkEntity = chunk?.firstOrNull() ?: return result
-        val isUnlinked = chunkEntity.isUnlinked()
+        val isUnlinked = chunkEntity.isUnlinked
         var senderMembershipEvent: EventEntity?
         var senderRoomMemberContent: String?
         var senderRoomMemberPrevContent: String?
@@ -142,5 +145,4 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
         result.senderMembershipEventId = senderMembershipEvent?.eventId
         return result
     }
-
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt
index 577c391b3a..94d4a9043f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt
@@ -30,7 +30,8 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
                                 var backwardsDisplayIndex: Int? = null,
                                 var forwardsDisplayIndex: Int? = null,
                                 var backwardsStateIndex: Int? = null,
-                                var forwardsStateIndex: Int? = null
+                                var forwardsStateIndex: Int? = null,
+                                var isUnlinked: Boolean = false
 ) : RealmObject() {
 
     fun identifier() = "${prevToken}_$nextToken"
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt
index 69402ac1de..b8c058e667 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt
@@ -57,9 +57,15 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str
     return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull()
 }
 
-internal fun ChunkEntity.Companion.create(realm: Realm, prevToken: String?, nextToken: String?): ChunkEntity {
+internal fun ChunkEntity.Companion.create(
+        realm: Realm,
+        prevToken: String?,
+        nextToken: String?,
+        isUnlinked: Boolean
+): ChunkEntity {
     return realm.createObject().apply {
         this.prevToken = prevToken
         this.nextToken = nextToken
+        this.isUnlinked = isUnlinked
     }
 }
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 9773eff507..87c59e832b 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
@@ -18,10 +18,6 @@ package im.vector.matrix.android.internal.session.room.timeline
 
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.internal.database.helper.*
-import im.vector.matrix.android.internal.database.helper.add
-import im.vector.matrix.android.internal.database.helper.addOrUpdate
-import im.vector.matrix.android.internal.database.helper.addStateEvent
-import im.vector.matrix.android.internal.database.helper.deleteOnCascade
 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
@@ -136,14 +132,14 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
 
                     // The current chunk is the one we will keep all along the merge processChanges.
                     // We try to look for a chunk next to the token,
-                    // otherwise we create a whole new one
+                    // otherwise we create a whole new one which is unlinked (not live)
 
                     var currentChunk = if (direction == PaginationDirection.FORWARDS) {
                         prevChunk?.apply { this.nextToken = nextToken }
                     } else {
                         nextChunk?.apply { this.prevToken = prevToken }
                     }
-                            ?: ChunkEntity.create(realm, prevToken, nextToken)
+                            ?: ChunkEntity.create(realm, prevToken, nextToken, isUnlinked = true)
 
                     if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
                         Timber.v("Reach end of $roomId")
@@ -151,7 +147,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
                     } else if (!shouldSkip) {
                         Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
                         val timelineEvents = receivedChunk.events.mapNotNull {
-                            currentChunk.add(roomId, it, direction, isUnlinked = currentChunk.isUnlinked())
+                            currentChunk.add(roomId, it, direction)
                         }
                         // Then we merge chunks if needed
                         if (currentChunk != prevChunk && prevChunk != null) {
@@ -169,7 +165,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
                         }
                         roomEntity.addOrUpdate(currentChunk)
                         for (stateEvent in receivedChunk.stateEvents) {
-                            roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
+                            roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked)
                         }
                         timelineEventSenderVisitor.visit(timelineEvents)
                     }
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 647635343e..488b9ce83d 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
@@ -190,6 +190,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         }
         lastChunk?.isLastForward = false
         chunkEntity.isLastForward = true
+        chunkEntity.isUnlinked = false
 
         val timelineEvents = ArrayList(eventList.size)
         for (event in eventList) {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
index 2913ce80fe..85f14e99a8 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
@@ -198,5 +198,4 @@ class HomeDetailFragment @Inject constructor(
         RoomListDisplayMode.ROOMS  -> R.id.bottom_action_rooms
         else                       -> R.id.bottom_action_home
     }
-
 }

From 3cc15387ae3ec1dca308a2070eabca65bbd73551 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Mon, 6 Jan 2020 18:41:09 +0100
Subject: [PATCH 13/26] Realm: compatch on launch

---
 .../internal/database/SessionRealmConfigurationFactory.kt        | 1 +
 1 file changed, 1 insertion(+)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt
index bc806a56a4..24f8a83c56 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt
@@ -57,6 +57,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
                 .apply()
 
         val realmConfiguration = RealmConfiguration.Builder()
+                .compactOnLaunch()
                 .directory(directory)
                 .name(REALM_NAME)
                 .apply {

From 99c523b710bec38d3ce80515530925761cb1c6e5 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Mon, 6 Jan 2020 18:43:34 +0100
Subject: [PATCH 14/26] Update libs

---
 matrix-sdk-android/build.gradle | 5 ++---
 vector/build.gradle             | 1 +
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 799a619748..7a1348a54c 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -102,7 +102,6 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
 
     implementation "androidx.appcompat:appcompat:1.1.0"
-    implementation "androidx.recyclerview:recyclerview:1.1.0-beta05"
 
     implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
     kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
@@ -119,14 +118,14 @@ dependencies {
     implementation "ru.noties.markwon:core:$markwon_version"
 
     // Image
-    implementation 'androidx.exifinterface:exifinterface:1.0.0'
+    implementation 'androidx.exifinterface:exifinterface:1.1.0'
 
     // Database
     implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
     kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
 
     // Work
-    implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01"
+    implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
 
     // FP
     implementation "io.arrow-kt:arrow-core:$arrow_version"
diff --git a/vector/build.gradle b/vector/build.gradle
index 410e478ee2..461168a7a5 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -236,6 +236,7 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
 
+    implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01"
     implementation 'androidx.appcompat:appcompat:1.1.0'
     implementation "androidx.fragment:fragment:$fragment_version"
     implementation "androidx.fragment:fragment-ktx:$fragment_version"

From f9487f89959f0fee398ce31357035d97146c2e8f Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Mon, 6 Jan 2020 18:44:04 +0100
Subject: [PATCH 15/26] Work on timeline

---
 .../api/session/room/model/RoomSummary.kt     |  3 +-
 .../database/helper/ChunkEntityHelper.kt      |  4 +-
 .../database/mapper/RoomSummaryMapper.kt      |  3 +-
 .../database/model/RoomSummaryEntity.kt       |  3 +-
 .../session/room/RoomSummaryUpdater.kt        | 10 +-
 .../session/room/timeline/DefaultTimeline.kt  | 52 +++++-----
 .../home/room/detail/RoomDetailFragment.kt    | 38 ++++---
 .../home/room/detail/RoomDetailViewModel.kt   | 99 ++++++++++---------
 .../home/room/detail/RoomDetailViewState.kt   |  2 -
 .../timeline/TimelineEventController.kt       |  7 +-
 10 files changed, 112 insertions(+), 109 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt
index 129c35a17e..c18645ddbd 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt
@@ -41,7 +41,8 @@ data class RoomSummary(
         val membership: Membership = Membership.NONE,
         val versioningState: VersioningState = VersioningState.NONE,
         val readMarkerId: String? = null,
-        val userDrafts: List = emptyList()
+        val userDrafts: List = emptyList(),
+        var isEncrypted: Boolean
 ) {
 
     val isVersioned: Boolean
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
index 4060e21102..3fa355fe3c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
@@ -92,7 +92,7 @@ internal fun ChunkEntity.add(roomId: String,
         }
     }
 
-    val isUnlinked = isUnlinked
+    val isChunkUnlinked = isUnlinked
     val localId = TimelineEventEntity.nextId(realm)
     val eventId = event.eventId ?: ""
     val senderId = event.senderId ?: ""
@@ -121,7 +121,7 @@ internal fun ChunkEntity.add(roomId: String,
         this.stateIndex = currentStateIndex
         this.displayIndex = currentDisplayIndex
         this.sendState = SendState.SYNCED
-        this.isUnlinked = isUnlinked
+        this.isUnlinked = isChunkUnlinked
     }
     val eventEntity = realm.createObject().also {
         it.localId = localId
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt
index eeb340eacb..7d25a846ff 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt
@@ -70,7 +70,8 @@ internal class RoomSummaryMapper @Inject constructor(
                 readMarkerId = roomSummaryEntity.readMarkerId,
                 userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
                 canonicalAlias = roomSummaryEntity.canonicalAlias,
-                aliases = roomSummaryEntity.aliases.toList()
+                aliases = roomSummaryEntity.aliases.toList(),
+                isEncrypted = roomSummaryEntity.isEncrypted
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt
index 2fa892d874..4c99832b39 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt
@@ -43,7 +43,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
                                       var canonicalAlias: String? = null,
                                       var aliases: RealmList = RealmList(),
                                       // this is required for querying
-                                      var flatAliases: String = ""
+                                      var flatAliases: String = "",
+                                      var isEncrypted: Boolean = false
 ) : RealmObject() {
 
     private var membershipStr: String = Membership.NONE.name
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
index 536484de5a..30d9969f15 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
@@ -93,10 +93,12 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
         val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
         val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev()
         val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
+        val encryptionEvent = EventEntity.where(realm, roomId, EventType.ENCRYPTION).prev()
 
         roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
-                // avoid this call if we are sure there are unread events
-                || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
+                                              // avoid this call if we are sure there are unread events
+                                              || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
+
 
         roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
         roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
@@ -105,10 +107,12 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
         roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel()
                 ?.canonicalAlias
 
-        val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases ?: emptyList()
+        val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases
+                          ?: emptyList()
         roomSummaryEntity.aliases.clear()
         roomSummaryEntity.aliases.addAll(roomAliases)
         roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
+        roomSummaryEntity.isEncrypted = encryptionEvent != null
 
         if (updateMembers) {
             val otherRoomMembers = RoomMembers(realm, roomId)
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 85bab5d706..7835cf7e3e 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
@@ -52,8 +52,8 @@ import io.realm.RealmQuery
 import io.realm.RealmResults
 import io.realm.Sort
 import timber.log.Timber
-import java.util.Collections
-import java.util.UUID
+import java.util.*
+import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicReference
 import kotlin.collections.ArrayList
@@ -77,11 +77,9 @@ internal class DefaultTimeline(
         private val hiddenReadReceipts: TimelineHiddenReadReceipts
 ) : Timeline, TimelineHiddenReadReceipts.Delegate {
 
-    private companion object {
-        val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
-    }
+    val backgroundHandler = createBackgroundHandler("TIMELINE_DB_THREAD_${System.currentTimeMillis()}")
 
-    private val listeners = ArrayList()
+    private val listeners = CopyOnWriteArrayList()
     private val isStarted = AtomicBoolean(false)
     private val isReady = AtomicBoolean(false)
     private val mainHandler = createUIHandler()
@@ -137,7 +135,7 @@ internal class DefaultTimeline(
 // Public methods ******************************************************************************
 
     override fun paginate(direction: Timeline.Direction, count: Int) {
-        BACKGROUND_HANDLER.post {
+        backgroundHandler.post {
             if (!canPaginate(direction)) {
                 return@post
             }
@@ -165,7 +163,7 @@ internal class DefaultTimeline(
     override fun start() {
         if (isStarted.compareAndSet(false, true)) {
             Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
-            BACKGROUND_HANDLER.post {
+            backgroundHandler.post {
                 eventDecryptor.start()
                 val realm = Realm.getInstance(realmConfiguration)
                 backgroundRealm.set(realm)
@@ -199,8 +197,8 @@ internal class DefaultTimeline(
             isReady.set(false)
             Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
             cancelableBag.cancel()
-            BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
-            BACKGROUND_HANDLER.post {
+            backgroundHandler.removeCallbacksAndMessages(null)
+            backgroundHandler.post {
                 roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
                 if (this::eventRelations.isInitialized) {
                     eventRelations.removeAllChangeListeners()
@@ -288,20 +286,20 @@ internal class DefaultTimeline(
         return hasMoreInCache(direction) || !hasReachedEnd(direction)
     }
 
-    override fun addListener(listener: Timeline.Listener) = synchronized(listeners) {
+    override fun addListener(listener: Timeline.Listener): Boolean {
         if (listeners.contains(listener)) {
             return false
         }
-        listeners.add(listener).also {
+        return listeners.add(listener).also {
             postSnapshot()
         }
     }
 
-    override fun removeListener(listener: Timeline.Listener) = synchronized(listeners) {
-        listeners.remove(listener)
+    override fun removeListener(listener: Timeline.Listener): Boolean {
+        return listeners.remove(listener)
     }
 
-    override fun removeAllListeners() = synchronized(listeners) {
+    override fun removeAllListeners() {
         listeners.clear()
     }
 
@@ -497,9 +495,9 @@ internal class DefaultTimeline(
             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")
         cancelableBag += paginationTask
@@ -516,7 +514,7 @@ internal class DefaultTimeline(
                                 }
                                 TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
                                     // Database won't be updated, so we force pagination request
-                                    BACKGROUND_HANDLER.post {
+                                    backgroundHandler.post {
                                         executePaginationTask(direction, limit)
                                     }
                             }
@@ -575,7 +573,7 @@ internal class DefaultTimeline(
             val timelineEvent = buildTimelineEvent(eventEntity)
 
             if (timelineEvent.isEncrypted()
-                    && timelineEvent.root.mxDecryptionResult == null) {
+                && timelineEvent.root.mxDecryptionResult == null) {
                 timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
             }
 
@@ -649,17 +647,15 @@ internal class DefaultTimeline(
     }
 
     private fun postSnapshot() {
-        BACKGROUND_HANDLER.post {
+        backgroundHandler.post {
             if (isReady.get().not()) {
                 return@post
             }
             updateLoadingStates(filteredEvents)
             val snapshot = createSnapshot()
             val runnable = Runnable {
-                synchronized(listeners) {
-                    listeners.forEach {
-                        it.onTimelineUpdated(snapshot)
-                    }
+                listeners.forEach {
+                    it.onTimelineUpdated(snapshot)
                 }
             }
             debouncer.debounce("post_snapshot", runnable, 50)
@@ -671,10 +667,8 @@ internal class DefaultTimeline(
             return
         }
         val runnable = Runnable {
-            synchronized(listeners) {
-                listeners.forEach {
-                    it.onTimelineFailure(throwable)
-                }
+            listeners.forEach {
+                it.onTimelineFailure(throwable)
             }
         }
         mainHandler.post(runnable)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index fa483d675c..8c985c27c0 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -40,6 +40,7 @@ import androidx.core.util.Pair
 import androidx.core.view.ViewCompat
 import androidx.core.view.forEach
 import androidx.core.view.isVisible
+import androidx.lifecycle.observe
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -71,6 +72,7 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
 import im.vector.matrix.android.api.util.MatrixItem
 import im.vector.matrix.android.api.util.toMatrixItem
 import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
+import im.vector.matrix.rx.rx
 import im.vector.riotx.R
 import im.vector.riotx.core.dialogs.withColoredButton
 import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
@@ -225,6 +227,8 @@ class RoomDetailFragment @Inject constructor(
         setupNotificationView()
         setupJumpToReadMarkerView()
         setupJumpToBottomView()
+
+
         roomDetailViewModel.subscribe { renderState(it) }
         textComposerViewModel.subscribe { renderTextComposerState(it) }
         roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) }
@@ -325,12 +329,10 @@ class RoomDetailFragment @Inject constructor(
         jumpToBottomView.setOnClickListener {
             roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
             jumpToBottomView.visibility = View.INVISIBLE
-            withState(roomDetailViewModel) { state ->
-                if (state.timeline?.isLive == false) {
-                    state.timeline.restartWithEventId(null)
-                } else {
-                    layoutManager.scrollToPosition(0)
-                }
+            if (!roomDetailViewModel.timeline.isLive) {
+                roomDetailViewModel.timeline.restartWithEventId(null)
+            } else {
+                layoutManager.scrollToPosition(0)
             }
         }
     }
@@ -343,9 +345,9 @@ class RoomDetailFragment @Inject constructor(
         AlertDialog.Builder(requireActivity())
                 .setTitle(R.string.dialog_title_error)
                 .setMessage(getString(R.string.error_file_too_big,
-                        error.filename,
-                        TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
-                        TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
+                                      error.filename,
+                                      TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
+                                      TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
                 ))
                 .setPositiveButton(R.string.ok, null)
                 .show()
@@ -431,7 +433,8 @@ class RoomDetailFragment @Inject constructor(
         composerLayout.sendButton.setContentDescription(getString(descriptionRes))
 
         avatarRenderer.render(
-                MatrixItem.UserItem(event.root.senderId ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
+                MatrixItem.UserItem(event.root.senderId
+                                    ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
                 composerLayout.composerRelatedMessageAvatar
         )
         composerLayout.expand {
@@ -449,7 +452,7 @@ class RoomDetailFragment @Inject constructor(
             // Ignore update to avoid saving a draft
             composerLayout.composerEditText.setText(text)
             composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length
-                    ?: 0)
+                                                         ?: 0)
         }
     }
 
@@ -481,6 +484,9 @@ class RoomDetailFragment @Inject constructor(
 // PRIVATE METHODS *****************************************************************************
 
     private fun setupRecyclerView() {
+        timelineEventController.callback = this
+        timelineEventController.timeline = roomDetailViewModel.timeline
+
         val epoxyVisibilityTracker = EpoxyVisibilityTracker()
         epoxyVisibilityTracker.attach(recyclerView)
         layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
@@ -514,8 +520,6 @@ class RoomDetailFragment @Inject constructor(
             }
         })
 
-        timelineEventController.callback = this
-
         if (vectorPreferences.swipeToReplyIsEnabled()) {
             val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler {
                 override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
@@ -789,11 +793,12 @@ class RoomDetailFragment @Inject constructor(
     }
 
     private fun renderState(state: RoomDetailViewState) {
+        Timber.v("Render state summary complete: ${state.asyncRoomSummary.complete}")
         renderRoomSummary(state)
         val summary = state.asyncRoomSummary()
         val inviter = state.asyncInviter()
         if (summary?.membership == Membership.JOIN) {
-            scrollOnHighlightedEventCallback.timeline = state.timeline
+            scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
             timelineEventController.update(state)
             inviteView.visibility = View.GONE
             val uid = session.myUserId
@@ -808,9 +813,10 @@ class RoomDetailFragment @Inject constructor(
         } else if (state.asyncInviter.complete) {
             vectorBaseActivity.finish()
         }
+        val isRoomEncrypted = summary?.isEncrypted ?: false
         if (state.tombstoneEvent == null) {
             composerLayout.visibility = View.VISIBLE
-            composerLayout.setRoomEncrypted(state.isEncrypted)
+            composerLayout.setRoomEncrypted(isRoomEncrypted)
             notificationAreaView.render(NotificationAreaView.State.Hidden)
         } else {
             composerLayout.visibility = View.GONE
@@ -1312,7 +1318,7 @@ class RoomDetailFragment @Inject constructor(
         val startToCompose = composerLayout.composerEditText.text.isNullOrBlank()
 
         if (startToCompose
-                && userId == session.myUserId) {
+            && userId == session.myUserId) {
             // Empty composer, current user: start an emote
             composerLayout.composerEditText.setText(Command.EMOTE.command + " ")
             composerLayout.composerEditText.setSelection(Command.EMOTE.length)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index 467148302f..c93358a04e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -20,14 +20,18 @@ import android.net.Uri
 import androidx.annotation.IdRes
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import com.airbnb.mvrx.*
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.ViewModelContext
 import com.jakewharton.rxrelay2.BehaviorRelay
 import com.jakewharton.rxrelay2.PublishRelay
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.MatrixPatterns
-import im.vector.matrix.android.api.failure.Failure
 import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.events.model.EventType
 import im.vector.matrix.android.api.session.events.model.isImageMessage
@@ -89,20 +93,21 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
     private val visibleEventsObservable = BehaviorRelay.create()
     private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
         TimelineSettings(30,
-                filterEdits = false,
-                filterTypes = true,
-                allowedTypes = TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES,
-                buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
+                         filterEdits = false,
+                         filterTypes = true,
+                         allowedTypes = TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES,
+                         buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
     } else {
         TimelineSettings(30,
-                filterEdits = true,
-                filterTypes = true,
-                allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES,
-                buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
+                         filterEdits = true,
+                         filterTypes = true,
+                         allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES,
+                         buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
     }
 
     private var timelineEvents = PublishRelay.create>()
-    private var timeline = room.createTimeline(eventId, timelineSettings)
+    var timeline = room.createTimeline(eventId, timelineSettings)
+        private set
 
     private val _viewEvents = PublishDataSource()
     val viewEvents: DataSource = _viewEvents
@@ -138,18 +143,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
     }
 
     init {
+        timeline.start()
+        timeline.addListener(this)
+        observeRoomSummary()
+        observeSummaryState()
         getUnreadState()
         observeSyncState()
-        observeRoomSummary()
         observeEventDisplayedActions()
-        observeSummaryState()
         observeDrafts()
         observeUnreadState()
+        room.getRoomSummaryLive()
         room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
-        timeline.addListener(this)
-        timeline.start()
-        setState { copy(timeline = this@RoomDetailViewModel.timeline) }
-
         // Inform the SDK that the room is displayed
         session.onRoomDisplayed(initialState.roomId)
     }
@@ -233,23 +237,23 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                         copy(
                                 // Create a sendMode from a draft and retrieve the TimelineEvent
                                 sendMode = when (draft) {
-                                    is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
-                                    is UserDraft.QUOTE   -> {
-                                        room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
-                                            SendMode.QUOTE(timelineEvent, draft.text)
-                                        }
-                                    }
-                                    is UserDraft.REPLY   -> {
-                                        room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
-                                            SendMode.REPLY(timelineEvent, draft.text)
-                                        }
-                                    }
-                                    is UserDraft.EDIT    -> {
-                                        room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
-                                            SendMode.EDIT(timelineEvent, draft.text)
-                                        }
-                                    }
-                                } ?: SendMode.REGULAR("")
+                                               is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
+                                               is UserDraft.QUOTE   -> {
+                                                   room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
+                                                       SendMode.QUOTE(timelineEvent, draft.text)
+                                                   }
+                                               }
+                                               is UserDraft.REPLY   -> {
+                                                   room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
+                                                       SendMode.REPLY(timelineEvent, draft.text)
+                                                   }
+                                               }
+                                               is UserDraft.EDIT    -> {
+                                                   room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
+                                                       SendMode.EDIT(timelineEvent, draft.text)
+                                                   }
+                                               }
+                                           } ?: SendMode.REGULAR("")
                         )
                     }
                 }
@@ -258,7 +262,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
 
     private fun handleTombstoneEvent(action: RoomDetailAction.HandleTombstoneEvent) {
         val tombstoneContent = action.event.getClearContent().toModel()
-                ?: return
+                               ?: return
 
         val roomId = tombstoneContent.replacementRoom ?: ""
         val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
@@ -310,7 +314,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
         else                     -> false
     }
 
-    // PRIVATE METHODS *****************************************************************************
+// PRIVATE METHODS *****************************************************************************
 
     private fun handleSendMessage(action: RoomDetailAction.SendMessage) {
         withState { state ->
@@ -396,7 +400,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                 is SendMode.EDIT    -> {
                     // is original event a reply?
                     val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId
-                            ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId
+                                    ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId
                     if (inReplyTo != null) {
                         // TODO check if same content?
                         room.getTimeLineEvent(inReplyTo)?.let {
@@ -405,13 +409,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                     } else {
                         val messageContent: MessageContent? =
                                 state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
-                                        ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
+                                ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
                         val existingBody = messageContent?.body ?: ""
                         if (existingBody != action.text) {
                             room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "",
-                                    messageContent?.type ?: MessageType.MSGTYPE_TEXT,
-                                    action.text,
-                                    action.autoMarkdown)
+                                                 messageContent?.type ?: MessageType.MSGTYPE_TEXT,
+                                                 action.text,
+                                                 action.autoMarkdown)
                         } else {
                             Timber.w("Same message content, do not send edition")
                         }
@@ -422,7 +426,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                 is SendMode.QUOTE   -> {
                     val messageContent: MessageContent? =
                             state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
-                                    ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
+                            ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
                     val textMsg = messageContent?.body
 
                     val finalText = legacyRiotQuoteText(textMsg, action.text.toString())
@@ -538,7 +542,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
             when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
                 null -> room.sendMedias(attachments)
                 else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name
-                        ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
+                                                                             ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
             }
         }
     }
@@ -728,7 +732,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                 .filter { it.isNotEmpty() }
                 .subscribeBy(onNext = { actions ->
                     val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event
-                            ?: return@subscribeBy
+                                                           ?: return@subscribeBy
                     val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
                     if (trackUnreadMessages.get()) {
                         if (globalMostRecentDisplayedEvent == null) {
@@ -791,10 +795,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
         room.rx().liveRoomSummary()
                 .unwrap()
                 .execute { async ->
-                    copy(
-                            asyncRoomSummary = async,
-                            isEncrypted = room.isEncrypted()
-                    )
+                    copy(asyncRoomSummary = async)
                 }
     }
 
@@ -880,7 +881,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
 
     override fun onCleared() {
         timeline.dispose()
-        timeline.removeListener(this)
+        timeline.removeAllListeners()
         super.onCleared()
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
index b2ad29668e..3067b7f1b2 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
@@ -51,11 +51,9 @@ sealed class UnreadState {
 data class RoomDetailViewState(
         val roomId: String,
         val eventId: String?,
-        val timeline: Timeline? = null,
         val asyncInviter: Async = Uninitialized,
         val asyncRoomSummary: Async = Uninitialized,
         val sendMode: SendMode = SendMode.REGULAR(""),
-        val isEncrypted: Boolean = false,
         val tombstoneEvent: Event? = null,
         val tombstoneEventHandling: Async = Uninitialized,
         val syncState: SyncState = SyncState.Idle,
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt
index 582544ce8a..a08669da3b 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt
@@ -95,12 +95,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
     private val modelCache = arrayListOf()
     private var currentSnapshot: List = emptyList()
     private var inSubmitList: Boolean = false
-    private var timeline: Timeline? = null
     private var unreadState: UnreadState = UnreadState.Unknown
     private var positionOfReadMarker: Int? = null
     private var eventIdToHighlight: String? = null
 
     var callback: Callback? = null
+    var timeline: Timeline? = null
 
     private val listUpdateCallback = object : ListUpdateCallback {
 
@@ -176,10 +176,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
     }
 
     fun update(viewState: RoomDetailViewState) {
-        if (timeline?.timelineID != viewState.timeline?.timelineID) {
-            timeline = viewState.timeline
-            timeline?.addListener(this)
-        }
         var requestModelBuild = false
         if (eventIdToHighlight != viewState.highlightedEventId) {
             // Clear cache to force a refresh
@@ -205,6 +201,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
 
     override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
         super.onAttachedToRecyclerView(recyclerView)
+        timeline?.addListener(this)
         timelineMediaSizeProvider.recyclerView = recyclerView
     }
 

From f09bf61750e934fe5bcd1320d15e1a31f187cfb7 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 7 Jan 2020 13:31:34 +0100
Subject: [PATCH 16/26] Room detail: try to get some better perfs with fetching
 data. LiveData is slow as we only use one HandlerThread at the time. Might
 want Realm 7.0 and frozen objects to rework that

---
 .../main/java/im/vector/matrix/rx/RxRoom.kt   |  3 +
 .../session/room/timeline/DefaultTimeline.kt  | 16 ++--
 .../home/room/detail/RoomDetailFragment.kt    | 20 ++--
 .../home/room/detail/RoomDetailViewModel.kt   |  1 +
 .../action/MessageActionsEpoxyController.kt   |  2 +-
 .../action/MessageActionsViewModel.kt         | 94 +++++++++----------
 6 files changed, 64 insertions(+), 72 deletions(-)

diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
index bf4e924cf0..8a94fa30f7 100644
--- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
+++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
 import im.vector.matrix.android.api.session.room.send.UserDraft
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
 import im.vector.matrix.android.api.util.Optional
+import im.vector.matrix.android.api.util.toOptional
 import io.reactivex.Observable
 import io.reactivex.Single
 
@@ -29,6 +30,7 @@ class RxRoom(private val room: Room) {
 
     fun liveRoomSummary(): Observable> {
         return room.getRoomSummaryLive().asObservable()
+                .startWith(room.roomSummary().toOptional())
     }
 
     fun liveRoomMembers(memberships: List): Observable> {
@@ -41,6 +43,7 @@ class RxRoom(private val room: Room) {
 
     fun liveTimelineEvent(eventId: String): Observable> {
         return room.getTimeLineEventLive(eventId).asObservable()
+                .startWith(room.getTimeLineEvent(eventId).toOptional())
     }
 
     fun liveReadMarker(): Observable> {
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 7835cf7e3e..3e0f488d1a 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
@@ -77,7 +77,9 @@ internal class DefaultTimeline(
         private val hiddenReadReceipts: TimelineHiddenReadReceipts
 ) : Timeline, TimelineHiddenReadReceipts.Delegate {
 
-    val backgroundHandler = createBackgroundHandler("TIMELINE_DB_THREAD_${System.currentTimeMillis()}")
+    companion object{
+        val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
+    }
 
     private val listeners = CopyOnWriteArrayList()
     private val isStarted = AtomicBoolean(false)
@@ -135,7 +137,7 @@ internal class DefaultTimeline(
 // Public methods ******************************************************************************
 
     override fun paginate(direction: Timeline.Direction, count: Int) {
-        backgroundHandler.post {
+        BACKGROUND_HANDLER.post {
             if (!canPaginate(direction)) {
                 return@post
             }
@@ -163,7 +165,7 @@ internal class DefaultTimeline(
     override fun start() {
         if (isStarted.compareAndSet(false, true)) {
             Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
-            backgroundHandler.post {
+            BACKGROUND_HANDLER.post {
                 eventDecryptor.start()
                 val realm = Realm.getInstance(realmConfiguration)
                 backgroundRealm.set(realm)
@@ -197,8 +199,8 @@ internal class DefaultTimeline(
             isReady.set(false)
             Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
             cancelableBag.cancel()
-            backgroundHandler.removeCallbacksAndMessages(null)
-            backgroundHandler.post {
+            BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
+            BACKGROUND_HANDLER.post {
                 roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
                 if (this::eventRelations.isInitialized) {
                     eventRelations.removeAllChangeListeners()
@@ -514,7 +516,7 @@ internal class DefaultTimeline(
                                 }
                                 TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
                                     // Database won't be updated, so we force pagination request
-                                    backgroundHandler.post {
+                                    BACKGROUND_HANDLER.post {
                                         executePaginationTask(direction, limit)
                                     }
                             }
@@ -647,7 +649,7 @@ internal class DefaultTimeline(
     }
 
     private fun postSnapshot() {
-        backgroundHandler.post {
+        BACKGROUND_HANDLER.post {
             if (isReady.get().not()) {
                 return@post
             }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index 8c985c27c0..d8502a75b6 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -40,7 +40,6 @@ import androidx.core.util.Pair
 import androidx.core.view.ViewCompat
 import androidx.core.view.forEach
 import androidx.core.view.isVisible
-import androidx.lifecycle.observe
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -72,7 +71,6 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
 import im.vector.matrix.android.api.util.MatrixItem
 import im.vector.matrix.android.api.util.toMatrixItem
 import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
-import im.vector.matrix.rx.rx
 import im.vector.riotx.R
 import im.vector.riotx.core.dialogs.withColoredButton
 import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
@@ -89,8 +87,8 @@ import im.vector.riotx.features.attachments.ContactAttachment
 import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
 import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
 import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
-import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
 import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter
+import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
 import im.vector.riotx.features.command.Command
 import im.vector.riotx.features.home.AvatarRenderer
 import im.vector.riotx.features.home.getColorFromUserId
@@ -228,10 +226,9 @@ class RoomDetailFragment @Inject constructor(
         setupJumpToReadMarkerView()
         setupJumpToBottomView()
 
-
         roomDetailViewModel.subscribe { renderState(it) }
         textComposerViewModel.subscribe { renderTextComposerState(it) }
-        roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) }
+        roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) }
 
         roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair ->
             val message = requireContext().getString(pair.first, *pair.second.toTypedArray())
@@ -345,9 +342,9 @@ class RoomDetailFragment @Inject constructor(
         AlertDialog.Builder(requireActivity())
                 .setTitle(R.string.dialog_title_error)
                 .setMessage(getString(R.string.error_file_too_big,
-                                      error.filename,
-                                      TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
-                                      TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
+                        error.filename,
+                        TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
+                        TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
                 ))
                 .setPositiveButton(R.string.ok, null)
                 .show()
@@ -434,7 +431,7 @@ class RoomDetailFragment @Inject constructor(
 
         avatarRenderer.render(
                 MatrixItem.UserItem(event.root.senderId
-                                    ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
+                        ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
                 composerLayout.composerRelatedMessageAvatar
         )
         composerLayout.expand {
@@ -452,7 +449,7 @@ class RoomDetailFragment @Inject constructor(
             // Ignore update to avoid saving a draft
             composerLayout.composerEditText.setText(text)
             composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length
-                                                         ?: 0)
+                    ?: 0)
         }
     }
 
@@ -793,7 +790,6 @@ class RoomDetailFragment @Inject constructor(
     }
 
     private fun renderState(state: RoomDetailViewState) {
-        Timber.v("Render state summary complete: ${state.asyncRoomSummary.complete}")
         renderRoomSummary(state)
         val summary = state.asyncRoomSummary()
         val inviter = state.asyncInviter()
@@ -1318,7 +1314,7 @@ class RoomDetailFragment @Inject constructor(
         val startToCompose = composerLayout.composerEditText.text.isNullOrBlank()
 
         if (startToCompose
-            && userId == session.myUserId) {
+                && userId == session.myUserId) {
             // Empty composer, current user: start an emote
             composerLayout.composerEditText.setText(Command.EMOTE.command + " ")
             composerLayout.composerEditText.setSelection(Command.EMOTE.length)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index c93358a04e..d4145a7de2 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -51,6 +51,7 @@ 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.session.room.timeline.TimelineSettings
 import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
+import im.vector.matrix.android.api.util.toOptional
 import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
 import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
 import im.vector.matrix.rx.rx
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
index 939564e780..9a2fb4b6de 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
@@ -93,7 +93,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
         }
 
         // Action
-        state.actions()?.forEachIndexed { index, action ->
+        state.actions.forEachIndexed { index, action ->
             if (action is EventSharedAction.Separator) {
                 bottomSheetSeparatorItem {
                     id("separator_$index")
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index d537b66ec3..6bf5746735 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -31,8 +31,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
 import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
 import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
-import im.vector.matrix.android.api.util.Optional
-import im.vector.matrix.rx.RxRoom
+import im.vector.matrix.rx.rx
 import im.vector.matrix.rx.unwrap
 import im.vector.riotx.R
 import im.vector.riotx.core.extensions.canReact
@@ -62,7 +61,7 @@ data class MessageActionState(
         // For quick reactions
         val quickStates: Async> = Uninitialized,
         // For actions
-        val actions: Async> = Uninitialized,
+        val actions: List = emptyList(),
         val expendedReportContentMenu: Boolean = false
 ) : MvRxState {
 
@@ -112,7 +111,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
     init {
         observeEvent()
         observeReactions()
-        observeEventAction()
+        observeTimelineEventState()
     }
 
     override fun handle(action: MessageActionsAction) {
@@ -131,32 +130,17 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
 
     private fun observeEvent() {
         if (room == null) return
-        RxRoom(room)
+        room.rx()
                 .liveTimelineEvent(eventId)
                 .unwrap()
                 .execute {
-                    copy(
-                            timelineEvent = it,
-                            messageBody = computeMessageBody(it)
-                    )
-                }
-    }
-
-    private fun observeEventAction() {
-        if (room == null) return
-        RxRoom(room)
-                .liveTimelineEvent(eventId)
-                .map {
-                    actionsForEvent(it)
-                }
-                .execute {
-                    copy(actions = it)
+                    copy(timelineEvent = it)
                 }
     }
 
     private fun observeReactions() {
         if (room == null) return
-        RxRoom(room)
+        room.rx()
                 .liveAnnotationSummary(eventId)
                 .map { annotations ->
                     quickEmojis.map { emoji ->
@@ -168,11 +152,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
                 }
     }
 
-    private fun computeMessageBody(timelineEvent: Async): CharSequence? {
-        return when (timelineEvent()?.root?.getClearType()) {
+    private fun observeTimelineEventState() {
+        asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent ->
+            val computedMessage = computeMessageBody(timelineEvent)
+            val actions = actionsForEvent(timelineEvent)
+            setState { copy(messageBody = computedMessage, actions = actions) }
+        }
+    }
+
+    private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? {
+        return when (timelineEvent.root.getClearType()) {
             EventType.MESSAGE,
             EventType.STICKER     -> {
-                val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
+                val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
                 if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
                     val html = messageContent.formattedBody
                             ?.takeIf { it.isNotBlank() }
@@ -193,41 +185,39 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
             EventType.CALL_INVITE,
             EventType.CALL_HANGUP,
             EventType.CALL_ANSWER -> {
-                timelineEvent()?.let { noticeEventFormatter.format(it) }
+                noticeEventFormatter.format(timelineEvent)
             }
             else                  -> null
         }
     }
 
-    private fun actionsForEvent(optionalEvent: Optional): List {
-        val event = optionalEvent.getOrNull() ?: return emptyList()
-
-        val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
-                ?: event.root.getClearContent().toModel()
+    private fun actionsForEvent(timelineEvent: TimelineEvent): List {
+        val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
+                ?: timelineEvent.root.getClearContent().toModel()
         val type = messageContent?.type
 
         return arrayListOf().apply {
-            if (event.root.sendState.hasFailed()) {
-                if (canRetry(event)) {
+            if (timelineEvent.root.sendState.hasFailed()) {
+                if (canRetry(timelineEvent)) {
                     add(EventSharedAction.Resend(eventId))
                 }
                 add(EventSharedAction.Remove(eventId))
-            } else if (event.root.sendState.isSending()) {
+            } else if (timelineEvent.root.sendState.isSending()) {
                 // TODO is uploading attachment?
-                if (canCancel(event)) {
+                if (canCancel(timelineEvent)) {
                     add(EventSharedAction.Cancel(eventId))
                 }
-            } else if (event.root.sendState == SendState.SYNCED) {
-                if (!event.root.isRedacted()) {
-                    if (canReply(event, messageContent)) {
+            } else if (timelineEvent.root.sendState == SendState.SYNCED) {
+                if (!timelineEvent.root.isRedacted()) {
+                    if (canReply(timelineEvent, messageContent)) {
                         add(EventSharedAction.Reply(eventId))
                     }
 
-                    if (canEdit(event, session.myUserId)) {
+                    if (canEdit(timelineEvent, session.myUserId)) {
                         add(EventSharedAction.Edit(eventId))
                     }
 
-                    if (canRedact(event, session.myUserId)) {
+                    if (canRedact(timelineEvent, session.myUserId)) {
                         add(EventSharedAction.Delete(eventId))
                     }
 
@@ -236,19 +226,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
                         add(EventSharedAction.Copy(messageContent!!.body))
                     }
 
-                    if (event.canReact()) {
+                    if (timelineEvent.canReact()) {
                         add(EventSharedAction.AddReaction(eventId))
                     }
 
-                    if (canQuote(event, messageContent)) {
+                    if (canQuote(timelineEvent, messageContent)) {
                         add(EventSharedAction.Quote(eventId))
                     }
 
-                    if (canViewReactions(event)) {
+                    if (canViewReactions(timelineEvent)) {
                         add(EventSharedAction.ViewReactions(informationData))
                     }
 
-                    if (event.hasBeenEdited()) {
+                    if (timelineEvent.hasBeenEdited()) {
                         add(EventSharedAction.ViewEditHistory(informationData))
                     }
 
@@ -261,29 +251,29 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
                         // TODO
                     }
 
-                    if (event.root.sendState == SendState.SENT) {
+                    if (timelineEvent.root.sendState == SendState.SENT) {
                         // TODO Can be redacted
 
                         // TODO sent by me or sufficient power level
                     }
                 }
 
-                add(EventSharedAction.ViewSource(event.root.toContentStringWithIndent()))
-                if (event.isEncrypted()) {
-                    val decryptedContent = event.root.toClearContentStringWithIndent()
+                add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent()))
+                if (timelineEvent.isEncrypted()) {
+                    val decryptedContent = timelineEvent.root.toClearContentStringWithIndent()
                             ?: stringProvider.getString(R.string.encryption_information_decryption_error)
                     add(EventSharedAction.ViewDecryptedSource(decryptedContent))
                 }
                 add(EventSharedAction.CopyPermalink(eventId))
 
-                if (session.myUserId != event.root.senderId) {
+                if (session.myUserId != timelineEvent.root.senderId) {
                     // not sent by me
-                    if (event.root.getClearType() == EventType.MESSAGE) {
-                        add(EventSharedAction.ReportContent(eventId, event.root.senderId))
+                    if (timelineEvent.root.getClearType() == EventType.MESSAGE) {
+                        add(EventSharedAction.ReportContent(eventId, timelineEvent.root.senderId))
                     }
 
                     add(EventSharedAction.Separator)
-                    add(EventSharedAction.IgnoreUser(event.root.senderId))
+                    add(EventSharedAction.IgnoreUser(timelineEvent.root.senderId))
                 }
             }
         }

From d710106bbbea953ec888532dd2ab3c7d0a5b4201 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 7 Jan 2020 14:09:04 +0100
Subject: [PATCH 17/26] Clean code

---
 .../android/internal/session/room/RoomSummaryUpdater.kt     | 1 -
 .../android/internal/session/room/membership/RoomMembers.kt | 6 ++++--
 .../internal/session/room/timeline/DefaultTimeline.kt       | 2 +-
 .../riotx/features/home/room/detail/RoomDetailViewModel.kt  | 1 -
 .../riotx/features/home/room/detail/RoomDetailViewState.kt  | 1 -
 5 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
index 30d9969f15..ea5c2e858c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt
@@ -99,7 +99,6 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
                                               // avoid this call if we are sure there are unread events
                                               || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
 
-
         roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
         roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
         roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel()?.topic
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt
index f2dd733978..e3775f5ade 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt
@@ -71,11 +71,13 @@ internal class RoomMembers(private val realm: Realm,
     }
 
     fun queryJoinedRoomMembersEvent(): RealmQuery {
-        return queryRoomMembersEvent().equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
+        return queryRoomMembersEvent()
+                .equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
     }
 
     fun queryInvitedRoomMembersEvent(): RealmQuery {
-        return queryRoomMembersEvent().equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
+        return queryRoomMembersEvent()
+                .equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
     }
 
     fun queryActiveRoomMembersEvent(): RealmQuery {
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 3e0f488d1a..d90cb60a15 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
@@ -77,7 +77,7 @@ internal class DefaultTimeline(
         private val hiddenReadReceipts: TimelineHiddenReadReceipts
 ) : Timeline, TimelineHiddenReadReceipts.Delegate {
 
-    companion object{
+    companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
     }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index d4145a7de2..c93358a04e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -51,7 +51,6 @@ 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.session.room.timeline.TimelineSettings
 import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
-import im.vector.matrix.android.api.util.toOptional
 import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
 import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
 import im.vector.matrix.rx.rx
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
index 3067b7f1b2..165ef7b625 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
@@ -21,7 +21,6 @@ import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.Uninitialized
 import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.room.model.RoomSummary
-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.session.sync.SyncState
 import im.vector.matrix.android.api.session.user.model.User

From 38c198fe02c5bc9151e45cbcc65640b8f415a85a Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 7 Jan 2020 18:15:07 +0100
Subject: [PATCH 18/26] Rx: fetch first before returning live data  results

---
 .../src/main/java/im/vector/matrix/rx/RxRoom.kt    |  4 +++-
 .../api/session/room/members/MembershipService.kt  |  8 ++++++++
 .../session/room/model/relation/RelationService.kt | 14 +++++++++++++-
 .../room/membership/DefaultMembershipService.kt    | 11 +++++++++++
 .../room/relation/DefaultRelationService.kt        | 11 ++++++++++-
 5 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
index 8a94fa30f7..48ff66720f 100644
--- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
+++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
@@ -35,10 +35,12 @@ class RxRoom(private val room: Room) {
 
     fun liveRoomMembers(memberships: List): Observable> {
         return room.getRoomMembersLive(memberships).asObservable()
+                .startWith(room.getRoomMembers(memberships))
     }
 
     fun liveAnnotationSummary(eventId: String): Observable> {
-        return room.getEventSummaryLive(eventId).asObservable()
+        return room.getEventAnnotationsSummaryLive(eventId).asObservable()
+                .startWith(room.getEventAnnotationsSummary(eventId).toOptional())
     }
 
     fun liveTimelineEvent(eventId: String): Observable> {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
index 12f0378af7..281697816c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
@@ -41,6 +41,14 @@ interface MembershipService {
      */
     fun getRoomMember(userId: String): RoomMember?
 
+
+    /**
+     * Return all the roomMembers of the room filtered by memberships
+     * @param memberships list of accepted memberships
+     * @return a roomMember list.
+     */
+    fun getRoomMembers(memberships: List): List
+
     /**
      * Return all the roomMembers of the room filtered by memberships
      * @param memberships list of accepted memberships
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
index 7d8f2f0bc1..d13d7db773 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
@@ -108,5 +108,17 @@ interface RelationService {
                        replyText: CharSequence,
                        autoMarkdown: Boolean = false): Cancelable?
 
-    fun getEventSummaryLive(eventId: String): LiveData>
+    /**
+     * Get the current EventAnnotationsSummary
+     * @param eventId the eventId to look for EventAnnotationsSummary
+     * @return the EventAnnotationsSummary found
+     */
+    fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
+
+    /**
+     * Get the a LiveData EventAnnotationsSummary
+     * @param eventId the eventId to look for EventAnnotationsSummary
+     * @return the LiveData of EventAnnotationsSummary
+     */
+    fun getEventAnnotationsSummaryLive(eventId: String): LiveData>
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
index 2b89f86d3e..789b783e3f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
@@ -64,6 +64,17 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
         return roomMemberEntity?.asDomain()
     }
 
+    override fun getRoomMembers(memberships: List): List {
+        return monarchy.fetchAllMappedSync(
+                {
+                    RoomMembers(it, roomId).queryRoomMembersEvent()
+                },
+                {
+                    it.asDomain()
+                }
+        )
+    }
+
     override fun getRoomMembersLive(memberships: List): LiveData> {
         return monarchy.findAllMappedWithChanges(
                 {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
index 8731045e14..180776ba8d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
@@ -215,7 +215,16 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
         return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain)
     }
 
-    override fun getEventSummaryLive(eventId: String): LiveData> {
+    override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
+        return monarchy.fetchCopyMap(
+                { EventAnnotationsSummaryEntity.where(it, eventId).findFirst() },
+                { entity, _ ->
+                    entity.asDomain()
+                }
+        )
+    }
+
+    override fun getEventAnnotationsSummaryLive(eventId: String): LiveData> {
         val liveData = monarchy.findAllMappedWithChanges(
                 { EventAnnotationsSummaryEntity.where(it, eventId) },
                 { it.asDomain() }

From 03c3c9ae57f2aea92a4ef9514d89c6417c05eee1 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 7 Jan 2020 18:15:48 +0100
Subject: [PATCH 19/26] Timeline: clear unlinked should use new parameters

---
 .../internal/session/room/timeline/ClearUnlinkedEventsTask.kt   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt
index 04cf810fe4..d92320abf9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt
@@ -38,7 +38,7 @@ internal class DefaultClearUnlinkedEventsTask @Inject constructor(private val mo
         monarchy.awaitTransaction { localRealm ->
             val unlinkedChunks = ChunkEntity
                     .where(localRealm, roomId = params.roomId)
-                    .equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true)
+                    .equalTo(ChunkEntityFields.IS_UNLINKED, true)
                     .findAll()
             unlinkedChunks.forEach {
                 it.deleteOnCascade()

From 2dd2a8db6ca21e4dc6106710ef95eacd62dc77e6 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Wed, 8 Jan 2020 11:54:42 +0100
Subject: [PATCH 20/26] Emoji data source as singleton

---
 .../src/main/java/im/vector/riotx/core/di/VectorComponent.kt   | 3 +++
 .../im/vector/riotx/features/reactions/data/EmojiDataSource.kt | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
index b78e291506..283b43a004 100644
--- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
+++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
@@ -44,6 +44,7 @@ import im.vector.riotx.features.notifications.*
 import im.vector.riotx.features.rageshake.BugReporter
 import im.vector.riotx.features.rageshake.VectorFileLogger
 import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
+import im.vector.riotx.features.reactions.data.EmojiDataSource
 import im.vector.riotx.features.session.SessionListener
 import im.vector.riotx.features.settings.VectorPreferences
 import im.vector.riotx.features.share.ShareRoomListDataSource
@@ -124,6 +125,8 @@ interface VectorComponent {
 
     fun uiStateRepository(): UiStateRepository
 
+    fun emojiDataSource(): EmojiDataSource
+
     @Component.Factory
     interface Factory {
         fun create(@BindsInstance context: Context): VectorComponent
diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt
index 9317c645c4..2917dce68a 100644
--- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt
+++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt
@@ -20,8 +20,9 @@ import com.squareup.moshi.Moshi
 import im.vector.riotx.R
 import im.vector.riotx.core.di.ScreenScope
 import javax.inject.Inject
+import javax.inject.Singleton
 
-@ScreenScope
+@Singleton
 class EmojiDataSource @Inject constructor(
         resources: Resources
 ) {

From 9970d7ffa0ec666c6365fa6f91318d0d29e25259 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Wed, 8 Jan 2020 11:55:22 +0100
Subject: [PATCH 21/26] SDK: get some better queries

---
 .../java/im/vector/matrix/rx/RxSession.kt     | 24 ++++---
 .../matrix/android/api/session/Session.kt     |  2 +-
 .../android/api/session/group/GroupService.kt |  8 ++-
 .../api/session/pushers/PushersService.kt     |  2 +-
 .../matrix/android/api/session/room/Room.kt   |  3 +
 .../android/api/session/room/RoomService.kt   | 20 ++++--
 .../session/room/RoomSummaryQueryParams.kt    | 38 +++++++++++
 .../api/session/room/model/Membership.kt      |  4 ++
 .../android/api/session/user/UserService.kt   |  8 +--
 .../internal/session/DefaultSession.kt        |  2 +-
 .../session/group/DefaultGroupService.kt      | 20 +++++-
 .../session/pushers/DefaultPusherService.kt   |  2 +-
 .../session/room/DefaultRoomService.kt        | 65 ++++++++++++++-----
 .../membership/DefaultMembershipService.kt    | 21 +++++-
 .../internal/session/sync/job/SyncService.kt  |  2 +-
 .../session/user/DefaultUserService.kt        |  8 +--
 .../java/im/vector/riotx/AppStateHandler.kt   |  3 +-
 .../riotx/features/home/HomeDrawerFragment.kt |  2 +-
 .../detail/composer/TextComposerViewModel.kt  |  3 +-
 .../roomdirectory/RoomDirectoryViewModel.kt   |  5 +-
 .../roompreview/RoomPreviewViewModel.kt       |  5 +-
 .../features/share/IncomingShareViewModel.kt  |  3 +-
 22 files changed, 192 insertions(+), 58 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt

diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
index c9381b861d..c502e4d564 100644
--- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
@@ -20,6 +20,7 @@ import androidx.paging.PagedList
 import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.group.model.GroupSummary
 import im.vector.matrix.android.api.session.pushers.Pusher
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.RoomSummary
 import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
 import im.vector.matrix.android.api.session.sync.SyncState
@@ -30,40 +31,43 @@ import io.reactivex.Single
 
 class RxSession(private val session: Session) {
 
-    fun liveRoomSummaries(): Observable> {
-        return session.liveRoomSummaries().asObservable()
+    fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable> {
+        return session.getRoomSummariesLive(queryParams).asObservable()
+                .startWith(session.getRoomSummaries(queryParams))
     }
 
     fun liveGroupSummaries(): Observable> {
-        return session.liveGroupSummaries().asObservable()
+        return session.getGroupSummariesLive().asObservable()
+                .startWith(session.getGroupSummaries())
     }
 
     fun liveBreadcrumbs(): Observable> {
-        return session.liveBreadcrumbs().asObservable()
+        return session.getBreadcrumbsLive().asObservable()
+                .startWith(session.getBreadcrumbs())
     }
 
     fun liveSyncState(): Observable {
-        return session.syncState().asObservable()
+        return session.getSyncStateLive().asObservable()
     }
 
     fun livePushers(): Observable> {
-        return session.livePushers().asObservable()
+        return session.getPushersLive().asObservable()
     }
 
     fun liveUser(userId: String): Observable> {
-        return session.liveUser(userId).asObservable().distinctUntilChanged()
+        return session.getUserLive(userId).asObservable().distinctUntilChanged()
     }
 
     fun liveUsers(): Observable> {
-        return session.liveUsers().asObservable()
+        return session.getUsersLive().asObservable()
     }
 
     fun liveIgnoredUsers(): Observable> {
-        return session.liveIgnoredUsers().asObservable()
+        return session.getIgnoredUsersLive().asObservable()
     }
 
     fun livePagedUsers(filter: String? = null): Observable> {
-        return session.livePagedUsers(filter).asObservable()
+        return session.getPagedUsersLive(filter).asObservable()
     }
 
     fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt
index ab545dbce6..1c73d4c5d1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt
@@ -107,7 +107,7 @@ interface Session :
      * This method allows to listen the sync state.
      * @return a [LiveData] of [SyncState].
      */
-    fun syncState(): LiveData
+    fun getSyncStateLive(): LiveData
 
     /**
      * This methods return true if an initial sync has been processed
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
index 2d55d0be57..b01bfa34ab 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
@@ -38,9 +38,15 @@ interface GroupService {
      */
     fun getGroupSummary(groupId: String): GroupSummary?
 
+    /**
+     * Get a list of group summaries.This list is a snapshot of the data.
+     * @return the list of [GroupSummary]
+     */
+    fun getGroupSummaries(): List
+
     /**
      * Get a live list of group summaries. This list is refreshed as soon as the data changes.
      * @return the [LiveData] of [GroupSummary]
      */
-    fun liveGroupSummaries(): LiveData>
+    fun getGroupSummariesLive(): LiveData>
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt
index d082faa7c7..129bfa3011 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt
@@ -58,7 +58,7 @@ interface PushersService {
         const val EVENT_ID_ONLY = "event_id_only"
     }
 
-    fun livePushers(): LiveData>
+    fun getPushersLive(): LiveData>
 
     fun pushers() : List
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt
index 90790a6ab0..3221c355e8 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt
@@ -56,5 +56,8 @@ interface Room :
      */
     fun getRoomSummaryLive(): LiveData>
 
+    /**
+     * A current snapshot of [RoomSummary] associated with the room
+     */
     fun roomSummary(): RoomSummary?
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt
index ba3b5ded78..f3167c8461 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt
@@ -60,16 +60,28 @@ interface RoomService {
     fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
 
     /**
-     * Get a live list of room summaries. This list is refreshed as soon as the data changes.
-     * @return the [LiveData] of [RoomSummary]
+     * Get a snapshot list of room summaries.
+     * @return the immutable list of [RoomSummary]
      */
-    fun liveRoomSummaries(): LiveData>
+    fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List
+
+    /**
+     * Get a live list of room summaries. This list is refreshed as soon as the data changes.
+     * @return the [LiveData] of List[RoomSummary]
+     */
+    fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData>
+
+    /**
+     * Get a snapshot list of Breadcrumbs
+     * @return the immutable list of [RoomSummary]
+     */
+    fun getBreadcrumbs(): List
 
     /**
      * Get a live list of Breadcrumbs
      * @return the [LiveData] of [RoomSummary]
      */
-    fun liveBreadcrumbs(): LiveData>
+    fun getBreadcrumbsLive(): LiveData>
 
     /**
      * Inform the Matrix SDK that a room is displayed.
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt
new file mode 100644
index 0000000000..00a17f083d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 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
+
+import im.vector.matrix.android.api.session.room.model.Membership
+
+/**
+ * This class can be used to filter room summaries to use with:
+ * [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
+ */
+data class RoomSummaryQueryParams(
+        /**
+         * Set to true if you want only non null display name. True by default
+         */
+        val filterDisplayName: Boolean = true,
+        /**
+         * Set to true if you want only non null canonical alias. False by default.
+         */
+        val filterCanonicalAlias: Boolean = false,
+        /**
+         * Set the list of memberships you want to filter on. By default, all memberships.
+         */
+        val memberships: List = Membership.all()
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt
index 93eb54fbd3..5182d2099e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt
@@ -48,5 +48,9 @@ enum class Membership(val value: String) {
         fun activeMemberships(): List {
             return listOf(INVITE, JOIN)
         }
+
+        fun all(): List{
+            return values().asList()
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt
index 2a93a876f6..453400bc99 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt
@@ -50,25 +50,25 @@ interface UserService {
      * @param userId the userId to look for.
      * @return a LiveData of user with userId
      */
-    fun liveUser(userId: String): LiveData>
+    fun getUserLive(userId: String): LiveData>
 
     /**
      * Observe a live list of users sorted alphabetically
      * @return a Livedata of users
      */
-    fun liveUsers(): LiveData>
+    fun getUsersLive(): LiveData>
 
     /**
      * Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
      * @param filter the filter. It will look into userId and displayName.
      * @return a Livedata of users
      */
-    fun livePagedUsers(filter: String? = null): LiveData>
+    fun getPagedUsersLive(filter: String? = null): LiveData>
 
     /**
      * Get list of ignored users
      */
-    fun liveIgnoredUsers(): LiveData>
+    fun getIgnoredUsersLive(): LiveData>
 
     /**
      * Ignore users
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt
index d52379eb6e..b0bf70eb70 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt
@@ -156,7 +156,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
         syncTaskSequencer.close()
     }
 
-    override fun syncState(): LiveData {
+    override fun getSyncStateLive(): LiveData {
         return getSyncThread().liveState()
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt
index 192c6fe40c..584b577443 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt
@@ -26,6 +26,8 @@ import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
 import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.util.fetchCopyMap
+import io.realm.Realm
+import io.realm.RealmQuery
 import javax.inject.Inject
 
 internal class DefaultGroupService @Inject constructor(private val monarchy: Monarchy) : GroupService {
@@ -41,10 +43,22 @@ internal class DefaultGroupService @Inject constructor(private val monarchy: Mon
         )
     }
 
-    override fun liveGroupSummaries(): LiveData> {
-        return monarchy.findAllMappedWithChanges(
-                { realm -> GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME) },
+    override fun getGroupSummaries(): List {
+        return monarchy.fetchAllMappedSync(
+                { groupSummariesQuery(it) },
                 { it.asDomain() }
         )
     }
+
+
+    override fun getGroupSummariesLive(): LiveData> {
+        return monarchy.findAllMappedWithChanges(
+                { groupSummariesQuery(it) },
+                { it.asDomain() }
+        )
+    }
+
+    private fun groupSummariesQuery(realm: Realm): RealmQuery {
+        return GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
index 8c7e9fb263..fcce69c2fc 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt
@@ -86,7 +86,7 @@ internal class DefaultPusherService @Inject constructor(private val context: Con
                 .executeBy(taskExecutor)
     }
 
-    override fun livePushers(): LiveData> {
+    override fun getPushersLive(): LiveData> {
         return monarchy.findAllMappedWithChanges(
                 { realm -> PusherEntity.where(realm) },
                 { it.asDomain() }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt
index b53fa3ce33..19c347ba8c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt
@@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.room.Room
 import im.vector.matrix.android.api.session.room.RoomService
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.RoomSummary
 import im.vector.matrix.android.api.session.room.model.VersioningState
 import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
@@ -41,6 +42,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
 import im.vector.matrix.android.internal.task.configureWith
 import im.vector.matrix.android.internal.util.fetchCopyMap
 import io.realm.Realm
+import io.realm.RealmQuery
 import javax.inject.Inject
 
 internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
@@ -86,30 +88,63 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
                 })
     }
 
-    override fun liveRoomSummaries(): LiveData> {
-        return monarchy.findAllMappedWithChanges(
-                { realm ->
-                    RoomSummaryEntity.where(realm)
-                            .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
-                            .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
-                },
+    override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List {
+        return monarchy.fetchAllMappedSync(
+                { roomSummariesQuery(it, queryParams) },
                 { roomSummaryMapper.map(it) }
         )
     }
 
-    override fun liveBreadcrumbs(): LiveData> {
+    override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> {
         return monarchy.findAllMappedWithChanges(
-                { realm ->
-                    RoomSummaryEntity.where(realm)
-                            .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
-                            .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
-                            .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
-                            .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
-                },
+                { roomSummariesQuery(it, queryParams) },
                 { roomSummaryMapper.map(it) }
         )
     }
 
+    private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery {
+        val query = RoomSummaryEntity.where(realm)
+        if (queryParams.filterCanonicalAlias) {
+            query.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
+        }
+        if (queryParams.filterCanonicalAlias) {
+            query.isNotEmpty(RoomSummaryEntityFields.CANONICAL_ALIAS)
+        }
+        val lastMembership = queryParams.memberships.lastOrNull()
+        query.beginGroup()
+        for (membership in queryParams.memberships) {
+            query.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, membership.name)
+            if (membership != lastMembership) {
+                query.or()
+            }
+        }
+        query.endGroup()
+        query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
+        return query
+    }
+
+    override fun getBreadcrumbs(): List {
+        return monarchy.fetchAllMappedSync(
+                { breadcrumbsQuery(it) },
+                { roomSummaryMapper.map(it) }
+        )
+    }
+
+    override fun getBreadcrumbsLive(): LiveData> {
+        return monarchy.findAllMappedWithChanges(
+                { breadcrumbsQuery(it) },
+                { roomSummaryMapper.map(it) }
+        )
+    }
+
+    private fun breadcrumbsQuery(realm: Realm): RealmQuery {
+        return RoomSummaryEntity.where(realm)
+                .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
+                .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
+                .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
+                .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
+    }
+
     override fun onRoomDisplayed(roomId: String): Cancelable {
         return updateBreadcrumbsTask
                 .configureWith(UpdateBreadcrumbsTask.Params(roomId))
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
index 789b783e3f..2f8ce763b6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
@@ -26,6 +26,8 @@ import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.matrix.android.api.util.Cancelable
 import im.vector.matrix.android.internal.database.mapper.asDomain
+import im.vector.matrix.android.internal.database.model.RoomMemberEntity
+import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
 import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
 import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
 import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
@@ -33,6 +35,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
 import im.vector.matrix.android.internal.task.configureWith
 import im.vector.matrix.android.internal.util.fetchCopied
 import io.realm.Realm
+import io.realm.RealmQuery
 
 internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                     private val monarchy: Monarchy,
@@ -67,7 +70,7 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
     override fun getRoomMembers(memberships: List): List {
         return monarchy.fetchAllMappedSync(
                 {
-                    RoomMembers(it, roomId).queryRoomMembersEvent()
+                    roomMembersQuery(it, memberships)
                 },
                 {
                     it.asDomain()
@@ -78,7 +81,7 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
     override fun getRoomMembersLive(memberships: List): LiveData> {
         return monarchy.findAllMappedWithChanges(
                 {
-                    RoomMembers(it, roomId).queryRoomMembersEvent()
+                    roomMembersQuery(it, memberships)
                 },
                 {
                     it.asDomain()
@@ -86,6 +89,20 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
         )
     }
 
+    private fun roomMembersQuery(realm: Realm, memberships: List): RealmQuery {
+        val query = RoomMembers(realm, roomId).queryRoomMembersEvent()
+        val lastMembership = memberships.lastOrNull()
+        query.beginGroup()
+        for (membership in memberships) {
+            query.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, membership.name)
+            if (membership != lastMembership) {
+                query.or()
+            }
+        }
+        query.endGroup()
+        return query
+    }
+
     override fun getNumberOfJoinedMembers(): Int {
         return Realm.getInstance(monarchy.realmConfiguration).use {
             RoomMembers(it, roomId).getNumberOfJoinedMembers()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt
index 37bcc225c1..9fe3e38d36 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt
@@ -112,7 +112,7 @@ abstract class SyncService : Service() {
         try {
             syncTask.execute(params)
             // Start sync if we were doing an initial sync and the syncThread is not launched yet
-            if (isInitialSync && session.syncState().value == SyncState.Idle) {
+            if (isInitialSync && session.getSyncStateLive().value == SyncState.Idle) {
                 val isForeground = !backgroundDetectionObserver.isInBackground
                 session.startSync(isForeground)
             }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt
index d314c8d108..761c810b41 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt
@@ -70,7 +70,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
         return userEntity.asDomain()
     }
 
-    override fun liveUser(userId: String): LiveData> {
+    override fun getUserLive(userId: String): LiveData> {
         val liveData = monarchy.findAllMappedWithChanges(
                 { UserEntity.where(it, userId) },
                 { it.asDomain() }
@@ -80,7 +80,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
         }
     }
 
-    override fun liveUsers(): LiveData> {
+    override fun getUsersLive(): LiveData> {
         return monarchy.findAllMappedWithChanges(
                 { realm ->
                     realm.where(UserEntity::class.java)
@@ -91,7 +91,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
         )
     }
 
-    override fun livePagedUsers(filter: String?): LiveData> {
+    override fun getPagedUsersLive(filter: String?): LiveData> {
         realmDataSourceFactory.updateQuery { realm ->
             val query = realm.where(UserEntity::class.java)
             if (filter.isNullOrEmpty()) {
@@ -121,7 +121,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
                 .executeBy(taskExecutor)
     }
 
-    override fun liveIgnoredUsers(): LiveData> {
+    override fun getIgnoredUsersLive(): LiveData> {
         return monarchy.findAllMappedWithChanges(
                 { realm ->
                     realm.where(IgnoredUserEntity::class.java)
diff --git a/vector/src/main/java/im/vector/riotx/AppStateHandler.kt b/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
index cfbed0ee13..78a880854e 100644
--- a/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
@@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.OnLifecycleEvent
 import arrow.core.Option
 import im.vector.matrix.android.api.session.group.model.GroupSummary
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.RoomSummary
 import im.vector.matrix.rx.rx
 import im.vector.riotx.features.home.HomeRoomListDataSource
@@ -65,7 +66,7 @@ class AppStateHandler @Inject constructor(
                         sessionDataSource.observe()
                                 .observeOn(AndroidSchedulers.mainThread())
                                 .switchMap {
-                                    it.orNull()?.rx()?.liveRoomSummaries()
+                                    it.orNull()?.rx()?.liveRoomSummaries(RoomSummaryQueryParams())
                                             ?: Observable.just(emptyList())
                                 }
                                 .throttleLast(300, TimeUnit.MILLISECONDS),
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt
index 6ff836e8c8..bc3bc2f9d5 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt
@@ -40,7 +40,7 @@ class HomeDrawerFragment @Inject constructor(
         if (savedInstanceState == null) {
             replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java)
         }
-        session.liveUser(session.myUserId).observeK(this) { optionalUser ->
+        session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser ->
             val user = optionalUser?.getOrNull()
             if (user != null) {
                 avatarRenderer.render(user.toMatrixItem(), homeDrawerHeaderAvatarView)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
index a2c9787bdf..37e01fcacf 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
@@ -25,6 +25,7 @@ import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.group.model.GroupSummary
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.matrix.android.api.session.room.model.RoomSummary
@@ -114,7 +115,7 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState:
 
     private fun observeRoomsQuery() {
         Observable.combineLatest, Option, List>(
-                session.rx().liveRoomSummaries(),
+                session.rx().liveRoomSummaries(RoomSummaryQueryParams(filterCanonicalAlias = true)),
                 roomsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
                 BiFunction { roomSummaries, query ->
                     val filter = query.orNull() ?: ""
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt
index dcd64c6a46..cc57775028 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt
@@ -24,6 +24,7 @@ import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.failure.Failure
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsFilter
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
@@ -81,11 +82,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
     private fun observeJoinedRooms() {
         session
                 .rx()
-                .liveRoomSummaries()
+                .liveRoomSummaries(RoomSummaryQueryParams(memberships = listOf(Membership.JOIN)))
                 .subscribe { list ->
                     val joinedRoomIds = list
-                            // Keep only joined room
-                            ?.filter { it.membership == Membership.JOIN }
                             ?.map { it.roomId }
                             ?.toSet()
                             ?: emptySet()
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
index 54c86537d2..005a930fdc 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
@@ -23,6 +23,7 @@ import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.rx.rx
 import im.vector.riotx.core.platform.VectorViewModel
@@ -55,12 +56,10 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
     private fun observeJoinedRooms() {
         session
                 .rx()
-                .liveRoomSummaries()
+                .liveRoomSummaries(RoomSummaryQueryParams(memberships = listOf(Membership.JOIN)))
                 .subscribe { list ->
                     withState { state ->
                         val isRoomJoined = list
-                                // Keep only joined room
-                                ?.filter { it.membership == Membership.JOIN }
                                 ?.map { it.roomId }
                                 ?.toList()
                                 ?.contains(state.roomId) == true
diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
index 72c98cdc45..904407708f 100644
--- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
@@ -22,6 +22,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory
 import com.airbnb.mvrx.ViewModelContext
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
+import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.rx.rx
 import im.vector.riotx.ActiveSessionDataSource
 import im.vector.riotx.core.platform.EmptyAction
@@ -62,7 +63,7 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState:
         sessionObservableStore.observe()
                 .observeOn(AndroidSchedulers.mainThread())
                 .switchMap {
-                    it.orNull()?.rx()?.liveRoomSummaries()
+                    it.orNull()?.rx()?.liveRoomSummaries(RoomSummaryQueryParams())
                             ?: Observable.just(emptyList())
                 }
                 .throttleLast(300, TimeUnit.MILLISECONDS)

From c60b4ddb5a2332de1ccfd10606b090b873ca7443 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Wed, 8 Jan 2020 13:59:43 +0100
Subject: [PATCH 22/26] Timeline: don't wait for realm notification to come
 back, use it right away to init

---
 .../session/room/timeline/DefaultTimeline.kt  | 43 ++++++-------------
 1 file changed, 14 insertions(+), 29 deletions(-)

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 d90cb60a15..057295ec44 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,13 +27,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
 import im.vector.matrix.android.api.util.CancelableBag
 import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
 import im.vector.matrix.android.internal.database.mapper.asDomain
-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.EventAnnotationsSummaryEntity
-import im.vector.matrix.android.internal.database.model.EventEntity
-import im.vector.matrix.android.internal.database.model.RoomEntity
-import im.vector.matrix.android.internal.database.model.TimelineEventEntity
-import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
+import im.vector.matrix.android.internal.database.model.*
 import im.vector.matrix.android.internal.database.query.FilterContent
 import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
 import im.vector.matrix.android.internal.database.query.where
@@ -44,13 +38,7 @@ import im.vector.matrix.android.internal.task.configureWith
 import im.vector.matrix.android.internal.util.Debouncer
 import im.vector.matrix.android.internal.util.createBackgroundHandler
 import im.vector.matrix.android.internal.util.createUIHandler
-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.CopyOnWriteArrayList
@@ -113,11 +101,7 @@ internal class DefaultTimeline(
         if (!results.isLoaded || !results.isValid) {
             return@OrderedRealmCollectionChangeListener
         }
-        if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
-            handleInitialLoad()
-        } else {
-            handleUpdates(changeSet)
-        }
+        handleUpdates(changeSet)
     }
 
     private val relationsListener = OrderedRealmCollectionChangeListener> { collection, changeSet ->
@@ -179,8 +163,9 @@ internal class DefaultTimeline(
                 nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING).findAll()
                 filteredEvents = nonFilteredEvents.where()
                         .filterEventsWithSettings()
-                        .findAllAsync()
-                        .also { it.addChangeListener(eventsChangeListener) }
+                        .findAll()
+                handleInitialLoad()
+                filteredEvents.addChangeListener(eventsChangeListener)
 
                 eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
                         .findAllAsync()
@@ -402,14 +387,14 @@ internal class DefaultTimeline(
 
     private fun getState(direction: Timeline.Direction): State {
         return when (direction) {
-            Timeline.Direction.FORWARDS  -> forwardsState.get()
+            Timeline.Direction.FORWARDS -> forwardsState.get()
             Timeline.Direction.BACKWARDS -> backwardsState.get()
         }
     }
 
     private fun updateState(direction: Timeline.Direction, update: (State) -> State) {
         val stateReference = when (direction) {
-            Timeline.Direction.FORWARDS  -> forwardsState
+            Timeline.Direction.FORWARDS -> forwardsState
             Timeline.Direction.BACKWARDS -> backwardsState
         }
         val currentValue = stateReference.get()
@@ -497,9 +482,9 @@ internal class DefaultTimeline(
             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")
         cancelableBag += paginationTask
@@ -508,10 +493,10 @@ internal class DefaultTimeline(
                     this.callback = object : MatrixCallback {
                         override fun onSuccess(data: TokenChunkEventPersistor.Result) {
                             when (data) {
-                                TokenChunkEventPersistor.Result.SUCCESS           -> {
+                                TokenChunkEventPersistor.Result.SUCCESS -> {
                                     Timber.v("Success fetching $limit items $direction from pagination request")
                                 }
-                                TokenChunkEventPersistor.Result.REACHED_END       -> {
+                                TokenChunkEventPersistor.Result.REACHED_END -> {
                                     postSnapshot()
                                 }
                                 TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
@@ -575,7 +560,7 @@ internal class DefaultTimeline(
             val timelineEvent = buildTimelineEvent(eventEntity)
 
             if (timelineEvent.isEncrypted()
-                && timelineEvent.root.mxDecryptionResult == null) {
+                    && timelineEvent.root.mxDecryptionResult == null) {
                 timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
             }
 

From 383605274c3b92969d1a1bdc436013d37417173a Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Wed, 8 Jan 2020 22:17:32 +0100
Subject: [PATCH 23/26] Introduce a very simple query langage and refact
 autocomplete

---
 .../main/java/im/vector/matrix/rx/RxRoom.kt   |  12 +-
 .../java/im/vector/matrix/rx/RxSession.kt     |   7 +-
 .../android/api/query/QueryStringValue.kt     |  35 ++++
 .../android/api/session/group/GroupService.kt |   4 +-
 .../session/group/GroupSummaryQueryParams.kt  |  44 +++++
 .../session/room/RoomSummaryQueryParams.kt    |  36 ++--
 .../session/room/members/MembershipService.kt |  12 +-
 .../room/members/RoomMemberQueryParams.kt     |  44 +++++
 .../api/session/room/model/Membership.kt      |   2 +-
 .../internal/query/QueryEnumListProcessor.kt  |  22 ++-
 .../query/QueryStringValueProcessor.kt        |  47 ++++++
 .../session/group/DefaultGroupService.kt      |  17 +-
 .../session/room/DefaultRoomService.kt        |  19 +--
 .../membership/DefaultMembershipService.kt    |  26 ++-
 .../room/timeline/ClearUnlinkedEventsTask.kt  |   1 -
 .../java/im/vector/riotx/AppStateHandler.kt   |   5 +-
 .../emoji/AutocompleteEmojiController.kt      |   8 +-
 .../group/AutocompleteGroupPresenter.kt       |  29 ++--
 .../member/AutocompleteMemberPresenter.kt     |  37 +++-
 .../room/AutocompleteRoomController.kt        |   4 +-
 .../room/AutocompleteRoomPresenter.kt         |  30 ++--
 .../features/home/group/GroupListViewModel.kt |  10 +-
 .../home/room/detail/AutoCompleter.kt         |  48 +++---
 .../home/room/detail/RoomDetailFragment.kt    | 127 ++++++--------
 .../detail/composer/TextComposerViewModel.kt  | 158 ------------------
 .../detail/composer/TextComposerViewState.kt  |  34 ----
 .../reactions/data/EmojiDataSource.kt         |   3 +-
 .../roomdirectory/RoomDirectoryViewModel.kt   |  13 +-
 .../roompreview/RoomPreviewViewModel.kt       |   7 +-
 .../features/settings/VectorPreferences.kt    |   4 +-
 .../features/share/IncomingShareViewModel.kt  |   5 +-
 31 files changed, 417 insertions(+), 433 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/query/QueryStringValue.kt
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupSummaryQueryParams.kt
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt
 rename vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt => matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt (52%)
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt
 delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
 delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt

diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
index 48ff66720f..bbf0e76823 100644
--- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
+++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt
@@ -17,7 +17,11 @@
 package im.vector.matrix.rx
 
 import im.vector.matrix.android.api.session.room.Room
-import im.vector.matrix.android.api.session.room.model.*
+import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
+import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
+import im.vector.matrix.android.api.session.room.model.ReadReceipt
+import im.vector.matrix.android.api.session.room.model.RoomMember
+import im.vector.matrix.android.api.session.room.model.RoomSummary
 import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
 import im.vector.matrix.android.api.session.room.send.UserDraft
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@@ -33,9 +37,9 @@ class RxRoom(private val room: Room) {
                 .startWith(room.roomSummary().toOptional())
     }
 
-    fun liveRoomMembers(memberships: List): Observable> {
-        return room.getRoomMembersLive(memberships).asObservable()
-                .startWith(room.getRoomMembers(memberships))
+    fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable> {
+        return room.getRoomMembersLive(queryParams).asObservable()
+                .startWith(room.getRoomMembers(queryParams))
     }
 
     fun liveAnnotationSummary(eventId: String): Observable> {
diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
index c502e4d564..084f497de5 100644
--- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
@@ -18,6 +18,7 @@ package im.vector.matrix.rx
 
 import androidx.paging.PagedList
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
 import im.vector.matrix.android.api.session.group.model.GroupSummary
 import im.vector.matrix.android.api.session.pushers.Pusher
 import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
@@ -36,9 +37,9 @@ class RxSession(private val session: Session) {
                 .startWith(session.getRoomSummaries(queryParams))
     }
 
-    fun liveGroupSummaries(): Observable> {
-        return session.getGroupSummariesLive().asObservable()
-                .startWith(session.getGroupSummaries())
+    fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable> {
+        return session.getGroupSummariesLive(queryParams).asObservable()
+                .startWith(session.getGroupSummaries(queryParams))
     }
 
     fun liveBreadcrumbs(): Observable> {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/query/QueryStringValue.kt
new file mode 100644
index 0000000000..5d3e76f1d3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/query/QueryStringValue.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 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.query
+
+/**
+ * Basic query language. All these cases are mutually exclusive.
+ */
+sealed class QueryStringValue {
+    object NoCondition : QueryStringValue()
+    object IsNull : QueryStringValue()
+    object IsNotNull : QueryStringValue()
+    object IsEmpty : QueryStringValue()
+    object IsNotEmpty : QueryStringValue()
+    data class Equals(val string: String, val case: Case) : QueryStringValue()
+    data class Contains(val string: String, val case: Case) : QueryStringValue()
+
+    enum class Case {
+        SENSITIVE,
+        INSENSITIVE
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
index b01bfa34ab..76bac1ae1a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
@@ -42,11 +42,11 @@ interface GroupService {
      * Get a list of group summaries.This list is a snapshot of the data.
      * @return the list of [GroupSummary]
      */
-    fun getGroupSummaries(): List
+    fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List
 
     /**
      * Get a live list of group summaries. This list is refreshed as soon as the data changes.
      * @return the [LiveData] of [GroupSummary]
      */
-    fun getGroupSummariesLive(): LiveData>
+    fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData>
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupSummaryQueryParams.kt
new file mode 100644
index 0000000000..702b8c2523
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupSummaryQueryParams.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 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.group
+
+import im.vector.matrix.android.api.query.QueryStringValue
+import im.vector.matrix.android.api.session.room.model.Membership
+
+fun groupSummaryQueryParams(init: (GroupSummaryQueryParams.Builder.() -> Unit) = {}): GroupSummaryQueryParams {
+    return GroupSummaryQueryParams.Builder().apply(init).build()
+}
+
+/**
+ * This class can be used to filter group summaries
+ */
+data class GroupSummaryQueryParams(
+        val displayName: QueryStringValue,
+        val memberships: List
+) {
+
+    class Builder {
+
+        var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
+        var memberships: List = Membership.all()
+
+        fun build() = GroupSummaryQueryParams(
+                displayName = displayName,
+                memberships = memberships
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt
index 00a17f083d..6983bda225 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt
@@ -16,23 +16,33 @@
 
 package im.vector.matrix.android.api.session.room
 
+import im.vector.matrix.android.api.query.QueryStringValue
 import im.vector.matrix.android.api.session.room.model.Membership
 
+fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
+    return RoomSummaryQueryParams.Builder().apply(init).build()
+}
+
 /**
  * This class can be used to filter room summaries to use with:
  * [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
  */
 data class RoomSummaryQueryParams(
-        /**
-         * Set to true if you want only non null display name. True by default
-         */
-        val filterDisplayName: Boolean = true,
-        /**
-         * Set to true if you want only non null canonical alias. False by default.
-         */
-        val filterCanonicalAlias: Boolean = false,
-        /**
-         * Set the list of memberships you want to filter on. By default, all memberships.
-         */
-        val memberships: List = Membership.all()
-)
+        val displayName: QueryStringValue,
+        val canonicalAlias: QueryStringValue,
+        val memberships: List
+) {
+
+    class Builder {
+
+        var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
+        var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
+        var memberships: List = Membership.all()
+
+        fun build() = RoomSummaryQueryParams(
+                displayName = displayName,
+                canonicalAlias = canonicalAlias,
+                memberships = memberships
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
index 281697816c..6c117d3be7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
@@ -18,7 +18,6 @@ package im.vector.matrix.android.api.session.room.members
 
 import androidx.lifecycle.LiveData
 import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.matrix.android.api.util.Cancelable
 
@@ -41,20 +40,19 @@ interface MembershipService {
      */
     fun getRoomMember(userId: String): RoomMember?
 
-
     /**
-     * Return all the roomMembers of the room filtered by memberships
-     * @param memberships list of accepted memberships
+     * Return all the roomMembers of the room with params
+     * @param queryParams the params to query for
      * @return a roomMember list.
      */
-    fun getRoomMembers(memberships: List): List
+    fun getRoomMembers(queryParams: RoomMemberQueryParams): List
 
     /**
      * Return all the roomMembers of the room filtered by memberships
-     * @param memberships list of accepted memberships
+     * @param queryParams the params to query for
      * @return a [LiveData] of roomMember list.
      */
-    fun getRoomMembersLive(memberships: List): LiveData>
+    fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData>
 
     fun getNumberOfJoinedMembers(): Int
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt
new file mode 100644
index 0000000000..19003632ca
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 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.members
+
+import im.vector.matrix.android.api.query.QueryStringValue
+import im.vector.matrix.android.api.session.room.model.Membership
+
+fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {}): RoomMemberQueryParams {
+    return RoomMemberQueryParams.Builder().apply(init).build()
+}
+
+/**
+ * This class can be used to filter room members
+ */
+data class RoomMemberQueryParams(
+        val displayName: QueryStringValue,
+        val memberships: List
+) {
+
+    class Builder {
+
+        var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
+        var memberships: List = Membership.all()
+
+        fun build() = RoomMemberQueryParams(
+                displayName = displayName,
+                memberships = memberships
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt
index 5182d2099e..7c6a931373 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/Membership.kt
@@ -49,7 +49,7 @@ enum class Membership(val value: String) {
             return listOf(INVITE, JOIN)
         }
 
-        fun all(): List{
+        fun all(): List {
             return values().asList()
         }
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt
similarity index 52%
rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt
index 0f5bf2a8c5..7cb5ca1047 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 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.
@@ -14,12 +14,20 @@
  * limitations under the License.
  */
 
-package im.vector.riotx.features.home.room.detail.composer
+package im.vector.matrix.android.internal.query
 
-import im.vector.riotx.core.platform.VectorViewModelAction
+import io.realm.RealmObject
+import io.realm.RealmQuery
 
-sealed class TextComposerAction : VectorViewModelAction {
-    data class QueryUsers(val query: CharSequence?) : TextComposerAction()
-    data class QueryRooms(val query: CharSequence?) : TextComposerAction()
-    data class QueryGroups(val query: CharSequence?) : TextComposerAction()
+fun > RealmQuery.process(field: String, enums: List>): RealmQuery {
+    val lastEnumValue = enums.lastOrNull()
+    this.beginGroup()
+    for (enumValue in enums) {
+        this.equalTo(field, enumValue.name)
+        if (enumValue != lastEnumValue) {
+            this.or()
+        }
+    }
+    this.endGroup()
+    return this
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt
new file mode 100644
index 0000000000..d2418ee84f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 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.query
+
+import im.vector.matrix.android.api.query.QueryStringValue
+import io.realm.Case
+import io.realm.RealmObject
+import io.realm.RealmQuery
+import timber.log.Timber
+
+fun  RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery {
+    when (queryStringValue) {
+        is QueryStringValue.NoCondition -> Timber.v("No condition to process")
+        is QueryStringValue.IsNotNull -> this.isNotNull(field)
+        is QueryStringValue.IsNull -> this.isNull(field)
+        is QueryStringValue.IsEmpty -> this.isEmpty(field)
+        is QueryStringValue.IsNotEmpty -> this.isNotEmpty(field)
+        is QueryStringValue.Equals -> {
+            this.equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase())
+        }
+        is QueryStringValue.Contains -> {
+            this.contains(field, queryStringValue.string, queryStringValue.case.toRealmCase())
+        }
+    }
+    return this
+}
+
+private fun QueryStringValue.Case.toRealmCase(): Case {
+    return when (this) {
+        QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
+        QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt
index 584b577443..baa8f5218d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt
@@ -20,11 +20,13 @@ import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.session.group.Group
 import im.vector.matrix.android.api.session.group.GroupService
+import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
 import im.vector.matrix.android.api.session.group.model.GroupSummary
 import im.vector.matrix.android.internal.database.mapper.asDomain
 import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
 import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields
 import im.vector.matrix.android.internal.database.query.where
+import im.vector.matrix.android.internal.query.process
 import im.vector.matrix.android.internal.util.fetchCopyMap
 import io.realm.Realm
 import io.realm.RealmQuery
@@ -43,22 +45,23 @@ internal class DefaultGroupService @Inject constructor(private val monarchy: Mon
         )
     }
 
-    override fun getGroupSummaries(): List {
+    override fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List {
         return monarchy.fetchAllMappedSync(
-                { groupSummariesQuery(it) },
+                { groupSummariesQuery(it, groupSummaryQueryParams) },
                 { it.asDomain() }
         )
     }
 
-
-    override fun getGroupSummariesLive(): LiveData> {
+    override fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData> {
         return monarchy.findAllMappedWithChanges(
-                { groupSummariesQuery(it) },
+                { groupSummariesQuery(it, groupSummaryQueryParams) },
                 { it.asDomain() }
         )
     }
 
-    private fun groupSummariesQuery(realm: Realm): RealmQuery {
-        return GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME)
+    private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery {
+        return GroupSummaryEntity.where(realm)
+                .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
+                .process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt
index 19c347ba8c..0cfc5aad3c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt
@@ -33,6 +33,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
 import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
 import im.vector.matrix.android.internal.database.query.findByAlias
 import im.vector.matrix.android.internal.database.query.where
+import im.vector.matrix.android.internal.query.process
 import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
 import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
 import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
@@ -104,21 +105,9 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
 
     private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery {
         val query = RoomSummaryEntity.where(realm)
-        if (queryParams.filterCanonicalAlias) {
-            query.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
-        }
-        if (queryParams.filterCanonicalAlias) {
-            query.isNotEmpty(RoomSummaryEntityFields.CANONICAL_ALIAS)
-        }
-        val lastMembership = queryParams.memberships.lastOrNull()
-        query.beginGroup()
-        for (membership in queryParams.memberships) {
-            query.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, membership.name)
-            if (membership != lastMembership) {
-                query.or()
-            }
-        }
-        query.endGroup()
+        query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
+        query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
+        query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
         query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
         return query
     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
index 2f8ce763b6..679f4a050b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
@@ -22,12 +22,14 @@ import com.squareup.inject.assisted.AssistedInject
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.room.members.MembershipService
+import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.matrix.android.api.util.Cancelable
 import im.vector.matrix.android.internal.database.mapper.asDomain
 import im.vector.matrix.android.internal.database.model.RoomMemberEntity
 import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
+import im.vector.matrix.android.internal.query.process
 import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
 import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
 import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
@@ -67,10 +69,10 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
         return roomMemberEntity?.asDomain()
     }
 
-    override fun getRoomMembers(memberships: List): List {
+    override fun getRoomMembers(queryParams: RoomMemberQueryParams): List {
         return monarchy.fetchAllMappedSync(
                 {
-                    roomMembersQuery(it, memberships)
+                    roomMembersQuery(it, queryParams)
                 },
                 {
                     it.asDomain()
@@ -78,10 +80,10 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
         )
     }
 
-    override fun getRoomMembersLive(memberships: List): LiveData> {
+    override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData> {
         return monarchy.findAllMappedWithChanges(
                 {
-                    roomMembersQuery(it, memberships)
+                    roomMembersQuery(it, queryParams)
                 },
                 {
                     it.asDomain()
@@ -89,18 +91,10 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
         )
     }
 
-    private fun roomMembersQuery(realm: Realm, memberships: List): RealmQuery {
-        val query = RoomMembers(realm, roomId).queryRoomMembersEvent()
-        val lastMembership = memberships.lastOrNull()
-        query.beginGroup()
-        for (membership in memberships) {
-            query.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, membership.name)
-            if (membership != lastMembership) {
-                query.or()
-            }
-        }
-        query.endGroup()
-        return query
+    private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery {
+        return RoomMembers(realm, roomId).queryRoomMembersEvent()
+                .process(RoomMemberEntityFields.MEMBERSHIP_STR, queryParams.memberships)
+                .process(RoomMemberEntityFields.DISPLAY_NAME, queryParams.displayName)
     }
 
     override fun getNumberOfJoinedMembers(): Int {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt
index d92320abf9..b532d61914 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt
@@ -21,7 +21,6 @@ import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.internal.database.helper.deleteOnCascade
 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.EventEntityFields
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.task.Task
 import im.vector.matrix.android.internal.util.awaitTransaction
diff --git a/vector/src/main/java/im/vector/riotx/AppStateHandler.kt b/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
index 78a880854e..936253de28 100644
--- a/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/AppStateHandler.kt
@@ -21,8 +21,8 @@ import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.OnLifecycleEvent
 import arrow.core.Option
 import im.vector.matrix.android.api.session.group.model.GroupSummary
-import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.RoomSummary
+import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
 import im.vector.matrix.rx.rx
 import im.vector.riotx.features.home.HomeRoomListDataSource
 import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
@@ -66,7 +66,8 @@ class AppStateHandler @Inject constructor(
                         sessionDataSource.observe()
                                 .observeOn(AndroidSchedulers.mainThread())
                                 .switchMap {
-                                    it.orNull()?.rx()?.liveRoomSummaries(RoomSummaryQueryParams())
+                                    val query = roomSummaryQueryParams {}
+                                    it.orNull()?.rx()?.liveRoomSummaries(query)
                                             ?: Observable.just(emptyList())
                                 }
                                 .throttleLast(300, TimeUnit.MILLISECONDS),
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt
index 010b362b68..6d498de2d2 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt
@@ -37,10 +37,6 @@ class AutocompleteEmojiController @Inject constructor(
         }
     }
 
-    init {
-        fontProvider.addListener(fontProviderListener)
-    }
-
     var listener: AutocompleteClickListener? = null
 
     override fun buildModels(data: List?) {
@@ -71,6 +67,10 @@ class AutocompleteEmojiController @Inject constructor(
         }
     }
 
+    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+        fontProvider.addListener(fontProviderListener)
+    }
+
     override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
         super.onDetachedFromRecyclerView(recyclerView)
         fontProvider.removeListener(fontProviderListener)
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt
index 822ce451e7..8a726a81b1 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt
@@ -19,18 +19,19 @@ package im.vector.riotx.features.autocomplete.group
 import android.content.Context
 import androidx.recyclerview.widget.RecyclerView
 import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Success
 import com.otaliastudios.autocomplete.RecyclerViewPresenter
+import im.vector.matrix.android.api.query.QueryStringValue
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.group.groupSummaryQueryParams
 import im.vector.matrix.android.api.session.group.model.GroupSummary
 import im.vector.riotx.features.autocomplete.AutocompleteClickListener
 import javax.inject.Inject
 
 class AutocompleteGroupPresenter @Inject constructor(context: Context,
-                                                     private val controller: AutocompleteGroupController
+                                                     private val controller: AutocompleteGroupController,
+                                                     private val session: Session
 ) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
-    var callback: Callback? = null
-
     init {
         controller.listener = this
     }
@@ -46,16 +47,20 @@ class AutocompleteGroupPresenter @Inject constructor(context: Context,
     }
 
     override fun onQuery(query: CharSequence?) {
-        callback?.onQueryGroups(query)
+        val queryParams = groupSummaryQueryParams {
+            displayName = if (query.isNullOrBlank()) {
+                QueryStringValue.IsNotEmpty
+            } else {
+                QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
+            }
+        }
+        val groups = session.getGroupSummaries(queryParams)
+                .asSequence()
+                .sortedBy { it.displayName }
+        controller.setData(groups.toList())
     }
 
     fun render(groups: Async>) {
-        if (groups is Success) {
-            controller.setData(groups())
-        }
-    }
-
-    interface Callback {
-        fun onQueryGroups(query: CharSequence?)
+        controller.setData(groups())
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt
index b39d1d9b44..5bf573ecb1 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -21,20 +21,32 @@ import androidx.recyclerview.widget.RecyclerView
 import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.Success
 import com.otaliastudios.autocomplete.RecyclerViewPresenter
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.matrix.android.api.query.QueryStringValue
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams
+import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.riotx.features.autocomplete.AutocompleteClickListener
-import javax.inject.Inject
 
-class AutocompleteMemberPresenter @Inject constructor(context: Context,
-                                                      private val controller: AutocompleteMemberController
+class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
+                                                              @Assisted val roomId: String,
+                                                              private val session: Session,
+                                                              private val controller: AutocompleteMemberController
 ) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
-    var callback: Callback? = null
+    private val room = session.getRoom(roomId)!!
 
     init {
         controller.listener = this
     }
 
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(roomId: String): AutocompleteMemberPresenter
+    }
+
     override fun instantiateAdapter(): RecyclerView.Adapter<*> {
         // Also remove animation
         recyclerView?.itemAnimator = null
@@ -46,7 +58,18 @@ class AutocompleteMemberPresenter @Inject constructor(context: Context,
     }
 
     override fun onQuery(query: CharSequence?) {
-        callback?.onQueryMembers(query)
+        val queryParams = roomMemberQueryParams {
+            displayName = if (query.isNullOrBlank()) {
+                QueryStringValue.IsNotEmpty
+            } else {
+                QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
+            }
+            memberships = listOf(Membership.JOIN)
+        }
+        val members = room.getRoomMembers(queryParams)
+                .asSequence()
+                .sortedBy { it.displayName }
+        controller.setData(members.toList())
     }
 
     fun render(members: Async>) {
@@ -54,8 +77,4 @@ class AutocompleteMemberPresenter @Inject constructor(context: Context,
             controller.setData(members())
         }
     }
-
-    interface Callback {
-        fun onQueryMembers(query: CharSequence?)
-    }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt
index 51285b02b7..aae95502d9 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt
@@ -24,12 +24,10 @@ import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
 import im.vector.riotx.features.home.AvatarRenderer
 import javax.inject.Inject
 
-class AutocompleteRoomController @Inject constructor() : TypedEpoxyController>() {
+class AutocompleteRoomController @Inject constructor(private val avatarRenderer: AvatarRenderer) : TypedEpoxyController>() {
 
     var listener: AutocompleteClickListener? = null
 
-    @Inject lateinit var avatarRenderer: AvatarRenderer
-
     override fun buildModels(data: List?) {
         if (data.isNullOrEmpty()) {
             return
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt
index 53fed7f859..17787a22ef 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt
@@ -18,19 +18,19 @@ package im.vector.riotx.features.autocomplete.room
 
 import android.content.Context
 import androidx.recyclerview.widget.RecyclerView
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Success
 import com.otaliastudios.autocomplete.RecyclerViewPresenter
+import im.vector.matrix.android.api.query.QueryStringValue
+import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.room.model.RoomSummary
+import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
 import im.vector.riotx.features.autocomplete.AutocompleteClickListener
 import javax.inject.Inject
 
 class AutocompleteRoomPresenter @Inject constructor(context: Context,
-                                                    private val controller: AutocompleteRoomController
+                                                    private val controller: AutocompleteRoomController,
+                                                    private val session: Session
 ) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
-    var callback: Callback? = null
-
     init {
         controller.listener = this
     }
@@ -46,16 +46,16 @@ class AutocompleteRoomPresenter @Inject constructor(context: Context,
     }
 
     override fun onQuery(query: CharSequence?) {
-        callback?.onQueryRooms(query)
-    }
-
-    fun render(rooms: Async>) {
-        if (rooms is Success) {
-            controller.setData(rooms())
+        val queryParams = roomSummaryQueryParams {
+            canonicalAlias = if (query.isNullOrBlank()) {
+                QueryStringValue.IsNotNull
+            } else {
+                QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
+            }
         }
-    }
-
-    interface Callback {
-        fun onQueryRooms(query: CharSequence?)
+        val rooms = session.getRoomSummaries(queryParams)
+                .asSequence()
+                .sortedBy { it.displayName }
+        controller.setData(rooms.toList())
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt
index 24318bc508..a00ee24b49 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt
@@ -24,7 +24,9 @@ import com.airbnb.mvrx.MvRxViewModelFactory
 import com.airbnb.mvrx.ViewModelContext
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
+import im.vector.matrix.android.api.query.QueryStringValue
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.group.groupSummaryQueryParams
 import im.vector.matrix.android.api.session.group.model.GroupSummary
 import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.rx.rx
@@ -96,6 +98,10 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
     }
 
     private fun observeGroupSummaries() {
+        val groupSummariesQueryParams = groupSummaryQueryParams {
+            memberships = listOf(Membership.JOIN)
+            displayName = QueryStringValue.IsNotEmpty
+        }
         Observable.combineLatest, List>(
                 session
                         .rx()
@@ -109,9 +115,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
                         },
                 session
                         .rx()
-                        .liveGroupSummaries()
-                        // Keep only joined groups. Group invitations will be managed later
-                        .map { it.filter { groupSummary -> groupSummary.membership == Membership.JOIN } },
+                        .liveGroupSummaries(groupSummariesQueryParams),
                 BiFunction { allCommunityGroup, communityGroups ->
                     listOf(allCommunityGroup) + communityGroups
                 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt
index 1500fb4dfb..6ae937fed2 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt
@@ -24,6 +24,8 @@ import android.widget.EditText
 import com.otaliastudios.autocomplete.Autocomplete
 import com.otaliastudios.autocomplete.AutocompleteCallback
 import com.otaliastudios.autocomplete.CharPolicy
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.session.group.model.GroupSummary
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.matrix.android.api.session.room.model.RoomSummary
@@ -40,20 +42,25 @@ import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter
 import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
 import im.vector.riotx.features.command.Command
 import im.vector.riotx.features.home.AvatarRenderer
-import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
 import im.vector.riotx.features.html.PillImageSpan
 import im.vector.riotx.features.themes.ThemeUtils
-import javax.inject.Inject
 
-class AutoCompleter @Inject constructor(
+class AutoCompleter @AssistedInject constructor(
+        @Assisted val roomId: String,
         private val avatarRenderer: AvatarRenderer,
         private val commandAutocompletePolicy: CommandAutocompletePolicy,
         private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
-        private val autocompleteMemberPresenter: AutocompleteMemberPresenter,
+        private val autocompleteMemberPresenter: AutocompleteMemberPresenter.Factory,
         private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
         private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
         private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
 ) {
+
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(roomId: String): AutoCompleter
+    }
+
     private lateinit var editText: EditText
 
     fun enterSpecialMode() {
@@ -68,22 +75,14 @@ class AutoCompleter @Inject constructor(
         GlideApp.with(editText)
     }
 
-    fun setup(editText: EditText, listener: AutoCompleterListener) {
+    fun setup(editText: EditText) {
         this.editText = editText
-
         val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, R.attr.riotx_background))
-
         setupCommands(backgroundDrawable, editText)
-        setupUsers(backgroundDrawable, editText, listener)
-        setupRooms(backgroundDrawable, editText, listener)
-        setupGroups(backgroundDrawable, editText, listener)
+        setupMembers(backgroundDrawable, editText)
+        setupGroups(backgroundDrawable, editText)
         setupEmojis(backgroundDrawable, editText)
-    }
-
-    fun render(state: TextComposerViewState) {
-        autocompleteMemberPresenter.render(state.asyncMembers)
-        autocompleteRoomPresenter.render(state.asyncRooms)
-        autocompleteGroupPresenter.render(state.asyncGroups)
+        setupRooms(backgroundDrawable, editText)
     }
 
     private fun setupCommands(backgroundDrawable: Drawable, editText: EditText) {
@@ -107,11 +106,11 @@ class AutoCompleter @Inject constructor(
                 .build()
     }
 
-    private fun setupUsers(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteMemberPresenter.Callback) {
-        autocompleteMemberPresenter.callback = listener
+    private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
+        val membersPresenter = autocompleteMemberPresenter.create(roomId)
         Autocomplete.on(editText)
                 .with(CharPolicy('@', true))
-                .with(autocompleteMemberPresenter)
+                .with(membersPresenter)
                 .with(ELEVATION)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
@@ -126,8 +125,7 @@ class AutoCompleter @Inject constructor(
                 .build()
     }
 
-    private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteRoomPresenter.Callback) {
-        autocompleteRoomPresenter.callback = listener
+    private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText) {
         Autocomplete.on(editText)
                 .with(CharPolicy('#', true))
                 .with(autocompleteRoomPresenter)
@@ -145,8 +143,7 @@ class AutoCompleter @Inject constructor(
                 .build()
     }
 
-    private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteGroupPresenter.Callback) {
-        autocompleteGroupPresenter.callback = listener
+    private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText) {
         Autocomplete.on(editText)
                 .with(CharPolicy('+', true))
                 .with(autocompleteGroupPresenter)
@@ -226,11 +223,6 @@ class AutoCompleter @Inject constructor(
         editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
     }
 
-    interface AutoCompleterListener :
-            AutocompleteMemberPresenter.Callback,
-            AutocompleteRoomPresenter.Callback,
-            AutocompleteGroupPresenter.Callback
-
     companion object {
         private const val ELEVATION = 6f
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index 757507fc1e..6a13b22f82 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -78,10 +78,7 @@ import im.vector.riotx.features.attachments.ContactAttachment
 import im.vector.riotx.features.command.Command
 import im.vector.riotx.features.home.AvatarRenderer
 import im.vector.riotx.features.home.getColorFromUserId
-import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
 import im.vector.riotx.features.home.room.detail.composer.TextComposerView
-import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
-import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
 import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
@@ -127,17 +124,15 @@ class RoomDetailFragment @Inject constructor(
         private val session: Session,
         private val avatarRenderer: AvatarRenderer,
         private val timelineEventController: TimelineEventController,
-        private val autoCompleter: AutoCompleter,
+        autoCompleterFactory: AutoCompleter.Factory,
         private val permalinkHandler: PermalinkHandler,
         private val notificationDrawerManager: NotificationDrawerManager,
         val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
-        val textComposerViewModelFactory: TextComposerViewModel.Factory,
         private val eventHtmlRenderer: EventHtmlRenderer,
         private val vectorPreferences: VectorPreferences
 ) :
         VectorBaseFragment(),
         TimelineEventController.Callback,
-        AutoCompleter.AutoCompleterListener,
         VectorInviteView.Callback,
         JumpToReadMarkerView.Callback,
         AttachmentTypeSelectorView.Callback,
@@ -167,9 +162,10 @@ class RoomDetailFragment @Inject constructor(
         GlideApp.with(this)
     }
 
+    private val autoCompleter: AutoCompleter by lazy {
+        autoCompleterFactory.create(roomDetailArgs.roomId)
+    }
     private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
-    private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
-
     private val debouncer = Debouncer(createUIHandler())
 
     private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
@@ -206,7 +202,6 @@ class RoomDetailFragment @Inject constructor(
         setupJumpToBottomView()
 
         roomDetailViewModel.subscribe { renderState(it) }
-        textComposerViewModel.subscribe { renderTextComposerState(it) }
         roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) }
 
         roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair ->
@@ -250,9 +245,9 @@ class RoomDetailFragment @Inject constructor(
         roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode ->
             when (mode) {
                 is SendMode.REGULAR -> renderRegularMode(mode.text)
-                is SendMode.EDIT    -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
-                is SendMode.QUOTE   -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
-                is SendMode.REPLY   -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
+                is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
+                is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
+                is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
             }
         }
 
@@ -279,9 +274,9 @@ class RoomDetailFragment @Inject constructor(
         super.onActivityCreated(savedInstanceState)
         if (savedInstanceState == null) {
             when (val sharedData = roomDetailArgs.sharedData) {
-                is SharedData.Text        -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false))
+                is SharedData.Text -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false))
                 is SharedData.Attachments -> roomDetailViewModel.handle(RoomDetailAction.SendMedia(sharedData.attachmentData))
-                null                      -> Timber.v("No share data to process")
+                null -> Timber.v("No share data to process")
             }
         }
     }
@@ -485,7 +480,7 @@ class RoomDetailFragment @Inject constructor(
         recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
             override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                 when (newState) {
-                    RecyclerView.SCROLL_STATE_IDLE     -> {
+                    RecyclerView.SCROLL_STATE_IDLE -> {
                         updateJumpToBottomViewVisibility()
                     }
                     RecyclerView.SCROLL_STATE_DRAGGING,
@@ -512,7 +507,7 @@ class RoomDetailFragment @Inject constructor(
                         is MessageTextItem -> {
                             return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED
                         }
-                        else               -> false
+                        else -> false
                     }
                 }
             }
@@ -527,9 +522,9 @@ class RoomDetailFragment @Inject constructor(
             withState(roomDetailViewModel) {
                 val showJumpToUnreadBanner = when (it.unreadState) {
                     UnreadState.Unknown,
-                    UnreadState.HasNoUnread            -> false
+                    UnreadState.HasNoUnread -> false
                     is UnreadState.ReadMarkerNotLoaded -> true
-                    is UnreadState.HasUnread           -> {
+                    is UnreadState.HasUnread -> {
                         if (it.canShowJumpToReadMarker) {
                             val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
                             val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
@@ -560,7 +555,7 @@ class RoomDetailFragment @Inject constructor(
     }
 
     private fun setupComposer() {
-        autoCompleter.setup(composerLayout.composerEditText, this)
+        autoCompleter.setup(composerLayout.composerEditText)
         composerLayout.callback = object : TextComposerView.Callback {
             override fun onAddAttachment() {
                 if (!::attachmentTypeSelector.isInitialized) {
@@ -656,10 +651,6 @@ class RoomDetailFragment @Inject constructor(
         }
     }
 
-    private fun renderTextComposerState(state: TextComposerViewState) {
-        autoCompleter.render(state)
-    }
-
     private fun renderTombstoneEventHandling(async: Async) {
         when (async) {
             is Loading -> {
@@ -672,7 +663,7 @@ class RoomDetailFragment @Inject constructor(
                 navigator.openRoom(vectorBaseActivity, async())
                 vectorBaseActivity.finish()
             }
-            is Fail    -> {
+            is Fail -> {
                 vectorBaseActivity.hideWaitingView()
                 vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
             }
@@ -681,23 +672,23 @@ class RoomDetailFragment @Inject constructor(
 
     private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
         when (sendMessageResult) {
-            is SendMessageResult.MessageSent                -> {
+            is SendMessageResult.MessageSent -> {
                 updateComposerText("")
             }
-            is SendMessageResult.SlashCommandHandled        -> {
+            is SendMessageResult.SlashCommandHandled -> {
                 sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
                 updateComposerText("")
             }
-            is SendMessageResult.SlashCommandError          -> {
+            is SendMessageResult.SlashCommandError -> {
                 displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
             }
-            is SendMessageResult.SlashCommandUnknown        -> {
+            is SendMessageResult.SlashCommandUnknown -> {
                 displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
             }
-            is SendMessageResult.SlashCommandResultOk       -> {
+            is SendMessageResult.SlashCommandResultOk -> {
                 updateComposerText("")
             }
-            is SendMessageResult.SlashCommandResultError    -> {
+            is SendMessageResult.SlashCommandResultError -> {
                 displayCommandError(sendMessageResult.throwable.localizedMessage)
             }
             is SendMessageResult.SlashCommandNotImplemented -> {
@@ -735,7 +726,7 @@ class RoomDetailFragment @Inject constructor(
 
     private fun displayRoomDetailActionResult(result: Async) {
         when (result) {
-            is Fail    -> {
+            is Fail -> {
                 AlertDialog.Builder(requireActivity())
                         .setTitle(R.string.dialog_title_error)
                         .setMessage(errorFormatter.toHumanReadable(result.error))
@@ -746,7 +737,7 @@ class RoomDetailFragment @Inject constructor(
                 when (val data = result.invoke()) {
                     is RoomDetailAction.ReportContent -> {
                         when {
-                            data.spam          -> {
+                            data.spam -> {
                                 AlertDialog.Builder(requireActivity())
                                         .setTitle(R.string.content_reported_as_spam_title)
                                         .setMessage(R.string.content_reported_as_spam_content)
@@ -768,7 +759,7 @@ class RoomDetailFragment @Inject constructor(
                                         .show()
                                         .withColoredButton(DialogInterface.BUTTON_NEGATIVE)
                             }
-                            else               -> {
+                            else -> {
                                 AlertDialog.Builder(requireActivity())
                                         .setTitle(R.string.content_reported_title)
                                         .setMessage(R.string.content_reported_content)
@@ -881,14 +872,14 @@ class RoomDetailFragment @Inject constructor(
     override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
         if (allGranted(grantResults)) {
             when (requestCode) {
-                PERMISSION_REQUEST_CODE_DOWNLOAD_FILE   -> {
+                PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> {
                     val action = roomDetailViewModel.pendingAction
                     if (action != null) {
                         roomDetailViewModel.pendingAction = null
                         roomDetailViewModel.handle(action)
                     }
                 }
-                PERMISSION_REQUEST_CODE_INCOMING_URI    -> {
+                PERMISSION_REQUEST_CODE_INCOMING_URI -> {
                     val pendingUri = roomDetailViewModel.pendingUri
                     if (pendingUri != null) {
                         roomDetailViewModel.pendingUri = null
@@ -984,43 +975,25 @@ class RoomDetailFragment @Inject constructor(
         roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
     }
 
-    // AutocompleteMemberPresenter.Callback
-
-    override fun onQueryMembers(query: CharSequence?) {
-        textComposerViewModel.handle(TextComposerAction.QueryUsers(query))
-    }
-
-    // AutocompleteRoomPresenter.Callback
-
-    override fun onQueryRooms(query: CharSequence?) {
-        textComposerViewModel.handle(TextComposerAction.QueryRooms(query))
-    }
-
-    // AutocompleteGroupPresenter.Callback
-
-    override fun onQueryGroups(query: CharSequence?) {
-        textComposerViewModel.handle(TextComposerAction.QueryGroups(query))
-    }
-
     private fun handleActions(action: EventSharedAction) {
         when (action) {
-            is EventSharedAction.AddReaction                -> {
+            is EventSharedAction.AddReaction -> {
                 startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
             }
-            is EventSharedAction.ViewReactions              -> {
+            is EventSharedAction.ViewReactions -> {
                 ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
                         .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
             }
-            is EventSharedAction.Copy                       -> {
+            is EventSharedAction.Copy -> {
                 // I need info about the current selected message :/
                 copyToClipboard(requireContext(), action.content, false)
                 val msg = requireContext().getString(R.string.copied_to_clipboard)
                 showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
             }
-            is EventSharedAction.Delete                     -> {
+            is EventSharedAction.Delete -> {
                 roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason)))
             }
-            is EventSharedAction.Share                      -> {
+            is EventSharedAction.Share -> {
                 // TODO current data communication is too limited
                 // Need to now the media type
                 // TODO bad, just POC
@@ -1048,10 +1021,10 @@ class RoomDetailFragment @Inject constructor(
                         }
                 )
             }
-            is EventSharedAction.ViewEditHistory            -> {
+            is EventSharedAction.ViewEditHistory -> {
                 onEditedDecorationClicked(action.messageInformationData)
             }
-            is EventSharedAction.ViewSource                 -> {
+            is EventSharedAction.ViewSource -> {
                 val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
                 view.findViewById(R.id.event_content_text_view)?.let {
                     it.text = action.content
@@ -1062,7 +1035,7 @@ class RoomDetailFragment @Inject constructor(
                         .setPositiveButton(R.string.ok, null)
                         .show()
             }
-            is EventSharedAction.ViewDecryptedSource        -> {
+            is EventSharedAction.ViewDecryptedSource -> {
                 val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
                 view.findViewById(R.id.event_content_text_view)?.let {
                     it.text = action.content
@@ -1073,31 +1046,31 @@ class RoomDetailFragment @Inject constructor(
                         .setPositiveButton(R.string.ok, null)
                         .show()
             }
-            is EventSharedAction.QuickReact                 -> {
+            is EventSharedAction.QuickReact -> {
                 // eventId,ClickedOn,Add
                 roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
             }
-            is EventSharedAction.Edit                       -> {
+            is EventSharedAction.Edit -> {
                 roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString()))
             }
-            is EventSharedAction.Quote                      -> {
+            is EventSharedAction.Quote -> {
                 roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString()))
             }
-            is EventSharedAction.Reply                      -> {
+            is EventSharedAction.Reply -> {
                 roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString()))
             }
-            is EventSharedAction.CopyPermalink              -> {
+            is EventSharedAction.CopyPermalink -> {
                 val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId)
                 copyToClipboard(requireContext(), permalink, false)
                 showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
             }
-            is EventSharedAction.Resend                     -> {
+            is EventSharedAction.Resend -> {
                 roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
             }
-            is EventSharedAction.Remove                     -> {
+            is EventSharedAction.Remove -> {
                 roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
             }
-            is EventSharedAction.ReportContentSpam          -> {
+            is EventSharedAction.ReportContentSpam -> {
                 roomDetailViewModel.handle(RoomDetailAction.ReportContent(
                         action.eventId, action.senderId, "This message is spam", spam = true))
             }
@@ -1105,19 +1078,19 @@ class RoomDetailFragment @Inject constructor(
                 roomDetailViewModel.handle(RoomDetailAction.ReportContent(
                         action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
             }
-            is EventSharedAction.ReportContentCustom        -> {
+            is EventSharedAction.ReportContentCustom -> {
                 promptReasonToReportContent(action)
             }
-            is EventSharedAction.IgnoreUser                 -> {
+            is EventSharedAction.IgnoreUser -> {
                 roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(action.senderId))
             }
-            is EventSharedAction.OnUrlClicked               -> {
+            is EventSharedAction.OnUrlClicked -> {
                 onUrlClicked(action.url)
             }
-            is EventSharedAction.OnUrlLongClicked           -> {
+            is EventSharedAction.OnUrlLongClicked -> {
                 onUrlLongClicked(action.url)
             }
-            else                                            -> {
+            else -> {
                 Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show()
             }
         }
@@ -1225,10 +1198,10 @@ class RoomDetailFragment @Inject constructor(
 
     private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
         when (type) {
-            AttachmentTypeSelectorView.Type.CAMERA  -> attachmentsHelper.openCamera()
-            AttachmentTypeSelectorView.Type.FILE    -> attachmentsHelper.selectFile()
+            AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera()
+            AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile()
             AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery()
-            AttachmentTypeSelectorView.Type.AUDIO   -> attachmentsHelper.selectAudio()
+            AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio()
             AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact()
             AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
deleted file mode 100644
index 37e01fcacf..0000000000
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.riotx.features.home.room.detail.composer
-
-import arrow.core.Option
-import com.airbnb.mvrx.FragmentViewModelContext
-import com.airbnb.mvrx.MvRxViewModelFactory
-import com.airbnb.mvrx.ViewModelContext
-import com.jakewharton.rxrelay2.BehaviorRelay
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
-import im.vector.matrix.android.api.session.Session
-import im.vector.matrix.android.api.session.group.model.GroupSummary
-import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
-import im.vector.matrix.android.api.session.room.model.Membership
-import im.vector.matrix.android.api.session.room.model.RoomMember
-import im.vector.matrix.android.api.session.room.model.RoomSummary
-import im.vector.matrix.rx.rx
-import im.vector.riotx.core.platform.VectorViewModel
-import im.vector.riotx.features.home.room.detail.RoomDetailFragment
-import io.reactivex.Observable
-import io.reactivex.functions.BiFunction
-import java.util.concurrent.TimeUnit
-
-typealias AutocompleteQuery = CharSequence
-
-class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: TextComposerViewState,
-                                                        private val session: Session
-) : VectorViewModel(initialState) {
-
-    private val room = session.getRoom(initialState.roomId)!!
-
-    private val usersQueryObservable = BehaviorRelay.create>()
-    private val roomsQueryObservable = BehaviorRelay.create>()
-    private val groupsQueryObservable = BehaviorRelay.create>()
-
-    @AssistedInject.Factory
-    interface Factory {
-        fun create(initialState: TextComposerViewState): TextComposerViewModel
-    }
-
-    companion object : MvRxViewModelFactory {
-
-        @JvmStatic
-        override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel? {
-            val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
-            return fragment.textComposerViewModelFactory.create(state)
-        }
-    }
-
-    init {
-        observeUsersQuery()
-        observeRoomsQuery()
-        observeGroupsQuery()
-    }
-
-    override fun handle(action: TextComposerAction) {
-        when (action) {
-            is TextComposerAction.QueryUsers  -> handleQueryUsers(action)
-            is TextComposerAction.QueryRooms  -> handleQueryRooms(action)
-            is TextComposerAction.QueryGroups -> handleQueryGroups(action)
-        }
-    }
-
-    private fun handleQueryUsers(action: TextComposerAction.QueryUsers) {
-        val query = Option.fromNullable(action.query)
-        usersQueryObservable.accept(query)
-    }
-
-    private fun handleQueryRooms(action: TextComposerAction.QueryRooms) {
-        val query = Option.fromNullable(action.query)
-        roomsQueryObservable.accept(query)
-    }
-
-    private fun handleQueryGroups(action: TextComposerAction.QueryGroups) {
-        val query = Option.fromNullable(action.query)
-        groupsQueryObservable.accept(query)
-    }
-
-    private fun observeUsersQuery() {
-        Observable.combineLatest, Option, List>(
-                room.rx().liveRoomMembers(Membership.activeMemberships()),
-                usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
-                BiFunction { roomMembers, query ->
-                    val filter = query.orNull()
-                    if (filter.isNullOrBlank()) {
-                        roomMembers
-                    } else {
-                        roomMembers.filter {
-                            it.displayName?.contains(filter, ignoreCase = true) ?: false
-                        }
-                    }
-                            .sortedBy { it.displayName }
-                }
-        ).execute { async ->
-            copy(
-                    asyncMembers = async
-            )
-        }
-    }
-
-    private fun observeRoomsQuery() {
-        Observable.combineLatest, Option, List>(
-                session.rx().liveRoomSummaries(RoomSummaryQueryParams(filterCanonicalAlias = true)),
-                roomsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
-                BiFunction { roomSummaries, query ->
-                    val filter = query.orNull() ?: ""
-                    // Keep only room with a canonical alias
-                    roomSummaries
-                            .filter {
-                                it.canonicalAlias?.contains(filter, ignoreCase = true) == true
-                            }
-                            .sortedBy { it.displayName }
-                }
-        ).execute { async ->
-            copy(
-                    asyncRooms = async
-            )
-        }
-    }
-
-    private fun observeGroupsQuery() {
-        Observable.combineLatest, Option, List>(
-                session.rx().liveGroupSummaries(),
-                groupsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
-                BiFunction { groupSummaries, query ->
-                    val filter = query.orNull()
-                    if (filter.isNullOrBlank()) {
-                        groupSummaries
-                    } else {
-                        groupSummaries
-                                .filter {
-                                    it.groupId.contains(filter, ignoreCase = true)
-                                }
-                    }
-                            .sortedBy { it.displayName }
-                }
-        ).execute { async ->
-            copy(
-                    asyncGroups = async
-            )
-        }
-    }
-}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt
deleted file mode 100644
index f2a1f07c57..0000000000
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.riotx.features.home.room.detail.composer
-
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.MvRxState
-import com.airbnb.mvrx.Uninitialized
-import im.vector.matrix.android.api.session.group.model.GroupSummary
-import im.vector.matrix.android.api.session.room.model.RoomMember
-import im.vector.matrix.android.api.session.room.model.RoomSummary
-import im.vector.riotx.features.home.room.detail.RoomDetailArgs
-
-data class TextComposerViewState(val roomId: String,
-                                 val asyncMembers: Async> = Uninitialized,
-                                 val asyncRooms: Async> = Uninitialized,
-                                 val asyncGroups: Async> = Uninitialized
-) : MvRxState {
-
-    constructor(args: RoomDetailArgs) : this(roomId = args.roomId)
-}
diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt
index 2917dce68a..1d7338e2a4 100644
--- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt
+++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt
@@ -18,7 +18,6 @@ package im.vector.riotx.features.reactions.data
 import android.content.res.Resources
 import com.squareup.moshi.Moshi
 import im.vector.riotx.R
-import im.vector.riotx.core.di.ScreenScope
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -42,6 +41,7 @@ class EmojiDataSource @Inject constructor(
 
         // First add emojis with name matching query, sorted by name
         return (rawData.emojis.values
+                .asSequence()
                 .filter { emojiItem ->
                     emojiItem.name.contains(query, true)
                 }
@@ -56,6 +56,7 @@ class EmojiDataSource @Inject constructor(
                         .sortedBy { it.name })
                 // and ensure they will not be present twice
                 .distinct()
+                .toList()
     }
 
     fun getQuickReactions(): List {
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt
index cc57775028..c4a91a520a 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt
@@ -24,12 +24,12 @@ import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.failure.Failure
 import im.vector.matrix.android.api.session.Session
-import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsFilter
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
 import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
+import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
 import im.vector.matrix.android.api.util.Cancelable
 import im.vector.matrix.rx.rx
 import im.vector.riotx.core.extensions.postLiveEvent
@@ -80,9 +80,12 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
     }
 
     private fun observeJoinedRooms() {
+        val queryParams = roomSummaryQueryParams {
+            memberships = listOf(Membership.JOIN)
+        }
         session
                 .rx()
-                .liveRoomSummaries(RoomSummaryQueryParams(memberships = listOf(Membership.JOIN)))
+                .liveRoomSummaries(queryParams)
                 .subscribe { list ->
                     val joinedRoomIds = list
                             ?.map { it.roomId }
@@ -105,9 +108,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
     override fun handle(action: RoomDirectoryAction) {
         when (action) {
             is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action)
-            is RoomDirectoryAction.FilterWith           -> filterWith(action)
-            RoomDirectoryAction.LoadMore                -> loadMore()
-            is RoomDirectoryAction.JoinRoom             -> joinRoom(action)
+            is RoomDirectoryAction.FilterWith -> filterWith(action)
+            RoomDirectoryAction.LoadMore -> loadMore()
+            is RoomDirectoryAction.JoinRoom -> joinRoom(action)
         }
     }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
index 005a930fdc..3de5cb4334 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
@@ -23,8 +23,8 @@ import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.Session
-import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
+import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
 import im.vector.matrix.rx.rx
 import im.vector.riotx.core.platform.VectorViewModel
 import im.vector.riotx.features.roomdirectory.JoinState
@@ -54,9 +54,12 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
     }
 
     private fun observeJoinedRooms() {
+        val queryParams = roomSummaryQueryParams {
+            memberships = listOf(Membership.JOIN)
+        }
         session
                 .rx()
-                .liveRoomSummaries(RoomSummaryQueryParams(memberships = listOf(Membership.JOIN)))
+                .liveRoomSummaries(queryParams)
                 .subscribe { list ->
                     withState { state ->
                         val isRoomJoined = list
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
index 0ec67789fe..774e3741e6 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
@@ -398,7 +398,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
     fun getNotificationRingTone(): Uri? {
         val url = defaultPrefs.getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null)
 
-        // the user selects "None"
+        // the user selects "NoCondition"
         if (url == "") {
             return null
         }
@@ -425,7 +425,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
     /**
      * Provide the notification ringtone filename
      *
-     * @return the filename or null if "None" is selected
+     * @return the filename or null if "NoCondition" is selected
      */
     fun getNotificationRingToneName(): String? {
         val toneUri = getNotificationRingTone() ?: return null
diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
index 904407708f..bfe08a5c52 100644
--- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt
@@ -22,7 +22,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory
 import com.airbnb.mvrx.ViewModelContext
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
-import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
+import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
 import im.vector.matrix.rx.rx
 import im.vector.riotx.ActiveSessionDataSource
 import im.vector.riotx.core.platform.EmptyAction
@@ -60,10 +60,11 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState:
     }
 
     private fun observeRoomSummaries() {
+        val queryParams = roomSummaryQueryParams()
         sessionObservableStore.observe()
                 .observeOn(AndroidSchedulers.mainThread())
                 .switchMap {
-                    it.orNull()?.rx()?.liveRoomSummaries(RoomSummaryQueryParams())
+                    it.orNull()?.rx()?.liveRoomSummaries(queryParams)
                             ?: Observable.just(emptyList())
                 }
                 .throttleLast(300, TimeUnit.MILLISECONDS)

From e14b9b3b20bf90ec82e3c65741c888e24427e4e1 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Thu, 9 Jan 2020 08:03:14 +0100
Subject: [PATCH 24/26] Fix test compilation issue

---
 .../matrix/android/api/pushrules/PushrulesConditionTest.kt  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt
index a29f5d5542..1d1bbe1406 100644
--- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt
+++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt
@@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.events.model.toContent
 import im.vector.matrix.android.api.session.room.Room
 import im.vector.matrix.android.api.session.room.RoomService
 import im.vector.matrix.android.api.session.room.model.Membership
-import im.vector.matrix.android.api.session.room.model.RoomMember
+import im.vector.matrix.android.api.session.room.model.RoomMemberContent
 import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
 import io.mockk.every
 import io.mockk.mockk
@@ -40,7 +40,7 @@ class PushrulesConditionTest {
                 content = MessageTextContent("m.text", "Yo wtf?").toContent(),
                 originServerTs = 0)
 
-        val rm = RoomMember(
+        val rm = RoomMemberContent(
                 Membership.INVITE,
                 displayName = "Foo",
                 avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
@@ -72,7 +72,7 @@ class PushrulesConditionTest {
                 type = "m.room.member",
                 eventId = "mx0",
                 stateKey = "@foo:matrix.org",
-                content = RoomMember(
+                content = RoomMemberContent(
                         Membership.INVITE,
                         displayName = "Foo",
                         avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"

From 0f7d59a8c786efc4006a6cdf95b236f742060be4 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Thu, 9 Jan 2020 09:42:34 +0100
Subject: [PATCH 25/26] Cleanup during PR review

---
 .../android/api/session/group/GroupService.kt  |  2 +-
 .../room/model/relation/RelationService.kt     |  2 +-
 .../internal/query/QueryEnumListProcessor.kt   |  8 ++++----
 .../query/QueryStringValueProcessor.kt         | 18 +++++++-----------
 .../room/membership/RoomMemberEventHandler.kt  |  2 +-
 .../group/AutocompleteGroupPresenter.kt        |  5 -----
 .../member/AutocompleteMemberPresenter.kt      |  8 --------
 .../features/home/room/detail/AutoCompleter.kt |  6 +++---
 .../room/detail/timeline/item/BaseEventItem.kt |  1 -
 .../features/settings/VectorPreferences.kt     |  4 ++--
 .../res/layout/item_bottom_sheet_action.xml    |  2 +-
 11 files changed, 20 insertions(+), 38 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
index 76bac1ae1a..c01e5b5cd8 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt
@@ -39,7 +39,7 @@ interface GroupService {
     fun getGroupSummary(groupId: String): GroupSummary?
 
     /**
-     * Get a list of group summaries.This list is a snapshot of the data.
+     * Get a list of group summaries. This list is a snapshot of the data.
      * @return the list of [GroupSummary]
      */
     fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
index d13d7db773..31ed4e9986 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
@@ -116,7 +116,7 @@ interface RelationService {
     fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
 
     /**
-     * Get the a LiveData EventAnnotationsSummary
+     * Get a LiveData of EventAnnotationsSummary for the specified eventId
      * @param eventId the eventId to look for EventAnnotationsSummary
      * @return the LiveData of EventAnnotationsSummary
      */
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt
index 7cb5ca1047..2bc05eacec 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryEnumListProcessor.kt
@@ -21,13 +21,13 @@ import io.realm.RealmQuery
 
 fun > RealmQuery.process(field: String, enums: List>): RealmQuery {
     val lastEnumValue = enums.lastOrNull()
-    this.beginGroup()
+    beginGroup()
     for (enumValue in enums) {
-        this.equalTo(field, enumValue.name)
+        equalTo(field, enumValue.name)
         if (enumValue != lastEnumValue) {
-            this.or()
+            or()
         }
     }
-    this.endGroup()
+    endGroup()
     return this
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt
index d2418ee84f..ebe10cad9c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/query/QueryStringValueProcessor.kt
@@ -25,16 +25,12 @@ import timber.log.Timber
 fun  RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery {
     when (queryStringValue) {
         is QueryStringValue.NoCondition -> Timber.v("No condition to process")
-        is QueryStringValue.IsNotNull -> this.isNotNull(field)
-        is QueryStringValue.IsNull -> this.isNull(field)
-        is QueryStringValue.IsEmpty -> this.isEmpty(field)
-        is QueryStringValue.IsNotEmpty -> this.isNotEmpty(field)
-        is QueryStringValue.Equals -> {
-            this.equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase())
-        }
-        is QueryStringValue.Contains -> {
-            this.contains(field, queryStringValue.string, queryStringValue.case.toRealmCase())
-        }
+        is QueryStringValue.IsNotNull   -> isNotNull(field)
+        is QueryStringValue.IsNull      -> isNull(field)
+        is QueryStringValue.IsEmpty     -> isEmpty(field)
+        is QueryStringValue.IsNotEmpty  -> isNotEmpty(field)
+        is QueryStringValue.Equals      -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase())
+        is QueryStringValue.Contains    -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase())
     }
     return this
 }
@@ -42,6 +38,6 @@ fun  RealmQuery.process(field: String, queryStringValue: Que
 private fun QueryStringValue.Case.toRealmCase(): Case {
     return when (this) {
         QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
-        QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE
+        QueryStringValue.Case.SENSITIVE   -> Case.SENSITIVE
     }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt
index 16d6aacbc9..9bd97cec10 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt
@@ -35,7 +35,7 @@ internal class RoomMemberEventHandler @Inject constructor() {
         val userId = event.stateKey ?: return false
         val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember)
         realm.insertOrUpdate(roomMemberEntity)
-        if (roomMember.membership == Membership.JOIN || roomMember.membership == Membership.INVITE) {
+        if (roomMember.membership in Membership.activeMemberships()) {
             val userEntity = UserEntityFactory.create(userId, roomMember)
             realm.insertOrUpdate(userEntity)
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt
index 8a726a81b1..b6f45b477c 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt
@@ -18,7 +18,6 @@ package im.vector.riotx.features.autocomplete.group
 
 import android.content.Context
 import androidx.recyclerview.widget.RecyclerView
-import com.airbnb.mvrx.Async
 import com.otaliastudios.autocomplete.RecyclerViewPresenter
 import im.vector.matrix.android.api.query.QueryStringValue
 import im.vector.matrix.android.api.session.Session
@@ -59,8 +58,4 @@ class AutocompleteGroupPresenter @Inject constructor(context: Context,
                 .sortedBy { it.displayName }
         controller.setData(groups.toList())
     }
-
-    fun render(groups: Async>) {
-        controller.setData(groups())
-    }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 5bf573ecb1..84a33173b8 100644
--- a/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -18,8 +18,6 @@ package im.vector.riotx.features.autocomplete.member
 
 import android.content.Context
 import androidx.recyclerview.widget.RecyclerView
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Success
 import com.otaliastudios.autocomplete.RecyclerViewPresenter
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
@@ -71,10 +69,4 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                 .sortedBy { it.displayName }
         controller.setData(members.toList())
     }
-
-    fun render(members: Async>) {
-        if (members is Success) {
-            controller.setData(members())
-        }
-    }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt
index 6ae937fed2..7ca647ea3e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt
@@ -50,7 +50,7 @@ class AutoCompleter @AssistedInject constructor(
         private val avatarRenderer: AvatarRenderer,
         private val commandAutocompletePolicy: CommandAutocompletePolicy,
         private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
-        private val autocompleteMemberPresenter: AutocompleteMemberPresenter.Factory,
+        private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory,
         private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
         private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
         private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
@@ -107,10 +107,10 @@ class AutoCompleter @AssistedInject constructor(
     }
 
     private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
-        val membersPresenter = autocompleteMemberPresenter.create(roomId)
+        val autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId)
         Autocomplete.on(editText)
                 .with(CharPolicy('@', true))
-                .with(membersPresenter)
+                .with(autocompleteMemberPresenter)
                 .with(ELEVATION)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt
index 5db87fff0f..02b7341c72 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt
@@ -27,7 +27,6 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
 import im.vector.riotx.core.platform.CheckableView
 import im.vector.riotx.core.ui.views.ReadReceiptsView
 import im.vector.riotx.core.utils.DimensionConverter
-import kotlinx.coroutines.*
 
 /**
  * Children must override getViewType()
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
index 774e3741e6..0ec67789fe 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
@@ -398,7 +398,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
     fun getNotificationRingTone(): Uri? {
         val url = defaultPrefs.getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null)
 
-        // the user selects "NoCondition"
+        // the user selects "None"
         if (url == "") {
             return null
         }
@@ -425,7 +425,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
     /**
      * Provide the notification ringtone filename
      *
-     * @return the filename or null if "NoCondition" is selected
+     * @return the filename or null if "None" is selected
      */
     fun getNotificationRingToneName(): String? {
         val toneUri = getNotificationRingTone() ?: return null
diff --git a/vector/src/main/res/layout/item_bottom_sheet_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml
index db01db0a2f..66a096799d 100644
--- a/vector/src/main/res/layout/item_bottom_sheet_action.xml
+++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml
@@ -47,12 +47,12 @@
         android:maxLines="2"
         android:textColor="?riotx_text_secondary"
         android:textSize="17sp"
+        app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toStartOf="@+id/actionSelected"
         app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintStart_toEndOf="@id/actionStartSpace"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constrainedWidth="true"
         tools:text="zbla azjazjaz s sdkqdskdsqk kqsdkdqsk kdqsksqdk" />
 
 

From bd4a595f96746bc31e6db0db8ec54edb529939ec Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Thu, 9 Jan 2020 11:19:08 +0100
Subject: [PATCH 26/26] ChunkEntityTest: make it compile again

---
 .../session/room/timeline/ChunkEntityTest.kt  | 40 +++----------------
 1 file changed, 6 insertions(+), 34 deletions(-)

diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt
index 5cca9f6696..3980094175 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt
@@ -21,7 +21,6 @@ import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.InstrumentedTest
 import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.internal.database.helper.add
-import im.vector.matrix.android.internal.database.helper.isUnlinked
 import im.vector.matrix.android.internal.database.helper.lastStateIndex
 import im.vector.matrix.android.internal.database.helper.merge
 import im.vector.matrix.android.internal.database.model.ChunkEntity
@@ -33,7 +32,6 @@ import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeR
 import io.realm.Realm
 import io.realm.RealmConfiguration
 import io.realm.kotlin.createObject
-import org.amshove.kluent.shouldBeFalse
 import org.amshove.kluent.shouldBeTrue
 import org.amshove.kluent.shouldEqual
 import org.junit.Before
@@ -150,30 +148,6 @@ internal class ChunkEntityTest : InstrumentedTest {
         }
     }
 
-    @Test
-    fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
-        monarchy.runTransactionSync { realm ->
-            val chunk1: ChunkEntity = realm.createObject()
-            val chunk2: ChunkEntity = realm.createObject()
-            chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
-            chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
-            chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
-            chunk1.isUnlinked().shouldBeFalse()
-        }
-    }
-
-    @Test
-    fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
-        monarchy.runTransactionSync { realm ->
-            val chunk1: ChunkEntity = realm.createObject()
-            val chunk2: ChunkEntity = realm.createObject()
-            chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
-            chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
-            chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
-            chunk1.isUnlinked().shouldBeTrue()
-        }
-    }
-
     @Test
     fun merge_shouldPrevTokenMerged_whenMergingForwards() {
         monarchy.runTransactionSync { realm ->
@@ -181,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest {
             val chunk2: ChunkEntity = realm.createObject()
             val prevToken = "prev_token"
             chunk1.prevToken = prevToken
-            chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
-            chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
+            chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
+            chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
             chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
             chunk1.prevToken shouldEqual prevToken
         }
@@ -195,8 +169,8 @@ internal class ChunkEntityTest : InstrumentedTest {
             val chunk2: ChunkEntity = realm.createObject()
             val nextToken = "next_token"
             chunk1.nextToken = nextToken
-            chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
-            chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
+            chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
+            chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
             chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
             chunk1.nextToken shouldEqual nextToken
         }
@@ -205,11 +179,9 @@ internal class ChunkEntityTest : InstrumentedTest {
     private fun ChunkEntity.addAll(roomId: String,
                                    events: List,
                                    direction: PaginationDirection,
-                                   stateIndexOffset: Int = 0,
-            // Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
-                                   isUnlinked: Boolean = false) {
+                                   stateIndexOffset: Int = 0) {
         events.forEach { event ->
-            add(roomId, event, direction, stateIndexOffset, isUnlinked)
+            add(roomId, event, direction, stateIndexOffset)
         }
     }
 }