From 35c8eb8b155683d84a8aada2ce652389289de30d Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 11 Jul 2023 12:50:50 +0200 Subject: [PATCH 01/10] Timeline: improve "jump to bottom" button --- .../messages/impl/timeline/TimelineView.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index bb33981a64..a8da9d5543 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -21,6 +21,7 @@ package io.element.android.features.messages.impl.timeline import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Box @@ -278,8 +279,8 @@ internal fun BoxScope.TimelineScrollHelper( .align(Alignment.BottomEnd) .padding(end = 24.dp, bottom = 12.dp), visible = showScrollToBottomButton || LocalInspectionMode.current, - enter = scaleIn(), - exit = scaleOut(), + enter = scaleIn(animationSpec = tween(100)), + exit = scaleOut(animationSpec = tween(100)), ) { FloatingActionButton( onClick = { @@ -293,14 +294,7 @@ internal fun BoxScope.TimelineScrollHelper( }, elevation = FloatingActionButtonDefaults.elevation(4.dp, 4.dp, 4.dp, 4.dp), shape = CircleShape, - modifier = Modifier - .shadow( - elevation = 4.dp, - shape = CircleShape, - ambientColor = ElementTheme.materialColors.primary, - spotColor = ElementTheme.materialColors.primary, - ) - .size(36.dp), + modifier = Modifier.size(36.dp), containerColor = ElementTheme.colors.bgSubtleSecondary, contentColor = ElementTheme.colors.iconSecondary ) { From 51a7b8edd349f5453e7ea9efe9233d28172bdb1d Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 11 Jul 2023 12:51:01 +0200 Subject: [PATCH 02/10] Timeline: remove duplicated code --- .../features/messages/impl/timeline/TimelineView.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index a8da9d5543..4e0ca6124b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -101,13 +101,6 @@ fun TimelineView( // TODO implement this logic once we have support to 'jump to event X' in sliding sync } - // Send an event to the presenter when the scrolling is finished, with the first visible index at the bottom. - val firstVisibleIndex by remember { derivedStateOf { lazyListState.firstVisibleItemIndex } } - val isScrollFinished by remember { derivedStateOf { !lazyListState.isScrollInProgress } } - LaunchedEffect(firstVisibleIndex, isScrollFinished) { - if (!isScrollFinished) return@LaunchedEffect - state.eventSink(TimelineEvents.OnScrollFinished(firstVisibleIndex)) - } Box(modifier = modifier) { LazyColumn( From dc4e36147d49af1ac619c3232752876b051a47da Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jul 2023 13:05:56 +0200 Subject: [PATCH 03/10] Timeline: introduce origin on timeline items --- .../impl/timeline/TimelineStateProvider.kt | 1 + .../event/TimelineItemEventFactory.kt | 1 + .../impl/timeline/model/TimelineItem.kt | 2 ++ .../timeline/item/event/EventTimelineItem.kt | 1 + .../item/event/TimelineItemEventOrigin.kt | 21 +++++++++++++++++++ .../item/event/EventTimelineItemMapper.kt | 11 ++++++++++ 6 files changed, 37 insertions(+) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index c92658f7e8..b3c6f01303 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -126,6 +126,7 @@ internal fun aTimelineItemEvent( localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, + origin = null ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 9a09c77a34..6bc5df1e79 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -85,6 +85,7 @@ class TimelineItemEventFactory @Inject constructor( localSendState = currentTimelineItem.event.localSendState, inReplyTo = currentTimelineItem.event.inReplyTo(), debugInfo = currentTimelineItem.event.debugInfo, + origin = currentTimelineItem.event.origin, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 21b7e8607f..7f95b30409 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import kotlinx.collections.immutable.ImmutableList @Immutable @@ -65,6 +66,7 @@ sealed interface TimelineItem { val localSendState: LocalEventSendState?, val inReplyTo: InReplyTo?, val debugInfo: TimelineItemDebugInfo, + val origin: TimelineItemEventOrigin?, ) : TimelineItem { val showSenderInformation = groupPosition.isNew() && !isMine diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 854d38b7dd..67dd580426 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -34,6 +34,7 @@ data class EventTimelineItem( val timestamp: Long, val content: EventContent, val debugInfo: TimelineItemDebugInfo, + val origin: TimelineItemEventOrigin?, ) { fun inReplyTo(): InReplyTo? { return (content as? MessageContent)?.inReplyTo diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt new file mode 100644 index 0000000000..0f906e6719 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.timeline.item.event + +enum class TimelineItemEventOrigin { + LOCAL, SYNC, PAGINATION; +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index b4887faf68..ec6ca0897b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -19,11 +19,13 @@ package io.element.android.libraries.matrix.impl.timeline.item.event import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import org.matrix.rustcomponents.sdk.Reaction +import org.matrix.rustcomponents.sdk.EventItemOrigin as RustEventItemOrigin import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo @@ -46,6 +48,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap timestamp = it.timestamp().toLong(), content = contentMapper.map(it.content()), debugInfo = it.debugInfo().map(), + origin = it.origin()?.map() ) } } @@ -90,3 +93,11 @@ private fun RustEventTimelineItemDebugInfo.map(): TimelineItemDebugInfo { latestEditedJson = latestEditJson, ) } + +private fun RustEventItemOrigin.map(): TimelineItemEventOrigin { + return when (this) { + RustEventItemOrigin.LOCAL -> TimelineItemEventOrigin.LOCAL + RustEventItemOrigin.SYNC -> TimelineItemEventOrigin.SYNC + RustEventItemOrigin.PAGINATION -> TimelineItemEventOrigin.PAGINATION + } +} From b9676c1ec0cf7ad85c5707e1b9f697e3bb5b7449 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jul 2023 13:08:25 +0200 Subject: [PATCH 04/10] Timeline : improve auto-scroll --- .../impl/timeline/TimelinePresenter.kt | 61 ++++++++++++---- .../messages/impl/timeline/TimelineState.kt | 1 + .../impl/timeline/TimelineStateProvider.kt | 3 +- .../messages/impl/timeline/TimelineView.kt | 69 ++++++++++--------- 4 files changed, 87 insertions(+), 47 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 0833ff2205..353d94e390 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -22,21 +22,25 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.ui.room.canSendEventAsState import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject private const val backPaginationEventLimit = 20 @@ -45,42 +49,57 @@ private const val backPaginationPageSize = 50 class TimelinePresenter @Inject constructor( private val timelineItemsFactory: TimelineItemsFactory, private val room: MatrixRoom, + private val dispatchers: CoroutineDispatchers, + private val appScope: CoroutineScope, ) : Presenter { private val timeline = room.timeline @Composable override fun present(): TimelineState { - val localCoroutineScope = rememberCoroutineScope() + val localScope = rememberCoroutineScope() val highlightedEventId: MutableState = rememberSaveable { mutableStateOf(null) } - var lastReadMarkerIndex by rememberSaveable { mutableStateOf(Int.MAX_VALUE) } - var lastReadMarkerId by rememberSaveable { mutableStateOf(null) } + var lastReadReceiptIndex by rememberSaveable { mutableStateOf(Int.MAX_VALUE) } + var lastReadReceiptId by rememberSaveable { mutableStateOf(null) } val timelineItems by timelineItemsFactory.collectItemsAsState() val paginationState by timeline.paginationState.collectAsState() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) + val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } + val hasNewItems = remember { mutableStateOf(false) } + + fun CoroutineScope.sendReadReceiptIfNeeded(firstVisibleIndex: Int) = launch(dispatchers.computation) { + // Get last valid EventId seen by the user, as the first index might refer to a Virtual item + val eventId = getLastEventIdBeforeOrAt(firstVisibleIndex, timelineItems) + if (eventId != null && firstVisibleIndex <= lastReadReceiptIndex && eventId != lastReadReceiptId) { + lastReadReceiptIndex = firstVisibleIndex + lastReadReceiptId = eventId + timeline.sendReadReceipt(eventId) + } + } + fun handleEvents(event: TimelineEvents) { when (event) { - TimelineEvents.LoadMore -> localCoroutineScope.paginateBackwards() + TimelineEvents.LoadMore -> localScope.paginateBackwards() is TimelineEvents.SetHighlightedEvent -> highlightedEventId.value = event.eventId is TimelineEvents.OnScrollFinished -> { - // Get last valid EventId seen by the user, as the first index might refer to a Virtual item - val eventId = getLastEventIdBeforeOrAt(event.firstIndex, timelineItems) ?: return - if (event.firstIndex <= lastReadMarkerIndex && eventId != lastReadMarkerId) { - lastReadMarkerIndex = event.firstIndex - lastReadMarkerId = eventId - localCoroutineScope.sendReadReceipt(eventId) + if (event.firstIndex == 0) { + hasNewItems.value = false } + appScope.sendReadReceiptIfNeeded(event.firstIndex) } } } + LaunchedEffect(timelineItems.size) { + computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems) + } + LaunchedEffect(Unit) { timeline .timelineItems @@ -98,10 +117,26 @@ class TimelinePresenter @Inject constructor( canReply = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, + hasNewItems = hasNewItems.value, eventSink = ::handleEvents ) } + private suspend fun computeHasNewItems( + timelineItems: ImmutableList, + prevMostRecentItemId: MutableState, + hasNewItemsState: MutableState + ) = withContext(dispatchers.computation) { + val newMostRecentItem = timelineItems.firstOrNull() + val prevMostRecentItemIdValue = prevMostRecentItemId.value + val newMostRecentItemId = newMostRecentItem?.identifier() + hasNewItemsState.value = prevMostRecentItemIdValue != null && + newMostRecentItem is TimelineItem.Event && + newMostRecentItem.origin != TimelineItemEventOrigin.PAGINATION && + newMostRecentItemId != prevMostRecentItemIdValue + prevMostRecentItemId.value = newMostRecentItemId + } + private fun getLastEventIdBeforeOrAt(index: Int, items: ImmutableList): EventId? { for (item in items.subList(index, items.count())) { if (item is TimelineItem.Event) { @@ -114,8 +149,4 @@ class TimelinePresenter @Inject constructor( private fun CoroutineScope.paginateBackwards() = launch { timeline.paginateBackwards(backPaginationEventLimit, backPaginationPageSize) } - - private fun CoroutineScope.sendReadReceipt(eventId: EventId) = launch { - timeline.sendReadReceipt(eventId) - } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 0aa1bd0160..ab5874d39c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -28,5 +28,6 @@ data class TimelineState( val highlightedEventId: EventId?, val canReply: Boolean, val paginationState: MatrixTimeline.PaginationState, + val hasNewItems: Boolean, val eventSink: (TimelineEvents) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index b3c6f01303..4e62b8649f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -44,7 +44,8 @@ fun aTimelineState(timelineItems: ImmutableList = persistentListOf paginationState = MatrixTimeline.PaginationState(isBackPaginating = false, hasMoreToLoadBackwards = true), highlightedEventId = null, canReply = true, - eventSink = {} + hasNewItems = false, + eventSink = {}, ) internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 1e00bea1a9..3bbaffab9f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -49,7 +49,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.tooling.preview.Preview @@ -72,7 +71,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.theme.ElementTheme -import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.launch @Composable @@ -141,8 +139,8 @@ fun TimelineView( TimelineScrollHelper( lazyListState = lazyListState, - timelineItems = state.timelineItems, - onScrollFinishedAt = ::onScrollFinishedAt, + hasNewItems = state.hasNewItems, + onScrollFinishedAt = ::onScrollFinishedAt ) } } @@ -238,53 +236,62 @@ fun TimelineItemRow( } @Composable -internal fun BoxScope.TimelineScrollHelper( +private fun BoxScope.TimelineScrollHelper( lazyListState: LazyListState, - timelineItems: ImmutableList, - onScrollFinishedAt: (Int) -> Unit = {}, + hasNewItems: Boolean, + onScrollFinishedAt: (Int) -> Unit, ) { val coroutineScope = rememberCoroutineScope() - val firstVisibleItemIndex by remember { derivedStateOf { lazyListState.firstVisibleItemIndex } } val isScrollFinished by remember { derivedStateOf { !lazyListState.isScrollInProgress } } - val shouldAutoScrollToBottom by remember { derivedStateOf { lazyListState.firstVisibleItemIndex < 2 } } - val showScrollToBottomButton by remember { derivedStateOf { lazyListState.firstVisibleItemIndex > 0 } } + val canAutoScroll by remember { derivedStateOf { lazyListState.firstVisibleItemIndex < 3 } } - LaunchedEffect(timelineItems, firstVisibleItemIndex) { - if (!isScrollFinished) return@LaunchedEffect - - // Auto-scroll when new timeline items appear - if (shouldAutoScrollToBottom) { + LaunchedEffect(canAutoScroll, hasNewItems) { + val shouldAutoScroll = isScrollFinished && canAutoScroll && hasNewItems + if (shouldAutoScroll) { coroutineScope.launch { lazyListState.animateScrollToItem(0) } } } - LaunchedEffect(isScrollFinished) { - if (!isScrollFinished) return@LaunchedEffect - // Notify the parent composable about the first visible item index when scrolling finishes - onScrollFinishedAt(firstVisibleItemIndex) + LaunchedEffect(isScrollFinished) { + if (isScrollFinished) { + // Notify the parent composable about the first visible item index when scrolling finishes + onScrollFinishedAt(lazyListState.firstVisibleItemIndex) + } } - // Jump to bottom button (display also in previews) - AnimatedVisibility( + JumpToBottomButton( + isVisible = !canAutoScroll, modifier = Modifier .align(Alignment.BottomEnd) .padding(end = 24.dp, bottom = 12.dp), - visible = showScrollToBottomButton || LocalInspectionMode.current, + onClick = { + coroutineScope.launch { + if (lazyListState.firstVisibleItemIndex > 10) { + lazyListState.scrollToItem(0) + } else { + lazyListState.animateScrollToItem(0) + } + } + } + ) +} + +@Composable +private fun JumpToBottomButton( + isVisible: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + modifier = modifier, + visible = isVisible || LocalInspectionMode.current, enter = scaleIn(animationSpec = tween(100)), exit = scaleOut(animationSpec = tween(100)), ) { FloatingActionButton( - onClick = { - coroutineScope.launch { - if (firstVisibleItemIndex > 10) { - lazyListState.scrollToItem(0) - } else { - lazyListState.animateScrollToItem(0) - } - } - }, + onClick = onClick, elevation = FloatingActionButtonDefaults.elevation(4.dp, 4.dp, 4.dp, 4.dp), shape = CircleShape, modifier = Modifier.size(36.dp), From f80f6f5bd9cee9c78c64cf76ac4746d3e3290fad Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jul 2023 17:09:20 +0200 Subject: [PATCH 05/10] Timeline: fix some tests and a one more --- .../impl/timeline/TimelineStateProvider.kt | 5 +- .../messages/impl/timeline/TimelineView.kt | 1 + .../messages/MessagesPresenterTest.kt | 2 + .../messages/fixtures/aMessageEvent.kt | 1 + .../timeline/TimelinePresenterTest.kt | 123 +++++++++++------- .../groups/TimelineItemGrouperTest.kt | 1 + .../matrix/test/room/RoomSummaryFixture.kt | 24 +++- .../test/timeline/FakeMatrixTimeline.kt | 14 +- .../android/tests/testutils/LongTask.kt | 20 +++ 9 files changed, 137 insertions(+), 54 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 4e62b8649f..5b5319f952 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -154,13 +154,14 @@ internal fun aTimelineItemDebugInfo( model, originalJson, latestEditedJson ) -fun aGroupedEvents(): TimelineItem.GroupedEvents { +fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents { val event = aTimelineItemEvent( isMine = true, content = aTimelineItemStateEventContent(), groupPosition = TimelineItemGroupPosition.None ) return TimelineItem.GroupedEvents( + id = id.toString(), events = listOf( event, event, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 3bbaffab9f..0404edf7db 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -262,6 +262,7 @@ private fun BoxScope.TimelineScrollHelper( } JumpToBottomButton( + // Use inverse of canAutoScroll otherwise we might briefly see the before the scroll animation is triggered isVisible = !canAutoScroll, modifier = Modifier .align(Alignment.BottomEnd) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 53034131dc..abbbeccf37 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -572,6 +572,8 @@ class MessagesPresenterTest { val timelinePresenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), room = matrixRoom, + dispatchers = coroutineDispatchers, + appScope = this ) val buildMeta = aBuildMeta() val actionListPresenter = ActionListPresenter(buildMeta = buildMeta) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt index 088df6060f..4f1edcb64f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt @@ -52,4 +52,5 @@ internal fun aMessageEvent( localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, + origin = null ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index 2835155b14..ad6e41e483 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -23,22 +23,25 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter +import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory +import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aMessageContent import io.element.android.libraries.matrix.test.room.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline +import io.element.android.tests.testutils.awaitWithLatch +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test class TimelinePresenterTest { @Test fun `present - initial state`() = runTest { - val presenter = TimelinePresenter( - timelineItemsFactory = aTimelineItemsFactory(), - room = FakeMatrixRoom(), - ) + val presenter = createTimelinePresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -51,10 +54,7 @@ class TimelinePresenterTest { @Test fun `present - load more`() = runTest { - val presenter = TimelinePresenter( - timelineItemsFactory = aTimelineItemsFactory(), - room = FakeMatrixRoom(), - ) + val presenter = createTimelinePresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -73,10 +73,7 @@ class TimelinePresenterTest { @Test fun `present - set highlighted event`() = runTest { - val presenter = TimelinePresenter( - timelineItemsFactory = aTimelineItemsFactory(), - room = FakeMatrixRoom(), - ) + val presenter = createTimelinePresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -94,70 +91,106 @@ class TimelinePresenterTest { @Test fun `present - on scroll finished send read receipt if an event is before the index`() = runTest { - val timeline = FakeMatrixTimeline() - val timelineItemsFactory = aTimelineItemsFactory().apply { - replaceWith(listOf(MatrixTimelineItem.Event(0, anEventTimelineItem()))) - } - val room = FakeMatrixRoom(matrixTimeline = timeline) - val presenter = TimelinePresenter( - timelineItemsFactory = timelineItemsFactory, - room = room, + val timeline = FakeMatrixTimeline( + initialTimelineItems = listOf( + MatrixTimelineItem.Event(0, anEventTimelineItem()) + ) ) + val presenter = createTimelinePresenter(timeline) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitItem() - - initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) - + awaitWithLatch { latch -> + timeline.sendReadReceiptLatch = latch + initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) + } assertThat(timeline.sendReadReceiptCount).isEqualTo(1) + cancelAndIgnoreRemainingEvents() } } @Test fun `present - on scroll finished will not send read receipt no event is before the index`() = runTest { - val timeline = FakeMatrixTimeline() - val timelineItemsFactory = aTimelineItemsFactory().apply { - replaceWith(listOf(MatrixTimelineItem.Event(0, anEventTimelineItem()))) - } - val room = FakeMatrixRoom(matrixTimeline = timeline) - val presenter = TimelinePresenter( - timelineItemsFactory = timelineItemsFactory, - room = room, + val timeline = FakeMatrixTimeline( + initialTimelineItems = listOf( + MatrixTimelineItem.Event(0, anEventTimelineItem()) + ) ) + val presenter = createTimelinePresenter(timeline) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitItem() - - initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) - + awaitWithLatch { latch -> + timeline.sendReadReceiptLatch = latch + initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) + } assertThat(timeline.sendReadReceiptCount).isEqualTo(0) + cancelAndIgnoreRemainingEvents() } } @Test fun `present - on scroll finished will not send read receipt only virtual events exist before the index`() = runTest { - val timeline = FakeMatrixTimeline() - val timelineItemsFactory = aTimelineItemsFactory().apply { - replaceWith(listOf(MatrixTimelineItem.Virtual(0, VirtualTimelineItem.ReadMarker))) - } - val room = FakeMatrixRoom(matrixTimeline = timeline) - val presenter = TimelinePresenter( - timelineItemsFactory = timelineItemsFactory, - room = room, + val timeline = FakeMatrixTimeline( + initialTimelineItems = listOf( + MatrixTimelineItem.Virtual(0, VirtualTimelineItem.ReadMarker) + ) ) + val presenter = createTimelinePresenter(timeline) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitItem() - - initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) - + awaitWithLatch { latch -> + timeline.sendReadReceiptLatch = latch + initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) + } assertThat(timeline.sendReadReceiptCount).isEqualTo(0) + cancelAndIgnoreRemainingEvents() } } + + @Test + fun `present - covers hasNewItems scenarios`() = runTest { + val timeline = FakeMatrixTimeline() + val presenter = createTimelinePresenter(timeline) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.hasNewItems).isFalse() + assertThat(initialState.timelineItems.size).isEqualTo(0) + timeline.updateTimelineItems { + listOf(MatrixTimelineItem.Event(0, anEventTimelineItem(content = aMessageContent()))) + } + skipItems(1) + assertThat(awaitItem().timelineItems.size).isEqualTo(1) + timeline.updateTimelineItems { items -> + items + listOf(MatrixTimelineItem.Event(1, anEventTimelineItem(content = aMessageContent()))) + } + skipItems(1) + assertThat(awaitItem().timelineItems.size).isEqualTo(2) + assertThat(awaitItem().hasNewItems).isTrue() + initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) + assertThat(awaitItem().hasNewItems).isFalse() + cancelAndIgnoreRemainingEvents() + } + } + + private fun TestScope.createTimelinePresenter( + timeline: MatrixTimeline = FakeMatrixTimeline(), + timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory() + ): TimelinePresenter { + return TimelinePresenter( + timelineItemsFactory = timelineItemsFactory, + room = FakeMatrixRoom(matrixTimeline = timeline), + dispatchers = testCoroutineDispatchers(), + appScope = this + ) + } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt index 3cab1fe44c..71836e9f5e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt @@ -45,6 +45,7 @@ class TimelineItemGrouperTest { localSendState = LocalEventSendState.Sent(AN_EVENT_ID), inReplyTo = null, debugInfo = aTimelineItemDebugInfo(), + origin = null ) private val aNonGroupableItem = aMessageEvent() private val aNonGroupableItemNoEvent = TimelineItem.Virtual("virtual", aTimelineItemDaySeparatorModel("Today")) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 0a956577ca..d9cd6dc867 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -25,14 +25,17 @@ import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent +import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType 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_UNIQUE_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME @@ -114,6 +117,7 @@ fun anEventTimelineItem( timestamp = timestamp, content = content, debugInfo = debugInfo, + origin = null, ) fun aProfileTimelineDetails( @@ -138,6 +142,21 @@ fun aProfileChangeMessageContent( prevAvatarUrl = prevAvatarUrl, ) +fun aMessageContent( + body: String = "body", + inReplyTo: InReplyTo? = null, + isEdited: Boolean = false, + messageType: MessageType = TextMessageType( + body = body, + formatted = null + ) +) = MessageContent( + body = body, + inReplyTo = inReplyTo, + isEdited = isEdited, + type = messageType +) + fun aTimelineItemDebugInfo( model: String = "Rust(Model())", originalJson: String? = null, @@ -145,3 +164,4 @@ fun aTimelineItemDebugInfo( ) = TimelineItemDebugInfo( model, originalJson, latestEditedJson ) + diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt index b49a5490ce..73bc5fb597 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt @@ -19,6 +19,8 @@ package io.element.android.libraries.matrix.test.timeline import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -36,6 +38,8 @@ class FakeMatrixTimeline( var sendReadReceiptCount = 0 private set + var sendReadReceiptLatch: CompletableDeferred? = null + fun updatePaginationState(update: (MatrixTimeline.PaginationState.() -> MatrixTimeline.PaginationState)) { _paginationState.getAndUpdate(update) } @@ -62,13 +66,13 @@ class FakeMatrixTimeline( return Result.success(Unit) } - - override suspend fun fetchDetailsForEvent(eventId: EventId): Result { - return Result.success(Unit) + override suspend fun fetchDetailsForEvent(eventId: EventId): Result = simulateLongTask { + Result.success(Unit) } - override suspend fun sendReadReceipt(eventId: EventId): Result { + override suspend fun sendReadReceipt(eventId: EventId): Result = simulateLongTask { sendReadReceiptCount++ - return Result.success(Unit) + sendReadReceiptLatch?.complete(Unit) + Result.success(Unit) } } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt index 154877efe9..8a5158dbf8 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt @@ -16,7 +16,12 @@ package io.element.android.tests.testutils +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.delay +import kotlinx.coroutines.withTimeout +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds /** * Workaround for https://github.com/cashapp/molecule/issues/249. @@ -26,3 +31,18 @@ suspend inline fun simulateLongTask(lambda: () -> T): T { delay(1) return lambda() } + +/** + * Can be used for testing events in Presenter, where the event does not emit new state. + * If the (virtual) timeout is passed, we release the latch manually. + */ +suspend fun awaitWithLatch(timeout: Duration = 300.milliseconds, block: (CompletableDeferred) -> Unit) { + val latch = CompletableDeferred() + try { + withTimeout(timeout) { + latch.also(block).await() + } + } catch (exception: TimeoutCancellationException) { + latch.complete(Unit) + } +} From 32ab1f40e28cbce91a2950909da098c1611112d0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jul 2023 17:21:56 +0200 Subject: [PATCH 06/10] Timeline: make group id really stable --- .../timeline/groups/TimelineItemGrouper.kt | 39 ++++++++++++++++--- .../impl/timeline/model/TimelineItem.kt | 7 ++-- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt index b04509ba22..bee2b055f3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -17,10 +17,20 @@ package io.element.android.features.messages.impl.timeline.groups import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SingleIn import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject +@SingleIn(RoomScope::class) class TimelineItemGrouper @Inject constructor() { + + /** + * Keys are identifier of items in a group, only one by group will be kept. + * Values are the actual groupIds. + */ + private val groupIds = HashMap() + /** * Create a new list of [TimelineItem] by grouping some of them into [TimelineItem.GroupedEvents]. */ @@ -34,14 +44,14 @@ class TimelineItemGrouper @Inject constructor() { // timelineItem cannot be grouped if (currentGroup.isNotEmpty()) { // There is a pending group, create a TimelineItem.GroupedEvents if there is more than 1 Event in the pending group. - result.addGroup(currentGroup) + result.addGroup(groupIds, currentGroup) currentGroup.clear() } result.add(timelineItem) } } if (currentGroup.isNotEmpty()) { - result.addGroup(currentGroup) + result.addGroup(groupIds, currentGroup) } return result } @@ -51,16 +61,33 @@ class TimelineItemGrouper @Inject constructor() { * Will add a group if there is more than 1 item, else add the item to the list. */ private fun MutableList.addGroup( - group: MutableList + groupIds: MutableMap, + groupOfItems: MutableList ) { - if (group.size == 1) { + if (groupOfItems.size == 1) { // Do not create a group with just 1 item, just add the item to the result - add(group.first()) + add(groupOfItems.first()) } else { + val groupId = groupIds.getOrPutGroupId(groupOfItems) add( TimelineItem.GroupedEvents( - events = group.toImmutableList() + id = groupId, + events = groupOfItems.toImmutableList() ) ) } } + +private fun MutableMap.getOrPutGroupId(timelineItems: List): String { + assert(timelineItems.isNotEmpty()) + for (item in timelineItems) { + val itemIdentifier = item.identifier() + if (this.contains(itemIdentifier)) { + return this[itemIdentifier]!! + } + } + val itemIdentifier = timelineItems.first().identifier() + return "${itemIdentifier}_group".also { groupId -> + this[itemIdentifier] = groupId + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 7f95b30409..b415d128a2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -82,9 +82,8 @@ sealed interface TimelineItem { @Immutable data class GroupedEvents( + val id: String, val events: ImmutableList, - ) : TimelineItem { - // use last id with a suffix. Last will not change in cas of new event from backpagination. - val id = "${events.last().id}_group" - } + ) : TimelineItem + } From edb025a549ca211338b90da0f4599a440e337ddd Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 13 Jul 2023 15:38:07 +0000 Subject: [PATCH 07/10] Update screenshots --- ...Group_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...oup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png | 4 ++-- ...oup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png | 4 ++-- ...oup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- 36 files changed, 72 insertions(+), 72 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 1d34a00941..9f5b7d48c6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:651319df939573207244eb84d9cea2bfac1c216e704f58c27533d3b2d98c4c64 -size 51933 +oid sha256:1a3b5bbcdd1593e81384b335045bdcb0b3e01782868993e9f6437a15ca39dbca +size 51380 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 4fab21b0af..ba9f8a8d63 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0e751aee2e0f3fa735bbbb6b96d561fa485ebb3244dd51e3842c1f685cb1772 -size 63335 +oid sha256:7e26db0a0a8d767d6e732c1d2742cba3c3475d761b0c3017ff01cee7e3d362a5 +size 62773 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png index dce1f75bc4..7d12d81b18 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3182e3cd2971af26007edfbee2d0ad058eb1bae0fb79d110800f0980c982035d -size 50045 +oid sha256:3d5cae7d73178aecc12fbf5f1f27c9e66608b90d462deec862a881b403469e93 +size 49475 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png index 3fb99d2f69..44fd92b664 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e836190c744d6a65232c870544d4f1ec1020074c752585966488cd56e7bf6709 -size 66264 +oid sha256:fe9456f4446104142221e28167a578a2b5cac772dc35aeb14352c0d422dd6fb0 +size 65726 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png index 00ccd6eb51..ee3a9ae3fe 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba5b9161bd13d3185c4233b1c991c377f86ab7d33e61d549f1be0c89879db8f3 -size 56672 +oid sha256:cad3b5ce023d890fd4a5ff0f5bbabb678bc6d5c76d3476e8053673c06f360c8b +size 56102 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index abdb75b960..8e578cb1f9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9417f2631c429f047e799eba52183dc4a8be202f0110280332f837dd7b60c3d2 -size 229653 +oid sha256:71b3c712fb8e4bca178afed2de6f4184bb717e3415622e97f14bb4fe36aaf9d5 +size 229097 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 16994e6b8a..13d92ee329 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51bfd93f2bd0bf313e675d63a89fddacfb007fbe207ca15871d3f94df14c9d24 -size 230633 +oid sha256:f44d772e5b9fe65a79e05aaaa82536b19382f9d821c0412c47a7d330d539ffd2 +size 230080 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index 09e1fb4c35..4a58253b47 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:085091b7cb44ada72a16af6e3c652dd85589455648949db3bf37ef4160a8ebe9 -size 71401 +oid sha256:41a6c2dd81698696708802276b615a78ff22cc7cb8ae2d4b8d8cc862bfe44a24 +size 70856 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png index 2066060843..92bbf1dd70 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1eed02746fd9373275f6d66a283f3c9b0719e19d44194a1ba0cbb701c7c5d729 -size 85494 +oid sha256:f729f89dce978bb9e061826389e0db1af0e18835e4cd1ab6cbfaa31f4fd3152f +size 84942 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png index f01d3832e6..ace73de65e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eddf42039c5629333db7d7b56abbd7dba9af8c70bb4787e4101a768e264c3199 -size 174401 +oid sha256:03a4b62920af61098e850cfca6a6f43acd7b310094fb70e1c3e4b7e29465e244 +size 173851 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png index 94b7b6f75c..afb2440466 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:147052b5017396f02c0381ba8084b6c22d893e6ccc9e3551ce34ade7c05c61bc -size 165153 +oid sha256:ebcbb40acf19fb489f6a8068a22af0b79c304be46863e77fa12b3b333065a1b2 +size 164622 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png index e32ec387f4..08c13f8f2a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7655aa9de18743923bbe410b7ea05fe2d00ce716dc43f9673c8007951ec924ca -size 53316 +oid sha256:789ee5ccad8356198cdc0634b4e9a65ed44be2d26e7ce83a8662598c1bd8d4c2 +size 52765 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png index cc48e8a3fd..300bcf7167 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f81af9d9badaa6141390f83675ebeac7b3e9ec7412bb7ce2506e7123a5ec958 -size 65345 +oid sha256:7b40a2e5d60a906d7c35c3ece1854671f5b319b00ad077322d094cbf906c07f7 +size 64803 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 518e317011..7c99f5e856 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9546f65d0347f7c7c07f07e730ec8a344549a43e47a21585b7d9375ac0a1c838 -size 53692 +oid sha256:54c12970e3563de958f88e4c538dd368f9810266060627393256a91741f7c6cf +size 53340 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index bc40cfe66b..def9cbe0d1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8a62fb60461f2f699ea010663f617ad12c949365b3f9125804cc9c674bb1f03 -size 65959 +oid sha256:3b7ae3084cb9d1ecee2e4db49c228516bdd7352683e797edf737c8d216922dec +size 65601 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png index b9a3dd7e2c..258ce3f3de 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c34993f6832f141eeee8bbae9068828d58ebc900a21a658d5b0627096fe563c4 -size 51582 +oid sha256:dd034d439c08793e0dfd59f6bd5dcd88c06ded6cbe98172bf3cf296888e6d575 +size 51244 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png index 9f4542c3f8..b2f16a9d23 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d11d593be6d5ade5417aacef2aff79bc8940c971451669f01de4efa74fd7673 -size 69100 +oid sha256:d9d835cb1a420117b4d967181e2ca0fad71ba243d6a17bea08b82cae41f6b8e2 +size 68760 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png index c2c6fb7af6..3bbd94a1c8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3abc3d49852c70fe1d1c50e87cf86e91eb3cd91ff1cecca5ef0f1a30ab8dec9a -size 58883 +oid sha256:4ad09278ae2ebb8171adba96d9f6e91d0cc4f120b0b2368087796dadb37eeb87 +size 58539 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 5149d82b20..1c32b14777 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac58823ba2ae3c457f4199e71167693c6ea42839d912eb9b8a8d079aa26ec303 -size 230198 +oid sha256:145856c3a7ff43702403ee5b86a7119b0475f03fdbc0f2e7f84e10350a64b150 +size 229842 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index 19a4f805d5..53f57d95d2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5af0e9fa17bdf8562c0f4e2f31c0ff0e36e243ec3ae51f23eaca6fe67da9f6a6 -size 231168 +oid sha256:c1f0939b0c22ab89466953889e5bb63e11c45f02af79eeba3f377409af61d356 +size 230809 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index fa988a7752..4c991e3c30 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a84fdf49767a07d801a745ffaf659f15ead717f8edd6aeea31acf0cf82f1e1e5 -size 73991 +oid sha256:082206122d4e6d9e6171b3b2444c576ff7bd47fb3946d8d98e2812654f39cd40 +size 73641 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png index bbb417ede8..96270c69ae 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f494dc4f4b95e50fec21ee5c33a467711ce96dae8d2e68c091efc7874d682d8d -size 90101 +oid sha256:14ecdfd226a25743e3fac8850318216d6ec193755fa7559fd5523236b7835bc6 +size 89754 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png index 6c41c7f016..b62d934c0c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7359910c6bf938e2bb226b1ef7072de3441bd514f1c62286dfa5a1a9b2797d33 -size 364102 +oid sha256:d08de923f29ec6c3c2bf735ca0d015ca1097c95484119d1d39bb8c3f93f31100 +size 363776 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png index 92bddc4e40..3e79fb521a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59a85793b104485cf64dc090ce295d21248502d98696c1d41c3906936a7990a6 -size 323105 +oid sha256:3e8832da336aea6c7ebb3621668569ff16238042fb9650afcd938594a59fd3ae +size 322771 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png index 06e1aab216..ef081b76b8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1841a5b097309e8809b4a0ce8e798a19794e77bdb29d0037a98ff507fd8ba64a -size 55252 +oid sha256:9a243d53d10ca2eaa249b22af8a9fddf1a8ccf60db4f3ad9374e31cf494fe878 +size 54909 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png index 1028848722..0b13a93b0c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:373ab2d1c52b7fd818be21a3b8ef167b7ec27a0bb9147d040c680316f30ac346 -size 67816 +oid sha256:3fdd478be89b47fcaf9725ca14c1e775087f1ccf2a266f3476a537bbc2b29922 +size 67476 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index a20978d372..101f913aa8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6659d49217bb75dd62310ab1e5012466402203f2b9d11597da037f21e1273df -size 52885 +oid sha256:7c724bc77185a9ceec2cf092cb1d7865b13718d5320bd7ac4850bb85590f05b2 +size 52294 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 4b7eb025f1..cb28314caa 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9bc30e1ff19b87b1ee95e78f9b5c000cda8760a6e84c6364b02e8b725092586 -size 54301 +oid sha256:a303894134ed06348b609e41cf109dcafcd994c3dffabc6d9ab436fe92605245 +size 53710 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 4ec03a627d..2ba437739f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20689b49bd2ef39ea8a9b2d492f1e6ebb7841bacca74a80fdbde33964407e635 -size 53116 +oid sha256:7523ec0d6defd7074af0c804fdde64fb8d421f6cfa729bc1c4f9858bd87c42d0 +size 52554 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index afc72af080..73715a33a2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c3dd1a28aa9ef018e58435efe308822bfb49a99dc19366328edc6131039614f -size 55948 +oid sha256:1f48089147f7e089abfa64254143a80857ccc9f840aa60403e1aabc67e2b6d51 +size 55458 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index c682612eee..9ce6f92309 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:565e8b74a4e88fdd1533c982a7210a0d79a01efb2d1059bc03b40b05555a23bb -size 51661 +oid sha256:7ff682ee8363d450bb76db72ea06deea87fa47692ce319b7dff315d2a10dfb6a +size 51033 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 0de93cadc1..57eeca6786 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d449f35be765f1c5f38ff7bc026b34ebeeb46dc2e17ddd11d153b916ac8aa3c -size 54626 +oid sha256:e84edf8adf1a89153dd45272d36e04561d66f2ea765ad9edecfd8d750ba99f97 +size 54237 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 2f3be581f2..8baf81c8b1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7c8f63609634975edd98298264de50da56cd0a805a71e0a10cce846fcb077de -size 56077 +oid sha256:0bc9521bd1576d47ca6f643adf43ce3638d40328207d908ec863c72503d34f24 +size 55682 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 7ccf9a8669..40900d7e92 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e9579c9292407a7e837871a98785e135559c6584cca54b6ab997974f0ce24ae -size 54986 +oid sha256:c2209c3cc4e7de32ed92b3b0da4616b54d19091d43d21c0e4013728450d0d3f7 +size 54595 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index a3de6acb2e..5777c7f77e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf176efd967d4e00bae6589cc39cd24849f64f32d4d90e1d6461e29aa9f480f5 -size 57961 +oid sha256:d7bdd0ca39534b31c9d421e28337712b1cf2aaf841a766fcc6bdaf996c756bfa +size 57524 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 9908572779..2e2bfa89cb 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92a4c16a14f645b3db415bad8a69db1d8c3097f30087be45702f6a9c45ee6cd6 -size 53055 +oid sha256:ba349f81d5c417c612cae1263504ea5b4e83dc606ec20c3942368e8992b87ad4 +size 52886 From d7101f5170effffd720675ef27942331eb4d940d Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jul 2023 21:29:01 +0200 Subject: [PATCH 08/10] Timeline: fix tests --- .../timeline/groups/TimelineItemGrouper.kt | 10 ++++-- .../groups/TimelineItemGrouperTest.kt | 31 +++++++++++++++---- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt index bee2b055f3..e9e9af6445 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.timeline.groups +import androidx.annotation.VisibleForTesting import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn @@ -86,8 +87,11 @@ private fun MutableMap.getOrPutGroupId(timelineItems: List - this[itemIdentifier] = groupId + val timelineItem = timelineItems.first() + return computeGroupIdWith(timelineItem).also { groupId -> + this[timelineItem.identifier()] = groupId } } + +@VisibleForTesting +internal fun computeGroupIdWith(timelineItem: TimelineItem): String = "${timelineItem.identifier()}_group" diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt index 71836e9f5e..d5ce31f87a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt @@ -20,13 +20,13 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper +import io.element.android.features.messages.impl.timeline.groups.computeGroupIdWith import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel import io.element.android.libraries.designsystem.components.avatar.anAvatarData import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.test.AN_EVENT_ID -import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.aTimelineItemDebugInfo import kotlinx.collections.immutable.toImmutableList @@ -36,7 +36,7 @@ class TimelineItemGrouperTest { private val sut = TimelineItemGrouper() private val aGroupableItem = TimelineItem.Event( - id = AN_EVENT_ID.value, + id = "0", senderId = A_USER_ID, senderAvatar = anAvatarData(), senderDisplayName = "", @@ -76,16 +76,17 @@ class TimelineItemGrouperTest { fun `test groupables and ensure reordering`() { val result = sut.group( listOf( - aGroupableItem.copy(id = AN_EVENT_ID_2.value), - aGroupableItem, + aGroupableItem.copy(id = "1"), + aGroupableItem.copy(id = "0"), ), ) assertThat(result).isEqualTo( listOf( TimelineItem.GroupedEvents( + computeGroupIdWith(aGroupableItem), events = listOf( - aGroupableItem, - aGroupableItem.copy(id = AN_EVENT_ID_2.value), + aGroupableItem.copy("0"), + aGroupableItem.copy(id = "1"), ).toImmutableList() ), ) @@ -128,6 +129,7 @@ class TimelineItemGrouperTest { assertThat(result).isEqualTo( listOf( TimelineItem.GroupedEvents( + computeGroupIdWith(aGroupableItem), events = listOf( aGroupableItem, aGroupableItem, @@ -135,6 +137,7 @@ class TimelineItemGrouperTest { ), aNonGroupableItem, TimelineItem.GroupedEvents( + computeGroupIdWith(aGroupableItem), events = listOf( aGroupableItem, aGroupableItem, @@ -144,4 +147,20 @@ class TimelineItemGrouperTest { ) ) } + + @Test + fun `when calling multiple time the method group over a growing list of groupable items, then groupId is stable`() { + // When + val groupableItems = mutableListOf( + aGroupableItem.copy(id = "1"), + aGroupableItem.copy(id = "2") + ) + val expectedGroupId = sut.group(groupableItems).first().identifier() + groupableItems.add(0, aGroupableItem.copy("3")) + groupableItems.add(2, aGroupableItem.copy("4")) + groupableItems.add(aGroupableItem.copy("5")) + val actualGroupId = sut.group(groupableItems).first().identifier() + // Then + assertThat(actualGroupId).isEqualTo(expectedGroupId) + } } From 3d1bd343316fc20779f7aeb08a27c2fe90c773b9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 17 Jul 2023 23:47:00 +0200 Subject: [PATCH 09/10] Timeline: changes after pr review --- .../impl/timeline/TimelinePresenter.kt | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 353d94e390..488b6093cd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -25,7 +25,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.architecture.Presenter @@ -62,8 +61,8 @@ class TimelinePresenter @Inject constructor( mutableStateOf(null) } - var lastReadReceiptIndex by rememberSaveable { mutableStateOf(Int.MAX_VALUE) } - var lastReadReceiptId by rememberSaveable { mutableStateOf(null) } + val lastReadReceiptIndex = rememberSaveable { mutableStateOf(Int.MAX_VALUE) } + val lastReadReceiptId = rememberSaveable { mutableStateOf(null) } val timelineItems by timelineItemsFactory.collectItemsAsState() val paginationState by timeline.paginationState.collectAsState() @@ -73,16 +72,6 @@ class TimelinePresenter @Inject constructor( val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val hasNewItems = remember { mutableStateOf(false) } - fun CoroutineScope.sendReadReceiptIfNeeded(firstVisibleIndex: Int) = launch(dispatchers.computation) { - // Get last valid EventId seen by the user, as the first index might refer to a Virtual item - val eventId = getLastEventIdBeforeOrAt(firstVisibleIndex, timelineItems) - if (eventId != null && firstVisibleIndex <= lastReadReceiptIndex && eventId != lastReadReceiptId) { - lastReadReceiptIndex = firstVisibleIndex - lastReadReceiptId = eventId - timeline.sendReadReceipt(eventId) - } - } - fun handleEvents(event: TimelineEvents) { when (event) { TimelineEvents.LoadMore -> localScope.paginateBackwards() @@ -91,7 +80,12 @@ class TimelinePresenter @Inject constructor( if (event.firstIndex == 0) { hasNewItems.value = false } - appScope.sendReadReceiptIfNeeded(event.firstIndex) + appScope.sendReadReceiptIfNeeded( + firstVisibleIndex = event.firstIndex, + timelineItems = timelineItems, + lastReadReceiptIndex = lastReadReceiptIndex, + lastReadReceiptId = lastReadReceiptId + ) } } } @@ -122,6 +116,11 @@ class TimelinePresenter @Inject constructor( ) } + /** + * This method compute the hasNewItem state passed as a [MutableState] each time the timeline items size changes. + * 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 false from this method, but need to be reset from somewhere else. + */ private suspend fun computeHasNewItems( timelineItems: ImmutableList, prevMostRecentItemId: MutableState, @@ -130,13 +129,31 @@ class TimelinePresenter @Inject constructor( val newMostRecentItem = timelineItems.firstOrNull() val prevMostRecentItemIdValue = prevMostRecentItemId.value val newMostRecentItemId = newMostRecentItem?.identifier() - hasNewItemsState.value = prevMostRecentItemIdValue != null && + val hasNewItems = prevMostRecentItemIdValue != null && newMostRecentItem is TimelineItem.Event && newMostRecentItem.origin != TimelineItemEventOrigin.PAGINATION && newMostRecentItemId != prevMostRecentItemIdValue + if (hasNewItems) { + hasNewItemsState.value = true + } prevMostRecentItemId.value = newMostRecentItemId } + private fun CoroutineScope.sendReadReceiptIfNeeded( + firstVisibleIndex: Int, + timelineItems: ImmutableList, + lastReadReceiptIndex: MutableState, + lastReadReceiptId: MutableState, + ) = launch(dispatchers.computation) { + // Get last valid EventId seen by the user, as the first index might refer to a Virtual item + val eventId = getLastEventIdBeforeOrAt(firstVisibleIndex, timelineItems) + if (eventId != null && firstVisibleIndex <= lastReadReceiptIndex.value && eventId != lastReadReceiptId.value) { + lastReadReceiptIndex.value = firstVisibleIndex + lastReadReceiptId.value = eventId + timeline.sendReadReceipt(eventId) + } + } + private fun getLastEventIdBeforeOrAt(index: Int, items: ImmutableList): EventId? { for (item in items.subList(index, items.count())) { if (item is TimelineItem.Event) { From a74278c69034269403db0c1282b5a56515c60b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 18 Jul 2023 08:29:06 +0200 Subject: [PATCH 10/10] Fix `TimelinePresenterTests` --- .../features/messages/timeline/TimelinePresenterTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index ad6e41e483..c1d414c633 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -102,6 +102,8 @@ class TimelinePresenterTest { }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitItem() + // Wait for timeline items to be populated + skipItems(1) awaitWithLatch { latch -> timeline.sendReadReceiptLatch = latch initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) @@ -124,6 +126,8 @@ class TimelinePresenterTest { }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitItem() + // Wait for timeline items to be populated + skipItems(1) awaitWithLatch { latch -> timeline.sendReadReceiptLatch = latch initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) @@ -146,6 +150,8 @@ class TimelinePresenterTest { }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) val initialState = awaitItem() + // Wait for timeline items to be populated + skipItems(1) awaitWithLatch { latch -> timeline.sendReadReceiptLatch = latch initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))