mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-04 20:01:55 +00:00
Fold newMessagesCount into NewEventState.FromOther
This commit is contained in:
+9
-20
@@ -10,7 +10,6 @@ package io.element.android.features.messages.impl.timeline
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableIntState
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
@@ -137,8 +136,7 @@ class TimelinePresenter(
|
|||||||
|
|
||||||
val prevMostRecentItemId = rememberSaveable { mutableStateOf<UniqueId?>(null) }
|
val prevMostRecentItemId = rememberSaveable { mutableStateOf<UniqueId?>(null) }
|
||||||
|
|
||||||
val newEventState = remember { mutableStateOf(NewEventState.None) }
|
val newEventState = remember { mutableStateOf<NewEventState>(NewEventState.None) }
|
||||||
val newMessagesCount = remember { mutableIntStateOf(0) }
|
|
||||||
val messageShieldDialogData: MutableState<MessageShieldData?> = remember { mutableStateOf(null) }
|
val messageShieldDialogData: MutableState<MessageShieldData?> = remember { mutableStateOf(null) }
|
||||||
|
|
||||||
val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present()
|
val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present()
|
||||||
@@ -177,7 +175,6 @@ class TimelinePresenter(
|
|||||||
if (isLive) {
|
if (isLive) {
|
||||||
if (event.firstIndex == 0) {
|
if (event.firstIndex == 0) {
|
||||||
newEventState.value = NewEventState.None
|
newEventState.value = NewEventState.None
|
||||||
newMessagesCount.intValue = 0
|
|
||||||
}
|
}
|
||||||
Timber.tag(tag).d("## sendReadReceiptIfNeeded firstVisibleIndex: ${event.firstIndex}")
|
Timber.tag(tag).d("## sendReadReceiptIfNeeded firstVisibleIndex: ${event.firstIndex}")
|
||||||
sessionCoroutineScope.sendReadReceiptIfNeeded(
|
sessionCoroutineScope.sendReadReceiptIfNeeded(
|
||||||
@@ -278,7 +275,7 @@ class TimelinePresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(timelineItems.size) {
|
LaunchedEffect(timelineItems.size) {
|
||||||
computeNewItemState(timelineItems, prevMostRecentItemId, newEventState, newMessagesCount)
|
computeNewItemState(timelineItems, prevMostRecentItemId, newEventState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read marker position + unread count, scanned off the main thread. The UI gates display via
|
// Read marker position + unread count, scanned off the main thread. The UI gates display via
|
||||||
@@ -361,7 +358,6 @@ class TimelinePresenter(
|
|||||||
displayJumpToUnread = displayJumpToUnread,
|
displayJumpToUnread = displayJumpToUnread,
|
||||||
readMarkerIndex = readMarkerIndex.intValue,
|
readMarkerIndex = readMarkerIndex.intValue,
|
||||||
unreadMessagesCount = unreadMessagesCount.intValue,
|
unreadMessagesCount = unreadMessagesCount.intValue,
|
||||||
newMessagesCount = newMessagesCount.intValue,
|
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -420,16 +416,14 @@ class TimelinePresenter(
|
|||||||
* Basically, if we got new timeline event from sync or local, either from us or another user, we update the state so we tell we have new items.
|
* Basically, if we got new timeline event from sync or local, either from us or another user, we update the state so we tell we have new items.
|
||||||
* The state never goes back to None from this method, but need to be reset from somewhere else.
|
* The state never goes back to None from this method, but need to be reset from somewhere else.
|
||||||
*
|
*
|
||||||
* It also maintains [newMessagesCount], counting how many incoming messages from other users have arrived in
|
* The [NewEventState.FromOther] variant carries the running count of incoming messages from other users
|
||||||
* each batch since [prevMostRecentItemId] — this drives the badge on the scroll-to-bottom button. The count is
|
* since [prevMostRecentItemId] last advanced to a local-user event or the timeline returned to the bottom
|
||||||
* reset to zero when the most recent event is from the local user (they're back to active engagement); the
|
* — this drives the badge on the scroll-to-bottom button.
|
||||||
* scroll-finish handler resets it when the user returns to the bottom of the timeline.
|
|
||||||
*/
|
*/
|
||||||
private suspend fun computeNewItemState(
|
private suspend fun computeNewItemState(
|
||||||
timelineItems: ImmutableList<TimelineItem>,
|
timelineItems: ImmutableList<TimelineItem>,
|
||||||
prevMostRecentItemId: MutableState<UniqueId?>,
|
prevMostRecentItemId: MutableState<UniqueId?>,
|
||||||
newEventState: MutableState<NewEventState>,
|
newEventState: MutableState<NewEventState>,
|
||||||
newMessagesCount: MutableIntState,
|
|
||||||
) = withContext(dispatchers.computation) {
|
) = withContext(dispatchers.computation) {
|
||||||
// FromMe is prioritized over FromOther, so skip if we already have a FromMe
|
// FromMe is prioritized over FromOther, so skip if we already have a FromMe
|
||||||
if (newEventState.value == NewEventState.FromMe) {
|
if (newEventState.value == NewEventState.FromMe) {
|
||||||
@@ -448,14 +442,8 @@ class TimelinePresenter(
|
|||||||
|
|
||||||
if (hasNewEvent) {
|
if (hasNewEvent) {
|
||||||
// Scroll to bottom if the new event is from me, even if sent from another device
|
// Scroll to bottom if the new event is from me, even if sent from another device
|
||||||
val fromMe = newMostRecentItem.isMine
|
if (newMostRecentItem.isMine) {
|
||||||
newEventState.value = if (fromMe) {
|
newEventState.value = NewEventState.FromMe
|
||||||
NewEventState.FromMe
|
|
||||||
} else {
|
|
||||||
NewEventState.FromOther
|
|
||||||
}
|
|
||||||
if (fromMe) {
|
|
||||||
newMessagesCount.intValue = 0
|
|
||||||
} else {
|
} else {
|
||||||
var delta = 0
|
var delta = 0
|
||||||
for (item in timelineItems) {
|
for (item in timelineItems) {
|
||||||
@@ -464,7 +452,8 @@ class TimelinePresenter(
|
|||||||
delta++
|
delta++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newMessagesCount.intValue += delta
|
val previousCount = (newEventState.value as? NewEventState.FromOther)?.messageCount ?: 0
|
||||||
|
newEventState.value = NewEventState.FromOther(previousCount + delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prevMostRecentItemId.value = newMostRecentItemId
|
prevMostRecentItemId.value = newMostRecentItemId
|
||||||
|
|||||||
-1
@@ -38,7 +38,6 @@ data class TimelineState(
|
|||||||
val displayJumpToUnread: Boolean,
|
val displayJumpToUnread: Boolean,
|
||||||
val readMarkerIndex: Int,
|
val readMarkerIndex: Int,
|
||||||
val unreadMessagesCount: Int,
|
val unreadMessagesCount: Int,
|
||||||
val newMessagesCount: Int,
|
|
||||||
val eventSink: (TimelineEvent) -> Unit,
|
val eventSink: (TimelineEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event
|
private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event
|
||||||
|
|||||||
+2
-3
@@ -60,7 +60,7 @@ fun aTimelineState(
|
|||||||
displayJumpToUnread: Boolean = true,
|
displayJumpToUnread: Boolean = true,
|
||||||
readMarkerIndex: Int = -1,
|
readMarkerIndex: Int = -1,
|
||||||
unreadMessagesCount: Int = 0,
|
unreadMessagesCount: Int = 0,
|
||||||
newMessagesCount: Int = 0,
|
newEventState: NewEventState = NewEventState.None,
|
||||||
eventSink: (TimelineEvent) -> Unit = {},
|
eventSink: (TimelineEvent) -> Unit = {},
|
||||||
): TimelineState {
|
): TimelineState {
|
||||||
val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId
|
val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId
|
||||||
@@ -74,7 +74,7 @@ fun aTimelineState(
|
|||||||
timelineMode = timelineMode,
|
timelineMode = timelineMode,
|
||||||
timelineRoomInfo = timelineRoomInfo,
|
timelineRoomInfo = timelineRoomInfo,
|
||||||
renderReadReceipts = renderReadReceipts,
|
renderReadReceipts = renderReadReceipts,
|
||||||
newEventState = NewEventState.None,
|
newEventState = newEventState,
|
||||||
isLive = isLive,
|
isLive = isLive,
|
||||||
focusRequestState = focusRequestState,
|
focusRequestState = focusRequestState,
|
||||||
messageShieldDialogData = messageShield?.let { MessageShieldData(it) },
|
messageShieldDialogData = messageShield?.let { MessageShieldData(it) },
|
||||||
@@ -84,7 +84,6 @@ fun aTimelineState(
|
|||||||
displayJumpToUnread = displayJumpToUnread,
|
displayJumpToUnread = displayJumpToUnread,
|
||||||
readMarkerIndex = readMarkerIndex,
|
readMarkerIndex = readMarkerIndex,
|
||||||
unreadMessagesCount = unreadMessagesCount,
|
unreadMessagesCount = unreadMessagesCount,
|
||||||
newMessagesCount = newMessagesCount,
|
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -222,7 +222,6 @@ fun TimelineView(
|
|||||||
focusRequestState = state.focusRequestState,
|
focusRequestState = state.focusRequestState,
|
||||||
readMarkerIndex = state.readMarkerIndex,
|
readMarkerIndex = state.readMarkerIndex,
|
||||||
unreadMessagesCount = state.unreadMessagesCount,
|
unreadMessagesCount = state.unreadMessagesCount,
|
||||||
newMessagesCount = state.newMessagesCount,
|
|
||||||
displayJumpToUnread = state.displayJumpToUnread,
|
displayJumpToUnread = state.displayJumpToUnread,
|
||||||
topInset = floatingDateTopOffset,
|
topInset = floatingDateTopOffset,
|
||||||
onScrollFinishAt = ::onScrollFinishAt,
|
onScrollFinishAt = ::onScrollFinishAt,
|
||||||
@@ -310,7 +309,6 @@ private fun BoxScope.TimelineScrollHelper(
|
|||||||
focusRequestState: FocusRequestState,
|
focusRequestState: FocusRequestState,
|
||||||
readMarkerIndex: Int,
|
readMarkerIndex: Int,
|
||||||
unreadMessagesCount: Int,
|
unreadMessagesCount: Int,
|
||||||
newMessagesCount: Int,
|
|
||||||
displayJumpToUnread: Boolean,
|
displayJumpToUnread: Boolean,
|
||||||
topInset: Dp,
|
topInset: Dp,
|
||||||
onScrollFinishAt: (Int) -> Unit,
|
onScrollFinishAt: (Int) -> Unit,
|
||||||
@@ -386,8 +384,11 @@ private fun BoxScope.TimelineScrollHelper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(canAutoScroll, newEventState) {
|
LaunchedEffect(canAutoScroll, newEventState) {
|
||||||
val shouldScrollToBottom = isScrollFinished &&
|
val shouldScrollToBottom = isScrollFinished && when (newEventState) {
|
||||||
(canAutoScroll && newEventState == NewEventState.FromOther || newEventState == NewEventState.FromMe)
|
is NewEventState.FromOther -> canAutoScroll
|
||||||
|
NewEventState.FromMe -> true
|
||||||
|
NewEventState.None -> false
|
||||||
|
}
|
||||||
if (shouldScrollToBottom) {
|
if (shouldScrollToBottom) {
|
||||||
scrollToBottom(force = true)
|
scrollToBottom(force = true)
|
||||||
}
|
}
|
||||||
@@ -406,7 +407,7 @@ private fun BoxScope.TimelineScrollHelper(
|
|||||||
contentDescription = stringResource(id = CommonStrings.a11y_jump_to_bottom),
|
contentDescription = stringResource(id = CommonStrings.a11y_jump_to_bottom),
|
||||||
isVisible = isJumpToBottomVisible,
|
isVisible = isJumpToBottomVisible,
|
||||||
// Hide the badge entirely when the feature is off, regardless of the count value.
|
// Hide the badge entirely when the feature is off, regardless of the count value.
|
||||||
count = if (displayJumpToUnread) newMessagesCount else 0,
|
count = if (displayJumpToUnread) newEventState.messageCount else 0,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.padding(end = 24.dp, bottom = 12.dp),
|
.padding(end = 24.dp, bottom = 12.dp),
|
||||||
@@ -565,7 +566,7 @@ private fun TimelineViewWithReadMarker(
|
|||||||
// view. The actual scroll target doesn't matter for a static preview.
|
// view. The actual scroll target doesn't matter for a static preview.
|
||||||
readMarkerIndex = timelineItems.size,
|
readMarkerIndex = timelineItems.size,
|
||||||
unreadMessagesCount = unreadMessagesCount,
|
unreadMessagesCount = unreadMessagesCount,
|
||||||
newMessagesCount = newMessagesCount,
|
newEventState = if (newMessagesCount > 0) NewEventState.FromOther(newMessagesCount) else NewEventState.None,
|
||||||
),
|
),
|
||||||
timelineProtectionState = aTimelineProtectionState(),
|
timelineProtectionState = aTimelineProtectionState(),
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
|
|||||||
+12
-4
@@ -8,12 +8,20 @@
|
|||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.model
|
package io.element.android.features.messages.impl.timeline.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model if there is a new event in the timeline and if it is from me or from other.
|
* Model if there is a new event in the timeline and if it is from me or from other.
|
||||||
* This can be used to scroll to the bottom of the list when a new event is added.
|
* This can be used to scroll to the bottom of the list when a new event is added.
|
||||||
|
*
|
||||||
|
* [FromOther] also carries the running count of incoming messages from other users since the
|
||||||
|
* timeline was last at the bottom — used to drive the badge on the scroll-to-bottom button.
|
||||||
*/
|
*/
|
||||||
enum class NewEventState {
|
@Immutable
|
||||||
None,
|
sealed interface NewEventState {
|
||||||
FromMe,
|
val messageCount: Int get() = 0
|
||||||
FromOther
|
|
||||||
|
data object None : NewEventState
|
||||||
|
data object FromMe : NewEventState
|
||||||
|
data class FromOther(override val messageCount: Int) : NewEventState
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-31
@@ -362,7 +362,10 @@ class TimelinePresenterTest {
|
|||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.timelineItems.size == 4 }
|
consumeItemsUntilPredicate { it.timelineItems.size == 4 }
|
||||||
awaitLastSequentialItem().also { state ->
|
awaitLastSequentialItem().also { state ->
|
||||||
assertThat(state.newEventState).isEqualTo(NewEventState.FromOther)
|
// Both events received during the FromMe window are counted now that the timeline
|
||||||
|
// has progressed past it: prevMostRecentItemId points at the local user's "1",
|
||||||
|
// so "2" and "3" are both new countable messages.
|
||||||
|
assertThat(state.newEventState).isEqualTo(NewEventState.FromOther(2))
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
@@ -450,13 +453,13 @@ class TimelinePresenterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount increments by N when N events from others arrive in one batch`() = runTest {
|
fun `present - FromOther count increments by N when N events from others arrive in one batch`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(timelineItems = timelineItems)
|
val timeline = FakeTimeline(timelineItems = timelineItems)
|
||||||
val presenter = createTimelinePresenter(timeline)
|
val presenter = createTimelinePresenter(timeline)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.newMessagesCount).isEqualTo(0)
|
assertThat(initialState.newEventState).isEqualTo(NewEventState.None)
|
||||||
// Seed prevMostRecentItemId so subsequent emissions count as new events.
|
// Seed prevMostRecentItemId so subsequent emissions count as new events.
|
||||||
timelineItems.emit(
|
timelineItems.emit(
|
||||||
listOf(MatrixTimelineItem.Event(UniqueId("seed"), anEventTimelineItem(content = aMessageContent())))
|
listOf(MatrixTimelineItem.Event(UniqueId("seed"), anEventTimelineItem(content = aMessageContent())))
|
||||||
@@ -470,16 +473,15 @@ class TimelinePresenterTest {
|
|||||||
MatrixTimelineItem.Event(UniqueId("3"), anEventTimelineItem(content = aMessageContent())),
|
MatrixTimelineItem.Event(UniqueId("3"), anEventTimelineItem(content = aMessageContent())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 3 }.last().also { state ->
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(3) }.last().also { state ->
|
||||||
assertThat(state.newMessagesCount).isEqualTo(3)
|
assertThat(state.newEventState).isEqualTo(NewEventState.FromOther(3))
|
||||||
assertThat(state.newEventState).isEqualTo(NewEventState.FromOther)
|
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount resets to 0 on OnScrollFinished firstIndex 0`() = runTest {
|
fun `present - newEventState resets to None on OnScrollFinished firstIndex 0`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(
|
val timeline = FakeTimeline(
|
||||||
timelineItems = timelineItems,
|
timelineItems = timelineItems,
|
||||||
@@ -495,18 +497,18 @@ class TimelinePresenterTest {
|
|||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())))
|
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())))
|
||||||
}
|
}
|
||||||
val countedState = consumeItemsUntilPredicate { it.newMessagesCount == 1 }.last()
|
val countedState = consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(1) }.last()
|
||||||
assertThat(countedState.newMessagesCount).isEqualTo(1)
|
assertThat(countedState.newEventState).isEqualTo(NewEventState.FromOther(1))
|
||||||
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0))
|
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0))
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 0 }.last().also { state ->
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.None }.last().also { state ->
|
||||||
assertThat(state.newMessagesCount).isEqualTo(0)
|
assertThat(state.newEventState).isEqualTo(NewEventState.None)
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount resets to 0 when latest event is from me`() = runTest {
|
fun `present - newEventState transitions to FromMe when latest event is from me, dropping the count`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(timelineItems = timelineItems)
|
val timeline = FakeTimeline(timelineItems = timelineItems)
|
||||||
val presenter = createTimelinePresenter(timeline)
|
val presenter = createTimelinePresenter(timeline)
|
||||||
@@ -520,25 +522,23 @@ class TimelinePresenterTest {
|
|||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())))
|
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())))
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 1 }
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(1) }
|
||||||
// Then the local user sends a message: count should reset.
|
// Then the local user sends a message: state moves to FromMe (which carries no count).
|
||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(
|
items + listOf(
|
||||||
MatrixTimelineItem.Event(UniqueId("2"), anEventTimelineItem(content = aMessageContent(), isOwn = true)),
|
MatrixTimelineItem.Event(UniqueId("2"), anEventTimelineItem(content = aMessageContent(), isOwn = true)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate {
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromMe }.last().also { state ->
|
||||||
it.newEventState == NewEventState.FromMe && it.newMessagesCount == 0
|
|
||||||
}.last().also { state ->
|
|
||||||
assertThat(state.newMessagesCount).isEqualTo(0)
|
|
||||||
assertThat(state.newEventState).isEqualTo(NewEventState.FromMe)
|
assertThat(state.newEventState).isEqualTo(NewEventState.FromMe)
|
||||||
|
assertThat(state.newEventState.messageCount).isEqualTo(0)
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount does not reset on OnScrollFinished firstIndex other than 0`() = runTest {
|
fun `present - FromOther count does not reset on OnScrollFinished firstIndex other than 0`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(timelineItems = timelineItems)
|
val timeline = FakeTimeline(timelineItems = timelineItems)
|
||||||
val presenter = createTimelinePresenter(timeline)
|
val presenter = createTimelinePresenter(timeline)
|
||||||
@@ -551,19 +551,19 @@ class TimelinePresenterTest {
|
|||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())))
|
items + listOf(MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())))
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 1 }
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(1) }
|
||||||
// Scrolling stops above the bottom: the count must NOT reset.
|
// Scrolling stops above the bottom: the count must NOT reset.
|
||||||
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(5))
|
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(5))
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
// No state should emit with newMessagesCount == 0.
|
// No state should emit with the count back at 0.
|
||||||
val drained = consumeItemsUntilTimeout()
|
val drained = consumeItemsUntilTimeout()
|
||||||
assertThat(drained.any { it.newMessagesCount == 0 }).isFalse()
|
assertThat(drained.any { it.newEventState.messageCount == 0 }).isFalse()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount does not increment for events with PAGINATION origin`() = runTest {
|
fun `present - FromOther count does not increment for events with PAGINATION origin`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(timelineItems = timelineItems)
|
val timeline = FakeTimeline(timelineItems = timelineItems)
|
||||||
val presenter = createTimelinePresenter(timeline)
|
val presenter = createTimelinePresenter(timeline)
|
||||||
@@ -583,14 +583,14 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.timelineItems.size == 2 }.last().also { state ->
|
consumeItemsUntilPredicate { it.timelineItems.size == 2 }.last().also { state ->
|
||||||
assertThat(state.newMessagesCount).isEqualTo(0)
|
assertThat(state.newEventState.messageCount).isEqualTo(0)
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount does not increment for state events`() = runTest {
|
fun `present - FromOther count does not increment for state events`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(timelineItems = timelineItems)
|
val timeline = FakeTimeline(timelineItems = timelineItems)
|
||||||
val presenter = createTimelinePresenter(timeline)
|
val presenter = createTimelinePresenter(timeline)
|
||||||
@@ -607,7 +607,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.timelineItems.size == 2 }.last().also { state ->
|
consumeItemsUntilPredicate { it.timelineItems.size == 2 }.last().also { state ->
|
||||||
assertThat(state.newMessagesCount).isEqualTo(0)
|
assertThat(state.newEventState.messageCount).isEqualTo(0)
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
@@ -632,7 +632,7 @@ class TimelinePresenterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - newMessagesCount accumulates across multiple batches as prevMostRecentItemId advances`() = runTest {
|
fun `present - FromOther count accumulates across multiple batches as prevMostRecentItemId advances`() = runTest {
|
||||||
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
|
||||||
val timeline = FakeTimeline(timelineItems = timelineItems)
|
val timeline = FakeTimeline(timelineItems = timelineItems)
|
||||||
val presenter = createTimelinePresenter(timeline)
|
val presenter = createTimelinePresenter(timeline)
|
||||||
@@ -647,7 +647,7 @@ class TimelinePresenterTest {
|
|||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(MatrixTimelineItem.Event(UniqueId("b1-1"), anEventTimelineItem(content = aMessageContent())))
|
items + listOf(MatrixTimelineItem.Event(UniqueId("b1-1"), anEventTimelineItem(content = aMessageContent())))
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 1 }
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(1) }
|
||||||
// Batch 2: 2 more new events → count = 3.
|
// Batch 2: 2 more new events → count = 3.
|
||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(
|
items + listOf(
|
||||||
@@ -655,13 +655,13 @@ class TimelinePresenterTest {
|
|||||||
MatrixTimelineItem.Event(UniqueId("b2-2"), anEventTimelineItem(content = aMessageContent())),
|
MatrixTimelineItem.Event(UniqueId("b2-2"), anEventTimelineItem(content = aMessageContent())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 3 }
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(3) }
|
||||||
// Batch 3: 1 more new event → count = 4.
|
// Batch 3: 1 more new event → count = 4.
|
||||||
timelineItems.getAndUpdate { items ->
|
timelineItems.getAndUpdate { items ->
|
||||||
items + listOf(MatrixTimelineItem.Event(UniqueId("b3-1"), anEventTimelineItem(content = aMessageContent())))
|
items + listOf(MatrixTimelineItem.Event(UniqueId("b3-1"), anEventTimelineItem(content = aMessageContent())))
|
||||||
}
|
}
|
||||||
consumeItemsUntilPredicate { it.newMessagesCount == 4 }.last().also { state ->
|
consumeItemsUntilPredicate { it.newEventState == NewEventState.FromOther(4) }.last().also { state ->
|
||||||
assertThat(state.newMessagesCount).isEqualTo(4)
|
assertThat(state.newEventState).isEqualTo(NewEventState.FromOther(4))
|
||||||
}
|
}
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user