Merge branch 'develop' into feature/fga/csam_preferences_server

This commit is contained in:
ganfra 2025-06-30 21:42:06 +02:00
commit 773fa1657a
623 changed files with 4661 additions and 2049 deletions

View file

@ -131,7 +131,7 @@ class RustMatrixClient(
private val sessionStore: SessionStore,
private val appCoroutineScope: CoroutineScope,
private val sessionDelegate: RustClientSessionDelegate,
innerSyncService: ClientSyncService,
private val innerSyncService: ClientSyncService,
dispatchers: CoroutineDispatchers,
baseCacheDirectory: File,
clock: SystemClock,
@ -541,7 +541,7 @@ class RustMatrixClient(
}
override suspend fun clearCache() {
innerClient.clearCaches()
innerClient.clearCaches(innerSyncService)
destroy()
}

View file

@ -34,6 +34,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import uniffi.matrix_sdk_crypto.CollectStrategy
import uniffi.matrix_sdk_crypto.DecryptionSettings
import uniffi.matrix_sdk_crypto.TrustRequirement
import java.io.File
import javax.inject.Inject
@ -120,12 +121,14 @@ class RustMatrixClientFactory @Inject constructor(
CollectStrategy.ERROR_ON_VERIFIED_USER_PROBLEM
}
)
.roomDecryptionTrustRequirement(
trustRequirement = if (featureFlagService.isFeatureEnabled(FeatureFlags.OnlySignedDeviceIsolationMode)) {
TrustRequirement.CROSS_SIGNED_OR_LEGACY
} else {
TrustRequirement.UNTRUSTED
}
.decryptionSettings(
DecryptionSettings(
senderDeviceTrustRequirement = if (featureFlagService.isFeatureEnabled(FeatureFlags.OnlySignedDeviceIsolationMode)) {
TrustRequirement.CROSS_SIGNED_OR_LEGACY
} else {
TrustRequirement.UNTRUSTED
}
)
)
.enableShareHistoryOnInvite(featureFlagService.isFeatureEnabled(FeatureFlags.EnableKeyShareOnInvite))
.run {

View file

@ -139,7 +139,7 @@ class JoinedRustRoom(
}
init {
val powerLevelChanges = roomInfoFlow.map { it.userPowerLevels }.distinctUntilChanged()
val powerLevelChanges = roomInfoFlow.map { it.roomPowerLevels }.distinctUntilChanged()
val membershipChanges = liveTimeline.membershipChangeEventReceived.onStart { emit(Unit) }
combine(membershipChanges, powerLevelChanges) { _, _ -> }
// Skip initial one

View file

@ -14,12 +14,13 @@ 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.powerlevels.RoomPowerLevels
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
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsValuesMapper
import io.element.android.libraries.matrix.impl.room.tombstone.map
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap
import org.matrix.rustcomponents.sdk.Membership
@ -28,6 +29,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.RoomPowerLevels as RustRoomPowerLevels
class RoomInfoMapper {
fun map(rustRoomInfo: RustRoomInfo): RoomInfo = rustRoomInfo.let {
@ -55,7 +57,7 @@ class RoomInfoMapper {
activeMembersCount = it.activeMembersCount.toLong(),
invitedMembersCount = it.invitedMembersCount.toLong(),
joinedMembersCount = it.joinedMembersCount.toLong(),
userPowerLevels = mapPowerLevels(it.userPowerLevels),
roomPowerLevels = it.powerLevels?.let(::mapPowerLevels),
highlightCount = it.highlightCount.toLong(),
notificationCount = it.notificationCount.toLong(),
userDefinedNotificationMode = it.cachedUserDefinedNotificationMode?.map(),
@ -96,6 +98,9 @@ fun RoomHero.map(): MatrixUser = MatrixUser(
avatarUrl = avatarUrl
)
fun mapPowerLevels(powerLevels: Map<String, Long>): ImmutableMap<UserId, Long> {
return powerLevels.mapKeys { (key, _) -> UserId(key) }.toPersistentMap()
fun mapPowerLevels(roomPowerLevels: RustRoomPowerLevels): RoomPowerLevels {
return RoomPowerLevels(
values = RoomPowerLevelsValuesMapper.map(roomPowerLevels.values()),
users = roomPowerLevels.userPowerLevels().mapKeys { (key, _) -> UserId(key) }.toPersistentMap()
)
}

View file

@ -27,7 +27,7 @@ object RoomPreviewInfoMapper {
roomType = info.roomType.map(),
isHistoryWorldReadable = info.isHistoryWorldReadable.orFalse(),
membership = info.membership?.map(),
joinRule = info.joinRule.map(),
joinRule = info.joinRule?.map(),
)
}
}

View file

@ -28,14 +28,6 @@ internal class MatrixTimelineDiffProcessor(
private val _membershipChangeEventReceived = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val membershipChangeEventReceived: Flow<Unit> = _membershipChangeEventReceived
suspend fun postItems(items: List<TimelineItem>) {
updateTimelineItems {
Timber.v("Update timeline items from postItems (with ${items.size} items) on ${Thread.currentThread()}")
val mappedItems = items.map { it.asMatrixTimelineItem() }
addAll(0, mappedItems)
}
}
suspend fun postDiffs(diffs: List<TimelineDiff>) {
updateTimelineItems {
Timber.v("Update timeline items from postDiffs (with ${diffs.size} items) on ${Thread.currentThread()}")

View file

@ -48,7 +48,6 @@ import io.element.android.libraries.matrix.impl.timeline.postprocessor.TypingNot
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper
import io.element.android.libraries.matrix.impl.util.MessageEventContent
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -95,9 +94,6 @@ class RustTimeline(
private val featureFlagsService: FeatureFlagService,
onNewSyncedEvent: () -> Unit,
) : Timeline {
private val initLatch = CompletableDeferred<Unit>()
private val isTimelineInitialized = MutableStateFlow(false)
private val _timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
@ -119,8 +115,6 @@ class RustTimeline(
timeline = inner,
timelineCoroutineScope = coroutineScope,
timelineDiffProcessor = timelineDiffProcessor,
initLatch = initLatch,
isTimelineInitialized = isTimelineInitialized,
dispatcher = dispatcher,
onNewSyncedEvent = onNewSyncedEvent,
)
@ -181,7 +175,6 @@ class RustTimeline(
// Use NonCancellable to avoid breaking the timeline when the coroutine is cancelled.
override suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> = withContext(NonCancellable) {
withContext(dispatcher) {
initLatch.await()
runCatchingExceptions {
if (!canPaginate(direction)) throw TimelineException.CannotPaginate
updatePaginationStatus(direction) { it.copy(isPaginating = true) }
@ -203,7 +196,6 @@ class RustTimeline(
}
private fun canPaginate(direction: Timeline.PaginationDirection): Boolean {
if (!isTimelineInitialized.value) return false
return when (direction) {
Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus.value.canPaginate
Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.value.canPaginate
@ -215,12 +207,10 @@ class RustTimeline(
backwardPaginationStatus,
forwardPaginationStatus,
joinedRoom.roomInfoFlow.map { it.creator to it.isDm }.distinctUntilChanged(),
isTimelineInitialized,
) { timelineItems,
backwardPaginationStatus,
forwardPaginationStatus,
(roomCreator, isDm),
isTimelineInitialized ->
(roomCreator, isDm) ->
withContext(dispatcher) {
timelineItems
.let { items ->
@ -234,7 +224,6 @@ class RustTimeline(
.let { items ->
loadingIndicatorsPostProcessor.process(
items = items,
isTimelineInitialized = isTimelineInitialized,
hasMoreToLoadBackward = backwardPaginationStatus.hasMoreToLoad,
hasMoreToLoadForward = forwardPaginationStatus.hasMoreToLoad,
)
@ -244,10 +233,7 @@ class RustTimeline(
}
// Keep lastForwardIndicatorsPostProcessor last
.let { items ->
lastForwardIndicatorsPostProcessor.process(
items = items,
isTimelineInitialized = isTimelineInitialized,
)
lastForwardIndicatorsPostProcessor.process(items = items)
}
}
}.onStart {
@ -262,7 +248,6 @@ class RustTimeline(
}
private fun CoroutineScope.fetchMembers() = launch(dispatcher) {
initLatch.await()
try {
inner.fetchMembers()
} catch (exception: Exception) {

View file

@ -8,37 +8,26 @@
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.core.coroutine.childScope
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineChange
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import uniffi.matrix_sdk_ui.EventItemOrigin
private const val INITIAL_MAX_SIZE = 50
/**
* This class is responsible for subscribing to a timeline and post the items/diffs to the timelineDiffProcessor.
* It will also trigger a callback when a new synced event is received.
* It will also handle the initial items and make sure they are posted before any diff.
*/
internal class TimelineItemsSubscriber(
timelineCoroutineScope: CoroutineScope,
dispatcher: CoroutineDispatcher,
private val timeline: Timeline,
private val timelineDiffProcessor: MatrixTimelineDiffProcessor,
private val initLatch: CompletableDeferred<Unit>,
private val isTimelineInitialized: MutableStateFlow<Boolean>,
private val onNewSyncedEvent: () -> Unit,
) {
private var subscriptionCount = 0
@ -57,7 +46,7 @@ internal class TimelineItemsSubscriber(
if (diffs.any { diff -> diff.eventOrigin() == EventItemOrigin.SYNC }) {
onNewSyncedEvent()
}
postDiffs(diffs)
timelineDiffProcessor.postDiffs(diffs)
}
.launchIn(coroutineScope)
}
@ -78,35 +67,4 @@ internal class TimelineItemsSubscriber(
}
subscriptionCount--
}
private suspend fun postItems(items: List<TimelineItem>) = coroutineScope {
if (items.isEmpty()) {
// Makes sure to post empty list if there is no item, so you can handle empty state.
timelineDiffProcessor.postItems(emptyList())
} else {
// Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap.
items.chunked(INITIAL_MAX_SIZE).reversed().forEach {
ensureActive()
timelineDiffProcessor.postItems(it)
}
}
isTimelineInitialized.value = true
initLatch.complete(Unit)
}
private suspend fun postDiffs(diffs: List<TimelineDiff>) {
val diffsToProcess = diffs.toMutableList()
if (!isTimelineInitialized.value) {
val resetDiff = diffsToProcess.firstOrNull { it.change() == TimelineChange.RESET }
if (resetDiff != null) {
// Keep using the postItems logic so we can post the timelineItems asap.
postItems(resetDiff.reset() ?: emptyList())
diffsToProcess.remove(resetDiff)
}
}
initLatch.await()
if (diffsToProcess.isNotEmpty()) {
timelineDiffProcessor.postDiffs(diffsToProcess)
}
}
}

View file

@ -22,9 +22,7 @@ class LastForwardIndicatorsPostProcessor(
fun process(
items: List<MatrixTimelineItem>,
isTimelineInitialized: Boolean,
): List<MatrixTimelineItem> {
if (!isTimelineInitialized) return items
// We don't need to add the last forward indicator if we are not in the FOCUSED_ON_EVENT mode
if (mode != Timeline.Mode.FOCUSED_ON_EVENT) {
return items

View file

@ -16,11 +16,9 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
class LoadingIndicatorsPostProcessor(private val systemClock: SystemClock) {
fun process(
items: List<MatrixTimelineItem>,
isTimelineInitialized: Boolean,
hasMoreToLoadBackward: Boolean,
hasMoreToLoadForward: Boolean,
): List<MatrixTimelineItem> {
if (!isTimelineInitialized) return items
val shouldAddForwardLoadingIndicator = hasMoreToLoadForward && items.isNotEmpty()
val currentTimestamp = systemClock.epochMillis()
return buildList {

View file

@ -16,11 +16,12 @@ import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.flow.first
import org.matrix.rustcomponents.sdk.EncryptionSystem
import org.matrix.rustcomponents.sdk.VirtualElementCallWidgetOptions
import org.matrix.rustcomponents.sdk.newVirtualElementCallWidget
import uniffi.matrix_sdk.EncryptionSystem
import uniffi.matrix_sdk.HeaderStyle
import uniffi.matrix_sdk.VirtualElementCallWidgetOptions
import javax.inject.Inject
import org.matrix.rustcomponents.sdk.Intent as CallIntent
import uniffi.matrix_sdk.Intent as CallIntent
@ContributesBinding(AppScope::class)
class DefaultCallWidgetSettingsProvider @Inject constructor(
@ -48,8 +49,10 @@ class DefaultCallWidgetSettingsProvider @Inject constructor(
sentryDsn = callAnalyticsCredentialsProvider.sentryDsn.takeIf { isAnalyticsEnabled },
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) "RELEASE" else "DEBUG",
parentUrl = null,
// For backwards compatibility, it'll be ignored in recent versions of Element Call
hideHeader = true,
controlledMediaDevices = true,
header = HeaderStyle.APP_BAR,
)
val rustWidgetSettings = newVirtualElementCallWidget(options)
return MatrixWidgetSettings.fromRustWidgetSettings(rustWidgetSettings)

View file

@ -52,7 +52,6 @@ fun aRustNotificationRoomInfo(
isEncrypted: Boolean? = true,
isDirect: Boolean = false,
joinRule: JoinRule? = null,
isPublic: Boolean = true,
) = NotificationRoomInfo(
displayName = displayName,
avatarUrl = avatarUrl,
@ -61,7 +60,6 @@ fun aRustNotificationRoomInfo(
isEncrypted = isEncrypted,
isDirect = isDirect,
joinRule = joinRule,
isPublic = isPublic,
)
fun aRustNotificationEventTimeline(

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.fixtures.factories
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomPowerLevels
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import org.matrix.rustcomponents.sdk.JoinRule
@ -17,6 +18,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.RoomPowerLevels
import org.matrix.rustcomponents.sdk.SuccessorRoom
import uniffi.matrix_sdk_base.EncryptionState
@ -39,7 +41,7 @@ fun aRustRoomInfo(
activeMembersCount: ULong = 0uL,
invitedMembersCount: ULong = 0uL,
joinedMembersCount: ULong = 0uL,
userPowerLevels: Map<String, Long> = mapOf(),
roomPowerLevels: RoomPowerLevels = FakeFfiRoomPowerLevels(),
highlightCount: ULong = 0uL,
notificationCount: ULong = 0uL,
userDefinedNotificationMode: RoomNotificationMode? = null,
@ -73,7 +75,7 @@ fun aRustRoomInfo(
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
userPowerLevels = userPowerLevels,
powerLevels = roomPowerLevels,
highlightCount = highlightCount,
notificationCount = notificationCount,
cachedUserDefinedNotificationMode = userDefinedNotificationMode,

View file

@ -25,6 +25,7 @@ import org.matrix.rustcomponents.sdk.PusherKind
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.SessionVerificationController
import org.matrix.rustcomponents.sdk.SyncService
import org.matrix.rustcomponents.sdk.SyncServiceBuilder
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
@ -62,7 +63,7 @@ class FakeFfiClient(
) = Unit
override suspend fun deletePusher(identifiers: PusherIdentifiers) = Unit
override suspend fun clearCaches() = simulateLongTask { clearCachesResult() }
override suspend fun clearCaches(syncService: SyncService?) = simulateLongTask { clearCachesResult() }
override suspend fun setUtdDelegate(utdDelegate: UnableToDecryptDelegate) = withUtdHook(utdDelegate)
override suspend fun getSessionVerificationController(): SessionVerificationController = FakeFfiSessionVerificationController()
override suspend fun ignoredUsers(): List<String> {

View file

@ -18,7 +18,7 @@ import org.matrix.rustcomponents.sdk.RequestConfig
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import uniffi.matrix_sdk.BackupDownloadStrategy
import uniffi.matrix_sdk_crypto.CollectStrategy
import uniffi.matrix_sdk_crypto.TrustRequirement
import uniffi.matrix_sdk_crypto.DecryptionSettings
class FakeFfiClientBuilder : ClientBuilder(NoPointer) {
override fun addRootCertificates(certificates: List<ByteArray>) = this
@ -27,7 +27,7 @@ class FakeFfiClientBuilder : ClientBuilder(NoPointer) {
override fun backupDownloadStrategy(backupDownloadStrategy: BackupDownloadStrategy) = this
override fun disableAutomaticTokenRefresh() = this
override fun disableBuiltInRootCertificates() = this
override fun roomDecryptionTrustRequirement(trustRequirement: TrustRequirement) = this
override fun decryptionSettings(decryptionSettings: DecryptionSettings): ClientBuilder = this
override fun disableSslVerification() = this
override fun homeserverUrl(url: String) = this
override fun sessionPassphrase(passphrase: String?) = this

View file

@ -0,0 +1,33 @@
/*
* 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.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomPowerLevels
import org.matrix.rustcomponents.sdk.RoomPowerLevelsValues
class FakeFfiRoomPowerLevels(
private val values: RoomPowerLevelsValues = defaultFfiRoomPowerLevelValues(),
private val users: Map<String, Long> = emptyMap(),
) : RoomPowerLevels(NoPointer) {
override fun values(): RoomPowerLevelsValues = values
override fun userPowerLevels(): Map<String, Long> = users
}
fun defaultFfiRoomPowerLevelValues() = RoomPowerLevelsValues(
ban = 50,
invite = 0,
kick = 50,
eventsDefault = 0,
redact = 50,
roomName = 100,
roomAvatar = 100,
roomTopic = 100,
stateDefault = 0,
usersDefault = 0,
)

View file

@ -16,10 +16,12 @@ 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
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomHero
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomInfo
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomMember
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomPowerLevels
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
@ -28,8 +30,9 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.A_USER_ID_6
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toPersistentList
import org.junit.Test
import org.matrix.rustcomponents.sdk.Membership
@ -64,7 +67,7 @@ class RoomInfoMapperTest {
activeMembersCount = 2uL,
invitedMembersCount = 3uL,
joinedMembersCount = 4uL,
userPowerLevels = mapOf(A_USER_ID_6.value to 50L),
roomPowerLevels = FakeFfiRoomPowerLevels(users = mapOf(A_USER_ID_6.value to 50L)),
highlightCount = 10uL,
notificationCount = 11uL,
userDefinedNotificationMode = RustRoomNotificationMode.MUTE,
@ -99,7 +102,10 @@ class RoomInfoMapperTest {
activeMembersCount = 2L,
invitedMembersCount = 3L,
joinedMembersCount = 4L,
userPowerLevels = mapOf(A_USER_ID_6 to 50L).toImmutableMap(),
roomPowerLevels = RoomPowerLevels(
values = defaultRoomPowerLevelValues(),
users = persistentMapOf(A_USER_ID_6 to 50L)
),
highlightCount = 10L,
notificationCount = 11L,
userDefinedNotificationMode = RoomNotificationMode.MUTE,
@ -149,7 +155,7 @@ class RoomInfoMapperTest {
activeMembersCount = 2uL,
invitedMembersCount = 3uL,
joinedMembersCount = 4uL,
userPowerLevels = emptyMap(),
roomPowerLevels = FakeFfiRoomPowerLevels(),
highlightCount = 10uL,
notificationCount = 11uL,
userDefinedNotificationMode = null,
@ -184,7 +190,10 @@ class RoomInfoMapperTest {
activeMembersCount = 2L,
invitedMembersCount = 3L,
joinedMembersCount = 4L,
userPowerLevels = emptyMap<UserId, Long>().toImmutableMap(),
roomPowerLevels = RoomPowerLevels(
values = defaultRoomPowerLevelValues(),
users = persistentMapOf(),
),
highlightCount = 10L,
notificationCount = 11L,
userDefinedNotificationMode = null,

View file

@ -57,11 +57,6 @@ class RustTimelineTest {
)
)
)
with(awaitItem()) {
assertThat(size).isEqualTo(1)
// Typing notification
assertThat((get(0) as MatrixTimelineItem.Virtual).virtual).isEqualTo(VirtualTimelineItem.TypingNotification)
}
with(awaitItem()) {
assertThat(size).isEqualTo(2)
// The loading

View file

@ -16,10 +16,8 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineDi
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineItem
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@ -116,8 +114,6 @@ class TimelineItemsSubscriberTest {
private fun TestScope.createTimelineItemsSubscriber(
timeline: Timeline = FakeFfiTimeline(),
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE),
initLatch: CompletableDeferred<Unit> = CompletableDeferred(),
isTimelineInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false),
onNewSyncedEvent: () -> Unit = { lambdaError() },
): TimelineItemsSubscriber {
return TimelineItemsSubscriber(
@ -125,8 +121,6 @@ private fun TestScope.createTimelineItemsSubscriber(
dispatcher = StandardTestDispatcher(testScheduler),
timeline = timeline,
timelineDiffProcessor = createMatrixTimelineDiffProcessor(timelineItems),
initLatch = initLatch,
isTimelineInitialized = isTimelineInitialized,
onNewSyncedEvent = onNewSyncedEvent,
)
}

View file

@ -18,21 +18,14 @@ class LastForwardIndicatorsPostProcessorTest {
@Test
fun `LastForwardIndicatorsPostProcessor does not alter the items with mode not FOCUSED_ON_EVENT`() {
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.LIVE)
val result = sut.process(listOf(messageEvent), true)
assertThat(result).containsExactly(messageEvent)
}
@Test
fun `LastForwardIndicatorsPostProcessor does not alter the items with mode FOCUSED_ON_EVENT but timeline not initialized`() {
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
val result = sut.process(listOf(messageEvent), false)
val result = sut.process(listOf(messageEvent))
assertThat(result).containsExactly(messageEvent)
}
@Test
fun `LastForwardIndicatorsPostProcessor add virtual items`() {
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
val result = sut.process(listOf(messageEvent), true)
val result = sut.process(listOf(messageEvent))
assertThat(result).containsExactly(
messageEvent,
MatrixTimelineItem.Virtual(
@ -45,7 +38,7 @@ class LastForwardIndicatorsPostProcessorTest {
@Test
fun `LastForwardIndicatorsPostProcessor add virtual items on empty list`() {
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
val result = sut.process(listOf(), true)
val result = sut.process(listOf())
assertThat(result).containsExactly(
MatrixTimelineItem.Virtual(
uniqueId = UniqueId("last_forward_indicator_fake_id"),
@ -58,9 +51,9 @@ class LastForwardIndicatorsPostProcessorTest {
fun `LastForwardIndicatorsPostProcessor add virtual items but does not alter the list if called a second time`() {
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
// Process a first time
sut.process(listOf(messageEvent), true)
sut.process(listOf(messageEvent))
// Process a second time with the same Event
val result = sut.process(listOf(messageEvent), true)
val result = sut.process(listOf(messageEvent))
assertThat(result).containsExactly(
messageEvent,
MatrixTimelineItem.Virtual(
@ -74,9 +67,9 @@ class LastForwardIndicatorsPostProcessorTest {
fun `LastForwardIndicatorsPostProcessor add virtual items each time it is called with new Events`() {
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
// Process a first time
sut.process(listOf(dayEvent, messageEvent), true)
sut.process(listOf(dayEvent, messageEvent))
// Process a second time with the same Event
val result = sut.process(listOf(dayEvent, messageEvent, messageEvent2), true)
val result = sut.process(listOf(dayEvent, messageEvent, messageEvent2))
assertThat(result).containsExactly(
dayEvent,
messageEvent,

View file

@ -16,25 +16,12 @@ import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import org.junit.Test
class LoadingIndicatorsPostProcessorTest {
@Test
fun `LoadingIndicatorsPostProcessor does not alter the items is the timeline is not initialized`() {
val sut = LoadingIndicatorsPostProcessor(FakeSystemClock())
val result = sut.process(
items = listOf(messageEvent, messageEvent2),
isTimelineInitialized = false,
hasMoreToLoadBackward = true,
hasMoreToLoadForward = true,
)
assertThat(result).containsExactly(messageEvent, messageEvent2)
}
@Test
fun `LoadingIndicatorsPostProcessor adds Loading indicator at the top of the list if hasMoreToLoadBackward is true`() {
val clock = FakeSystemClock()
val sut = LoadingIndicatorsPostProcessor(clock)
val result = sut.process(
items = listOf(messageEvent, messageEvent2),
isTimelineInitialized = true,
hasMoreToLoadBackward = true,
hasMoreToLoadForward = false,
)
@ -57,7 +44,6 @@ class LoadingIndicatorsPostProcessorTest {
val sut = LoadingIndicatorsPostProcessor(clock)
val result = sut.process(
items = listOf(messageEvent, messageEvent2),
isTimelineInitialized = true,
hasMoreToLoadBackward = false,
hasMoreToLoadForward = true,
)
@ -80,7 +66,6 @@ class LoadingIndicatorsPostProcessorTest {
val sut = LoadingIndicatorsPostProcessor(clock)
val result = sut.process(
items = listOf(messageEvent, messageEvent2),
isTimelineInitialized = true,
hasMoreToLoadBackward = true,
hasMoreToLoadForward = true,
)
@ -110,7 +95,6 @@ class LoadingIndicatorsPostProcessorTest {
val sut = LoadingIndicatorsPostProcessor(clock)
val result = sut.process(
items = listOf(),
isTimelineInitialized = true,
hasMoreToLoadBackward = true,
hasMoreToLoadForward = true,
)