Merge branch 'develop' into feature/fga/user_moderation_bottomsheet

This commit is contained in:
ganfra 2025-06-03 10:46:12 +02:00
commit cfbbccb09d
647 changed files with 7223 additions and 2888 deletions

View file

@ -9,12 +9,14 @@ package io.element.android.libraries.matrix.api.notification
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.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
data class NotificationData(
val sessionId: SessionId,
val eventId: EventId,
val threadId: ThreadId?,
val roomId: RoomId,

View file

@ -11,5 +11,5 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
interface NotificationService {
suspend fun getNotification(roomId: RoomId, eventId: EventId): Result<NotificationData?>
suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): Result<Map<EventId, NotificationData>>
}

View file

@ -32,7 +32,6 @@ data class RoomInfo(
val isEncrypted: Boolean?,
val joinRule: JoinRule?,
val isSpace: Boolean,
val tombstone: RoomTombstone?,
val isFavorite: Boolean,
val canonicalAlias: RoomAlias?,
val alternativeAliases: ImmutableList<RoomAlias>,
@ -74,12 +73,8 @@ data class RoomInfo(
val pinnedEventIds: ImmutableList<EventId>,
val creator: UserId?,
val historyVisibility: RoomHistoryVisibility,
val successorRoom: SuccessorRoom?,
) {
val aliases: List<RoomAlias>
get() = listOfNotNull(canonicalAlias) + alternativeAliases
}
data class RoomTombstone(
val body: String,
val replacementRoomId: RoomId,
)

View file

@ -22,15 +22,12 @@ class RoomMembershipObserver {
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(extraBufferCapacity = 10)
val updates = _updates.asSharedFlow()
suspend fun notifyUserLeftRoom(roomId: RoomId) {
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.LEFT))
}
suspend fun notifyUserDeclinedInvite(roomId: RoomId) {
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.INVITATION_REJECTED))
}
suspend fun notifyUserCanceledKnock(roomId: RoomId) {
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.KNOCK_RETRACTED))
suspend fun notifyUserLeftRoom(roomId: RoomId, membershipBeforeLeft: CurrentUserMembership) {
val membershipChange = when (membershipBeforeLeft) {
CurrentUserMembership.INVITED -> MembershipChange.INVITATION_REJECTED
CurrentUserMembership.KNOCKED -> MembershipChange.KNOCK_RETRACTED
else -> MembershipChange.LEFT
}
_updates.emit(RoomMembershipUpdate(roomId, false, membershipChange))
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.RoomId
/**
*
* When a room A is tombstoned, it is replaced by a room B. The room A is the
* predecessor of B, and B is the successor of A. This type holds information
* about the successor room.
*
* A room is tombstoned if it has received a m.room.tombstone state event.
*
*/
data class SuccessorRoom(
/**
* The ID of the replacement room.
*/
val roomId: RoomId,
/**
* The message explaining why the room has been tombstoned.
*/
val reason: String?,
)

View file

@ -120,10 +120,6 @@ class RustClientSessionDelegate(
}
}
override fun didRefreshTokens() {
// This is done in `saveSessionInKeychain(Session)` instead.
}
override fun retrieveSessionFromKeychain(userId: String): Session {
// This should never be called, as it's only used for multi-process setups
error("retrieveSessionFromKeychain should never be called for Android")

View file

@ -152,7 +152,7 @@ class RustMatrixClient(
)
private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(innerSyncService)
private val innerNotificationClient = runBlocking { innerClient.notificationClient(notificationProcessSetup) }
private val notificationService = RustNotificationService(innerNotificationClient, dispatchers, clock)
private val notificationService = RustNotificationService(sessionId, innerNotificationClient, dispatchers, clock)
private val notificationSettingsService = RustNotificationSettingsService(innerClient, sessionCoroutineScope, dispatchers)
private val encryptionService = RustEncryptionService(
client = innerClient,
@ -189,7 +189,6 @@ class RustMatrixClient(
private val roomMembershipObserver = RoomMembershipObserver()
private val roomFactory = RustRoomFactory(
innerClient = innerClient,
roomListService = roomListService,
innerRoomListService = innerRoomListService,
sessionId = sessionId,

View file

@ -72,8 +72,9 @@ class RustMatrixClientFactory @Inject constructor(
suspend fun create(client: Client): RustMatrixClient {
val (anonymizedAccessToken, anonymizedRefreshToken) = client.session().anonymizedTokens()
client.setUtdDelegate(UtdTracker(analyticsService))
val syncService = client.syncService()
.withUtdHook(UtdTracker(analyticsService))
.withOfflineMode()
.finish()

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.bool.orFalse
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.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
@ -25,6 +26,7 @@ class NotificationMapper(
private val notificationContentMapper = NotificationContentMapper()
fun map(
sessionId: SessionId,
eventId: EventId,
roomId: RoomId,
notificationItem: NotificationItem
@ -35,6 +37,7 @@ class NotificationMapper(
activeMembersCount = item.roomInfo.joinedMembersCount.toInt(),
)
NotificationData(
sessionId = sessionId,
eventId = eventId,
// FIXME once the `NotificationItem` in the SDK returns the thread id
threadId = null,

View file

@ -10,28 +10,45 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
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.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
import timber.log.Timber
class RustNotificationService(
private val sessionId: SessionId,
private val notificationClient: NotificationClient,
private val dispatchers: CoroutineDispatchers,
clock: SystemClock,
) : NotificationService {
private val notificationMapper: NotificationMapper = NotificationMapper(clock)
override suspend fun getNotification(
roomId: RoomId,
eventId: EventId,
): Result<NotificationData?> = withContext(dispatchers.io) {
override suspend fun getNotifications(
ids: Map<RoomId, List<EventId>>
): Result<Map<EventId, NotificationData>> = withContext(dispatchers.io) {
runCatching {
val item = notificationClient.getNotification(roomId.value, eventId.value)
item?.use {
notificationMapper.map(eventId, roomId, it)
val requests = ids.map { (roomId, eventIds) ->
NotificationItemsRequest(
roomId = roomId.value,
eventIds = eventIds.map { it.value }
)
}
val items = notificationClient.getNotifications(requests)
buildMap {
val eventIds = requests.flatMap { it.eventIds }
for (eventId in eventIds) {
val item = items[eventId]
if (item != null) {
val roomId = RoomId(requests.find { it.eventIds.contains(eventId) }?.roomId!!)
put(EventId(eventId), notificationMapper.map(sessionId, EventId(eventId), roomId, item))
} else {
Timber.e("Could not retrieve event for notification with $eventId")
}
}
}
}
}

View file

@ -8,7 +8,9 @@
package io.element.android.libraries.matrix.impl.proxy
import android.content.Context
import android.net.ConnectivityManager
import android.provider.Settings
import androidx.core.content.getSystemService
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
@ -32,6 +34,13 @@ class DefaultProxyProvider @Inject constructor(
private val context: Context
) : ProxyProvider {
override fun provides(): String? {
val defaultProxy = context.getSystemService<ConnectivityManager>()?.defaultProxy
if (defaultProxy == null) {
// Note: can be tested by running:
// adb shell settings put global http_proxy :0
Timber.d("No default proxy")
return null
}
return Settings.Global.getString(context.contentResolver, Settings.Global.HTTP_PROXY)
?.also {
Timber.d("Using global proxy")

View file

@ -212,6 +212,7 @@ class JoinedRustRoom(
internalIdPrefix = internalIdPrefix,
dateDividerMode = dateDividerMode,
trackReadReceipts = trackReadReceipts,
reportUtds = true,
)
).let { innerTimeline ->
val mode = when (createTimelineParams) {
@ -416,7 +417,6 @@ class JoinedRustRoom(
RustWidgetDriver(
widgetSettings = widgetSettings,
room = innerRoom,
joinedRustRoom = this,
widgetCapabilitiesProvider = object : WidgetCapabilitiesProvider {
override fun acquireCapabilities(capabilities: WidgetCapabilities): WidgetCapabilities {
return getElementCallRequiredPermissions(sessionId.value, baseRoom.deviceId.value)

View file

@ -11,7 +11,6 @@ import io.element.android.libraries.core.coroutine.parallelMap
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.room.ForwardEventException
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
import io.element.android.libraries.matrix.impl.timeline.runWithTimelineListenerRegistered
import kotlinx.coroutines.CancellationException
@ -49,10 +48,7 @@ class RoomContentForwarder(
val content = (messageLikeContent.kind as? MsgLikeKind.Message)?.content
?: throw ForwardEventException(toRoomIds)
val targetSlidingSyncRooms = toRoomIds.mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
val targetRooms = targetSlidingSyncRooms.map { slidingSyncRoom ->
slidingSyncRoom.use { it.fullRoomWithTimeline(null) }
}
val targetRooms = toRoomIds.mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
val failedForwardingTo = mutableSetOf<RoomId>()
targetRooms.parallelMap { room ->
room.use { targetRoom ->

View file

@ -14,7 +14,7 @@ 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.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomTombstone
import io.element.android.libraries.matrix.api.room.SuccessorRoom
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.impl.room.history.map
import io.element.android.libraries.matrix.impl.room.join.map
@ -28,6 +28,7 @@ import uniffi.matrix_sdk_base.EncryptionState
import org.matrix.rustcomponents.sdk.Membership as RustMembership
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
import org.matrix.rustcomponents.sdk.SuccessorRoom as RustSuccessorRoom
class RoomInfoMapper {
fun map(rustRoomInfo: RustRoomInfo): RoomInfo = rustRoomInfo.let {
@ -47,9 +48,6 @@ class RoomInfoMapper {
},
joinRule = it.joinRule?.map(),
isSpace = it.isSpace,
tombstone = it.tombstone?.let {
RoomTombstone(it.body, RoomId(it.replacementRoomId))
},
isFavorite = it.isFavourite,
canonicalAlias = it.canonicalAlias?.let(::RoomAlias),
alternativeAliases = it.alternativeAliases.map(::RoomAlias).toImmutableList(),
@ -71,6 +69,7 @@ class RoomInfoMapper {
numUnreadMentions = it.numUnreadMentions.toLong(),
numUnreadNotifications = it.numUnreadNotifications.toLong(),
historyVisibility = it.historyVisibility.map(),
successorRoom = it.successorRoom?.map(),
)
}
}
@ -89,6 +88,11 @@ fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) {
RustRoomNotificationMode.MUTE -> RoomNotificationMode.MUTE
}
fun RustSuccessorRoom.map(): SuccessorRoom = SuccessorRoom(
roomId = RoomId(roomId),
reason = reason,
)
/**
* Map a RoomHero to a MatrixUser. There is not need to create a RoomHero type on the application side.
*/

View file

@ -137,10 +137,11 @@ class RustBaseRoom(
}
override suspend fun leave(): Result<Unit> = withContext(roomDispatcher) {
val membershipBeforeLeft = roomInfoFlow.value.currentUserMembership
runCatching {
innerRoom.leave()
}.onSuccess {
roomMembershipObserver.notifyUserLeftRoom(roomId)
roomMembershipObserver.notifyUserLeftRoom(roomId, membershipBeforeLeft)
}
}

View file

@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
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.room.preview.RoomPreviewInfoMapper
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
@ -28,10 +27,12 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.DateDividerMode
import org.matrix.rustcomponents.sdk.Membership
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.TimelineConfiguration
import org.matrix.rustcomponents.sdk.TimelineFilter
import org.matrix.rustcomponents.sdk.TimelineFocus
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
@ -39,7 +40,6 @@ import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
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,
@ -79,16 +79,11 @@ class RustRoomFactory(
Timber.d("Room factory is destroyed, returning null for $roomId")
return@withContext null
}
val roomListItem = awaitRoomListItem(roomId) ?: return@withContext null
getBaseRoom(roomListItem)
val room = awaitRoomInRoomList(roomId) ?: return@withContext null
getBaseRoom(room)
}
}
private suspend fun getBaseRoom(roomListItem: RoomListItem): RustBaseRoom? {
val sdkRoom = innerClient.getRoom(roomListItem.id()) ?: return null
return getBaseRoom(sdkRoom)
}
private suspend fun getBaseRoom(sdkRoom: Room): RustBaseRoom {
val initialRoomInfo = sdkRoom.roomInfo()
return RustBaseRoom(
@ -110,18 +105,27 @@ class RustRoomFactory(
Timber.d("Room factory is destroyed, returning null for $roomId")
return@withContext null
}
val roomListItem = awaitRoomListItem(roomId) ?: return@withContext null
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withContext null
if (roomListItem.membership() == Membership.JOINED) {
// Init the live timeline in the SDK from the RoomListItem
val sdkRoom = roomListItem.fullRoomWithTimeline(eventFilters)
if (sdkRoom.membership() == Membership.JOINED) {
// Init the live timeline in the SDK from the Room
val timeline = sdkRoom.timelineWithConfiguration(
TimelineConfiguration(
focus = TimelineFocus.Live,
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
internalIdPrefix = "live",
dateDividerMode = DateDividerMode.DAILY,
trackReadReceipts = true,
reportUtds = true,
)
)
GetRoomResult.Joined(
JoinedRustRoom(
baseRoom = getBaseRoom(sdkRoom),
notificationSettingsService = notificationSettingsService,
roomContentForwarder = roomContentForwarder,
liveInnerTimeline = sdkRoom.timeline(),
liveInnerTimeline = timeline,
coroutineDispatchers = dispatchers,
systemClock = systemClock,
featureFlagService = featureFlagService,
@ -129,7 +133,7 @@ class RustRoomFactory(
)
} else {
val preview = try {
roomListItem.previewRoom(via = emptyList())
sdkRoom.previewRoom(via = emptyList())
} catch (e: Exception) {
Timber.e(e, "Failed to get room preview for $roomId")
return@withContext null
@ -138,7 +142,7 @@ class RustRoomFactory(
GetRoomResult.NotJoined(
NotJoinedRustRoom(
sessionId = sessionId,
localRoom = getBaseRoom(roomListItem),
localRoom = getBaseRoom(sdkRoom),
previewInfo = RoomPreviewInfoMapper.map(preview.info()),
)
)
@ -147,22 +151,22 @@ class RustRoomFactory(
}
/**
* Get the Rust room list item for a room, retrying after the room list is loaded if necessary.
* Get the Rust room for a room, retrying after the room list is loaded if necessary.
*/
private suspend fun awaitRoomListItem(roomId: RoomId): RoomListItem? {
var roomListItem = innerRoomListService.roomOrNull(roomId.value)
if (roomListItem == null) {
private suspend fun awaitRoomInRoomList(roomId: RoomId): Room? {
var sdkRoom = innerRoomListService.roomOrNull(roomId.value)
if (sdkRoom == null) {
// ... otherwise, lets wait for the SS to load all rooms and check again.
roomListService.allRooms.awaitLoaded()
roomListItem = innerRoomListService.roomOrNull(roomId.value)
sdkRoom = innerRoomListService.roomOrNull(roomId.value)
}
if (roomListItem == null) {
if (sdkRoom == null) {
Timber.d("Room not found for $roomId")
return null
}
return roomListItem
return sdkRoom
}
}

View file

@ -19,11 +19,11 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind
import org.matrix.rustcomponents.sdk.RoomListEntriesListener
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListInterface
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomListLoadingState
import org.matrix.rustcomponents.sdk.RoomListLoadingStateListener
import org.matrix.rustcomponents.sdk.RoomListServiceInterface
@ -114,7 +114,7 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncI
)
}.buffer(Channel.UNLIMITED)
internal fun RoomListServiceInterface.roomOrNull(roomId: String): RoomListItem? {
internal fun RoomListServiceInterface.roomOrNull(roomId: String): Room? {
return tryOrNull(
onError = { Timber.e(it, "Failed finding room with id=$roomId.") }
) {

View file

@ -1,20 +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.roomlist
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
/** Returns a `Room` with an initialized timeline using the given [filter]. */
suspend fun RoomListItem.fullRoomWithTimeline(filter: TimelineEventTypeFilter? = null): Room {
if (!isTimelineInitialized()) {
initTimeline(filter, "live")
}
return fullRoom()
}

View file

@ -10,16 +10,16 @@ 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.RoomInfoMapper
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.use
class RoomSummaryFactory(
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
private val roomInfoMapper: RoomInfoMapper = RoomInfoMapper(),
) {
suspend fun create(roomListItem: RoomListItem): RoomSummary {
val roomInfo = roomListItem.roomInfo().let(roomInfoMapper::map)
val latestRoomMessage = roomListItem.latestEvent().use { event ->
suspend fun create(room: Room): RoomSummary {
val roomInfo = room.roomInfo().let(roomInfoMapper::map)
val latestRoomMessage = room.latestEvent().use { event ->
roomMessageFactory.create(event)
}
return RoomSummary(

View file

@ -12,8 +12,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow
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.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomListServiceInterface
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
@ -95,20 +95,16 @@ class RoomSummaryListProcessor(
}
}
private suspend fun buildSummaryForRoomListEntry(entry: RoomListItem): RoomSummary {
return buildRoomSummaryForRoomListItem(entry)
private suspend fun buildSummaryForRoomListEntry(entry: Room): RoomSummary {
return entry.use { roomSummaryDetailsFactory.create(room = it) }
}
private suspend fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary? {
return roomListService.roomOrNull(identifier)?.use { roomListItem ->
buildRoomSummaryForRoomListItem(roomListItem)
return roomListService.roomOrNull(identifier)?.let { room ->
buildSummaryForRoomListEntry(room)
}
}
private suspend fun buildRoomSummaryForRoomListItem(roomListItem: RoomListItem): RoomSummary {
return roomSummaryDetailsFactory.create(roomListItem = roomListItem)
}
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(coroutineContext) {
mutex.withLock {
val current = roomSummaries.replayCache.lastOrNull()

View file

@ -53,4 +53,5 @@ fun TracingConfiguration.map(): org.matrix.rustcomponents.sdk.TracingConfigurati
extraTargets = extraTargets,
traceLogPacks = traceLogPacks.map(),
writeToFiles = writesToFilesConfiguration.toTracingFileConfiguration(),
sentryDsn = null,
)

View file

@ -9,7 +9,6 @@ package io.element.android.libraries.matrix.impl.widget
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.room.JoinedRustRoom
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -25,7 +24,6 @@ import kotlin.coroutines.coroutineContext
class RustWidgetDriver(
widgetSettings: MatrixWidgetSettings,
private val room: Room,
private val joinedRustRoom: JoinedRustRoom,
private val widgetCapabilitiesProvider: WidgetCapabilitiesProvider,
) : MatrixWidgetDriver {
// It's important to have extra capacity here to make sure we don't drop any messages
@ -71,6 +69,5 @@ class RustWidgetDriver(
override fun close() {
receiveMessageJob?.cancel()
driverAndHandle.driver.close()
joinedRustRoom.destroy()
}
}

View file

@ -17,7 +17,7 @@ import org.matrix.rustcomponents.sdk.RoomHistoryVisibility
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomNotificationMode
import org.matrix.rustcomponents.sdk.RoomTombstoneInfo
import org.matrix.rustcomponents.sdk.SuccessorRoom
import uniffi.matrix_sdk_base.EncryptionState
fun aRustRoomInfo(
@ -30,7 +30,6 @@ fun aRustRoomInfo(
isDirect: Boolean = false,
isPublic: Boolean = false,
isSpace: Boolean = false,
tombstone: RoomTombstoneInfo? = null,
isFavourite: Boolean = false,
canonicalAlias: String? = null,
alternativeAliases: List<String> = listOf(),
@ -54,6 +53,7 @@ fun aRustRoomInfo(
roomCreator: UserId? = null,
joinRule: JoinRule? = null,
historyVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Joined,
successorRoom: SuccessorRoom? = null,
) = RoomInfo(
id = id,
displayName = displayName,
@ -64,7 +64,6 @@ fun aRustRoomInfo(
isDirect = isDirect,
isPublic = isPublic,
isSpace = isSpace,
tombstone = tombstone,
isFavourite = isFavourite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases,
@ -87,5 +86,6 @@ fun aRustRoomInfo(
pinnedEventIds = pinnedEventIds,
creator = roomCreator?.value,
joinRule = joinRule,
historyVisibility = historyVisibility
historyVisibility = historyVisibility,
successorRoom = successorRoom,
)

View file

@ -25,6 +25,7 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.SyncServiceBuilder
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
class FakeRustClient(
private val userId: String = A_USER_ID.value,
@ -34,6 +35,7 @@ class FakeRustClient(
private val encryption: Encryption = FakeRustEncryption(),
private val session: Session = aRustSession(),
private val clearCachesResult: () -> Unit = { lambdaError() },
private val withUtdHook: (UnableToDecryptDelegate) -> Unit = { lambdaError() },
private val closeResult: () -> Unit = {},
) : Client(NoPointer) {
override fun userId(): String = userId
@ -58,5 +60,6 @@ class FakeRustClient(
override suspend fun deletePusher(identifiers: PusherIdentifiers) = Unit
override suspend fun clearCaches() = simulateLongTask { clearCachesResult() }
override suspend fun setUtdDelegate(utdDelegate: UnableToDecryptDelegate) = withUtdHook(utdDelegate)
override fun close() = closeResult()
}

View file

@ -47,6 +47,6 @@ class FakeRustClientBuilder : ClientBuilder(NoPointer) {
}
override suspend fun build(): Client {
return FakeRustClient()
return FakeRustClient(withUtdHook = {})
}
}

View file

@ -7,15 +7,15 @@
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.tests.testutils.simulateLongTask
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.NotificationItem
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
class FakeRustNotificationClient(
var notificationItemResult: NotificationItem? = null
var notificationItemResult: Map<String, NotificationItem> = emptyMap(),
) : NotificationClient(NoPointer) {
override suspend fun getNotification(roomId: String, eventId: String): NotificationItem? = simulateLongTask {
notificationItemResult
override suspend fun getNotifications(requests: List<NotificationItemsRequest>): Map<String, NotificationItem> {
return notificationItemResult
}
}

View file

@ -8,16 +8,22 @@
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomInfo
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.tests.testutils.lambda.lambdaError
import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomMembersIterator
class FakeRustRoom(
private val roomId: RoomId = A_ROOM_ID,
private val getMembers: () -> RoomMembersIterator = { lambdaError() },
private val getMembersNoSync: () -> RoomMembersIterator = { lambdaError() },
private val leaveLambda: () -> Unit = { lambdaError() },
private val latestEventLambda: () -> EventTimelineItem? = { lambdaError() },
private val roomInfo: RoomInfo = aRustRoomInfo(id = roomId.value),
) : Room(NoPointer) {
override fun id(): String {
return roomId.value
@ -31,6 +37,18 @@ class FakeRustRoom(
return getMembersNoSync()
}
override suspend fun leave() {
leaveLambda()
}
override suspend fun roomInfo(): RoomInfo {
return roomInfo
}
override suspend fun latestEvent(): EventTimelineItem? {
return latestEventLambda()
}
override fun close() {
// No-op
}

View file

@ -1,33 +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.fixtures.fakes
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomInfo
import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomListItem
class FakeRustRoomListItem(
private val roomId: RoomId,
private val roomInfo: RoomInfo = aRustRoomInfo(id = roomId.value),
private val latestEvent: EventTimelineItem? = null,
) : RoomListItem(NoPointer) {
override fun id(): String {
return roomId.value
}
override suspend fun roomInfo(): RoomInfo {
return roomInfo
}
override suspend fun latestEvent(): EventTimelineItem? {
return latestEvent
}
}

View file

@ -10,10 +10,8 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.SyncService
import org.matrix.rustcomponents.sdk.SyncServiceBuilder
import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
class FakeRustSyncServiceBuilder : SyncServiceBuilder(NoPointer) {
override suspend fun withUtdHook(delegate: UnableToDecryptDelegate): SyncServiceBuilder = this
override fun withOfflineMode(): SyncServiceBuilder = this
override suspend fun finish(): SyncService = FakeRustSyncService()
}

View file

@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustNotificat
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
@ -28,12 +29,12 @@ class RustNotificationServiceTest {
@Test
fun test() = runTest {
val notificationClient = FakeRustNotificationClient(
notificationItemResult = aRustNotificationItem(),
notificationItemResult = mapOf(AN_EVENT_ID.value to aRustNotificationItem()),
)
val sut = createRustNotificationService(
notificationClient = notificationClient,
)
val result = sut.getNotification(A_ROOM_ID, AN_EVENT_ID).getOrThrow()!!
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!
assertThat(result.isEncrypted).isTrue()
assertThat(result.content).isEqualTo(
NotificationContent.MessageLike.RoomMessage(
@ -51,6 +52,7 @@ class RustNotificationServiceTest {
clock: SystemClock = FakeSystemClock(),
) =
RustNotificationService(
sessionId = A_SESSION_ID,
notificationClient = notificationClient,
dispatchers = testCoroutineDispatchers(),
clock = clock,

View file

@ -54,7 +54,7 @@ class RoomInfoMapperTest {
isPublic = false,
isSpace = false,
joinRule = RustJoinRule.Invite,
tombstone = null,
successorRoom = null,
isFavourite = false,
canonicalAlias = A_ROOM_ALIAS.value,
alternativeAliases = listOf(A_ROOM_ALIAS.value),
@ -90,7 +90,6 @@ class RoomInfoMapperTest {
isDirect = true,
isEncrypted = true,
isSpace = false,
tombstone = null,
isFavorite = false,
joinRule = JoinRule.Invite,
canonicalAlias = A_ROOM_ALIAS,
@ -120,6 +119,7 @@ class RoomInfoMapperTest {
numUnreadNotifications = 13L,
numUnreadMentions = 14L,
historyVisibility = RoomHistoryVisibility.Joined,
successorRoom = null
)
)
}
@ -139,7 +139,7 @@ class RoomInfoMapperTest {
isPublic = true,
joinRule = null,
isSpace = false,
tombstone = null,
successorRoom = null,
isFavourite = true,
canonicalAlias = null,
alternativeAliases = emptyList(),
@ -175,7 +175,7 @@ class RoomInfoMapperTest {
isDirect = false,
joinRule = null,
isSpace = false,
tombstone = null,
successorRoom = null,
isFavorite = true,
canonicalAlias = null,
alternativeAliases = emptyList<RoomAlias>().toPersistentList(),

View file

@ -7,15 +7,21 @@
package io.element.android.libraries.matrix.impl.room
import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoom
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.libraries.matrix.test.A_DEVICE_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.isActive
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@ -24,32 +30,121 @@ import org.junit.Test
class RustBaseRoomTest {
@Test
fun `RustBaseRoom should cancel the room coroutine scope when it is destroyed`() = runTest {
val rustBaseRoom = createRustBaseRoom(
// Not using backgroundScope here, but the test scope
sessionCoroutineScope = this
)
val rustBaseRoom = createRustBaseRoom()
assertThat(rustBaseRoom.roomCoroutineScope.isActive).isTrue()
rustBaseRoom.destroy()
assertThat(rustBaseRoom.roomCoroutineScope.isActive).isFalse()
}
@Test
fun `when currentUserMembership=JOINED and user leave room succeed then roomMembershipObserver emits change as LEFT`() = runTest {
val roomMembershipObserver = RoomMembershipObserver()
val rustBaseRoom = createRustBaseRoom(
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.JOINED),
innerRoom = FakeRustRoom(
leaveLambda = {
// Simulate a successful leave
}
),
roomMembershipObserver = roomMembershipObserver,
)
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
val membershipUpdate = awaitItem()
assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId)
assertThat(membershipUpdate.isUserInRoom).isFalse()
assertThat(membershipUpdate.change).isEqualTo(MembershipChange.LEFT)
}
}
@Test
fun `when currentUserMembership=KNOCKED and user leave room succeed then roomMembershipObserver emits change as KNOCK_RETRACTED`() = runTest {
val roomMembershipObserver = RoomMembershipObserver()
val rustBaseRoom = createRustBaseRoom(
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.KNOCKED),
innerRoom = FakeRustRoom(
leaveLambda = {
// Simulate a successful leave
}
),
roomMembershipObserver = roomMembershipObserver,
)
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
val membershipUpdate = awaitItem()
assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId)
assertThat(membershipUpdate.isUserInRoom).isFalse()
assertThat(membershipUpdate.change).isEqualTo(MembershipChange.KNOCK_RETRACTED)
}
}
@Test
fun `when currentUserMembership=INVITED and user leave room succeed then roomMembershipObserver emits change as INVITATION_REJECTED`() = runTest {
val roomMembershipObserver = RoomMembershipObserver()
val rustBaseRoom = createRustBaseRoom(
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED),
innerRoom = FakeRustRoom(
leaveLambda = {
// Simulate a successful leave
}
),
roomMembershipObserver = roomMembershipObserver,
)
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
val membershipUpdate = awaitItem()
assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId)
assertThat(membershipUpdate.isUserInRoom).isFalse()
assertThat(membershipUpdate.change).isEqualTo(MembershipChange.INVITATION_REJECTED)
}
}
@Test
fun `when user leave room fails then roomMembershipObserver emits nothing`() = runTest {
val roomMembershipObserver = RoomMembershipObserver()
val rustBaseRoom = createRustBaseRoom(
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED),
innerRoom = FakeRustRoom(
leaveLambda = { error("Leave failed") }
),
roomMembershipObserver = roomMembershipObserver,
)
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
// No emit
}
}
private suspend fun TestScope.leaveRoomAndObserveMembershipChange(
roomMembershipObserver: RoomMembershipObserver,
rustBaseRoom: RustBaseRoom,
validate: suspend TurbineTestContext<RoomMembershipObserver.RoomMembershipUpdate>.() -> Unit
) {
val shared = roomMembershipObserver.updates.shareIn(scope = backgroundScope, started = SharingStarted.Eagerly, replay = 1)
rustBaseRoom.leave()
shared.test {
validate()
ensureAllEventsConsumed()
}
rustBaseRoom.destroy()
}
private fun TestScope.createRustBaseRoom(
sessionCoroutineScope: CoroutineScope,
initialRoomInfo: RoomInfo = aRoomInfo(),
innerRoom: FakeRustRoom = FakeRustRoom(),
roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
): RustBaseRoom {
val dispatchers = testCoroutineDispatchers()
return RustBaseRoom(
sessionId = A_SESSION_ID,
deviceId = A_DEVICE_ID,
innerRoom = FakeRustRoom(),
innerRoom = innerRoom,
coroutineDispatchers = dispatchers,
roomSyncSubscriber = RoomSyncSubscriber(
roomListService = FakeRustRoomListService(),
dispatchers = dispatchers,
),
roomMembershipObserver = RoomMembershipObserver(),
sessionCoroutineScope = sessionCoroutineScope,
roomMembershipObserver = roomMembershipObserver,
// Not using backgroundScope here, but the test scope
sessionCoroutineScope = this,
roomInfoMapper = RoomInfoMapper(),
initialRoomInfo = aRoomInfo(),
initialRoomInfo = initialRoomInfo,
)
}
}

View file

@ -13,7 +13,6 @@ import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomDescription
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomDirectorySearch
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@ -31,7 +30,6 @@ class RustBaseRoomDirectoryListTest {
val mapper = RoomDescriptionMapper()
val sut = createRustRoomDirectoryList(
roomDirectorySearch = roomDirectorySearch,
scope = backgroundScope,
)
// Let the mxCallback be ready
runCurrent()
@ -81,10 +79,9 @@ class RustBaseRoomDirectoryListTest {
private fun TestScope.createRustRoomDirectoryList(
roomDirectorySearch: RoomDirectorySearch = FakeRustRoomDirectorySearch(),
scope: CoroutineScope,
) = RustRoomDirectoryList(
inner = roomDirectorySearch,
coroutineScope = scope,
coroutineScope = backgroundScope,
coroutineContext = StandardTestDispatcher(testScheduler),
)
}

View file

@ -8,8 +8,9 @@
package io.element.android.libraries.matrix.impl.roomlist
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListItem
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoom
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
@ -30,7 +31,7 @@ class RoomSummaryListProcessorTest {
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
val newEntry = FakeRustRoomListItem(A_ROOM_ID_2)
val newEntry = aRustRoom(A_ROOM_ID_2)
processor.postUpdate(listOf(RoomListEntriesUpdate.Append(listOf(newEntry, newEntry, newEntry))))
assertThat(summaries.value.count()).isEqualTo(4)
@ -41,7 +42,7 @@ class RoomSummaryListProcessorTest {
fun `PushBack adds a new entry at the end of the list`() = runTest {
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(FakeRustRoomListItem(A_ROOM_ID_2))))
processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(aRustRoom(A_ROOM_ID_2))))
assertThat(summaries.value.count()).isEqualTo(2)
assertThat(summaries.value.last().roomId).isEqualTo(A_ROOM_ID_2)
@ -51,7 +52,7 @@ class RoomSummaryListProcessorTest {
fun `PushFront inserts a new entry at the start of the list`() = runTest {
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(FakeRustRoomListItem(A_ROOM_ID_2))))
processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(aRustRoom(A_ROOM_ID_2))))
assertThat(summaries.value.count()).isEqualTo(2)
assertThat(summaries.value.first().roomId).isEqualTo(A_ROOM_ID_2)
@ -63,7 +64,7 @@ class RoomSummaryListProcessorTest {
val processor = createProcessor()
val index = 0
processor.postUpdate(listOf(RoomListEntriesUpdate.Set(index.toUInt(), FakeRustRoomListItem(A_ROOM_ID_2))))
processor.postUpdate(listOf(RoomListEntriesUpdate.Set(index.toUInt(), aRustRoom(A_ROOM_ID_2))))
assertThat(summaries.value.count()).isEqualTo(1)
assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_2)
@ -75,7 +76,7 @@ class RoomSummaryListProcessorTest {
val processor = createProcessor()
val index = 0
processor.postUpdate(listOf(RoomListEntriesUpdate.Insert(index.toUInt(), FakeRustRoomListItem(A_ROOM_ID_2))))
processor.postUpdate(listOf(RoomListEntriesUpdate.Insert(index.toUInt(), aRustRoom(A_ROOM_ID_2))))
assertThat(summaries.value.count()).isEqualTo(2)
assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_2)
@ -163,12 +164,17 @@ class RoomSummaryListProcessorTest {
val processor = createProcessor()
val index = 0
processor.postUpdate(listOf(RoomListEntriesUpdate.Reset(listOf(FakeRustRoomListItem(A_ROOM_ID_3)))))
processor.postUpdate(listOf(RoomListEntriesUpdate.Reset(listOf(aRustRoom(A_ROOM_ID_3)))))
assertThat(summaries.value.count()).isEqualTo(1)
assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_3)
}
private fun aRustRoom(roomId: RoomId = A_ROOM_ID) = FakeRustRoom(
roomId = roomId,
latestEventLambda = { null },
)
private fun TestScope.createProcessor() = RoomSummaryListProcessor(
summaries,
FakeRustRoomListService(),

View file

@ -12,7 +12,6 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@ -28,7 +27,6 @@ class RustBaseRoomListServiceTest {
fun `syncIndicator should emit the expected values`() = runTest {
val roomListService = FakeRustRoomListService()
val sut = createRustRoomListService(
sessionCoroutineScope = backgroundScope,
roomListService = roomListService,
)
// Give time for mxCallback to setup
@ -44,18 +42,17 @@ class RustBaseRoomListServiceTest {
}
private fun TestScope.createRustRoomListService(
sessionCoroutineScope: CoroutineScope,
roomListService: RustRoomListService = FakeRustRoomListService(),
) = RustRoomListService(
innerRoomListService = roomListService,
sessionDispatcher = StandardTestDispatcher(testScheduler),
roomListFactory = RoomListFactory(
innerRoomListService = roomListService,
sessionCoroutineScope = sessionCoroutineScope,
sessionCoroutineScope = backgroundScope,
),
roomSyncSubscriber = RoomSyncSubscriber(
roomListService = roomListService,
dispatchers = testCoroutineDispatchers(),
),
sessionCoroutineScope = sessionCoroutineScope,
sessionCoroutineScope = backgroundScope,
)

View file

@ -17,7 +17,6 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineI
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -38,7 +37,6 @@ class TimelineItemsSubscriberTest {
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
val timeline = FakeRustTimeline()
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
timeline = timeline,
timelineItems = timelineItems,
)
@ -59,7 +57,6 @@ class TimelineItemsSubscriberTest {
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
val timeline = FakeRustTimeline()
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
timeline = timeline,
timelineItems = timelineItems,
)
@ -81,7 +78,6 @@ class TimelineItemsSubscriberTest {
val timeline = FakeRustTimeline()
val onNewSyncedEventRecorder = lambdaRecorder<Unit> { }
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
timeline = timeline,
timelineItems = timelineItems,
onNewSyncedEvent = onNewSyncedEventRecorder,
@ -109,9 +105,7 @@ class TimelineItemsSubscriberTest {
@Test
fun `multiple subscriptions does not have side effect`() = runTest {
val timelineItemsSubscriber = createTimelineItemsSubscriber(
coroutineScope = backgroundScope,
)
val timelineItemsSubscriber = createTimelineItemsSubscriber()
timelineItemsSubscriber.subscribeIfNeeded()
timelineItemsSubscriber.subscribeIfNeeded()
timelineItemsSubscriber.unsubscribeIfNeeded()
@ -120,7 +114,6 @@ class TimelineItemsSubscriberTest {
}
private fun TestScope.createTimelineItemsSubscriber(
coroutineScope: CoroutineScope,
timeline: Timeline = FakeRustTimeline(),
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE),
initLatch: CompletableDeferred<Unit> = CompletableDeferred(),
@ -128,7 +121,7 @@ private fun TestScope.createTimelineItemsSubscriber(
onNewSyncedEvent: () -> Unit = { lambdaError() },
): TimelineItemsSubscriber {
return TimelineItemsSubscriber(
timelineCoroutineScope = coroutineScope,
timelineCoroutineScope = backgroundScope,
dispatcher = StandardTestDispatcher(testScheduler),
timeline = timeline,
timelineDiffProcessor = createMatrixTimelineDiffProcessor(timelineItems),

View file

@ -69,6 +69,10 @@ const val A_REDACTION_REASON = "A redaction reason"
const val A_HOMESERVER_URL = "matrix.org"
const val A_HOMESERVER_URL_2 = "matrix-client.org"
const val AN_ACCOUNT_PROVIDER = "matrix.org"
const val AN_ACCOUNT_PROVIDER_2 = "element.io"
const val AN_ACCOUNT_PROVIDER_3 = "other.io"
val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = true, supportsOidcLogin = false)
val A_HOMESERVER_OIDC = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = false, supportsOidcLogin = true)
val A_ROOM_NOTIFICATION_MODE = RoomNotificationMode.MUTE

View file

@ -13,16 +13,13 @@ import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService
class FakeNotificationService : NotificationService {
private var getNotificationResult: Result<NotificationData?> = Result.success(null)
private var getNotificationsResult: Result<Map<EventId, NotificationData>> = Result.success(emptyMap())
fun givenGetNotificationResult(result: Result<NotificationData?>) {
getNotificationResult = result
fun givenGetNotificationsResult(result: Result<Map<EventId, NotificationData>>) {
getNotificationsResult = result
}
override suspend fun getNotification(
roomId: RoomId,
eventId: EventId,
): Result<NotificationData?> {
return getNotificationResult
override suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): Result<Map<EventId, NotificationData>> {
return getNotificationsResult
}
}

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_TIMESTAMP
import io.element.android.libraries.matrix.test.A_USER_NAME_2
@ -27,6 +28,7 @@ fun aNotificationData(
roomDisplayName: String? = A_ROOM_NAME
): NotificationData {
return NotificationData(
sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID,
threadId = threadId,
roomId = A_ROOM_ID,

View file

@ -93,7 +93,15 @@ class FakeBaseRoom(
return powerLevelsResult()
}
override fun destroy() = Unit
private var isDestroyed = false
override fun destroy() {
isDestroyed = true
}
fun assertDestroyed() {
check(isDestroyed) { "Room should be destroyed" }
}
override suspend fun userDisplayName(userId: UserId): Result<String?> = simulateLongTask {
userDisplayNameResult(userId)

View file

@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
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.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomTombstone
import io.element.android.libraries.matrix.api.room.SuccessorRoom
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.user.MatrixUser
@ -39,7 +39,7 @@ fun aRoomInfo(
isEncrypted: Boolean = false,
joinRule: JoinRule? = JoinRule.Public,
isSpace: Boolean = false,
tombstone: RoomTombstone? = null,
successorRoom: SuccessorRoom? = null,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
@ -73,7 +73,7 @@ fun aRoomInfo(
isEncrypted = isEncrypted,
joinRule = joinRule,
isSpace = isSpace,
tombstone = tombstone,
successorRoom = successorRoom,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toImmutableList(),

View file

@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
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.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomTombstone
import io.element.android.libraries.matrix.api.room.SuccessorRoom
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.message.RoomMessage
@ -52,7 +52,7 @@ fun aRoomSummary(
isEncrypted: Boolean = false,
joinRule: JoinRule? = JoinRule.Public,
isSpace: Boolean = false,
tombstone: RoomTombstone? = null,
successorRoom: SuccessorRoom? = null,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
@ -88,7 +88,7 @@ fun aRoomSummary(
isEncrypted = isEncrypted,
joinRule = joinRule,
isSpace = isSpace,
tombstone = tombstone,
successorRoom = successorRoom,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toPersistentList(),