Merge pull request #5728 from element-hq/feature/fga/members_improvements
Changes : member list improvements
This commit is contained in:
commit
2120b5c5bd
29 changed files with 254 additions and 176 deletions
|
|
@ -41,6 +41,7 @@ fun ListDialog(
|
|||
submitText: String = stringResource(CommonStrings.action_ok),
|
||||
enabled: Boolean = true,
|
||||
applyPaddingToContents: Boolean = true,
|
||||
destructiveSubmit: Boolean = false,
|
||||
listItems: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let {
|
||||
|
|
@ -65,6 +66,7 @@ fun ListDialog(
|
|||
enabled = enabled,
|
||||
listItems = listItems,
|
||||
applyPaddingToContents = applyPaddingToContents,
|
||||
destructiveSubmit = destructiveSubmit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +81,7 @@ private fun ListDialogContent(
|
|||
title: String?,
|
||||
enabled: Boolean,
|
||||
applyPaddingToContents: Boolean,
|
||||
destructiveSubmit: Boolean,
|
||||
subtitle: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
SimpleAlertDialogContent(
|
||||
|
|
@ -90,6 +93,7 @@ private fun ListDialogContent(
|
|||
onSubmitClick = onSubmitClick,
|
||||
enabled = enabled,
|
||||
applyPaddingToContents = applyPaddingToContents,
|
||||
destructiveSubmit = destructiveSubmit,
|
||||
) {
|
||||
// No start padding if padding is already applied to the content
|
||||
val horizontalPadding = if (applyPaddingToContents) 0.dp else 8.dp
|
||||
|
|
@ -120,6 +124,7 @@ internal fun ListDialogContentPreview() {
|
|||
cancelText = "Cancel",
|
||||
submitText = "Save",
|
||||
enabled = true,
|
||||
destructiveSubmit = false,
|
||||
applyPaddingToContents = true,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,11 +43,13 @@ fun TextFieldDialog(
|
|||
validation: (String?) -> Boolean = { true },
|
||||
onValidationErrorMessage: String? = null,
|
||||
autoSelectOnDisplay: Boolean = true,
|
||||
maxLines: Int = 1,
|
||||
minLines: Int = 1,
|
||||
maxLines: Int = minLines,
|
||||
content: String? = null,
|
||||
label: String? = null,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
submitText: String = stringResource(CommonStrings.action_ok),
|
||||
destructiveSubmit: Boolean = false,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
|
|
@ -67,6 +69,7 @@ fun TextFieldDialog(
|
|||
onDismissRequest = onDismissRequest,
|
||||
enabled = canSubmit,
|
||||
submitText = submitText,
|
||||
destructiveSubmit = destructiveSubmit,
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (content != null) {
|
||||
|
|
@ -93,6 +96,7 @@ fun TextFieldDialog(
|
|||
onSubmit(textFieldContents.text)
|
||||
}
|
||||
}),
|
||||
minLines = minLines,
|
||||
maxLines = maxLines,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ fun TextFieldListItem(
|
|||
onTextChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
error: String? = null,
|
||||
maxLines: Int = 1,
|
||||
minLines: Int = 1,
|
||||
maxLines: Int = minLines,
|
||||
label: String? = null,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
|
|
@ -53,7 +54,8 @@ fun TextFieldListItem(
|
|||
onTextChange: (TextFieldValue) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
error: String? = null,
|
||||
maxLines: Int = 1,
|
||||
minLines: Int = 1,
|
||||
maxLines: Int = minLines,
|
||||
label: String? = null,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
|
|
@ -68,6 +70,7 @@ fun TextFieldListItem(
|
|||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
maxLines = maxLines,
|
||||
minLines = minLines,
|
||||
singleLine = maxLines == 1,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ interface Timeline : AutoCloseable {
|
|||
|
||||
val mode: Mode
|
||||
val membershipChangeEventReceived: Flow<Unit>
|
||||
val onSyncedEventReceived: Flow<Unit>
|
||||
suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result<Unit>
|
||||
suspend fun markAsRead(receiptType: ReceiptType): Result<Unit>
|
||||
suspend fun paginate(direction: PaginationDirection): Result<Boolean>
|
||||
|
|
|
|||
|
|
@ -51,13 +51,16 @@ import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
|
|||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.DateDividerMode
|
||||
import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener
|
||||
|
|
@ -92,8 +95,6 @@ class JoinedRustRoom(
|
|||
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
|
||||
private val innerRoom = baseRoom.innerRoom
|
||||
|
||||
override val syncUpdateFlow = MutableStateFlow(0L)
|
||||
|
||||
override val roomTypingMembersFlow: Flow<List<UserId>> = mxCallbackFlow {
|
||||
val initial = emptyList<UserId>()
|
||||
channel.trySend(initial)
|
||||
|
|
@ -136,11 +137,24 @@ class JoinedRustRoom(
|
|||
|
||||
override val roomNotificationSettingsStateFlow = MutableStateFlow<RoomNotificationSettingsState>(RoomNotificationSettingsState.Unknown)
|
||||
|
||||
override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.Live) {
|
||||
syncUpdateFlow.value = systemClock.epochMillis()
|
||||
}
|
||||
override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.Live)
|
||||
|
||||
override val syncUpdateFlow = flow {
|
||||
var counter = 0L
|
||||
liveTimeline.onSyncedEventReceived.collect {
|
||||
emit(++counter)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = roomCoroutineScope,
|
||||
started = WhileSubscribed(),
|
||||
initialValue = 0L,
|
||||
)
|
||||
|
||||
init {
|
||||
subscribeToRoomMembersChange()
|
||||
}
|
||||
|
||||
private fun subscribeToRoomMembersChange() {
|
||||
val powerLevelChanges = roomInfoFlow.map { it.roomPowerLevels }.distinctUntilChanged()
|
||||
val membershipChanges = liveTimeline.membershipChangeEventReceived.onStart { emit(Unit) }
|
||||
combine(membershipChanges, powerLevelChanges) { _, _ -> }
|
||||
|
|
@ -479,7 +493,6 @@ class JoinedRustRoom(
|
|||
|
||||
private fun InnerTimeline.map(
|
||||
mode: Timeline.Mode,
|
||||
onNewSyncedEvent: () -> Unit = {},
|
||||
): Timeline {
|
||||
val timelineCoroutineScope = roomCoroutineScope.childScope(coroutineDispatchers.main, "TimelineScope-$roomId-$this")
|
||||
return RustTimeline(
|
||||
|
|
@ -490,7 +503,6 @@ class JoinedRustRoom(
|
|||
coroutineScope = timelineCoroutineScope,
|
||||
dispatcher = roomDispatcher,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
onNewSyncedEvent = onNewSyncedEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.timeline
|
||||
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
|
@ -21,58 +22,60 @@ import timber.log.Timber
|
|||
|
||||
internal class MatrixTimelineDiffProcessor(
|
||||
private val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>>,
|
||||
private val timelineItemFactory: MatrixTimelineItemMapper,
|
||||
private val membershipChangeEventReceivedFlow: MutableSharedFlow<Unit>,
|
||||
private val syncedEventReceivedFlow: MutableSharedFlow<Unit>,
|
||||
private val timelineItemMapper: MatrixTimelineItemMapper,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
private val _membershipChangeEventReceived = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
val membershipChangeEventReceived: Flow<Unit> = _membershipChangeEventReceived
|
||||
|
||||
suspend fun postDiffs(diffs: List<TimelineDiff>) {
|
||||
updateTimelineItems {
|
||||
mutex.withLock {
|
||||
Timber.v("Update timeline items from postDiffs (with ${diffs.size} items) on ${Thread.currentThread()}")
|
||||
diffs.forEach { diff ->
|
||||
applyDiff(diff)
|
||||
val result = processDiffs(diffs)
|
||||
timelineItems.emit(result.items())
|
||||
if (result.hasNewEventsFromSync) {
|
||||
syncedEventReceivedFlow.emit(Unit)
|
||||
}
|
||||
if (result.hasMembershipChangeEventFromSync) {
|
||||
membershipChangeEventReceivedFlow.emit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateTimelineItems(block: MutableList<MatrixTimelineItem>.() -> Unit) =
|
||||
mutex.withLock {
|
||||
val mutableTimelineItems = if (timelineItems.replayCache.isNotEmpty()) {
|
||||
timelineItems.first().toMutableList()
|
||||
} else {
|
||||
mutableListOf()
|
||||
}
|
||||
block(mutableTimelineItems)
|
||||
timelineItems.tryEmit(mutableTimelineItems)
|
||||
private suspend fun processDiffs(diffs: List<TimelineDiff>): DiffingResult {
|
||||
val timelineItems = if (timelineItems.replayCache.isNotEmpty()) {
|
||||
timelineItems.first()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
val result = DiffingResult(timelineItems)
|
||||
diffs.forEach { diff ->
|
||||
result.applyDiff(diff)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun MutableList<MatrixTimelineItem>.applyDiff(diff: TimelineDiff) {
|
||||
private fun DiffingResult.applyDiff(diff: TimelineDiff) {
|
||||
when (diff) {
|
||||
is TimelineDiff.Append -> {
|
||||
val items = diff.values.map { it.asMatrixTimelineItem() }
|
||||
addAll(items)
|
||||
diff.values.fastForEach { item ->
|
||||
add(item.map())
|
||||
}
|
||||
}
|
||||
is TimelineDiff.PushBack -> {
|
||||
val item = diff.value.asMatrixTimelineItem()
|
||||
if (item is MatrixTimelineItem.Event && item.event.content is RoomMembershipContent) {
|
||||
// TODO - This is a temporary solution to notify the room screen about membership changes
|
||||
// Ideally, this should be implemented by the Rust SDK
|
||||
_membershipChangeEventReceived.tryEmit(Unit)
|
||||
}
|
||||
val item = diff.value.map()
|
||||
add(item)
|
||||
}
|
||||
is TimelineDiff.PushFront -> {
|
||||
val item = diff.value.asMatrixTimelineItem()
|
||||
val item = diff.value.map()
|
||||
add(0, item)
|
||||
}
|
||||
is TimelineDiff.Set -> {
|
||||
val item = diff.value.asMatrixTimelineItem()
|
||||
val item = diff.value.map()
|
||||
set(diff.index.toInt(), item)
|
||||
}
|
||||
is TimelineDiff.Insert -> {
|
||||
val item = diff.value.asMatrixTimelineItem()
|
||||
val item = diff.value.map()
|
||||
add(diff.index.toInt(), item)
|
||||
}
|
||||
is TimelineDiff.Remove -> {
|
||||
|
|
@ -80,25 +83,91 @@ internal class MatrixTimelineDiffProcessor(
|
|||
}
|
||||
is TimelineDiff.Reset -> {
|
||||
clear()
|
||||
val items = diff.values.map { it.asMatrixTimelineItem() }
|
||||
addAll(items)
|
||||
diff.values.fastForEach { item ->
|
||||
add(item.map())
|
||||
}
|
||||
}
|
||||
TimelineDiff.PopFront -> {
|
||||
removeFirstOrNull()
|
||||
removeFirst()
|
||||
}
|
||||
TimelineDiff.PopBack -> {
|
||||
removeLastOrNull()
|
||||
removeLast()
|
||||
}
|
||||
TimelineDiff.Clear -> {
|
||||
clear()
|
||||
}
|
||||
is TimelineDiff.Truncate -> {
|
||||
subList(diff.length.toInt(), size).clear()
|
||||
truncate(diff.length.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
|
||||
return timelineItemFactory.map(this)
|
||||
private fun TimelineItem.map(): MatrixTimelineItem {
|
||||
return timelineItemMapper.map(this)
|
||||
}
|
||||
}
|
||||
|
||||
private class DiffingResult(initialItems: List<MatrixTimelineItem>) {
|
||||
private val items = initialItems.toMutableList()
|
||||
var hasNewEventsFromSync: Boolean = false
|
||||
private set
|
||||
var hasMembershipChangeEventFromSync: Boolean = false
|
||||
private set
|
||||
|
||||
fun items(): List<MatrixTimelineItem> = items
|
||||
|
||||
fun add(item: MatrixTimelineItem) {
|
||||
processItem(item)
|
||||
items.add(item)
|
||||
}
|
||||
|
||||
fun add(index: Int, item: MatrixTimelineItem) {
|
||||
processItem(item)
|
||||
items.add(index, item)
|
||||
}
|
||||
|
||||
fun set(index: Int, item: MatrixTimelineItem) {
|
||||
processItem(item)
|
||||
items[index] = item
|
||||
}
|
||||
|
||||
fun removeAt(index: Int) {
|
||||
items.removeAt(index)
|
||||
}
|
||||
|
||||
fun removeFirst() {
|
||||
items.removeFirstOrNull()
|
||||
}
|
||||
|
||||
fun removeLast() {
|
||||
items.removeLastOrNull()
|
||||
}
|
||||
|
||||
fun truncate(length: Int) {
|
||||
items.subList(length, items.size).clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
items.clear()
|
||||
}
|
||||
|
||||
private fun processItem(item: MatrixTimelineItem) {
|
||||
if (skipProcessing()) return
|
||||
when (item) {
|
||||
is MatrixTimelineItem.Event -> {
|
||||
if (item.event.origin == TimelineItemEventOrigin.SYNC) {
|
||||
hasNewEventsFromSync = true
|
||||
when (item.event.content) {
|
||||
is RoomMembershipContent -> hasMembershipChangeEventFromSync = true
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun skipProcessing(): Boolean {
|
||||
return hasNewEventsFromSync && hasMembershipChangeEventFromSync
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,16 +81,18 @@ private const val PAGINATION_SIZE = 50
|
|||
class RustTimeline(
|
||||
private val inner: InnerTimeline,
|
||||
override val mode: Timeline.Mode,
|
||||
systemClock: SystemClock,
|
||||
private val systemClock: SystemClock,
|
||||
private val joinedRoom: JoinedRoom,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
private val roomContentForwarder: RoomContentForwarder,
|
||||
onNewSyncedEvent: () -> Unit,
|
||||
) : Timeline {
|
||||
private val _timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
|
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
|
||||
|
||||
private val _membershipChangeEventReceived = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
private val _onSyncedEventReceived: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
|
||||
|
||||
private val timelineEventContentMapper = TimelineEventContentMapper()
|
||||
private val inReplyToMapper = InReplyToMapper(timelineEventContentMapper)
|
||||
private val timelineItemMapper = MatrixTimelineItemMapper(
|
||||
|
|
@ -99,18 +101,19 @@ class RustTimeline(
|
|||
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
|
||||
eventTimelineItemMapper = EventTimelineItemMapper(
|
||||
contentMapper = timelineEventContentMapper
|
||||
)
|
||||
),
|
||||
)
|
||||
private val timelineDiffProcessor = MatrixTimelineDiffProcessor(
|
||||
timelineItems = _timelineItems,
|
||||
timelineItemFactory = timelineItemMapper,
|
||||
membershipChangeEventReceivedFlow = _membershipChangeEventReceived,
|
||||
syncedEventReceivedFlow = _onSyncedEventReceived,
|
||||
timelineItemMapper = timelineItemMapper,
|
||||
)
|
||||
private val timelineItemsSubscriber = TimelineItemsSubscriber(
|
||||
timeline = inner,
|
||||
timelineCoroutineScope = coroutineScope,
|
||||
timelineDiffProcessor = timelineDiffProcessor,
|
||||
dispatcher = dispatcher,
|
||||
onNewSyncedEvent = onNewSyncedEvent,
|
||||
)
|
||||
|
||||
private val roomBeginningPostProcessor = RoomBeginningPostProcessor(mode)
|
||||
|
|
@ -152,7 +155,13 @@ class RustTimeline(
|
|||
.launchIn(this)
|
||||
}
|
||||
|
||||
override val membershipChangeEventReceived: Flow<Unit> = timelineDiffProcessor.membershipChangeEventReceived
|
||||
override val membershipChangeEventReceived: Flow<Unit> = _membershipChangeEventReceived
|
||||
.onStart { timelineItemsSubscriber.subscribeIfNeeded() }
|
||||
.onCompletion { timelineItemsSubscriber.unsubscribeIfNeeded() }
|
||||
|
||||
override val onSyncedEventReceived: Flow<Unit> = _onSyncedEventReceived
|
||||
.onStart { timelineItemsSubscriber.subscribeIfNeeded() }
|
||||
.onCompletion { timelineItemsSubscriber.unsubscribeIfNeeded() }
|
||||
|
||||
override suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result<Unit> = withContext(dispatcher) {
|
||||
runCatchingExceptions {
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-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.timeline
|
||||
|
||||
import org.matrix.rustcomponents.sdk.TimelineDiff
|
||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
import uniffi.matrix_sdk_ui.EventItemOrigin
|
||||
|
||||
/**
|
||||
* Tries to get an event origin from the TimelineDiff.
|
||||
* If there is multiple events in the diff, uses the first one as it should be a good indicator.
|
||||
*/
|
||||
internal fun TimelineDiff.eventOrigin(): EventItemOrigin? {
|
||||
return when (this) {
|
||||
is TimelineDiff.Append -> values.firstOrNull()?.eventOrigin()
|
||||
is TimelineDiff.PushBack -> value.eventOrigin()
|
||||
is TimelineDiff.PushFront -> value.eventOrigin()
|
||||
is TimelineDiff.Set -> value.eventOrigin()
|
||||
is TimelineDiff.Insert -> value.eventOrigin()
|
||||
is TimelineDiff.Reset -> values.firstOrNull()?.eventOrigin()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun TimelineItem.eventOrigin(): EventItemOrigin? {
|
||||
return asEvent()?.origin
|
||||
}
|
||||
|
|
@ -12,13 +12,11 @@ import io.element.android.libraries.core.coroutine.childScope
|
|||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
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 uniffi.matrix_sdk_ui.EventItemOrigin
|
||||
|
||||
/**
|
||||
* This class is responsible for subscribing to a timeline and post the items/diffs to the timelineDiffProcessor.
|
||||
|
|
@ -29,7 +27,6 @@ internal class TimelineItemsSubscriber(
|
|||
dispatcher: CoroutineDispatcher,
|
||||
private val timeline: Timeline,
|
||||
private val timelineDiffProcessor: MatrixTimelineDiffProcessor,
|
||||
private val onNewSyncedEvent: () -> Unit,
|
||||
) {
|
||||
private var subscriptionCount = 0
|
||||
private val mutex = Mutex()
|
||||
|
|
@ -44,9 +41,6 @@ internal class TimelineItemsSubscriber(
|
|||
if (subscriptionCount == 0) {
|
||||
timeline.timelineDiffFlow()
|
||||
.onEach { diffs ->
|
||||
if (diffs.any { diff -> diff.eventOrigin() == EventItemOrigin.SYNC }) {
|
||||
onNewSyncedEvent()
|
||||
}
|
||||
timelineDiffProcessor.postDiffs(diffs)
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
|
|
|
|||
|
|
@ -169,10 +169,12 @@ class MatrixTimelineDiffProcessorTest {
|
|||
}
|
||||
|
||||
internal fun TestScope.createMatrixTimelineDiffProcessor(
|
||||
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>>,
|
||||
): MatrixTimelineDiffProcessor {
|
||||
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = MutableSharedFlow(),
|
||||
membershipChangeEventReceivedFlow: MutableSharedFlow<Unit> = MutableSharedFlow(),
|
||||
syncedEventReceivedFlow: MutableSharedFlow<Unit> = MutableSharedFlow(),
|
||||
): MatrixTimelineDiffProcessor {
|
||||
val timelineEventContentMapper = TimelineEventContentMapper()
|
||||
val timelineItemMapper = MatrixTimelineItemMapper(
|
||||
val timelineItemFactory = MatrixTimelineItemMapper(
|
||||
fetchDetailsForEvent = { _ -> Result.success(Unit) },
|
||||
coroutineScope = this,
|
||||
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
|
||||
|
|
@ -182,6 +184,8 @@ internal fun TestScope.createMatrixTimelineDiffProcessor(
|
|||
)
|
||||
return MatrixTimelineDiffProcessor(
|
||||
timelineItems = timelineItems,
|
||||
timelineItemFactory = timelineItemMapper,
|
||||
membershipChangeEventReceivedFlow = membershipChangeEventReceivedFlow,
|
||||
syncedEventReceivedFlow = syncedEventReceivedFlow,
|
||||
timelineItemMapper = timelineItemFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ private fun TestScope.createRustTimeline(
|
|||
coroutineScope: CoroutineScope = backgroundScope,
|
||||
dispatcher: CoroutineDispatcher = testCoroutineDispatchers().io,
|
||||
roomContentForwarder: RoomContentForwarder = RoomContentForwarder(FakeFfiRoomListService()),
|
||||
onNewSyncedEvent: () -> Unit = {},
|
||||
): RustTimeline {
|
||||
return RustTimeline(
|
||||
inner = inner,
|
||||
|
|
@ -109,6 +108,5 @@ private fun TestScope.createRustTimeline(
|
|||
coroutineScope = coroutineScope,
|
||||
dispatcher = dispatcher,
|
||||
roomContentForwarder = roomContentForwarder,
|
||||
onNewSyncedEvent = onNewSyncedEvent,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
|||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustEventTimelineItem
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimeline
|
||||
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.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
|
|
@ -36,9 +34,12 @@ class TimelineItemsSubscriberTest {
|
|||
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
|
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
|
||||
val timeline = FakeFfiTimeline()
|
||||
val diffProcessor = createMatrixTimelineDiffProcessor(
|
||||
timelineItems = timelineItems,
|
||||
)
|
||||
val timelineItemsSubscriber = createTimelineItemsSubscriber(
|
||||
timeline = timeline,
|
||||
timelineItems = timelineItems,
|
||||
timelineDiffProcessor = diffProcessor,
|
||||
)
|
||||
timelineItems.test {
|
||||
timelineItemsSubscriber.subscribeIfNeeded()
|
||||
|
|
@ -57,9 +58,12 @@ class TimelineItemsSubscriberTest {
|
|||
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
|
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
|
||||
val timeline = FakeFfiTimeline()
|
||||
val diffProcessor = createMatrixTimelineDiffProcessor(
|
||||
timelineItems = timelineItems,
|
||||
)
|
||||
val timelineItemsSubscriber = createTimelineItemsSubscriber(
|
||||
timeline = timeline,
|
||||
timelineItems = timelineItems,
|
||||
timelineDiffProcessor = diffProcessor,
|
||||
)
|
||||
timelineItems.test {
|
||||
timelineItemsSubscriber.subscribeIfNeeded()
|
||||
|
|
@ -74,15 +78,16 @@ class TimelineItemsSubscriberTest {
|
|||
|
||||
@Ignore("JNA direct mapping has broken unit tests with FFI fakes")
|
||||
@Test
|
||||
fun `when timeline emits an item with SYNC origin, the callback onNewSyncedEvent is invoked`() = runTest {
|
||||
fun `when timeline emits an item with SYNC origin`() = runTest {
|
||||
val timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
|
||||
MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
|
||||
val timeline = FakeFfiTimeline()
|
||||
val onNewSyncedEventRecorder = lambdaRecorder<Unit> { }
|
||||
val diffProcessor = createMatrixTimelineDiffProcessor(
|
||||
timelineItems = timelineItems,
|
||||
)
|
||||
val timelineItemsSubscriber = createTimelineItemsSubscriber(
|
||||
timeline = timeline,
|
||||
timelineItems = timelineItems,
|
||||
onNewSyncedEvent = onNewSyncedEventRecorder,
|
||||
timelineDiffProcessor = diffProcessor,
|
||||
)
|
||||
timelineItems.test {
|
||||
timelineItemsSubscriber.subscribeIfNeeded()
|
||||
|
|
@ -101,7 +106,6 @@ class TimelineItemsSubscriberTest {
|
|||
assertThat(final).isNotEmpty()
|
||||
timelineItemsSubscriber.unsubscribeIfNeeded()
|
||||
}
|
||||
onNewSyncedEventRecorder.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Ignore("JNA direct mapping has broken unit tests with FFI fakes")
|
||||
|
|
@ -117,14 +121,12 @@ class TimelineItemsSubscriberTest {
|
|||
|
||||
private fun TestScope.createTimelineItemsSubscriber(
|
||||
timeline: Timeline = FakeFfiTimeline(),
|
||||
timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE),
|
||||
onNewSyncedEvent: () -> Unit = { lambdaError() },
|
||||
timelineDiffProcessor: MatrixTimelineDiffProcessor = createMatrixTimelineDiffProcessor(),
|
||||
): TimelineItemsSubscriber {
|
||||
return TimelineItemsSubscriber(
|
||||
timelineCoroutineScope = backgroundScope,
|
||||
dispatcher = StandardTestDispatcher(testScheduler),
|
||||
timeline = timeline,
|
||||
timelineDiffProcessor = createMatrixTimelineDiffProcessor(timelineItems),
|
||||
onNewSyncedEvent = onNewSyncedEvent,
|
||||
timelineDiffProcessor = timelineDiffProcessor,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class FakeTimeline(
|
|||
)
|
||||
),
|
||||
override val membershipChangeEventReceived: Flow<Unit> = MutableSharedFlow(),
|
||||
override val onSyncedEventReceived: Flow<Unit> = MutableSharedFlow(),
|
||||
private val cancelSendResult: (TransactionId) -> Result<Unit> = { lambdaError() },
|
||||
override val mode: Timeline.Mode = Timeline.Mode.Live,
|
||||
private val markAsReadResult: (ReceiptType) -> Result<Unit> = { lambdaError() },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue