Adapt to new DM definition changes in the SDK (#6748)

* Set `DmRoomDefinition.TwoPeople` in `ClientBuilder`. This applies the 'direct and with at most 2 non-service members' rule to what the SDK should consider a DM.

* Map `RoomInfo.isDm` from the SDK

* Map `NotificationData.isDm` from `NotificationInfo.roomInfo.isDm`

* Remove `RoomIsDmCheck` file as its extension functions are now redundant. Move `Room.isDm` helper function to `BaseRoom`.

* Map `isDm` in `SpaceRoom` from the SDK too

* Replace `isDirect` with `isDm` where possible

* Map `RoomMember.isServiceMember` from the SDK and use it to tell apart normal members of a room from service members (i.e. `RoomMembersState.getDirectRoomMember`)
This commit is contained in:
Jorge Martin Espinosa 2026-05-11 17:22:16 +02:00 committed by GitHub
parent 5e5e0bbc6e
commit 11476c73cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 115 additions and 232 deletions

View file

@ -41,6 +41,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncVersion
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import uniffi.matrix_sdk_base.DmRoomDefinition
import uniffi.matrix_sdk_base.MediaRetentionPolicy
import uniffi.matrix_sdk_crypto.CollectStrategy
import uniffi.matrix_sdk_crypto.DecryptionSettings
@ -169,6 +170,7 @@ class RustMatrixClientFactory(
)
.enableShareHistoryOnInvite(true)
.threadsEnabled(featureFlagService.isFeatureEnabled(FeatureFlags.Threads), threadSubscriptions = false)
.dmRoomDefinition(DmRoomDefinition.TWO_MEMBERS)
.requestConfig(
RequestConfig(
timeout = 30_000uL,

View file

@ -11,7 +11,6 @@ package io.element.android.libraries.matrix.impl.analytics
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.isDm
import kotlinx.coroutines.flow.first
private fun Long.toAnalyticsRoomSize(): JoinedRoom.RoomSize {

View file

@ -17,7 +17,6 @@ import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.impl.room.join.map
import io.element.android.services.toolbox.api.systemclock.SystemClock
import org.matrix.rustcomponents.sdk.NotificationEvent
@ -37,10 +36,6 @@ class NotificationMapper(
): Result<NotificationData> {
return runCatchingExceptions {
notificationItem.use { item ->
val isDm = isDm(
isDirect = item.roomInfo.isDirect,
activeMembersCount = item.roomInfo.joinedMembersCount.toInt(),
)
val timestamp = item.timestamp() ?: clock.epochMillis()
NotificationData(
sessionId = sessionId,
@ -50,10 +45,10 @@ class NotificationMapper(
senderAvatarUrl = item.senderInfo.avatarUrl,
senderDisplayName = item.senderInfo.displayName,
senderIsNameAmbiguous = item.senderInfo.isNameAmbiguous,
roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { isDm },
roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { item.roomInfo.isDm },
roomDisplayName = item.roomInfo.displayName,
isDirect = item.roomInfo.isDirect,
isDm = isDm,
isDm = item.roomInfo.isDm,
isSpace = item.roomInfo.isSpace,
isEncrypted = item.roomInfo.isEncrypted.orFalse(),
isNoisy = item.isNoisy.orFalse(),

View file

@ -62,11 +62,11 @@ class RustNotificationSettingsService(
override suspend fun setDefaultRoomNotificationMode(
isEncrypted: Boolean,
mode: RoomNotificationMode,
isOneToOne: Boolean
isDM: Boolean
): Result<Unit> = withContext(dispatchers.io) {
runCatchingExceptions {
try {
notificationSettings.await().setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode))
notificationSettings.await().setDefaultRoomNotificationMode(isEncrypted, isDM, mode.let(RoomNotificationSettingsMapper::mapMode))
} catch (exception: NotificationSettingsException.RuleNotFound) {
// `setDefaultRoomNotificationMode` updates multiple rules including unstable rules (e.g. the polls push rules defined in the MSC3930)
// since production home servers may not have these rules yet, we drop the RuleNotFound error

View file

@ -348,7 +348,7 @@ class JoinedRustRoom(
roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings)
runCatchingExceptions {
val isEncrypted = roomInfoFlow.value.isEncrypted ?: getUpdatedIsEncrypted().getOrThrow()
notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
notificationSettingsService.getRoomNotificationSettings(roomId = roomId, isEncrypted = isEncrypted, isOneToOne = isDm()).getOrThrow()
}.map {
roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Ready(it)
}.onFailure {

View file

@ -18,7 +18,7 @@ import org.matrix.rustcomponents.sdk.RoomInfo
*/
fun RoomInfo.elementHeroes(): List<MatrixUser> {
return heroes
.takeIf { isDirect && activeMembersCount.toLong() == 2L }
.takeIf { isDm }
?.takeIf { it.size == 1 }
?.map { it.map() }
.orEmpty()

View file

@ -43,6 +43,7 @@ class RoomInfoMapper {
avatarUrl = it.avatarUrl,
isPublic = it.isPublic,
isDirect = it.isDirect,
isDm = it.isDm,
isEncrypted = when (it.encryptionState) {
EncryptionState.ENCRYPTED -> true
EncryptionState.NOT_ENCRYPTED -> false

View file

@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
@ -119,7 +118,7 @@ class RustBaseRoom(
innerRoom.membersNoSync().use { members ->
members.nextChunk(members.len())
?.map(RoomMemberMapper::map)
?.firstOrNull { roomMember -> roomMember.userId != sessionId && roomMember.membership.isActive() }
?.firstOrNull { roomMember -> !roomMember.isServiceMember && roomMember.userId != sessionId && roomMember.membership.isActive() }
}
} else {
null

View file

@ -28,7 +28,8 @@ object RoomMemberMapper {
powerLevel = powerLevel,
isIgnored = roomMember.isIgnored,
role = mapRole(roomMember.suggestedRoleForPowerLevel, powerLevel),
membershipChangeReason = roomMember.membershipChangeReason
membershipChangeReason = roomMember.membershipChangeReason,
isServiceMember = roomMember.isServiceMember,
)
}

View file

@ -36,6 +36,7 @@ class SpaceRoomMapper {
worldReadable = spaceRoom.worldReadable.orFalse(),
via = spaceRoom.via.toImmutableList(),
isDirect = spaceRoom.isDirect,
isDm = spaceRoom.isDm,
)
}
}

View file

@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.MsgType

View file

@ -43,6 +43,19 @@ class JoinedExtKtTest {
@Test
fun `test isDirect parameter mapping`() = runTest {
assertThat(aRoom(isDirect = true).toAnalyticsJoinedRoom(null))
.isEqualTo(
JoinedRoom(
isDM = false,
isSpace = false,
roomSize = JoinedRoom.RoomSize.One,
trigger = null
)
)
}
@Test
fun `test isDm parameter mapping`() = runTest {
assertThat(aRoom(isDm = true).toAnalyticsJoinedRoom(null))
.isEqualTo(
JoinedRoom(
isDM = true,
@ -80,12 +93,13 @@ class JoinedExtKtTest {
}
private fun aRoom(
isDm: Boolean = false,
isDirect: Boolean = false,
isSpace: Boolean = false,
joinedMemberCount: Long = 0
): FakeBaseRoom {
return FakeBaseRoom().apply {
givenRoomInfo(aRoomInfo(isDirect = isDirect, isSpace = isSpace, joinedMembersCount = joinedMemberCount))
givenRoomInfo(aRoomInfo(isDm = isDm, isDirect = isDirect, isSpace = isSpace, joinedMembersCount = joinedMemberCount))
}
}
}

View file

@ -50,5 +50,5 @@ internal fun aRustSpaceRoom(
childrenCount = childrenCount,
state = state,
heroes = heroes,
via = emptyList()
via = emptyList(),
)

View file

@ -17,6 +17,7 @@ import org.matrix.rustcomponents.sdk.RequestConfig
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import org.matrix.rustcomponents.sdk.SqliteStoreBuilder
import uniffi.matrix_sdk.BackupDownloadStrategy
import uniffi.matrix_sdk_base.DmRoomDefinition
import uniffi.matrix_sdk_crypto.CollectStrategy
import uniffi.matrix_sdk_crypto.DecryptionSettings
@ -47,5 +48,6 @@ class FakeFfiClientBuilder(
override fun sqliteStore(config: SqliteStoreBuilder): ClientBuilder = this
override fun inMemoryStore(): ClientBuilder = this
override fun crossProcessLockConfig(crossProcessLockConfig: CrossProcessLockConfig): ClientBuilder = this
override fun dmRoomDefinition(dmRoomDefinition: DmRoomDefinition): ClientBuilder = this
override suspend fun build() = buildResult()
}

View file

@ -20,8 +20,7 @@ class RoomInfoExtTest {
@Test
fun `get non empty element Heroes`() {
val result = aRustRoomInfo(
isDirect = true,
activeMembersCount = 2uL,
isDm = true,
heroes = listOf(aRustRoomHero())
).elementHeroes()
assertThat(result).isEqualTo(
@ -38,8 +37,7 @@ class RoomInfoExtTest {
@Test
fun `too many heroes and element Heroes is empty`() {
val result = aRustRoomInfo(
isDirect = true,
activeMembersCount = 2uL,
isDm = true,
heroes = listOf(aRustRoomHero(), aRustRoomHero())
).elementHeroes()
assertThat(result).isEmpty()
@ -48,18 +46,7 @@ class RoomInfoExtTest {
@Test
fun `not direct and element Heroes is empty`() {
val result = aRustRoomInfo(
isDirect = false,
activeMembersCount = 2uL,
heroes = listOf(aRustRoomHero())
).elementHeroes()
assertThat(result).isEmpty()
}
@Test
fun `too many members and element Heroes is empty`() {
val result = aRustRoomInfo(
isDirect = true,
activeMembersCount = 3uL,
isDm = false,
heroes = listOf(aRustRoomHero())
).elementHeroes()
assertThat(result).isEmpty()

View file

@ -86,6 +86,7 @@ class RoomInfoMapperTest {
privilegedCreatorsRole = true,
isLowPriority = true,
activeRoomCallConsensusIntent = RtcCallIntentConsensus.Full(RtcCallIntent.AUDIO),
isDm = true,
)
)
).isEqualTo(
@ -136,6 +137,7 @@ class RoomInfoMapperTest {
privilegedCreatorRole = true,
isLowPriority = true,
activeCallIntentConsensus = CallIntentConsensus.Full(CallIntent.AUDIO),
isDm = true,
)
)
}
@ -181,6 +183,7 @@ class RoomInfoMapperTest {
privilegedCreatorsRole = true,
isLowPriority = true,
activeRoomCallConsensusIntent = RtcCallIntentConsensus.None,
isDm = false,
)
)
).isEqualTo(
@ -225,6 +228,7 @@ class RoomInfoMapperTest {
privilegedCreatorRole = true,
isLowPriority = true,
activeCallIntentConsensus = CallIntentConsensus.None,
isDm = false,
)
)
}