Room member list: group by power level

This commit is contained in:
ganfra 2020-01-10 17:03:11 +01:00
parent 81712ae736
commit 171ec4fbdc
43 changed files with 498 additions and 324 deletions

View file

@ -22,3 +22,9 @@ import io.reactivex.Observable
fun <T : Any> Observable<Optional<T>>.unwrap(): Observable<T> {
return filter { it.hasValue() }.map { it.get() }
}
fun <T : Any, U : Any> Observable<Optional<T>>.mapOptional(fn: (T) -> U?): Observable<Optional<U>> {
return map {
it.map(fn)
}
}

View file

@ -16,11 +16,12 @@
package im.vector.matrix.rx
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
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.RoomMemberSummary
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
@ -37,7 +38,7 @@ class RxRoom(private val room: Room) {
.startWith(room.roomSummary().toOptional())
}
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMember>> {
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
return room.getRoomMembersLive(queryParams).asObservable()
.startWith(room.getRoomMembers(queryParams))
}
@ -52,6 +53,11 @@ class RxRoom(private val room: Room) {
.startWith(room.getTimeLineEvent(eventId).toOptional())
}
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
return room.getStateEventLive(eventType).asObservable()
.startWith(room.getStateEvent(eventType).toOptional())
}
fun liveReadMarker(): Observable<Optional<String>> {
return room.getReadMarkerLive().asObservable()
}

View file

@ -21,7 +21,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.toContent
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.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import kotlin.random.Random
@ -63,7 +63,7 @@ object RoomDataHelper {
}
fun createFakeRoomMemberEvent(): Event {
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
}
}

View file

@ -16,7 +16,8 @@
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsHelper
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
@ -28,7 +29,8 @@ class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.se
return "User power level <$key>"
}
fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean {
return event.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key)
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
return event.senderId != null && powerLevelsHelper.getUserPowerLevel(event.senderId) >= powerLevelsHelper.notificationLevel(key)
}
}

View file

@ -18,7 +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.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.Cancelable
/**
@ -38,21 +38,21 @@ interface MembershipService {
*
* @return the roomMember with userId or null
*/
fun getRoomMember(userId: String): RoomMember?
fun getRoomMember(userId: String): RoomMemberSummary?
/**
* Return all the roomMembers of the room with params
* @param queryParams the params to query for
* @return a roomMember list.
*/
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember>
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMemberSummary>
/**
* Return all the roomMembers of the room filtered by memberships
* @param queryParams the params to query for
* @return a [LiveData] of roomMember list.
*/
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>>
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>>
fun getNumberOfJoinedMembers(): Int

View file

@ -1,121 +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.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.EventType
/**
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
*/
@JsonClass(generateAdapter = true)
data class PowerLevels(
@Json(name = "ban") val ban: Int = 50,
@Json(name = "kick") val kick: Int = 50,
@Json(name = "invite") val invite: Int = 50,
@Json(name = "redact") val redact: Int = 50,
@Json(name = "events_default") val eventsDefault: Int = 0,
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
@Json(name = "users_default") val usersDefault: Int = 0,
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
@Json(name = "state_default") val stateDefault: Int = 50,
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
) {
/**
* Returns the user power level of a dedicated user Id
*
* @param userId the user id
* @return the power level
*/
fun getUserPowerLevel(userId: String): Int {
return users.getOrElse(userId) { usersDefault }
}
/**
* Updates the user power levels of a dedicated user id
*
* @param userId the user
* @param powerLevel the new power level
*/
fun setUserPowerLevel(userId: String, powerLevel: Int) {
users[userId] = powerLevel
}
/**
* Tell if an user can send an event of type 'eventTypeString'.
*
* @param eventTypeString the event type (in Event.EVENT_TYPE_XXX values)
* @param userId the user id
* @return true if the user can send the event
*/
fun maySendEventOfType(eventTypeString: String, userId: String): Boolean {
return if (eventTypeString.isNotEmpty() && userId.isNotEmpty()) {
getUserPowerLevel(userId) >= minimumPowerLevelForSendingEventAsMessage(eventTypeString)
} else false
}
/**
* Tells if an user can send a room message.
*
* @param userId the user id
* @return true if the user can send a room message
*/
fun maySendMessage(userId: String): Boolean {
return maySendEventOfType(EventType.MESSAGE, userId)
}
/**
* Helper to get the minimum power level the user must have to send an event of the given type
* as a message.
*
* @param eventTypeString the type of event (in Event.EVENT_TYPE_XXX values)
* @return the required minimum power level.
*/
fun minimumPowerLevelForSendingEventAsMessage(eventTypeString: String?): Int {
return events[eventTypeString] ?: eventsDefault
}
/**
* Helper to get the minimum power level the user must have to send an event of the given type
* as a state event.
*
* @param eventTypeString the type of event (in Event.EVENT_TYPE_STATE_ values).
* @return the required minimum power level.
*/
fun minimumPowerLevelForSendingEventAsStateEvent(eventTypeString: String?): Int {
return events[eventTypeString] ?: stateDefault
}
/**
* Get the notification level for a dedicated key.
*
* @param key the notification key
* @return the level
*/
fun notificationLevel(key: String): Int {
val valAsVoid = notifications[key] ?: return 50
// the first implementation was a string value
return if (valAsVoid is String) {
valAsVoid.toInt()
} else {
valAsVoid as Int
}
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.EventType
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsConstants
/**
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
*/
@JsonClass(generateAdapter = true)
data class PowerLevelsContent(
@Json(name = "ban") val ban: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "kick") val kick: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "invite") val invite: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "redact") val redact: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "events_default") val eventsDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
@Json(name = "users_default") val usersDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
@Json(name = "state_default") val stateDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
)

View file

@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room.model
/**
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
*/
data class RoomMember(
data class RoomMemberSummary(
val membership: Membership,
val userId: String,
val displayName: String? = null,

View file

@ -24,7 +24,7 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
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.toContent
import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
@ -113,7 +113,7 @@ class CreateRoomParams {
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
var powerLevelContentOverride: PowerLevels? = null
var powerLevelContentOverride: PowerLevelsContent? = null
/**
* Add the crypto algorithm to the room creation parameters.

View file

@ -0,0 +1,26 @@
/*
* 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.powerlevers
object PowerLevelsConstants {
const val DEFAULT_ROOM_ADMIN_LEVEL = 100
const val DEFAULT_ROOM_MODERATOR_LEVEL = 50
const val DEFAULT_ROOM_USER_LEVEL = 0
}

View file

@ -0,0 +1,77 @@
/*
* 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.powerlevers
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
/**
* This class is an helper around PowerLevelsContent.
*/
class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
/**
* Returns the user power level of a dedicated user Id
*
* @param userId the user id
* @return the power level
*/
fun getUserPowerLevel(userId: String): Int {
return powerLevelsContent.users.getOrElse(userId) {
powerLevelsContent.usersDefault
}
}
/**
* Tell if an user can send an event of a certain type
*
* @param eventType the event type to check for
* @param userId the user id
* @return true if the user can send this type of event
*/
fun isAllowedToSend(eventType: String, userId: String): Boolean {
return if (eventType.isNotEmpty() && userId.isNotEmpty()) {
val powerLevel = getUserPowerLevel(userId)
val minimumPowerLevel = powerLevelsContent.events[eventType]
?: if (EventType.isStateEvent(eventType)) {
powerLevelsContent.stateDefault
} else {
powerLevelsContent.eventsDefault
}
powerLevel >= minimumPowerLevel
} else false
}
/**
* Get the notification level for a dedicated key.
*
* @param key the notification key
* @return the level
*/
fun notificationLevel(key: String): Int {
val value = powerLevelsContent.notifications[key]
?: return PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL
return when (value) {
// the first implementation was a string value
is String -> value.toInt()
is Int -> value
else -> PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL
}
}
}

View file

@ -16,8 +16,10 @@
package im.vector.matrix.android.api.session.room.state
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Optional
interface StateService {
@ -27,4 +29,6 @@ interface StateService {
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
fun getStateEvent(eventType: String): Event?
fun getStateEventLive(eventType: String): LiveData<Optional<Event>>
}

View file

@ -18,7 +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.RoomMemberSummary
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
@ -147,4 +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)
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)

View file

@ -27,6 +27,14 @@ data class Optional<T : Any> constructor(private val value: T?) {
return value
}
fun <U : Any> map(fn: (T) -> U?): Optional<U> {
return if (value == null) {
from(null)
} else {
from(fn(value))
}
}
fun getOrElse(fn: () -> T): T {
return value ?: fn()
}

View file

@ -38,7 +38,7 @@ 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.RoomHistoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
@ -64,7 +64,7 @@ import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -697,9 +697,9 @@ internal class DefaultCryptoService @Inject constructor(
&& shouldEncryptForInvitedMembers(roomId)
userIds = if (encryptForInvitedMembers) {
RoomMembers(realm, roomId).getActiveRoomMemberIds()
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
} else {
RoomMembers(realm, roomId).getJoinedRoomMemberIds()
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
}
}
return userIds
@ -722,7 +722,7 @@ internal class DefaultCryptoService @Inject constructor(
return
}
event.stateKey?.let { userId ->
val roomMember: RoomMember? = event.content.toModel()
val roomMember: RoomMemberSummary? = event.content.toModel()
val membership = roomMember?.membership
if (membership == Membership.JOIN) {
// make sure we are tracking the deviceList for this user.

View file

@ -24,7 +24,7 @@ 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.fastContains
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
@ -59,7 +59,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
val eventEntity = event.toEntity(roomId).apply {
this.sendState = SendState.UNSENT
}
val roomMembers = RoomMembers(realm, roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
val myUser = roomMembers.getLastRoomMember(senderId)
val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also {

View file

@ -26,7 +26,7 @@ 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 im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import io.realm.RealmList
import io.realm.RealmQuery
import javax.inject.Inject
@ -128,7 +128,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
result.senderAvatar = it.avatarUrl
result.senderName = it.displayName
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
result.isUniqueDisplayName = RoomMemberHelper(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) {
@ -138,7 +138,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
}
if (result.senderName == null && it.displayName != null) {
result.senderName = it.displayName
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
}
}
}

View file

@ -16,21 +16,21 @@
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
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
internal object RoomMemberMapper {
internal object RoomMemberSummaryMapper {
fun map(roomMemberEntity: RoomMemberEntity): RoomMember {
return RoomMember(
userId = roomMemberEntity.userId,
avatarUrl = roomMemberEntity.avatarUrl,
displayName = roomMemberEntity.displayName,
membership = roomMemberEntity.membership
fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
return RoomMemberSummary(
userId = roomMemberSummaryEntity.userId,
avatarUrl = roomMemberSummaryEntity.avatarUrl,
displayName = roomMemberSummaryEntity.displayName,
membership = roomMemberSummaryEntity.membership
)
}
}
internal fun RoomMemberEntity.asDomain(): RoomMember {
return RoomMemberMapper.map(this)
internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
return RoomMemberSummaryMapper.map(this)
}

View file

@ -21,13 +21,13 @@ 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? = null,
var avatarUrl: String? = null ,
var reason: String? = null,
var isDirect: Boolean = false
internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "",
@Index var userId: String = "",
@Index var roomId: String = "",
var displayName: String? = null,
var avatarUrl: String? = null,
var reason: String? = null,
var isDirect: Boolean = false
) : RealmObject() {
private var membershipStr: String = Membership.NONE.name

View file

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

View file

@ -60,20 +60,7 @@ internal fun EventEntity.Companion.types(realm: Realm,
return query
}
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
if (from != null) {
if (strict) {
this.greaterThan(EventEntityFields.STATE_INDEX, from)
} else {
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
}
}
return this
.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
.findFirst()
}
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
internal fun RealmQuery<EventEntity>.descending(since: Int? = null, strict: Boolean = false): RealmQuery<EventEntity> {
if (since != null) {
if (strict) {
this.lessThan(EventEntityFields.STATE_INDEX, since)
@ -81,9 +68,26 @@ internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean =
this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, since)
}
}
return this
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.findFirst()
return this.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
}
internal fun RealmQuery<EventEntity>.ascending(from: Int? = null, strict: Boolean = true): RealmQuery<EventEntity> {
if (from != null) {
if (strict) {
this.greaterThan(EventEntityFields.STATE_INDEX, from)
} else {
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
}
}
return this.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
}
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
return this.ascending(from, strict).findFirst()
}
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
return descending(since, strict).findFirst()
}
internal fun RealmList<EventEntity>.find(eventId: String): EventEntity? {

View file

@ -16,19 +16,19 @@
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.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
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> {
internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberSummaryEntity> {
val query = realm
.where<RoomMemberEntity>()
.equalTo(RoomMemberEntityFields.ROOM_ID, roomId)
.where<RoomMemberSummaryEntity>()
.equalTo(RoomMemberSummaryEntityFields.ROOM_ID, roomId)
if (userId != null) {
query.equalTo(RoomMemberEntityFields.USER_ID, userId)
query.equalTo(RoomMemberSummaryEntityFields.USER_ID, userId)
}
return query
}

View file

@ -22,11 +22,11 @@ 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.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
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
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import javax.inject.Inject
internal class RoomAvatarResolver @Inject constructor(private val monarchy: Monarchy,
@ -45,13 +45,13 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
val roomMembers = RoomMembers(realm, roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
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()?.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.where().notEqualTo(RoomMemberEntityFields.USER_ID, userId).findFirst()
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
res = firstOtherMember?.avatarUrl
}
}

View file

@ -35,7 +35,7 @@ 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
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
import io.realm.Realm
@ -114,9 +114,9 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
roomSummaryEntity.isEncrypted = encryptionEvent != null
if (updateMembers) {
val otherRoomMembers = RoomMembers(realm, roomId)
val otherRoomMembers = RoomMemberHelper(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
.findAll()
.asSequence()
.map { it.userId }

View file

@ -24,11 +24,11 @@ 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.session.room.model.RoomMemberSummary
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.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
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
@ -62,14 +62,14 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
.executeBy(taskExecutor)
}
override fun getRoomMember(userId: String): RoomMember? {
override fun getRoomMember(userId: String): RoomMemberSummary? {
val roomMemberEntity = monarchy.fetchCopied {
RoomMembers(it, roomId).getLastRoomMember(userId)
RoomMemberHelper(it, roomId).getLastRoomMember(userId)
}
return roomMemberEntity?.asDomain()
}
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember> {
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMemberSummary> {
return monarchy.fetchAllMappedSync(
{
roomMembersQuery(it, queryParams)
@ -80,7 +80,7 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
)
}
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>> {
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>> {
return monarchy.findAllMappedWithChanges(
{
roomMembersQuery(it, queryParams)
@ -91,15 +91,15 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
)
}
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberEntity> {
return RoomMembers(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.process(RoomMemberEntityFields.DISPLAY_NAME, queryParams.displayName)
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
}
override fun getNumberOfJoinedMembers(): Int {
return Realm.getInstance(monarchy.realmConfiguration).use {
RoomMembers(it, roomId).getNumberOfJoinedMembers()
RoomMemberHelper(it, roomId).getNumberOfJoinedMembers()
}
}

View file

@ -26,7 +26,7 @@ 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.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
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,7 +75,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
return@doWithRealm
}
val roomMembers = RoomMembers(realm, roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll()
if (roomEntity?.membership == Membership.INVITE) {
@ -83,7 +83,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
val inviterId = inviteMeEvent?.sender
name = if (inviterId != null) {
activeMembers.where()
.equalTo(RoomMemberEntityFields.USER_ID, inviterId)
.equalTo(RoomMemberSummaryEntityFields.USER_ID, inviterId)
.findFirst()
?.displayName
} else {
@ -91,7 +91,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
} else if (roomEntity?.membership == Membership.JOIN) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val otherMembersSubset: List<RoomMemberEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes.mapNotNull { userId ->
roomMembers.getLastRoomMember(userId)?.takeIf {
it.membership == Membership.INVITE || it.membership == Membership.JOIN
@ -99,7 +99,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
} else {
activeMembers.where()
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
.limit(3)
.findAll()
.createSnapshot()
@ -123,14 +123,14 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
return name ?: roomId
}
private fun resolveRoomMemberName(roomMember: RoomMemberEntity?,
roomMembers: RoomMembers): String? {
if (roomMember == null) return null
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?,
roomMemberHelper: RoomMemberHelper): String? {
if (roomMemberSummary == null) return null
val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName)
return if (isUnique) {
roomMember.displayName
roomMemberSummary.displayName
} else {
"${roomMember.displayName} (${roomMember.userId})"
"${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
}
}
}

View file

@ -17,13 +17,13 @@
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
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
internal object RoomMemberEntityFactory {
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberEntity {
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity {
val primaryKey = "${roomId}_$userId"
return RoomMemberEntity(
return RoomMemberSummaryEntity(
primaryKey = primaryKey,
userId = userId,
roomId = roomId,

View file

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.Membership
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
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
@ -32,8 +32,8 @@ import io.realm.Sort
* It allows to get the live membership of a user.
*/
internal class RoomMembers(private val realm: Realm,
private val roomId: String
internal class RoomMemberHelper(private val realm: Realm,
private val roomId: String
) {
private val roomSummary: RoomSummaryEntity? by lazy {
@ -48,8 +48,8 @@ internal class RoomMembers(private val realm: Realm,
.findFirst()
}
fun getLastRoomMember(userId: String): RoomMemberEntity? {
return RoomMemberEntity
fun getLastRoomMember(userId: String): RoomMemberSummaryEntity? {
return RoomMemberSummaryEntity
.where(realm, roomId, userId)
.findFirst()
}
@ -66,26 +66,26 @@ internal class RoomMembers(private val realm: Realm,
.size == 1
}
fun queryRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
return RoomMemberEntity.where(realm, roomId)
fun queryRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return RoomMemberSummaryEntity.where(realm, roomId)
}
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return queryRoomMembersEvent()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
}
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return queryRoomMembersEvent()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
}
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return queryRoomMembersEvent()
.beginGroup()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
.or()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.endGroup()
}

View file

@ -16,25 +16,34 @@
package im.vector.matrix.android.internal.session.room.state
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
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.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
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.descending
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.SessionDatabase
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 im.vector.matrix.android.internal.util.fetchCopyMap
import io.realm.Realm
import io.realm.RealmConfiguration
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val sendStateTask: SendStateTask
) : StateService {
@ -45,11 +54,21 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
}
override fun getStateEvent(eventType: String): Event? {
return Realm.getInstance(realmConfiguration).use { realm ->
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
EventEntity.where(realm, roomId, eventType).prev()?.asDomain()
}
}
override fun getStateEventLive(eventType: String): LiveData<Optional<Event>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm -> EventEntity.where(realm, roomId, eventType).descending()},
{ it.asDomain() }
)
return Transformations.map(liveData) { results ->
results.firstOrNull().toOptional()
}
}
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
val params = SendStateTask.Params(roomId,
EventType.STATE_ROOM_TOPIC,

View file

@ -28,7 +28,7 @@ import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
@ -69,7 +69,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
var hasUpdate = false
monarchy.doWithRealm { realm ->
invites.forEach { (roomId, _) ->
val myUserStateEvent = RoomMembers(realm, roomId).getLastStateEvent(userId)
val myUserStateEvent = RoomMemberHelper(realm, roomId).getLastStateEvent(userId)
val inviterId = myUserStateEvent?.sender
val myUserRoomMember: RoomMemberContent? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
val isDirect = myUserRoomMember?.isDirect

View file

@ -0,0 +1,55 @@
/*
* 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.riotx.core.epoxy.profiles
import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyController
import im.vector.riotx.core.epoxy.DividerItem_
fun EpoxyController.buildProfileSection(title: String) {
profileItemSection {
id("section_$title")
title(title)
}
}
fun EpoxyController.buildProfileAction(
id: String,
title: String,
subtitle: String? = null,
@DrawableRes icon: Int = 0,
destructive: Boolean = false,
divider: Boolean = true,
action: () -> Unit
) {
profileItemAction {
iconRes(icon)
id("action_$id")
subtitle(subtitle)
destructive(destructive)
title(title)
listener { _ ->
action()
}
}
DividerItem_()
.id("divider_$title")
.addIf(divider, this)
}

View file

@ -17,20 +17,20 @@
package im.vector.riotx.features.autocomplete.member
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
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 AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMember>>() {
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMemberSummary>>() {
var listener: AutocompleteClickListener<RoomMember>? = null
var listener: AutocompleteClickListener<RoomMemberSummary>? = null
@Inject lateinit var avatarRenderer: AvatarRenderer
override fun buildModels(data: List<RoomMember>?) {
override fun buildModels(data: List<RoomMemberSummary>?) {
if (data.isNullOrEmpty()) {
return
}

View file

@ -25,14 +25,14 @@ 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.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
@Assisted val roomId: String,
private val session: Session,
private val controller: AutocompleteMemberController
) : RecyclerViewPresenter<RoomMember>(context), AutocompleteClickListener<RoomMember> {
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
private val room = session.getRoom(roomId)!!
@ -51,7 +51,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
return controller.adapter
}
override fun onItemClick(t: RoomMember) {
override fun onItemClick(t: RoomMemberSummary) {
dispatchClick(t)
}

View file

@ -27,7 +27,7 @@ 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.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
@ -108,13 +108,13 @@ class AutoCompleter @AssistedInject constructor(
private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
val autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId)
Autocomplete.on<RoomMember>(editText)
Autocomplete.on<RoomMemberSummary>(editText)
.with(CharPolicy('@', true))
.with(autocompleteMemberPresenter)
.with(ELEVATION)
.with(backgroundDrawable)
.with(object : AutocompleteCallback<RoomMember> {
override fun onPopupItemClicked(editable: Editable, item: RoomMember): Boolean {
.with(object : AutocompleteCallback<RoomMemberSummary> {
override fun onPopupItemClicked(editable: Editable, item: RoomMemberSummary): Boolean {
insertMatrixItem(editText, editable, "@", item.toMatrixItem())
return true
}

View file

@ -40,7 +40,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
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.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
@ -210,7 +210,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
invisibleEventsObservable.accept(action)
}
fun getMember(userId: String): RoomMember? {
fun getMember(userId: String): RoomMemberSummary? {
return room.getRoomMember(userId)
}

View file

@ -20,6 +20,7 @@ import com.airbnb.mvrx.Async
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.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.sync.SyncState

View file

@ -23,6 +23,8 @@ import com.airbnb.epoxy.TypedEpoxyController
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.DividerItem_
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
import im.vector.riotx.core.epoxy.profiles.profileItemAction
import im.vector.riotx.core.epoxy.profiles.profileItemSection
import im.vector.riotx.core.resources.StringProvider
@ -50,13 +52,13 @@ class RoomProfileController @Inject constructor(private val stringProvider: Stri
val roomSummary = data.roomSummary()
// Security
buildSection(stringProvider.getString(R.string.room_profile_section_security))
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
val learnMoreSubtitle = if (data.isEncrypted) {
R.string.room_profile_encrypted_subtitle
} else {
R.string.room_profile_not_encrypted_subtitle
}
buildAction(
buildProfileAction(
id = "learn_more",
title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
subtitle = stringProvider.getString(learnMoreSubtitle),
@ -64,33 +66,33 @@ class RoomProfileController @Inject constructor(private val stringProvider: Stri
)
// More
buildSection(stringProvider.getString(R.string.room_profile_section_more))
buildAction(
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(
id = "settings",
title = stringProvider.getString(R.string.room_profile_section_more_settings),
icon = R.drawable.ic_room_profile_settings,
action = { callback?.onSettingsClicked() }
)
buildAction(
buildProfileAction(
id = "notifications",
title = stringProvider.getString(R.string.room_profile_section_more_notifications),
icon = R.drawable.ic_room_profile_notification,
action = { callback?.onNotificationsClicked() }
)
val numberOfMembers = roomSummary?.joinedMembersCount?.toString() ?: "-"
buildAction(
buildProfileAction(
id = "member_list",
title = stringProvider.getString(R.string.room_profile_section_more_member_list, numberOfMembers),
icon = R.drawable.ic_room_profile_member_list,
action = { callback?.onMemberListClicked() }
)
buildAction(
buildProfileAction(
id = "uploads",
title = stringProvider.getString(R.string.room_profile_section_more_uploads),
icon = R.drawable.ic_room_profile_uploads,
action = { callback?.onUploadsClicked() }
)
buildAction(
buildProfileAction(
id = "leave",
title = stringProvider.getString(R.string.room_profile_section_more_leave),
divider = false,
@ -99,38 +101,6 @@ class RoomProfileController @Inject constructor(private val stringProvider: Stri
)
}
private fun buildSection(title: String) {
profileItemSection {
id("section_$title")
title(title)
}
}
private fun buildAction(
id: String,
title: String,
subtitle: String? = null,
@DrawableRes icon: Int = 0,
destructive: Boolean = false,
divider: Boolean = true,
action: () -> Unit
) {
profileItemAction {
iconRes(icon)
id("action_$id")
subtitle(subtitle)
destructive(destructive)
title(title)
listener { _ ->
action()
}
}
DividerItem_()
.id("divider_$title")
.addIf(divider, this)
}
}

View file

@ -17,33 +17,48 @@
package im.vector.riotx.features.roomprofile.members
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.epoxy.profiles.profileItemAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
import im.vector.riotx.core.epoxy.profiles.profileMatrixItem
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
class RoomMemberListController @Inject constructor(private val avatarRenderer: AvatarRenderer) : TypedEpoxyController<RoomMemberListViewState>() {
class RoomMemberListController @Inject constructor(private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider) : TypedEpoxyController<RoomMemberListViewState>() {
interface Callback {
fun onRoomMemberClicked(roomMember: RoomMember)
fun onRoomMemberClicked(roomMember: RoomMemberSummary)
}
var callback: Callback? = null
init {
setData(null)
}
override fun buildModels(data: RoomMemberListViewState?) {
data?.roomMembers?.invoke()?.forEach { roomMember ->
profileMatrixItem {
id(roomMember.userId)
matrixItem(roomMember.toMatrixItem())
avatarRenderer(avatarRenderer)
clickListener { _ ->
callback?.onRoomMemberClicked(roomMember)
val roomMembersByPowerLevel = data?.roomMemberSummaries?.invoke() ?: return
for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) {
if (roomMemberList.isEmpty()) {
continue
}
buildProfileSection(
stringProvider.getString(powerLevelCategory.titleRes)
)
roomMemberList.forEach { roomMember ->
profileMatrixItem {
id(roomMember.userId)
matrixItem(roomMember.toMatrixItem())
avatarRenderer(avatarRenderer)
clickListener { _ ->
callback?.onRoomMemberClicked(roomMember)
}
}
}
}
}
}

View file

@ -21,7 +21,7 @@ import android.view.View
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
@ -63,7 +63,7 @@ class RoomMemberListFragment @Inject constructor(
renderRoomSummary(viewState)
}
override fun onRoomMemberClicked(roomMember: RoomMember) {
override fun onRoomMemberClicked(roomMember: RoomMemberSummary) {
navigator.openRoomMemberProfile(roomMember.userId, roomId = roomProfileArgs.roomId, context = requireActivity())
}

View file

@ -23,14 +23,20 @@ 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.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
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.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsConstants
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsHelper
import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.features.roomprofile.RoomProfileViewEvents
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
private val session: Session)
@ -53,10 +59,32 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomMembers()
observeRoomMemberSummaries()
observeRoomSummary()
}
private fun observeRoomMemberSummaries() {
val roomMemberQueryParams = roomMemberQueryParams {
displayName = QueryStringValue.IsNotEmpty
memberships = Membership.activeMemberships()
}
Observable
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
room.rx().liveRoomMembers(roomMemberQueryParams),
room.rx()
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
.mapOptional { it.content.toModel<PowerLevelsContent>() }
.unwrap(),
BiFunction { roomMembers, powerLevelsContent ->
buildRoomMemberSummaries(powerLevelsContent, roomMembers)
}
)
.execute { async ->
copy(roomMemberSummaries = async)
}
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
@ -65,16 +93,33 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
}
}
private fun observeRoomMembers() {
val queryParams = roomMemberQueryParams {
displayName = QueryStringValue.IsNotEmpty
memberships = Membership.activeMemberships()
}
room.rx()
.liveRoomMembers(queryParams)
.execute {
copy(roomMembers = it)
private fun buildRoomMemberSummaries(powerLevelsContent: PowerLevelsContent, roomMembers: List<RoomMemberSummary>): RoomMemberSummaries {
val admins = ArrayList<RoomMemberSummary>()
val moderators = ArrayList<RoomMemberSummary>()
val users = ArrayList<RoomMemberSummary>(roomMembers.size)
val customs = ArrayList<RoomMemberSummary>()
val invites = ArrayList<RoomMemberSummary>()
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
roomMembers
.forEach { roomMember ->
val memberPowerLevel = powerLevelsHelper.getUserPowerLevel(roomMember.userId)
when {
roomMember.membership == Membership.INVITE -> invites.add(roomMember)
memberPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL -> admins.add(roomMember)
memberPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL -> moderators.add(roomMember)
memberPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL -> users.add(roomMember)
else -> customs.add(roomMember)
}
}
return mapOf(
PowerLevelCategory.ADMIN to admins,
PowerLevelCategory.MODERATOR to moderators,
PowerLevelCategory.CUSTOM to customs,
PowerLevelCategory.INVITE to invites,
PowerLevelCategory.USER to users
)
}
override fun handle(action: RoomMemberListAction) {

View file

@ -16,19 +16,31 @@
package im.vector.riotx.features.roomprofile.members
import androidx.annotation.StringRes
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.R
import im.vector.riotx.features.roomprofile.RoomProfileArgs
data class RoomMemberListViewState(
val roomId: String,
val roomSummary: Async<RoomSummary> = Uninitialized,
val roomMembers: Async<List<RoomMember>> = Uninitialized
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}
typealias RoomMemberSummaries = Map<PowerLevelCategory, List<RoomMemberSummary>>
enum class PowerLevelCategory(@StringRes val titleRes: Int) {
ADMIN(R.string.room_member_power_level_admins),
MODERATOR(R.string.room_member_power_level_moderators),
CUSTOM(R.string.room_member_power_level_custom),
INVITE(R.string.room_member_power_level_invites),
USER(R.string.room_member_power_level_users)
}

View file

@ -17,7 +17,7 @@
style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent"
app:contentScrim="?riotx_background"
app:scrimAnimationDuration="400"
app:scrimAnimationDuration="250"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:titleEnabled="false"

View file

@ -35,4 +35,10 @@
<string name="room_profile_section_more_leave">Leave Room</string>
<string name="room_profile_leaving_room">"Leaving the room..."</string>
<string name="room_member_power_level_admins">Admins</string>
<string name="room_member_power_level_moderators">Moderators</string>
<string name="room_member_power_level_custom">Custom</string>
<string name="room_member_power_level_invites">Invites</string>
<string name="room_member_power_level_users">Users</string>
</resources>