Skip to content

Commit

Permalink
Merge pull request #8700 from vector-im/feature/fga/handle_functional…
Browse files Browse the repository at this point in the history
…_members

Support Functional members #3736
  • Loading branch information
bmarty authored Dec 6, 2023
2 parents bb9d1fc + bb86660 commit 5e4b8ed
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.d/3736.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support Functional members (https://github.com/vector-im/element-meta/blob/develop/spec/functional_members.md)
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider

class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {

override fun excludedUserIds(roomId: String) = emptyList<String>()

override fun getNameForRoomInvite() =
"Room invite"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ package org.matrix.android.sdk.api.provider
* *Limitation*: if the locale of the device changes, the methods will not be called again.
*/
interface RoomDisplayNameFallbackProvider {
/**
* Return the list of user ids to ignore when computing the room display name.
*/
fun excludedUserIds(roomId: String): List<String>
fun getNameForRoomInvite(): String
fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>): String
fun getNameFor1member(name: String): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.room

import io.realm.Realm
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
Expand All @@ -31,7 +32,12 @@ import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import javax.inject.Inject

internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
internal class RoomAvatarResolver @Inject constructor(
matrixConfiguration: MatrixConfiguration,
@UserId private val userId: String
) {

private val roomDisplayNameFallbackProvider = matrixConfiguration.roomDisplayNameFallbackProvider

/**
* Compute the room avatar url.
Expand All @@ -40,21 +46,26 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
* @return the room avatar url, can be a fallback to a room member avatar or null
*/
fun resolve(realm: Realm, roomId: String): String? {
val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")
val roomAvatarUrl = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")
?.root
?.asDomain()
?.content
?.toModel<RoomAvatarContent>()
?.avatarUrl
if (!roomName.isNullOrEmpty()) {
return roomName
if (!roomAvatarUrl.isNullOrEmpty()) {
return roomAvatarUrl
}
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)
val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect.orFalse()

if (isDirectRoom) {
val excludedUserIds = roomDisplayNameFallbackProvider.excludedUserIds(roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
val members = roomMembers
.queryActiveRoomMembersEvent()
.not().`in`(RoomMemberSummaryEntityFields.USER_ID, excludedUserIds.toTypedArray())
.findAll()

if (members.size == 1) {
// Use avatar of a left user
val firstLeftAvatarUrl = roomMembers.queryLeftRoomMembersEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,20 @@ internal class RoomDisplayNameResolver @Inject constructor(
}
?: roomDisplayNameFallbackProvider.getNameForRoomInvite()
} else if (roomEntity?.membership == Membership.JOIN) {
val excludedUserIds = roomDisplayNameFallbackProvider.excludedUserIds(roomId)
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val invitedCount = roomSummary?.invitedMembersCount ?: 0
val joinedCount = roomSummary?.joinedMembersCount ?: 0
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
(it.membership == Membership.INVITE || it.membership == Membership.JOIN) && !excludedUserIds.contains(it.userId)
}
}
} else {
activeMembers.where()
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
.not().`in`(RoomMemberSummaryEntityFields.USER_ID, excludedUserIds.toTypedArray())
.limit(5)
.findAll()
.createSnapshot()
Expand All @@ -113,6 +115,7 @@ internal class RoomDisplayNameResolver @Inject constructor(
0 -> {
// Get left members if any
val leftMembersNames = roomMembers.queryLeftRoomMembersEvent()
.not().`in`(RoomMemberSummaryEntityFields.USER_ID, excludedUserIds.toTypedArray())
.findAll()
.map { displayNameResolver.getBestName(it.toMatrixItem()) }
val directUserId = roomSummary?.directUserId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
package im.vector.app.core.utils

import androidx.test.platform.app.InstrumentationRegistry
import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.SyncConfig

fun getMatrixInstance(): Matrix {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val configuration = MatrixConfiguration(
roomDisplayNameFallbackProvider = VectorRoomDisplayNameFallbackProvider(context),
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider(),
syncConfig = SyncConfig(longPollTimeout = 5_000L),
)
return Matrix(context, configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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.app.core.utils

import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider

class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {

override fun excludedUserIds(roomId: String) = emptyList<String>()

override fun getNameForRoomInvite() =
"Room invite"

override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>) =
"Empty room"

override fun getNameFor1member(name: String) =
name

override fun getNameFor2members(name1: String, name2: String) =
"$name1 and $name2"

override fun getNameFor3members(name1: String, name2: String, name3: String) =
"$name1, $name2 and $name3"

override fun getNameFor4members(name1: String, name2: String, name3: String, name4: String) =
"$name1, $name2, $name3 and $name4"

override fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int) =
"$name1, $name2, $name3 and $remainingCount others"
}
6 changes: 6 additions & 0 deletions vector-config/src/main/java/im/vector/app/config/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ object Config {
const val ENABLE_LOCATION_SHARING = true
const val LOCATION_MAP_TILER_KEY = "fU3vlMsMn4Jb6dnEIFsx"

/**
* Whether to read the `io.element.functional_members` state event
* and exclude any service members when computing a room's name and avatar.
*/
const val SUPPORT_FUNCTIONAL_MEMBERS = true

/**
* The maximum length of voice messages in milliseconds.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 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.app.features.room

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.state.StateService

private const val FUNCTIONAL_MEMBERS_STATE_EVENT_TYPE = "io.element.functional_members"

@JsonClass(generateAdapter = true)
data class FunctionalMembersContent(
@Json(name = "service_members") val userIds: List<String>? = null
)

fun StateService.getFunctionalMembers(): List<String> {
return getStateEvent(FUNCTIONAL_MEMBERS_STATE_EVENT_TYPE, QueryStringValue.IsEmpty)
?.content
?.toModel<FunctionalMembersContent>()
?.userIds
.orEmpty()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,28 @@ package im.vector.app.features.room

import android.content.Context
import im.vector.app.R
import im.vector.app.config.Config
import im.vector.app.core.di.ActiveSessionHolder
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
import org.matrix.android.sdk.api.session.getRoom
import javax.inject.Inject
import javax.inject.Provider

class VectorRoomDisplayNameFallbackProvider @Inject constructor(
private val context: Context
private val context: Context,
private val activeSessionHolder: Provider<ActiveSessionHolder>,
) : RoomDisplayNameFallbackProvider {

override fun excludedUserIds(roomId: String): List<String> {
if (!Config.SUPPORT_FUNCTIONAL_MEMBERS) return emptyList()
return activeSessionHolder.get()
.getSafeActiveSession()
?.getRoom(roomId)
?.stateService()
?.getFunctionalMembers()
.orEmpty()
}

override fun getNameForRoomInvite(): String {
return context.getString(R.string.room_displayname_room_invite)
}
Expand Down

0 comments on commit 5e4b8ed

Please sign in to comment.