Room members: introduce RoomMemberEntity to be able to query. Still work to do.

This commit is contained in:
ganfra 2019-12-26 19:51:03 +01:00
parent 90f2199eb7
commit 00f316ba5d
32 changed files with 393 additions and 192 deletions

View File

@ -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<List<String>> {
return room.getRoomMemberIdsLive().asObservable()
fun liveRoomMembers(memberships: List<Membership>): Observable<List<RoomMember>> {
return room.getRoomMembersLive(memberships).asObservable()
}
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {

View File

@ -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<List<String>>
fun getRoomMembersLive(memberships: List<Membership>): LiveData<List<RoomMember>>
fun getNumberOfJoinedMembers(): Int

View File

@ -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<Membership> {
return listOf(INVITE, JOIN)
}
}
}

View File

@ -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
)

View File

@ -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() }
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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<RoomMember>()?.also {
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.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<RoomMember>()?.also {
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMemberContent>()?.also {
if (this.senderAvatar == null && it.avatarUrl != null) {
this.senderAvatar = it.avatarUrl
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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<String> = RealmList(),
// this is required for querying
var flatAliases: String = ""
) : RealmObject() {

View File

@ -49,6 +49,7 @@ import io.realm.annotations.RealmModule
ReadMarkerEntity::class,
UserDraftsEntity::class,
DraftEntity::class,
HomeServerCapabilitiesEntity::class
HomeServerCapabilitiesEntity::class,
RoomMemberEntity::class
])
internal class SessionRealmModule

View File

@ -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<RoomMemberEntity> {
val query = realm
.where<RoomMemberEntity>()
.equalTo(RoomMemberEntityFields.ROOM_ID, roomId)
if (userId != null) {
query.equalTo(RoomMemberEntityFields.USER_ID, userId)
}
return query
}

View File

@ -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<RoomMember>()
}
}

View File

@ -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)

View File

@ -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<List<String>> {
override fun getRoomMembersLive(memberships: List<Membership>): LiveData<List<RoomMember>> {
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<Unit>): Cancelable {

View File

@ -44,7 +44,8 @@ internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit>
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()

View File

@ -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<EventEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes.mapNotNull {
roomMembers.getStateEvent(it)
val otherMembersSubset: List<RoomMemberEntity> = 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<RoomMember>()
}
}

View File

@ -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
}
}
}

View File

@ -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<RoomMemberContent>() ?: 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
}
}

View File

@ -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<RoomMember>()
}
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<EventEntity> {
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<RoomMemberEntity> {
return RoomMemberEntity.where(realm, roomId)
}
fun queryJoinedRoomMembersEvent(): RealmQuery<EventEntity> {
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"join\"")
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
return queryRoomMembersEvent().equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
}
fun queryInvitedRoomMembersEvent(): RealmQuery<EventEntity> {
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"invite\"")
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
return queryRoomMembersEvent().equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
}
fun queryRoomMemberEvent(userId: String): RealmQuery<EventEntity> {
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
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<String> {
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<String> {
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN }
}
/* ==========================================================================================
* Private
* ========================================================================================== */
private fun getRoomMemberIdsFiltered(predicate: (RoomMember) -> Boolean): List<String> {
return RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.findAll()
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.filterValues { predicate(it.content.toModel<RoomMember>()!!) }
.keys
.toList()
return queryJoinedRoomMembersEvent().findAll().map { it.userId }
}
}

View File

@ -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)
}

View File

@ -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<String, RoomSync>) : 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

View File

@ -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

View File

@ -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<RoomMember>() ?: 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 ?: ""
)
}
}

View File

@ -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<List<User>>() {
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMember>>() {
var listener: AutocompleteClickListener<User>? = null
var listener: AutocompleteClickListener<RoomMember>? = null
@Inject lateinit var avatarRenderer: AvatarRenderer
override fun buildModels(data: List<User>?) {
override fun buildModels(data: List<RoomMember>?) {
if (data.isNullOrEmpty()) {
return
}

View File

@ -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<User>(context), AutocompleteClickListener<User> {
class AutocompleteMemberPresenter @Inject constructor(context: Context,
private val controller: AutocompleteMemberController
) : RecyclerViewPresenter<RoomMember>(context), AutocompleteClickListener<RoomMember> {
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<List<User>>) {
if (users is Success) {
controller.setData(users())
fun render(members: Async<List<RoomMember>>) {
if (members is Success) {
controller.setData(members())
}
}
interface Callback {
fun onQueryUsers(query: CharSequence?)
fun onQueryMembers(query: CharSequence?)
}
}

View File

@ -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<User>(composerLayout.composerEditText)
autocompleteMemberPresenter.callback = this
Autocomplete.on<RoomMember>(composerLayout.composerEditText)
.with(CharPolicy('@', true))
.with(autocompleteUserPresenter)
.with(autocompleteMemberPresenter)
.with(elevation)
.with(backgroundDrawable)
.with(object : AutocompleteCallback<User> {
override fun onPopupItemClicked(editable: Editable, item: User): Boolean {
.with(object : AutocompleteCallback<RoomMember> {
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))
}

View File

@ -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<List<String>, Option<AutocompleteQuery>, List<User>>(
room.rx().liveRoomMemberIds(),
Observable.combineLatest<List<RoomMember>, Option<AutocompleteQuery>, List<RoomMember>>(
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
)
}
}

View File

@ -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<List<User>> = Uninitialized,
val asyncMembers: Async<List<RoomMember>> = Uninitialized,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val asyncGroups: Async<List<GroupSummary>> = Uninitialized
) : MvRxState {

View File

@ -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) {

View File

@ -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<RoomMember>() ?: return null
val content = event.content?.toModel<RoomMemberContent>() ?: return null
val roomId = event.roomId ?: return null
val dName = event.senderId?.let { session.getUser(it)?.displayName }
if (Membership.INVITE == content.membership) {