Split MatrixRoom into BaseRoom and JoinedRoom (#4561)
`JoinedRoom` will now contain both a mandatory live timeline reference and all the functionality associated to it. `BaseRoom` on the other hand will contain only functionality that's shared for both joined and not joined rooms. `NotJoinedRoom` is a wrapper around `RoomPreviewInfo` data and a possible local `BaseRoom`, if it exists. The `RustRoomFactory` cache is now gone since the persistent event cache should have the same effect.
This commit is contained in:
parent
91cb84ce8d
commit
619aa6f2de
193 changed files with 2921 additions and 2567 deletions
|
|
@ -30,11 +30,12 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
|
|||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
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.BaseRoom
|
||||
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.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
|
||||
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.roomdirectory.RoomDirectoryService
|
||||
|
|
@ -55,13 +56,15 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer
|
|||
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.impl.oidc.toRustAction
|
||||
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
|
||||
import io.element.android.libraries.matrix.impl.room.GetRoomResult
|
||||
import io.element.android.libraries.matrix.impl.room.NotJoinedRustRoom
|
||||
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
||||
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
|
||||
import io.element.android.libraries.matrix.impl.room.RustRoomFactory
|
||||
import io.element.android.libraries.matrix.impl.room.RustRoomPreview
|
||||
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.room.history.map
|
||||
import io.element.android.libraries.matrix.impl.room.join.map
|
||||
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
||||
import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService
|
||||
import io.element.android.libraries.matrix.impl.roomdirectory.map
|
||||
import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory
|
||||
|
|
@ -186,6 +189,7 @@ class RustMatrixClient(
|
|||
|
||||
private val roomMembershipObserver = RoomMembershipObserver()
|
||||
private val roomFactory = RustRoomFactory(
|
||||
innerClient = innerClient,
|
||||
roomListService = roomListService,
|
||||
innerRoomListService = innerRoomListService,
|
||||
sessionId = sessionId,
|
||||
|
|
@ -263,12 +267,17 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||
return roomFactory.create(roomId)
|
||||
override suspend fun getRoom(roomId: RoomId): BaseRoom? {
|
||||
return roomFactory.getBaseRoom(roomId)
|
||||
}
|
||||
|
||||
override suspend fun getPendingRoom(roomId: RoomId): RoomPreview? {
|
||||
return roomFactory.createRoomPreview(roomId)
|
||||
override suspend fun getJoinedRoom(roomId: RoomId): JoinedRoom? {
|
||||
return try {
|
||||
(roomFactory.getJoinedRoomOrPreview(roomId) as GetRoomResult.Joined).joinedRoom
|
||||
} catch (e: ClassCastException) {
|
||||
Timber.e(e, "Room $roomId is not a joined room")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -455,13 +464,29 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreview> = withContext(sessionDispatcher) {
|
||||
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<NotJoinedRoom> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
val roomPreview = when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> innerClient.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
|
||||
is RoomIdOrAlias.Id -> innerClient.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
|
||||
when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
val roomId = innerClient.resolveRoomAlias(roomIdOrAlias.roomAlias.value)?.roomId?.let { RoomId(it) }
|
||||
|
||||
var room = (roomId?.let { roomFactory.getJoinedRoomOrPreview(it) } as? GetRoomResult.NotJoined)?.notJoinedRoom
|
||||
if (room == null) {
|
||||
val preview = innerClient.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
|
||||
room = NotJoinedRustRoom(sessionId, null, RoomPreviewInfoMapper.map(preview.info()))
|
||||
}
|
||||
room
|
||||
}
|
||||
is RoomIdOrAlias.Id -> {
|
||||
var room = (roomFactory.getJoinedRoomOrPreview(roomIdOrAlias.roomId) as? GetRoomResult.NotJoined)?.notJoinedRoom
|
||||
|
||||
if (room == null) {
|
||||
val preview = innerClient.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
|
||||
room = NotJoinedRustRoom(sessionId, null, RoomPreviewInfoMapper.map(preview.info()))
|
||||
}
|
||||
room
|
||||
}
|
||||
}
|
||||
RustRoomPreview(sessionId, roomPreview, roomMembershipObserver)
|
||||
}.mapFailure { it.mapClientException() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
package io.element.android.libraries.matrix.impl.analytics
|
||||
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ private fun Long.toAnalyticsRoomSize(): JoinedRoom.RoomSize {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun MatrixRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom {
|
||||
suspend fun BaseRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom {
|
||||
val roomInfo = roomInfoFlow.first()
|
||||
return JoinedRoom(
|
||||
isDM = roomInfo.isDm,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
|
|
@ -17,7 +17,6 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback
|
|||
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.core.SendHandle
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
|
||||
|
|
@ -28,55 +27,43 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
|||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
|
||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||
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.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettingsState
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import io.element.android.libraries.matrix.impl.core.RustSendHandle
|
||||
import io.element.android.libraries.matrix.impl.mapper.map
|
||||
import io.element.android.libraries.matrix.impl.room.draft.into
|
||||
import io.element.android.libraries.matrix.impl.room.history.map
|
||||
import io.element.android.libraries.matrix.impl.room.join.map
|
||||
import io.element.android.libraries.matrix.impl.room.knock.RustKnockRequest
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper
|
||||
import io.element.android.libraries.matrix.impl.roomdirectory.map
|
||||
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
|
||||
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
|
||||
import io.element.android.libraries.matrix.impl.util.MessageEventContent
|
||||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
||||
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
|
|
@ -89,7 +76,6 @@ import kotlinx.coroutines.withContext
|
|||
import org.matrix.rustcomponents.sdk.DateDividerMode
|
||||
import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener
|
||||
import org.matrix.rustcomponents.sdk.KnockRequestsListener
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo
|
||||
import org.matrix.rustcomponents.sdk.RoomInfoListener
|
||||
import org.matrix.rustcomponents.sdk.RoomMessageEventMessageType
|
||||
import org.matrix.rustcomponents.sdk.TimelineConfiguration
|
||||
|
|
@ -103,40 +89,36 @@ import org.matrix.rustcomponents.sdk.getElementCallRequiredPermissions
|
|||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk.RoomPowerLevelChanges
|
||||
import uniffi.matrix_sdk_base.EncryptionState
|
||||
import java.io.File
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange
|
||||
import org.matrix.rustcomponents.sdk.KnockRequest as InnerKnockRequest
|
||||
import org.matrix.rustcomponents.sdk.Room as InnerRoom
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo as InnerRoomInfo
|
||||
import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class RustMatrixRoom(
|
||||
override val sessionId: SessionId,
|
||||
private val deviceId: DeviceId,
|
||||
private val innerRoom: InnerRoom,
|
||||
innerTimeline: InnerTimeline,
|
||||
class JoinedRustRoom(
|
||||
private val baseRoom: RustBaseRoom,
|
||||
private val liveInnerTimeline: InnerTimeline,
|
||||
private val notificationSettingsService: NotificationSettingsService,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val roomInfoMapper: RoomInfoMapper,
|
||||
private val systemClock: SystemClock,
|
||||
private val roomContentForwarder: RoomContentForwarder,
|
||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||
private val matrixRoomInfoMapper: MatrixRoomInfoMapper,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
initialRoomInfo: MatrixRoomInfo,
|
||||
) : MatrixRoom {
|
||||
override val roomId = RoomId(innerRoom.id())
|
||||
) : JoinedRoom, BaseRoom by baseRoom {
|
||||
// Create a dispatcher for all room methods...
|
||||
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
|
||||
private val innerRoom = baseRoom.innerRoom
|
||||
|
||||
override val roomInfoFlow: StateFlow<MatrixRoomInfo> = mxCallbackFlow {
|
||||
override val syncUpdateFlow = MutableStateFlow(0L)
|
||||
|
||||
override val roomInfoFlow: StateFlow<io.element.android.libraries.matrix.api.room.RoomInfo> = mxCallbackFlow {
|
||||
innerRoom.subscribeToRoomInfoUpdates(object : RoomInfoListener {
|
||||
override fun call(roomInfo: RoomInfo) {
|
||||
channel.trySend(matrixRoomInfoMapper.map(roomInfo))
|
||||
override fun call(roomInfo: InnerRoomInfo) {
|
||||
channel.trySend(roomInfoMapper.map(roomInfo))
|
||||
}
|
||||
})
|
||||
}.stateIn(sessionCoroutineScope, started = SharingStarted.Lazily, initialValue = initialRoomInfo)
|
||||
}.stateIn(roomCoroutineScope, started = SharingStarted.Lazily, initialValue = baseRoom.info())
|
||||
|
||||
override val roomTypingMembersFlow: Flow<List<UserId>> = mxCallbackFlow {
|
||||
val initial = emptyList<UserId>()
|
||||
|
|
@ -178,27 +160,12 @@ class RustMatrixRoom(
|
|||
})
|
||||
}
|
||||
|
||||
// Create a dispatcher for all room methods...
|
||||
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
|
||||
override val roomNotificationSettingsStateFlow = MutableStateFlow<RoomNotificationSettingsState>(RoomNotificationSettingsState.Unknown)
|
||||
|
||||
// ...except getMember methods as it could quickly fill the roomDispatcher...
|
||||
private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8)
|
||||
|
||||
override val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId")
|
||||
private val _syncUpdateFlow = MutableStateFlow(0L)
|
||||
private val roomMemberListFetcher = RoomMemberListFetcher(innerRoom, roomMembersDispatcher)
|
||||
|
||||
private val _roomNotificationSettingsStateFlow = MutableStateFlow<MatrixRoomNotificationSettingsState>(MatrixRoomNotificationSettingsState.Unknown)
|
||||
override val roomNotificationSettingsStateFlow: StateFlow<MatrixRoomNotificationSettingsState> = _roomNotificationSettingsStateFlow
|
||||
|
||||
override val liveTimeline = createTimeline(innerTimeline, mode = Timeline.Mode.LIVE) {
|
||||
_syncUpdateFlow.value = systemClock.epochMillis()
|
||||
override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.LIVE) {
|
||||
syncUpdateFlow.value = systemClock.epochMillis()
|
||||
}
|
||||
|
||||
override val membersStateFlow: StateFlow<MatrixRoomMembersState> = roomMemberListFetcher.membersFlow
|
||||
|
||||
override val syncUpdateFlow: StateFlow<Long> = _syncUpdateFlow.asStateFlow()
|
||||
|
||||
init {
|
||||
val powerLevelChanges = roomInfoFlow.map { it.userPowerLevels }.distinctUntilChanged()
|
||||
val membershipChanges = liveTimeline.membershipChangeEventReceived.onStart { emit(Unit) }
|
||||
|
|
@ -206,12 +173,13 @@ class RustMatrixRoom(
|
|||
// Skip initial one
|
||||
.drop(1)
|
||||
// The new events should already be in the SDK cache, no need to fetch them from the server
|
||||
.onEach { roomMemberListFetcher.fetchRoomMembers(source = RoomMemberListFetcher.Source.CACHE) }
|
||||
.onEach { baseRoom.roomMemberListFetcher.fetchRoomMembers(source = RoomMemberListFetcher.Source.CACHE) }
|
||||
.launchIn(roomCoroutineScope)
|
||||
.invokeOnCompletion {
|
||||
Timber.d("Observing membership changes for room $roomId stopped, reason: $it")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId)
|
||||
|
||||
override suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline> = withContext(roomDispatcher) {
|
||||
|
|
@ -273,17 +241,14 @@ class RustMatrixRoom(
|
|||
dateDividerMode = dateDividerMode,
|
||||
trackReadReceipts = trackReadReceipts,
|
||||
)
|
||||
).let { inner ->
|
||||
).let { innerTimeline ->
|
||||
val mode = when (createTimelineParams) {
|
||||
is CreateTimelineParams.Focused -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||
is CreateTimelineParams.MediaOnly -> Timeline.Mode.MEDIA
|
||||
is CreateTimelineParams.MediaOnlyFocused -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||
CreateTimelineParams.PinnedOnly -> Timeline.Mode.PINNED_EVENTS
|
||||
}
|
||||
createTimeline(
|
||||
timeline = inner,
|
||||
mode = mode,
|
||||
)
|
||||
innerTimeline.map(mode = mode)
|
||||
}
|
||||
}.mapFailure {
|
||||
when (createTimelineParams) {
|
||||
|
|
@ -299,105 +264,8 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
roomCoroutineScope.cancel()
|
||||
liveTimeline.close()
|
||||
}
|
||||
|
||||
override suspend fun updateMembers() {
|
||||
val useCache = membersStateFlow.value is MatrixRoomMembersState.Unknown
|
||||
val source = if (useCache) {
|
||||
RoomMemberListFetcher.Source.CACHE_AND_SERVER
|
||||
} else {
|
||||
RoomMemberListFetcher.Source.SERVER
|
||||
}
|
||||
roomMemberListFetcher.fetchRoomMembers(source = source)
|
||||
}
|
||||
|
||||
override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.members().use {
|
||||
it.nextChunk(limit.toUInt()).orEmpty().map { roomMember ->
|
||||
RoomMemberMapper.map(roomMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomMemberMapper.map(innerRoom.member(userId.value))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userDisplayName(userId: UserId): Result<String?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.memberDisplayName(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateRoomNotificationSettings(): Result<Unit> = withContext(roomDispatcher) {
|
||||
val currentState = _roomNotificationSettingsStateFlow.value
|
||||
val currentRoomNotificationSettings = currentState.roomNotificationSettings()
|
||||
_roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings)
|
||||
runCatching {
|
||||
val isEncrypted = roomInfoFlow.value.isEncrypted ?: getUpdatedIsEncrypted().getOrThrow()
|
||||
notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
|
||||
}.map {
|
||||
_roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(it)
|
||||
}.onFailure {
|
||||
_roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Error(
|
||||
prevRoomNotificationSettings = currentRoomNotificationSettings,
|
||||
failure = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userRole(userId: UserId): Result<RoomMember.Role> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomMemberMapper.mapRole(innerRoom.suggestedRoleForUser(userId.value))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit> {
|
||||
return runCatching {
|
||||
val powerLevelChanges = changes.map { UserPowerLevelUpdate(it.userId.value, it.powerLevel) }
|
||||
innerRoom.updatePowerLevelsForUsers(powerLevelChanges)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun powerLevels(): Result<MatrixRoomPowerLevels> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomPowerLevelsMapper.map(innerRoom.getPowerLevels())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updatePowerLevels(matrixRoomPowerLevels: MatrixRoomPowerLevels): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
val changes = RoomPowerLevelChanges(
|
||||
ban = matrixRoomPowerLevels.ban,
|
||||
invite = matrixRoomPowerLevels.invite,
|
||||
kick = matrixRoomPowerLevels.kick,
|
||||
redact = matrixRoomPowerLevels.redactEvents,
|
||||
eventsDefault = matrixRoomPowerLevels.sendEvents,
|
||||
roomName = matrixRoomPowerLevels.roomName,
|
||||
roomAvatar = matrixRoomPowerLevels.roomAvatar,
|
||||
roomTopic = matrixRoomPowerLevels.roomTopic,
|
||||
)
|
||||
innerRoom.applyPowerLevelChanges(changes)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun resetPowerLevels(): Result<MatrixRoomPowerLevels> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomPowerLevelsMapper.map(innerRoom.resetPowerLevels())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.memberAvatarUrl(userId.value)
|
||||
}
|
||||
override suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit> {
|
||||
return liveTimeline.sendMessage(body, htmlBody, intentionalMentions)
|
||||
}
|
||||
|
||||
override suspend fun editMessage(
|
||||
|
|
@ -413,84 +281,6 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit> {
|
||||
return liveTimeline.sendMessage(body, htmlBody, intentionalMentions)
|
||||
}
|
||||
|
||||
override suspend fun leave(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.leave()
|
||||
}.onSuccess {
|
||||
roomMembershipObserver.notifyUserLeftRoom(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun join(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.join()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun inviteUserById(id: UserId): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.inviteUserById(id.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserInvite(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserInvite(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserKick(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserKick(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserBan(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserBan(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserRedactOwn(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserRedactOther(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserRedactOther(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserSendState(userId.value, type.map())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserSendMessage(userId.value, type.map())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserTriggerRoomNotification(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserPinUnpin(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
|
|
@ -593,93 +383,6 @@ class RustMatrixRoom(
|
|||
return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType)
|
||||
}
|
||||
|
||||
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> {
|
||||
return liveTimeline.toggleReaction(emoji, eventOrTransactionId)
|
||||
}
|
||||
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
|
||||
return liveTimeline.forwardEvent(eventId, roomIds)
|
||||
}
|
||||
|
||||
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> {
|
||||
return liveTimeline.cancelSend(transactionId)
|
||||
}
|
||||
|
||||
override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.uploadAvatar(mimeType, data, null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeAvatar(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.removeAvatar()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setName(name: String): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setName(name)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setTopic(topic: String): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setTopic(topic)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason)
|
||||
if (blockUserId != null) {
|
||||
innerRoom.ignoreUser(blockUserId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearEventCacheStorage(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.clearEventCacheStorage()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun kickUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.kickUser(userId.value, reason)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun banUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.banUser(userId.value, reason)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unbanUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.unbanUser(userId.value, reason)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setIsFavourite(isFavorite, null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.markAsRead(receiptType.toRustReceiptType())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setUnreadFlag(isUnread)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createPoll(
|
||||
question: String,
|
||||
answers: List<String>,
|
||||
|
|
@ -719,95 +422,70 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun generateWidgetWebViewUrl(
|
||||
widgetSettings: MatrixWidgetSettings,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?,
|
||||
) = withContext(roomDispatcher) {
|
||||
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> {
|
||||
return liveTimeline.toggleReaction(emoji, eventOrTransactionId)
|
||||
}
|
||||
|
||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
|
||||
return liveTimeline.forwardEvent(eventId, roomIds)
|
||||
}
|
||||
|
||||
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> {
|
||||
return liveTimeline.cancelSend(transactionId)
|
||||
}
|
||||
|
||||
override suspend fun inviteUserById(id: UserId): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
widgetSettings.generateWidgetWebViewUrl(innerRoom, clientId, languageTag, theme)
|
||||
innerRoom.inviteUserById(id.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> {
|
||||
return runCatching {
|
||||
RustWidgetDriver(
|
||||
widgetSettings = widgetSettings,
|
||||
room = innerRoom,
|
||||
widgetCapabilitiesProvider = object : WidgetCapabilitiesProvider {
|
||||
override fun acquireCapabilities(capabilities: WidgetCapabilities): WidgetCapabilities {
|
||||
return getElementCallRequiredPermissions(sessionId.value, deviceId.value)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPermalink(): Result<String> = withContext(roomDispatcher) {
|
||||
override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.matrixToPermalink()
|
||||
innerRoom.uploadAvatar(mimeType, data, null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPermalinkFor(eventId: EventId): Result<String> = withContext(roomDispatcher) {
|
||||
override suspend fun removeAvatar(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.matrixToEventPermalink(eventId.value)
|
||||
innerRoom.removeAvatar()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendCallNotificationIfNeeded(): Result<Unit> = withContext(roomDispatcher) {
|
||||
override suspend fun setName(name: String): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.sendCallNotificationIfNeeded()
|
||||
innerRoom.setName(name)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setSendQueueEnabled(enabled: Boolean) {
|
||||
withContext(roomDispatcher) {
|
||||
Timber.d("setSendQueuesEnabled: $enabled")
|
||||
runCatching {
|
||||
innerRoom.enableSendQueue(enabled)
|
||||
override suspend fun setTopic(topic: String): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setTopic(topic)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason)
|
||||
if (blockUserId != null) {
|
||||
innerRoom.ignoreUser(blockUserId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result<Unit> = withContext(roomDispatcher) {
|
||||
override suspend fun updateRoomNotificationSettings(): Result<Unit> = withContext(roomDispatcher) {
|
||||
val currentState = roomNotificationSettingsStateFlow.value
|
||||
val currentRoomNotificationSettings = currentState.roomNotificationSettings()
|
||||
roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings)
|
||||
runCatching {
|
||||
Timber.d("saveComposerDraft: $composerDraft into $roomId")
|
||||
innerRoom.saveComposerDraft(composerDraft.into())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadComposerDraft(): Result<ComposerDraft?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
Timber.d("loadComposerDraft for $roomId")
|
||||
innerRoom.loadComposerDraft()?.into()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearComposerDraft(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
Timber.d("clearComposerDraft for $roomId")
|
||||
innerRoom.clearComposerDraft()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, sendHandle: SendHandle) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.ignoreDeviceTrustAndResend(
|
||||
devices = devices.entries.associate { entry ->
|
||||
entry.key.value to entry.value.map { it.value }
|
||||
},
|
||||
sendHandle = (sendHandle as RustSendHandle).inner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun withdrawVerificationAndResend(userIds: List<UserId>, sendHandle: SendHandle) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.withdrawVerificationAndResend(
|
||||
userIds = userIds.map { it.value },
|
||||
sendHandle = (sendHandle as RustSendHandle).inner,
|
||||
val isEncrypted = roomInfoFlow.value.isEncrypted ?: getUpdatedIsEncrypted().getOrThrow()
|
||||
notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
|
||||
}.map {
|
||||
roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Ready(it)
|
||||
}.onFailure {
|
||||
roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Error(
|
||||
prevRoomNotificationSettings = currentRoomNotificationSettings,
|
||||
failure = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -842,12 +520,6 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getRoomVisibility(): Result<RoomVisibility> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.getRoomVisibility().map()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun enableEncryption(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.enableEncryption()
|
||||
|
|
@ -860,22 +532,128 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getUpdatedIsEncrypted(): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.latestEncryptionState() == EncryptionState.ENCRYPTED
|
||||
override suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit> {
|
||||
return runCatching {
|
||||
val powerLevelChanges = changes.map { UserPowerLevelUpdate(it.userId.value, it.powerLevel) }
|
||||
innerRoom.updatePowerLevelsForUsers(powerLevelChanges)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTimeline(
|
||||
timeline: InnerTimeline,
|
||||
override suspend fun updatePowerLevels(roomPowerLevels: RoomPowerLevels): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
val changes = RoomPowerLevelChanges(
|
||||
ban = roomPowerLevels.ban,
|
||||
invite = roomPowerLevels.invite,
|
||||
kick = roomPowerLevels.kick,
|
||||
redact = roomPowerLevels.redactEvents,
|
||||
eventsDefault = roomPowerLevels.sendEvents,
|
||||
roomName = roomPowerLevels.roomName,
|
||||
roomAvatar = roomPowerLevels.roomAvatar,
|
||||
roomTopic = roomPowerLevels.roomTopic,
|
||||
)
|
||||
innerRoom.applyPowerLevelChanges(changes)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun resetPowerLevels(): Result<RoomPowerLevels> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomPowerLevelsMapper.map(innerRoom.resetPowerLevels())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun kickUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.kickUser(userId.value, reason)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun banUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.banUser(userId.value, reason)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unbanUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.unbanUser(userId.value, reason)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun generateWidgetWebViewUrl(
|
||||
widgetSettings: MatrixWidgetSettings,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?,
|
||||
) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
widgetSettings.generateWidgetWebViewUrl(innerRoom, clientId, languageTag, theme)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> {
|
||||
return runCatching {
|
||||
RustWidgetDriver(
|
||||
widgetSettings = widgetSettings,
|
||||
room = innerRoom,
|
||||
widgetCapabilitiesProvider = object : WidgetCapabilitiesProvider {
|
||||
override fun acquireCapabilities(capabilities: WidgetCapabilities): WidgetCapabilities {
|
||||
return getElementCallRequiredPermissions(sessionId.value, baseRoom.deviceId.value)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendCallNotificationIfNeeded(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.sendCallNotificationIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setSendQueueEnabled(enabled: Boolean) {
|
||||
withContext(roomDispatcher) {
|
||||
Timber.d("setSendQueuesEnabled: $enabled")
|
||||
runCatching {
|
||||
innerRoom.enableSendQueue(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, sendHandle: SendHandle) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.ignoreDeviceTrustAndResend(
|
||||
devices = devices.entries.associate { entry ->
|
||||
entry.key.value to entry.value.map { it.value }
|
||||
},
|
||||
sendHandle = (sendHandle as RustSendHandle).inner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun withdrawVerificationAndResend(userIds: List<UserId>, sendHandle: SendHandle) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.withdrawVerificationAndResend(
|
||||
userIds = userIds.map { it.value },
|
||||
sendHandle = (sendHandle as RustSendHandle).inner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
baseRoom.destroy()
|
||||
liveInnerTimeline.close()
|
||||
roomCoroutineScope.cancel()
|
||||
}
|
||||
|
||||
private fun InnerTimeline.map(
|
||||
mode: Timeline.Mode,
|
||||
onNewSyncedEvent: () -> Unit = {},
|
||||
): Timeline {
|
||||
val timelineCoroutineScope = roomCoroutineScope.childScope(coroutineDispatchers.main, "TimelineScope-$roomId-$timeline")
|
||||
val timelineCoroutineScope = roomCoroutineScope.childScope(coroutineDispatchers.main, "TimelineScope-$roomId-$this")
|
||||
return RustTimeline(
|
||||
mode = mode,
|
||||
matrixRoom = this,
|
||||
inner = timeline,
|
||||
joinedRoom = this@JoinedRustRoom,
|
||||
inner = this@map,
|
||||
systemClock = systemClock,
|
||||
coroutineScope = timelineCoroutineScope,
|
||||
dispatcher = roomDispatcher,
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
|
||||
@Immutable
|
||||
class NotJoinedRustRoom(
|
||||
private val sessionId: SessionId,
|
||||
override val localRoom: RustBaseRoom?,
|
||||
override val previewInfo: RoomPreviewInfo,
|
||||
) : NotJoinedRoom {
|
||||
override suspend fun membershipDetails(): Result<RoomMembershipDetails?> = runCatching {
|
||||
val room = localRoom?.innerRoom ?: return@runCatching null
|
||||
val (ownMember, senderInfo) = room.memberWithSenderInfo(sessionId.value)
|
||||
RoomMembershipDetails(
|
||||
currentUserMember = RoomMemberMapper.map(ownMember),
|
||||
senderMember = senderInfo?.let { RoomMemberMapper.map(it) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
localRoom?.close()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ 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.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.impl.room.history.map
|
||||
|
|
@ -28,9 +28,9 @@ import org.matrix.rustcomponents.sdk.Membership as RustMembership
|
|||
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
|
||||
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
|
||||
|
||||
class MatrixRoomInfoMapper {
|
||||
fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.let {
|
||||
return MatrixRoomInfo(
|
||||
class RoomInfoMapper {
|
||||
fun map(rustRoomInfo: RustRoomInfo): RoomInfo = rustRoomInfo.let {
|
||||
return RoomInfo(
|
||||
id = RoomId(it.id),
|
||||
creator = it.creator?.let(::UserId),
|
||||
name = it.displayName,
|
||||
|
|
@ -70,44 +70,6 @@ class MatrixRoomInfoMapper {
|
|||
historyVisibility = it.historyVisibility.map(),
|
||||
)
|
||||
}
|
||||
|
||||
// fun map(rustRoom: Room): MatrixRoomInfo = with(rustRoom) {
|
||||
// return MatrixRoomInfo(
|
||||
// id = RoomId(id()),
|
||||
// name = rawName(),
|
||||
// rawName = displayName(),
|
||||
// topic = topic(),
|
||||
// avatarUrl = avatarUrl(),
|
||||
// isPublic = isPublic(),
|
||||
// isDirect = null,
|
||||
// isEncrypted = encryptionState() == EncryptionState.ENCRYPTED,
|
||||
// joinRule = null,
|
||||
// isSpace = isSpace(),
|
||||
// isTombstoned = isTombstoned(),
|
||||
// isFavorite = null,
|
||||
// canonicalAlias = canonicalAlias()?.let(::RoomAlias),
|
||||
// alternativeAliases = alternativeAliases().map(::RoomAlias).toImmutableList(),
|
||||
// currentUserMembership = membership().map(),
|
||||
// inviter = null,
|
||||
// activeMembersCount = activeMembersCount().toLong(),
|
||||
// invitedMembersCount = invitedMembersCount().toLong(),
|
||||
// joinedMembersCount = joinedMembersCount().toLong(),
|
||||
// userPowerLevels = persistentMapOf(),
|
||||
// highlightCount = 0,
|
||||
// notificationCount = 0,
|
||||
// userDefinedNotificationMode = null,
|
||||
// hasRoomCall = hasActiveRoomCall(),
|
||||
// activeRoomCallParticipants = activeRoomCallParticipants().map(::UserId).toImmutableList(),
|
||||
// isMarkedUnread = false,
|
||||
// numUnreadMessages = 0,
|
||||
// numUnreadNotifications = 0,
|
||||
// numUnreadMentions = 0,
|
||||
// heroes = heroes().map(RoomHero::map).toImmutableList(),
|
||||
// pinnedEventIds = persistentListOf(),
|
||||
// creator = null,
|
||||
// historyVisibility = null,
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
fun RustMembership.map(): CurrentUserMembership = when (this) {
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
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.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.impl.room.draft.into
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper
|
||||
import io.element.android.libraries.matrix.impl.roomdirectory.map
|
||||
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk_base.EncryptionState
|
||||
import org.matrix.rustcomponents.sdk.Room as InnerRoom
|
||||
|
||||
class RustBaseRoom(
|
||||
override val sessionId: SessionId,
|
||||
internal val deviceId: DeviceId,
|
||||
internal val innerRoom: InnerRoom,
|
||||
coroutineDispatchers: CoroutineDispatchers,
|
||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
initialRoomInfo: RoomInfo,
|
||||
) : BaseRoom {
|
||||
override val roomId = RoomId(innerRoom.id())
|
||||
|
||||
// Create a dispatcher for all room methods...
|
||||
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
|
||||
|
||||
// ...except getMember methods as it could quickly fill the roomDispatcher...
|
||||
private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8)
|
||||
|
||||
internal val roomMemberListFetcher = RoomMemberListFetcher(innerRoom, roomMembersDispatcher)
|
||||
|
||||
override val membersStateFlow: StateFlow<RoomMembersState> = roomMemberListFetcher.membersFlow
|
||||
|
||||
override val roomInfoFlow: StateFlow<RoomInfo> = MutableStateFlow(initialRoomInfo)
|
||||
|
||||
override val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId")
|
||||
|
||||
override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId)
|
||||
|
||||
override suspend fun updateMembers() {
|
||||
val useCache = membersStateFlow.value is RoomMembersState.Unknown
|
||||
val source = if (useCache) {
|
||||
RoomMemberListFetcher.Source.CACHE_AND_SERVER
|
||||
} else {
|
||||
RoomMemberListFetcher.Source.SERVER
|
||||
}
|
||||
roomMemberListFetcher.fetchRoomMembers(source = source)
|
||||
}
|
||||
|
||||
override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.members().use {
|
||||
it.nextChunk(limit.toUInt()).orEmpty().map { roomMember ->
|
||||
RoomMemberMapper.map(roomMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomMemberMapper.map(innerRoom.member(userId.value))
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
innerRoom.destroy()
|
||||
}
|
||||
|
||||
override suspend fun userDisplayName(userId: UserId): Result<String?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.memberDisplayName(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userRole(userId: UserId): Result<RoomMember.Role> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomMemberMapper.mapRole(innerRoom.suggestedRoleForUser(userId.value))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun powerLevels(): Result<RoomPowerLevels> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
RoomPowerLevelsMapper.map(innerRoom.getPowerLevels())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.memberAvatarUrl(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun leave(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.leave()
|
||||
}.onSuccess {
|
||||
roomMembershipObserver.notifyUserLeftRoom(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun join(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.join()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun forget(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.forget()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserInvite(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserInvite(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserKick(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserKick(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserBan(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserBan(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserRedactOwn(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserRedactOther(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserRedactOther(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserSendState(userId.value, type.map())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserSendMessage(userId.value, type.map())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserTriggerRoomNotification(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.canUserPinUnpin(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearEventCacheStorage(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.clearEventCacheStorage()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setIsFavourite(isFavorite, null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.markAsRead(receiptType.toRustReceiptType())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setUnreadFlag(isUnread)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPermalink(): Result<String> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.matrixToPermalink()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPermalinkFor(eventId: EventId): Result<String> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.matrixToEventPermalink(eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getRoomVisibility(): Result<RoomVisibility> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.getRoomVisibility().map()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUpdatedIsEncrypted(): Result<Boolean> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.latestEncryptionState() == EncryptionState.ENCRYPTED
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
Timber.d("saveComposerDraft: $composerDraft into $roomId")
|
||||
innerRoom.saveComposerDraft(composerDraft.into())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadComposerDraft(): Result<ComposerDraft?> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
Timber.d("loadComposerDraft for $roomId")
|
||||
innerRoom.loadComposerDraft()?.into()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearComposerDraft(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
Timber.d("clearComposerDraft for $roomId")
|
||||
innerRoom.clearComposerDraft()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,20 +7,20 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import androidx.collection.lruCache
|
||||
import io.element.android.appconfig.TimelineConfig
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
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.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
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
|
||||
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
||||
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -28,17 +28,18 @@ import kotlinx.coroutines.NonCancellable
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.Room
|
||||
import org.matrix.rustcomponents.sdk.RoomListException
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
import org.matrix.rustcomponents.sdk.RoomListItem
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import org.matrix.rustcomponents.sdk.Room as SdkRoom
|
||||
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
|
||||
|
||||
private const val CACHE_SIZE = 16
|
||||
|
||||
class RustRoomFactory(
|
||||
private val sessionId: SessionId,
|
||||
private val deviceId: DeviceId,
|
||||
private val innerClient: Client,
|
||||
private val notificationSettingsService: NotificationSettingsService,
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
|
|
@ -53,23 +54,14 @@ class RustRoomFactory(
|
|||
) {
|
||||
private val dispatcher = dispatchers.io.limitedParallelism(1)
|
||||
private val mutex = Mutex()
|
||||
private var isDestroyed: Boolean = false
|
||||
private val isDestroyed: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
private data class RustRoomReferences(
|
||||
val roomListItem: RoomListItem,
|
||||
val fullRoom: Room,
|
||||
val room: SdkRoom,
|
||||
)
|
||||
|
||||
private val cache = lruCache<RoomId, RustRoomReferences>(
|
||||
maxSize = CACHE_SIZE,
|
||||
onEntryRemoved = { evicted, roomId, oldRoom, _ ->
|
||||
Timber.d("On room removed from cache: $roomId, evicted: $evicted")
|
||||
oldRoom.roomListItem.close()
|
||||
oldRoom.fullRoom.close()
|
||||
}
|
||||
)
|
||||
|
||||
private val matrixRoomInfoMapper = MatrixRoomInfoMapper()
|
||||
private val roomInfoMapper = RoomInfoMapper()
|
||||
|
||||
private val eventFilters = TimelineConfig.excludedEvents
|
||||
.takeIf { it.isNotEmpty() }
|
||||
|
|
@ -81,102 +73,123 @@ class RustRoomFactory(
|
|||
withContext(NonCancellable + dispatcher) {
|
||||
mutex.withLock {
|
||||
Timber.d("Destroying room factory")
|
||||
cache.snapshot().values.forEach { (listItem, innerRoom) ->
|
||||
innerRoom.destroy()
|
||||
listItem.destroy()
|
||||
}
|
||||
cache.evictAll()
|
||||
isDestroyed = true
|
||||
isDestroyed.set(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun create(roomId: RoomId): MatrixRoom? = withContext(dispatcher) {
|
||||
suspend fun getBaseRoom(roomId: RoomId): RustBaseRoom? = withContext(dispatcher) {
|
||||
mutex.withLock {
|
||||
if (isDestroyed) {
|
||||
if (isDestroyed.get()) {
|
||||
Timber.d("Room factory is destroyed, returning null for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
var roomReferences: RustRoomReferences? = getRoomReferences(roomId)
|
||||
if (roomReferences == null) {
|
||||
// ... otherwise, lets wait for the SS to load all rooms and check again.
|
||||
roomListService.allRooms.awaitLoaded()
|
||||
roomReferences = getRoomReferences(roomId)
|
||||
}
|
||||
if (roomReferences == null) {
|
||||
Timber.d("No room found for $roomId, returning null")
|
||||
return@withContext null
|
||||
}
|
||||
val liveTimeline = roomReferences.fullRoom.timeline()
|
||||
val initialRoomInfo = roomReferences.fullRoom.roomInfo()
|
||||
RustMatrixRoom(
|
||||
sessionId = sessionId,
|
||||
deviceId = deviceId,
|
||||
innerRoom = roomReferences.fullRoom,
|
||||
innerTimeline = liveTimeline,
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
coroutineDispatchers = dispatchers,
|
||||
systemClock = systemClock,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
roomSyncSubscriber = roomSyncSubscriber,
|
||||
matrixRoomInfoMapper = matrixRoomInfoMapper,
|
||||
featureFlagService = featureFlagService,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
initialRoomInfo = matrixRoomInfoMapper.map(initialRoomInfo),
|
||||
)
|
||||
val roomReferences = awaitRoomReferences(roomId) ?: return@withContext null
|
||||
getBaseRoom(roomReferences)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createRoomPreview(roomId: RoomId): RoomPreview? = withContext(dispatcher) {
|
||||
if (isDestroyed) {
|
||||
Timber.d("Room factory is destroyed, returning null for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
val roomListItem = innerRoomListService.roomOrNull(roomId.value)
|
||||
if (roomListItem == null) {
|
||||
Timber.d("Room not found for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
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 room preview for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
RustRoomPreview(
|
||||
private suspend fun getBaseRoom(roomReferences: RustRoomReferences): RustBaseRoom? {
|
||||
val initialRoomInfo = roomReferences.room.roomInfo()
|
||||
return RustBaseRoom(
|
||||
sessionId = sessionId,
|
||||
inner = innerRoom,
|
||||
deviceId = deviceId,
|
||||
innerRoom = roomReferences.room,
|
||||
coroutineDispatchers = dispatchers,
|
||||
roomSyncSubscriber = roomSyncSubscriber,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
initialRoomInfo = roomInfoMapper.map(initialRoomInfo),
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getRoomReferences(roomId: RoomId): RustRoomReferences? {
|
||||
cache[roomId]?.let {
|
||||
Timber.d("Room found in cache for $roomId")
|
||||
return it
|
||||
suspend fun getJoinedRoomOrPreview(roomId: RoomId): GetRoomResult? = withContext(dispatcher) {
|
||||
mutex.withLock {
|
||||
if (isDestroyed.get()) {
|
||||
Timber.d("Room factory is destroyed, returning null for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
val roomReferences = awaitRoomReferences(roomId) ?: return@withContext null
|
||||
|
||||
if (roomReferences.room.membership() == Membership.JOINED) {
|
||||
val baseRoom = getBaseRoom(roomReferences) ?: return@withContext null
|
||||
|
||||
// Init the live timeline in the SDK from the RoomListItem
|
||||
if (!roomReferences.roomListItem.isTimelineInitialized()) {
|
||||
roomReferences.roomListItem.initTimeline(eventFilters, "LIVE")
|
||||
}
|
||||
|
||||
GetRoomResult.Joined(
|
||||
JoinedRustRoom(
|
||||
baseRoom = baseRoom,
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
liveInnerTimeline = roomReferences.room.timeline(),
|
||||
coroutineDispatchers = dispatchers,
|
||||
systemClock = systemClock,
|
||||
roomInfoMapper = roomInfoMapper,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val preview = try {
|
||||
roomReferences.roomListItem.previewRoom(via = emptyList())
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get room preview for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
GetRoomResult.NotJoined(
|
||||
NotJoinedRustRoom(
|
||||
sessionId = sessionId,
|
||||
localRoom = getBaseRoom(roomReferences),
|
||||
previewInfo = RoomPreviewInfoMapper.map(preview.info()),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRoomReferences(roomId: RoomId): RustRoomReferences? {
|
||||
val roomListItem = innerRoomListService.roomOrNull(roomId.value)
|
||||
if (roomListItem == null) {
|
||||
Timber.d("Room not found for $roomId")
|
||||
return null
|
||||
}
|
||||
val fullRoom = try {
|
||||
roomListItem.fullRoomWithTimeline(filter = eventFilters)
|
||||
} catch (e: RoomListException) {
|
||||
Timber.e(e, "Failed to get full room with timeline for $roomId")
|
||||
return null
|
||||
}
|
||||
Timber.d("Got full room with timeline for $roomId")
|
||||
val room = tryOrNull {
|
||||
innerClient.getRoom(roomId.value)
|
||||
} ?: error("Failed to get room for room id: $roomId")
|
||||
|
||||
Timber.d("Got room for $roomId")
|
||||
return RustRoomReferences(
|
||||
roomListItem = roomListItem,
|
||||
fullRoom = fullRoom,
|
||||
).also {
|
||||
cache.put(roomId, it)
|
||||
room = room,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Rust room references for a room, retrying after the room list is loaded if necessary.
|
||||
*/
|
||||
private suspend fun awaitRoomReferences(roomId: RoomId): RustRoomReferences? {
|
||||
var roomReferences = getRoomReferences(roomId)
|
||||
|
||||
if (roomReferences == null) {
|
||||
// ... otherwise, lets wait for the SS to load all rooms and check again.
|
||||
roomListService.allRooms.awaitLoaded()
|
||||
roomReferences = getRoomReferences(roomId)
|
||||
}
|
||||
|
||||
return roomReferences
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface GetRoomResult {
|
||||
data class Joined(val joinedRoom: JoinedRoom) : GetRoomResult
|
||||
data class NotJoined(val notJoinedRoom: NotJoinedRustRoom) : GetRoomResult
|
||||
|
||||
val room: BaseRoom?
|
||||
get() = when (this) {
|
||||
is Joined -> joinedRoom
|
||||
is NotJoined -> notJoinedRoom.localRoom
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
|
||||
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.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
import org.matrix.rustcomponents.sdk.RoomPreview as InnerRoomPreview
|
||||
|
||||
@Immutable
|
||||
class RustRoomPreview(
|
||||
override val sessionId: SessionId,
|
||||
private val inner: InnerRoomPreview,
|
||||
private val roomMembershipObserver: RoomMembershipObserver?,
|
||||
) : RoomPreview {
|
||||
companion object {
|
||||
val ALLOWED_MEMBERSHIPS = setOf(Membership.INVITED, Membership.KNOCKED, Membership.BANNED)
|
||||
}
|
||||
|
||||
override val info: RoomPreviewInfo = RoomPreviewInfoMapper.map(inner.info())
|
||||
|
||||
override suspend fun leave(): Result<Unit> = runCatching {
|
||||
inner.leave()
|
||||
}.onSuccess {
|
||||
when (info.membership) {
|
||||
CurrentUserMembership.INVITED -> roomMembershipObserver?.notifyUserDeclinedInvite(info.roomId)
|
||||
CurrentUserMembership.KNOCKED -> roomMembershipObserver?.notifyUserCanceledKnock(info.roomId)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun forget(): Result<Unit> = runCatching {
|
||||
inner.forget()
|
||||
}
|
||||
|
||||
override suspend fun membershipDetails(): Result<RoomMembershipDetails?> = runCatching {
|
||||
val details = inner.ownMembershipDetails() ?: return@runCatching null
|
||||
RoomMembershipDetails(
|
||||
currentUserMember = RoomMemberMapper.map(details.roomMember),
|
||||
senderMember = details.senderInfo?.let { RoomMemberMapper.map(it) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
inner.destroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.room.member
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
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.roomMembers
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -42,8 +42,8 @@ internal class RoomMemberListFetcher(
|
|||
private val updatedRoomMemberMutex = Mutex()
|
||||
private val roomId = room.id()
|
||||
|
||||
private val _membersFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown)
|
||||
val membersFlow: StateFlow<MatrixRoomMembersState> = _membersFlow
|
||||
private val _membersFlow = MutableStateFlow<RoomMembersState>(RoomMembersState.Unknown)
|
||||
val membersFlow: StateFlow<RoomMembersState> = _membersFlow
|
||||
|
||||
/**
|
||||
* Fetches the room members for the given room.
|
||||
|
|
@ -75,16 +75,16 @@ internal class RoomMemberListFetcher(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableStateFlow<MatrixRoomMembersState>.fetchCachedRoomMembers(asPendingState: Boolean = true) {
|
||||
private suspend fun MutableStateFlow<RoomMembersState>.fetchCachedRoomMembers(asPendingState: Boolean = true) {
|
||||
Timber.i("Loading cached members for room $roomId")
|
||||
try {
|
||||
// Send current member list with pending state to notify the UI that we are loading new members
|
||||
emit(pendingWithCurrentMembers())
|
||||
val members = parseAndEmitMembers(room.membersNoSync())
|
||||
val newState = if (asPendingState) {
|
||||
MatrixRoomMembersState.Pending(prevRoomMembers = members)
|
||||
RoomMembersState.Pending(prevRoomMembers = members)
|
||||
} else {
|
||||
MatrixRoomMembersState.Ready(members)
|
||||
RoomMembersState.Ready(members)
|
||||
}
|
||||
emit(newState)
|
||||
} catch (exception: CancellationException) {
|
||||
|
|
@ -92,22 +92,22 @@ internal class RoomMemberListFetcher(
|
|||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "Failed to load cached members for room $roomId")
|
||||
emit(MatrixRoomMembersState.Error(exception, _membersFlow.value.roomMembers()?.toImmutableList()))
|
||||
emit(RoomMembersState.Error(exception, _membersFlow.value.roomMembers()?.toImmutableList()))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableStateFlow<MatrixRoomMembersState>.fetchRemoteRoomMembers() {
|
||||
private suspend fun MutableStateFlow<RoomMembersState>.fetchRemoteRoomMembers() {
|
||||
try {
|
||||
// Send current member list with pending state to notify the UI that we are loading new members
|
||||
emit(pendingWithCurrentMembers())
|
||||
// Start loading new members
|
||||
emit(MatrixRoomMembersState.Ready(parseAndEmitMembers(room.members())))
|
||||
emit(RoomMembersState.Ready(parseAndEmitMembers(room.members())))
|
||||
} catch (exception: CancellationException) {
|
||||
Timber.d("Cancelled loading updated members for room $roomId")
|
||||
throw exception
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "Failed to load updated members for room $roomId")
|
||||
emit(MatrixRoomMembersState.Error(exception, _membersFlow.value.roomMembers()?.toImmutableList()))
|
||||
emit(RoomMembersState.Error(exception, _membersFlow.value.roomMembers()?.toImmutableList()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,5 +129,5 @@ internal class RoomMemberListFetcher(
|
|||
}
|
||||
}
|
||||
|
||||
private fun pendingWithCurrentMembers() = MatrixRoomMembersState.Pending(_membersFlow.value.roomMembers().orEmpty().toImmutableList())
|
||||
private fun pendingWithCurrentMembers() = RoomMembersState.Pending(_membersFlow.value.roomMembers().orEmpty().toImmutableList())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.room.powerlevels
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import org.matrix.rustcomponents.sdk.RoomPowerLevels as RustRoomPowerLevels
|
||||
|
||||
object RoomPowerLevelsMapper {
|
||||
fun map(roomPowerLevels: RustRoomPowerLevels): MatrixRoomPowerLevels {
|
||||
return MatrixRoomPowerLevels(
|
||||
fun map(roomPowerLevels: RustRoomPowerLevels): RoomPowerLevels {
|
||||
return RoomPowerLevels(
|
||||
ban = roomPowerLevels.ban,
|
||||
invite = roomPowerLevels.invite,
|
||||
kick = roomPowerLevels.kick,
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@
|
|||
package io.element.android.libraries.matrix.impl.roomlist
|
||||
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper
|
||||
import io.element.android.libraries.matrix.impl.room.RoomInfoMapper
|
||||
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
|
||||
import org.matrix.rustcomponents.sdk.RoomListItem
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
|
||||
class RoomSummaryFactory(
|
||||
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
|
||||
private val roomInfoMapper: MatrixRoomInfoMapper = MatrixRoomInfoMapper(),
|
||||
private val roomInfoMapper: RoomInfoMapper = RoomInfoMapper(),
|
||||
) {
|
||||
suspend fun create(roomListItem: RoomListItem): RoomSummary {
|
||||
val roomInfo = roomListItem.roomInfo().let(roomInfoMapper::map)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
|||
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.MatrixRoom
|
||||
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.room.message.ReplyParameters
|
||||
|
|
@ -86,7 +86,7 @@ class RustTimeline(
|
|||
private val inner: InnerTimeline,
|
||||
mode: Timeline.Mode,
|
||||
systemClock: SystemClock,
|
||||
private val matrixRoom: MatrixRoom,
|
||||
private val joinedRoom: JoinedRoom,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
private val roomContentForwarder: RoomContentForwarder,
|
||||
|
|
@ -137,7 +137,10 @@ class RustTimeline(
|
|||
)
|
||||
|
||||
init {
|
||||
coroutineScope.fetchMembers()
|
||||
if (mode != Timeline.Mode.PINNED_EVENTS) {
|
||||
coroutineScope.fetchMembers()
|
||||
}
|
||||
|
||||
if (mode == Timeline.Mode.LIVE) {
|
||||
// When timeline is live, we need to listen to the back pagination status as
|
||||
// sdk can automatically paginate backwards.
|
||||
|
|
@ -186,10 +189,10 @@ class RustTimeline(
|
|||
}
|
||||
}.onFailure { error ->
|
||||
if (error is TimelineException.CannotPaginate) {
|
||||
Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backwardPaginationStatus.value}")
|
||||
Timber.d("Can't paginate $direction on room ${joinedRoom.roomId} with paginationStatus: ${backwardPaginationStatus.value}")
|
||||
} else {
|
||||
updatePaginationStatus(direction) { it.copy(isPaginating = false) }
|
||||
Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}")
|
||||
Timber.e(error, "Error paginating $direction on room ${joinedRoom.roomId}")
|
||||
}
|
||||
}.onSuccess { hasReachedEnd ->
|
||||
updatePaginationStatus(direction) { it.copy(isPaginating = false, hasMoreToLoad = !hasReachedEnd) }
|
||||
|
|
@ -209,7 +212,7 @@ class RustTimeline(
|
|||
_timelineItems,
|
||||
backwardPaginationStatus,
|
||||
forwardPaginationStatus,
|
||||
matrixRoom.roomInfoFlow.map { it.creator to it.isDm }.distinctUntilChanged(),
|
||||
joinedRoom.roomInfoFlow.map { it.creator to it.isDm }.distinctUntilChanged(),
|
||||
isTimelineInitialized,
|
||||
) { timelineItems,
|
||||
backwardPaginationStatus,
|
||||
|
|
@ -261,7 +264,7 @@ class RustTimeline(
|
|||
try {
|
||||
inner.fetchMembers()
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "Error fetching members for room ${matrixRoom.roomId}")
|
||||
Timber.e(exception, "Error fetching members for room ${joinedRoom.roomId}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ package io.element.android.libraries.matrix.impl.analytics
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class JoinedRoomExtKtTest {
|
||||
class JoinedExtKtTest {
|
||||
@Test
|
||||
fun `test room size mapping`() = runTest {
|
||||
mapOf(
|
||||
|
|
@ -26,7 +26,7 @@ class JoinedRoomExtKtTest {
|
|||
listOf(1001L, 2000L) to JoinedRoom.RoomSize.MoreThanAThousand
|
||||
).forEach { (joinedMemberCounts, expectedRoomSize) ->
|
||||
joinedMemberCounts.forEach { joinedMemberCount ->
|
||||
assertThat(aMatrixRoom(joinedMemberCount = joinedMemberCount).toAnalyticsJoinedRoom(null))
|
||||
assertThat(aRoom(joinedMemberCount = joinedMemberCount).toAnalyticsJoinedRoom(null))
|
||||
.isEqualTo(
|
||||
JoinedRoom(
|
||||
isDM = false,
|
||||
|
|
@ -41,7 +41,7 @@ class JoinedRoomExtKtTest {
|
|||
|
||||
@Test
|
||||
fun `test isDirect parameter mapping`() = runTest {
|
||||
assertThat(aMatrixRoom(isDirect = true).toAnalyticsJoinedRoom(null))
|
||||
assertThat(aRoom(isDirect = true).toAnalyticsJoinedRoom(null))
|
||||
.isEqualTo(
|
||||
JoinedRoom(
|
||||
isDM = true,
|
||||
|
|
@ -54,7 +54,7 @@ class JoinedRoomExtKtTest {
|
|||
|
||||
@Test
|
||||
fun `test isSpace parameter mapping`() = runTest {
|
||||
assertThat(aMatrixRoom(isSpace = true).toAnalyticsJoinedRoom(null))
|
||||
assertThat(aRoom(isSpace = true).toAnalyticsJoinedRoom(null))
|
||||
.isEqualTo(
|
||||
JoinedRoom(
|
||||
isDM = false,
|
||||
|
|
@ -67,7 +67,7 @@ class JoinedRoomExtKtTest {
|
|||
|
||||
@Test
|
||||
fun `test trigger parameter mapping`() = runTest {
|
||||
assertThat(aMatrixRoom(isDirect = false, isSpace = false, joinedMemberCount = 1).toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
|
||||
assertThat(aRoom(isDirect = false, isSpace = false, joinedMemberCount = 1).toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
|
||||
.isEqualTo(
|
||||
JoinedRoom(
|
||||
isDM = false,
|
||||
|
|
@ -78,12 +78,12 @@ class JoinedRoomExtKtTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun aMatrixRoom(
|
||||
private fun aRoom(
|
||||
isDirect: Boolean = false,
|
||||
isSpace: Boolean = false,
|
||||
joinedMemberCount: Long = 0
|
||||
): FakeMatrixRoom {
|
||||
return FakeMatrixRoom().apply {
|
||||
): FakeBaseRoom {
|
||||
return FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo(isDirect = isDirect, isSpace = isSpace, joinedMembersCount = joinedMemberCount))
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
|
|
@ -38,11 +38,11 @@ import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule
|
|||
import org.matrix.rustcomponents.sdk.RoomHistoryVisibility as RustRoomHistoryVisibility
|
||||
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
|
||||
|
||||
class MatrixRoomInfoMapperTest {
|
||||
class RoomInfoMapperTest {
|
||||
@Test
|
||||
fun `mapping of RustRoomInfo should map all the fields`() {
|
||||
assertThat(
|
||||
MatrixRoomInfoMapper().map(
|
||||
RoomInfoMapper().map(
|
||||
aRustRoomInfo(
|
||||
id = A_ROOM_ID.value,
|
||||
displayName = "displayName",
|
||||
|
|
@ -80,7 +80,7 @@ class MatrixRoomInfoMapperTest {
|
|||
)
|
||||
)
|
||||
).isEqualTo(
|
||||
MatrixRoomInfo(
|
||||
RoomInfo(
|
||||
id = A_ROOM_ID,
|
||||
name = "displayName",
|
||||
rawName = "rawName",
|
||||
|
|
@ -127,7 +127,7 @@ class MatrixRoomInfoMapperTest {
|
|||
@Test
|
||||
fun `mapping of RustRoomInfo with null members should map all the fields`() {
|
||||
assertThat(
|
||||
MatrixRoomInfoMapper().map(
|
||||
RoomInfoMapper().map(
|
||||
aRustRoomInfo(
|
||||
id = A_ROOM_ID.value,
|
||||
displayName = null,
|
||||
|
|
@ -164,7 +164,7 @@ class MatrixRoomInfoMapperTest {
|
|||
)
|
||||
)
|
||||
).isEqualTo(
|
||||
MatrixRoomInfo(
|
||||
RoomInfo(
|
||||
id = A_ROOM_ID,
|
||||
name = null,
|
||||
rawName = null,
|
||||
|
|
@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
|||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
|
|
@ -33,7 +33,7 @@ class DefaultJoinRoomTest {
|
|||
val roomSummary = aRoomSummary()
|
||||
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
||||
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
||||
val roomResult = FakeMatrixRoom().apply {
|
||||
val roomResult = FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo())
|
||||
}
|
||||
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||
|
|
@ -70,7 +70,7 @@ class DefaultJoinRoomTest {
|
|||
val roomSummary = aRoomSummary()
|
||||
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
||||
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
||||
val roomResult = FakeMatrixRoom().apply {
|
||||
val roomResult = FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo())
|
||||
}
|
||||
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||
|
|
@ -108,7 +108,7 @@ class DefaultJoinRoomTest {
|
|||
val roomSummary = aRoomSummary()
|
||||
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
|
||||
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
|
||||
val roomResult = FakeMatrixRoom().apply {
|
||||
val roomResult = FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo())
|
||||
}
|
||||
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ package io.element.android.libraries.matrix.impl.room.member
|
|||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomMember
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoom
|
||||
|
|
@ -40,16 +40,16 @@ class RoomMemberListFetcherTest {
|
|||
|
||||
val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
|
||||
fetcher.membersFlow.test {
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
|
||||
fetcher.fetchRoomMembers(source = CACHE)
|
||||
|
||||
// Loading state
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
|
||||
val cachedItemsState = awaitItem()
|
||||
assertThat(cachedItemsState).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
|
||||
assertThat((cachedItemsState as? MatrixRoomMembersState.Ready)?.roomMembers).hasSize(3)
|
||||
assertThat(cachedItemsState).isInstanceOf(RoomMembersState.Ready::class.java)
|
||||
assertThat((cachedItemsState as? RoomMembersState.Ready)?.roomMembers).hasSize(3)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,9 +62,9 @@ class RoomMemberListFetcherTest {
|
|||
val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
|
||||
fetcher.membersFlow.test {
|
||||
fetcher.fetchRoomMembers(source = CACHE)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat((awaitItem() as? MatrixRoomMembersState.Ready)?.roomMembers).isEmpty()
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
assertThat((awaitItem() as? RoomMembersState.Ready)?.roomMembers).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,9 +77,9 @@ class RoomMemberListFetcherTest {
|
|||
val fetcher = RoomMemberListFetcher(room, Dispatchers.Default)
|
||||
fetcher.membersFlow.test {
|
||||
fetcher.fetchRoomMembers(source = CACHE)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Error::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Error::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,11 +100,11 @@ class RoomMemberListFetcherTest {
|
|||
fetcher.fetchRoomMembers(source = CACHE)
|
||||
|
||||
// Initial state
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
// Started loading cached members
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
// Finished loading cached members
|
||||
assertThat((awaitItem() as? MatrixRoomMembersState.Ready)?.roomMembers).hasSize(3)
|
||||
assertThat((awaitItem() as? RoomMembersState.Ready)?.roomMembers).hasSize(3)
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
|
|
@ -126,9 +126,9 @@ class RoomMemberListFetcherTest {
|
|||
fetcher.membersFlow.test {
|
||||
fetcher.fetchRoomMembers(source = SERVER)
|
||||
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat((awaitItem() as? MatrixRoomMembersState.Ready)?.roomMembers?.size).isEqualTo(3)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
assertThat((awaitItem() as? RoomMembersState.Ready)?.roomMembers?.size).isEqualTo(3)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,9 +140,9 @@ class RoomMemberListFetcherTest {
|
|||
fetcher.membersFlow.test {
|
||||
fetcher.fetchRoomMembers(source = SERVER)
|
||||
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Error::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Error::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,20 +167,20 @@ class RoomMemberListFetcherTest {
|
|||
fetcher.membersFlow.test {
|
||||
fetcher.fetchRoomMembers(source = CACHE_AND_SERVER)
|
||||
// Initial
|
||||
assertThat(awaitItem()).isInstanceOf(MatrixRoomMembersState.Unknown::class.java)
|
||||
assertThat(awaitItem()).isInstanceOf(RoomMembersState.Unknown::class.java)
|
||||
// Loading cached
|
||||
awaitItem().let { pending ->
|
||||
assertThat(pending).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat(pending).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
assertThat(pending.roomMembers()).isEmpty()
|
||||
}
|
||||
// Loaded cached
|
||||
awaitItem().let { cached ->
|
||||
assertThat(cached).isInstanceOf(MatrixRoomMembersState.Pending::class.java)
|
||||
assertThat(cached).isInstanceOf(RoomMembersState.Pending::class.java)
|
||||
assertThat(cached.roomMembers()).hasSize(1)
|
||||
}
|
||||
// Start loading new
|
||||
awaitItem().let { ready ->
|
||||
assertThat(ready).isInstanceOf(MatrixRoomMembersState.Ready::class.java)
|
||||
assertThat(ready).isInstanceOf(RoomMembersState.Ready::class.java)
|
||||
assertThat(ready.roomMembers()).hasSize(3)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
package io.element.android.libraries.matrix.impl.room.powerlevels
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomPowerLevels
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ class RoomPowerLevelsMapperTest {
|
|||
)
|
||||
)
|
||||
).isEqualTo(
|
||||
MatrixRoomPowerLevels(
|
||||
RoomPowerLevels(
|
||||
ban = 1,
|
||||
invite = 2,
|
||||
kick = 3,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearch
|
|||
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RustRoomDirectoryListTest {
|
||||
class RustBaseRoomDirectoryListTest {
|
||||
@Test
|
||||
fun `check that the state emits the expected values`() = runTest {
|
||||
val roomDirectorySearch = FakeRustRoomDirectorySearch()
|
||||
|
|
@ -12,7 +12,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class RustRoomDirectoryServiceTest {
|
||||
class RustBaseRoomDirectoryServiceTest {
|
||||
@Test
|
||||
fun test() = runTest {
|
||||
val client = FakeRustClient()
|
||||
|
|
@ -23,7 +23,7 @@ import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
|
|||
import org.matrix.rustcomponents.sdk.RoomListService as RustRoomListService
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RustRoomListServiceTest {
|
||||
class RustBaseRoomListServiceTest {
|
||||
@Test
|
||||
fun `syncIndicator should emit the expected values`() = runTest {
|
||||
val roomListService = FakeRustRoomListService()
|
||||
|
|
@ -12,7 +12,7 @@ import app.cash.turbine.test
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
|
|
@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListS
|
|||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimeline
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineDiff
|
||||
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||
|
|
@ -103,7 +103,7 @@ private fun TestScope.createRustTimeline(
|
|||
inner: InnerTimeline,
|
||||
mode: Timeline.Mode = Timeline.Mode.LIVE,
|
||||
systemClock: SystemClock = FakeSystemClock(),
|
||||
matrixRoom: MatrixRoom = FakeMatrixRoom().apply { givenRoomInfo(aRoomInfo()) },
|
||||
joinedRoom: JoinedRoom = FakeJoinedRoom().apply { givenRoomInfo(aRoomInfo()) },
|
||||
coroutineScope: CoroutineScope = backgroundScope,
|
||||
dispatcher: CoroutineDispatcher = testCoroutineDispatchers().io,
|
||||
roomContentForwarder: RoomContentForwarder = RoomContentForwarder(FakeRustRoomListService()),
|
||||
|
|
@ -114,7 +114,7 @@ private fun TestScope.createRustTimeline(
|
|||
inner = inner,
|
||||
mode = mode,
|
||||
systemClock = systemClock,
|
||||
matrixRoom = matrixRoom,
|
||||
joinedRoom = joinedRoom,
|
||||
coroutineScope = coroutineScope,
|
||||
dispatcher = dispatcher,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue