feature(room preview): Add option to forget room, improve the room preview screen for banned rooms.

Some internal refactoring was done too:
- Remove RoomInfo.isPublic to only use JoinRule.
- Also take into account restricted access rooms for previews.
This commit is contained in:
ganfra 2025-01-10 09:52:02 +01:00 committed by Jorge Martin Espinosa
parent 819503b162
commit a73bcb71d5
50 changed files with 886 additions and 357 deletions

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.DeviceId
@ -32,9 +33,9 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.PendingRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomPreview
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
@ -50,6 +51,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService
import io.element.android.libraries.matrix.impl.exception.mapClientException
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
@ -261,8 +263,8 @@ class RustMatrixClient(
return roomFactory.create(roomId)
}
override suspend fun getPendingRoom(roomId: RoomId): PendingRoom? {
return roomFactory.createPendingRoom(roomId)
override suspend fun getPendingRoom(roomId: RoomId): RoomPreview? {
return roomFactory.createRoomPreview(roomId)
}
/**
@ -393,7 +395,7 @@ class RustMatrixClient(
null
}
}
}
}.mapFailure { it.mapClientException() }
override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?> = withContext(sessionDispatcher) {
runCatching {
@ -407,7 +409,7 @@ class RustMatrixClient(
Timber.e(e, "Timeout waiting for the room to be available in the room list")
null
}
}
}.mapFailure { it.mapClientException() }
}
override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomSummary?> = withContext(
@ -421,7 +423,7 @@ class RustMatrixClient(
Timber.e(e, "Timeout waiting for the room to be available in the room list")
null
}
}
}.mapFailure { it.mapClientException() }
}
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
@ -456,7 +458,7 @@ class RustMatrixClient(
}.use { roomPreview ->
RoomPreviewInfoMapper.map(roomPreview.info())
}
}
}.mapFailure { it.mapClientException() }
}
override fun syncService(): SyncService = rustSyncService

View file

@ -37,7 +37,6 @@ class MatrixRoomInfoMapper {
topic = it.topic,
avatarUrl = it.avatarUrl,
isDirect = it.isDirect,
isPublic = it.isPublic,
joinRule = it.joinRule?.map(),
isSpace = it.isSpace,
isTombstoned = it.isTombstoned,

View file

@ -16,8 +16,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.PendingRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomPreview
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
@ -28,7 +28,6 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Membership
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListException
import org.matrix.rustcomponents.sdk.RoomListItem
@ -36,7 +35,6 @@ import timber.log.Timber
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
private const val CACHE_SIZE = 16
private val PENDING_MEMBERSHIPS = setOf(Membership.INVITED, Membership.KNOCKED)
class RustRoomFactory(
private val sessionId: SessionId,
@ -125,7 +123,7 @@ class RustRoomFactory(
}
}
suspend fun createPendingRoom(roomId: RoomId): PendingRoom? = withContext(dispatcher) {
suspend fun createRoomPreview(roomId: RoomId): RoomPreview? = withContext(dispatcher) {
if (isDestroyed) {
Timber.d("Room factory is destroyed, returning null for $roomId")
return@withContext null
@ -135,17 +133,17 @@ class RustRoomFactory(
Timber.d("Room not found for $roomId")
return@withContext null
}
if (roomListItem.membership() !in PENDING_MEMBERSHIPS) {
Timber.d("Room $roomId is not in pending state")
if (roomListItem.membership() !in RustRoomPreview.ALLOWED_MEMBERSHIPS) {
Timber.d("Room $roomId is not in allowed membership")
return@withContext null
}
val innerRoom = try {
roomListItem.previewRoom(via = emptyList())
} catch (e: Exception) {
Timber.e(e, "Failed to get pending room for $roomId")
Timber.e(e, "Failed to get room preview for $roomId")
return@withContext null
}
RustPendingRoom(
RustRoomPreview(
sessionId = sessionId,
roomId = roomId,
inner = innerRoom,

View file

@ -9,22 +9,31 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.room.PendingRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import org.matrix.rustcomponents.sdk.RoomPreview
import io.element.android.libraries.matrix.api.room.RoomPreview
import org.matrix.rustcomponents.sdk.Membership
import org.matrix.rustcomponents.sdk.RoomPreview as InnerRoomPreview
class RustPendingRoom(
class RustRoomPreview(
override val sessionId: SessionId,
override val roomId: RoomId,
private val inner: RoomPreview,
private val inner: InnerRoomPreview,
private val roomMembershipObserver: RoomMembershipObserver,
) : PendingRoom {
) : RoomPreview {
companion object {
val ALLOWED_MEMBERSHIPS = setOf(Membership.INVITED, Membership.KNOCKED, Membership.BANNED)
}
override suspend fun leave(): Result<Unit> = runCatching {
inner.leave()
}.onSuccess {
roomMembershipObserver.notifyUserLeftRoom(roomId)
}
override suspend fun forget(): Result<Unit> = runCatching {
inner.forget()
}
override fun close() {
inner.destroy()
}

View file

@ -25,6 +25,7 @@ object RoomMemberMapper {
normalizedPowerLevel = roomMember.normalizedPowerLevel,
isIgnored = roomMember.isIgnored,
role = mapRole(roomMember.suggestedRoleForPowerLevel),
membershipChangeReason = roomMember.membershipChangeReason
)
fun mapRole(role: RoomMemberRole): RoomMember.Role =

View file

@ -11,9 +11,8 @@ import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.libraries.matrix.impl.room.join.map
import io.element.android.libraries.matrix.impl.room.map
import org.matrix.rustcomponents.sdk.JoinRule
import org.matrix.rustcomponents.sdk.Membership
import org.matrix.rustcomponents.sdk.RoomPreviewInfo as RustRoomPreviewInfo
object RoomPreviewInfoMapper {
@ -27,10 +26,8 @@ object RoomPreviewInfoMapper {
numberOfJoinedMembers = info.numJoinedMembers.toLong(),
roomType = info.roomType.map(),
isHistoryWorldReadable = info.isHistoryWorldReadable.orFalse(),
isJoined = info.membership == Membership.JOINED,
isInvited = info.membership == Membership.INVITED,
isPublic = info.joinRule == JoinRule.Public,
canKnock = info.joinRule == JoinRule.Knock
membership = info.membership?.map(),
joinRule = info.joinRule.map(),
)
}
}

View file

@ -85,7 +85,6 @@ class MatrixRoomInfoMapperTest {
topic = "topic",
avatarUrl = AN_AVATAR_URL,
isDirect = true,
isPublic = false,
isSpace = false,
isTombstoned = false,
isFavorite = false,
@ -167,7 +166,6 @@ class MatrixRoomInfoMapperTest {
topic = null,
avatarUrl = null,
isDirect = false,
isPublic = true,
joinRule = null,
isSpace = false,
isTombstoned = false,

View file

@ -8,14 +8,16 @@
package io.element.android.libraries.matrix.impl.room.preview
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomPreviewInfo
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
import io.element.android.libraries.matrix.test.A_ROOM_ID
import org.junit.Test
import org.matrix.rustcomponents.sdk.JoinRule
import org.matrix.rustcomponents.sdk.Membership
import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule
class RoomPreviewInfoMapperTest {
@Test
@ -23,7 +25,7 @@ class RoomPreviewInfoMapperTest {
assertThat(
RoomPreviewInfoMapper.map(
info = aRustRoomPreviewInfo(
membership = null,
membership = Membership.JOINED,
)
)
).isEqualTo(
@ -36,10 +38,8 @@ class RoomPreviewInfoMapperTest {
numberOfJoinedMembers = 1L,
roomType = RoomType.Room,
isHistoryWorldReadable = true,
isJoined = false,
isInvited = false,
isPublic = true,
canKnock = false,
membership = CurrentUserMembership.JOINED,
joinRule = JoinRule.Public,
)
)
}
@ -51,7 +51,7 @@ class RoomPreviewInfoMapperTest {
info = aRustRoomPreviewInfo(
canonicalAlias = null,
membership = Membership.JOINED,
joinRule = JoinRule.Knock,
joinRule = RustJoinRule.Knock,
)
)
).isEqualTo(
@ -64,10 +64,8 @@ class RoomPreviewInfoMapperTest {
numberOfJoinedMembers = 1L,
roomType = RoomType.Room,
isHistoryWorldReadable = true,
isJoined = true,
isInvited = false,
isPublic = false,
canKnock = true,
membership = CurrentUserMembership.JOINED,
joinRule = JoinRule.Knock,
)
)
}