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 fdd182b694..1361415dca 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 @@ -147,9 +147,10 @@ class TimelinePresenter @Inject constructor( timelineItemsFactory.replaceWith( timelineItems = it, roomMembers = if (readReceiptsEnabled) { - membersState.roomMembers() + membersState.roomMembers().orEmpty() } else { - null + // Give an empty list to not affect performance + emptyList() } ) } @@ -166,6 +167,7 @@ class TimelinePresenter @Inject constructor( userHasPermissionToSendMessage = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, + showReadReceipts = readReceiptsEnabled, hasNewItems = hasNewItems.value, sessionState = sessionState, eventSink = ::handleEvents 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 173e33b9c9..62836db130 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 @@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList @Immutable data class TimelineState( val timelineItems: ImmutableList, + val showReadReceipts: Boolean, val highlightedEventId: EventId?, val userHasPermissionToSendMessage: Boolean, val paginationState: MatrixTimeline.PaginationState, 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 51fddf0c6d..3defab97f5 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 @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.timeline +import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions @@ -44,6 +45,7 @@ import kotlin.random.Random fun aTimelineState(timelineItems: ImmutableList = persistentListOf()) = TimelineState( timelineItems = timelineItems, + showReadReceipts = false, paginationState = MatrixTimeline.PaginationState( isBackPaginating = false, hasMoreToLoadBackwards = true, @@ -124,7 +126,7 @@ internal fun aTimelineItemEvent( isThreaded: Boolean = false, debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(), - readReceiptState: TimelineItemReadReceipts = TimelineItemReadReceipts.Hidden, + readReceiptState: TimelineItemReadReceipts = aTimelineItemReadReceipts(), ): TimelineItem.Event { return TimelineItem.Event( id = UUID.randomUUID().toString(), @@ -176,6 +178,12 @@ internal fun aTimelineItemDebugInfo( model, originalJson, latestEditedJson ) +internal fun aTimelineItemReadReceipts(): TimelineItemReadReceipts { + return TimelineItemReadReceipts( + receipts = emptyList().toImmutableList(), + ) +} + fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents { val event = aTimelineItemEvent( isMine = true, 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 1c08291f24..7e4030a7c3 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 @@ -127,6 +127,7 @@ fun TimelineView( ) { timelineItem -> TimelineItemRow( timelineItem = timelineItem, + showReadReceipts = state.showReadReceipts, highlightedItem = state.highlightedEventId?.value, userHasPermissionToSendMessage = state.userHasPermissionToSendMessage, onClick = onMessageClicked, @@ -171,6 +172,7 @@ fun TimelineView( @Composable private fun TimelineItemRow( timelineItem: TimelineItem, + showReadReceipts: Boolean, highlightedItem: String?, userHasPermissionToSendMessage: Boolean, sessionState: SessionState, @@ -208,6 +210,7 @@ private fun TimelineItemRow( } else { TimelineItemEventRow( event = timelineItem, + showReadReceipts = showReadReceipts, isHighlighted = highlightedItem == timelineItem.identifier(), canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(), onClick = { onClick(timelineItem) }, @@ -248,6 +251,7 @@ private fun TimelineItemRow( timelineItem.events.forEach { subGroupEvent -> TimelineItemRow( timelineItem = subGroupEvent, + showReadReceipts = showReadReceipts, highlightedItem = highlightedItem, sessionState = sessionState, userHasPermissionToSendMessage = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index ec277fb96b..5c816bef7b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -79,7 +79,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent -import io.element.android.features.messages.impl.timeline.model.receipts import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.components.EqualWidthColumn import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -117,6 +116,7 @@ import kotlin.math.roundToInt @Composable fun TimelineItemEventRow( event: TimelineItem.Event, + showReadReceipts: Boolean, isHighlighted: Boolean, canReply: Boolean, onClick: () -> Unit, @@ -177,6 +177,7 @@ fun TimelineItemEventRow( state = state.draggableState, ), event = event, + showReadReceipts = showReadReceipts, isHighlighted = isHighlighted, interactionSource = interactionSource, onClick = onClick, @@ -195,6 +196,7 @@ fun TimelineItemEventRow( } else { TimelineItemEventRowContent( event = event, + showReadReceipts = showReadReceipts, isHighlighted = isHighlighted, interactionSource = interactionSource, onClick = onClick, @@ -238,6 +240,7 @@ private fun SwipeSensitivity( @Composable private fun TimelineItemEventRowContent( event: TimelineItem.Event, + showReadReceipts: Boolean, isHighlighted: Boolean, interactionSource: MutableInteractionSource, onClick: () -> Unit, @@ -336,21 +339,23 @@ private fun TimelineItemEventRowContent( } // Read receipts / Send state - TimelineItemReadReceiptView( - state = ReadReceiptViewState( - sendState = event.localSendState, - receipts = event.readReceiptState.receipts(), - ), - onReadReceiptsClicked = onReadReceiptsClicked, - modifier = Modifier - .constrainAs(readReceipts) { - if (event.reactionsState.reactions.isNotEmpty()) { - top.linkTo(reactions.bottom, margin = 4.dp) - } else { - top.linkTo(message.bottom, margin = 4.dp) + if (showReadReceipts) { + TimelineItemReadReceiptView( + state = ReadReceiptViewState( + sendState = event.localSendState, + receipts = event.readReceiptState.receipts, + ), + onReadReceiptsClicked = onReadReceiptsClicked, + modifier = Modifier + .constrainAs(readReceipts) { + if (event.reactionsState.reactions.isNotEmpty()) { + top.linkTo(reactions.bottom, margin = 4.dp) + } else { + top.linkTo(message.bottom, margin = 4.dp) + } } - } - ) + ) + } } } @@ -679,6 +684,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { ), groupPosition = TimelineItemGroupPosition.First, ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -701,6 +707,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { ), groupPosition = TimelineItemGroupPosition.Last, ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -741,6 +748,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { inReplyTo = aInReplyToReady(replyContent), groupPosition = TimelineItemGroupPosition.First, ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -765,6 +773,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { isThreaded = true, groupPosition = TimelineItemGroupPosition.Last, ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -817,6 +826,7 @@ internal fun TimelineItemEventRowTimestampPreview( reactionsState = aTimelineItemReactions(count = 0), senderDisplayName = if (useDocument) "Document case" else "Text case", ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -850,6 +860,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { ), timelineItemReactions = aTimelineItemReactions(count = 20), ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -876,6 +887,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { event = aTimelineItemEvent( senderDisplayName = "a long sender display name to test single line and ellipsis at the end of the line", ), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, @@ -898,6 +910,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { TimelineItemEventRow( event = aTimelineItemEvent(content = aTimelineItemPollContent()), + showReadReceipts = false, isHighlighted = false, canReply = true, onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt index d17debe7da..391ec3de7f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.features.messages.impl.timeline.model.receipts import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -93,7 +92,7 @@ private fun ColumnScope.ReadReceiptBottomSheetContent( Text(text = stringResource(id = CommonStrings.common_seen_by)) } ) - val receipts = state.selectedEvent?.readReceiptState?.receipts().orEmpty() + val receipts = state.selectedEvent?.readReceiptState?.receipts.orEmpty() receipts.forEach { val userId = UserId(it.avatarData.id) MatrixUserRow( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt index 9b51dac76e..3a3bf1dbc4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt @@ -31,7 +31,7 @@ class ReadReceiptBottomSheetStateProvider : PreviewParameterProvider ReadReceiptBottomSheetState( selectedEvent = aTimelineItemEvent( - readReceiptState = TimelineItemReadReceipts.ReadReceipts( + readReceiptState = TimelineItemReadReceipts( receipts = readReceiptViewState.receipts.map { readReceiptData -> readReceiptData .copy(avatarData = readReceiptData.avatarData.copy(id = "@${readReceiptData.avatarData.id}:localhost")) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index e48720f92a..ffc1a1b3f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -67,7 +67,7 @@ class TimelineItemsFactory @Inject constructor( suspend fun replaceWith( timelineItems: List, - roomMembers: List?, + roomMembers: List, ) = withContext(dispatchers.computation) { lock.withLock { diffCacheUpdater.updateWith(timelineItems) @@ -77,7 +77,7 @@ class TimelineItemsFactory @Inject constructor( private suspend fun buildAndEmitTimelineItemStates( timelineItems: List, - roomMembers: List?, + roomMembers: List, ) { val newTimelineItemStates = ArrayList() for (index in diffCache.indices().reversed()) { @@ -97,7 +97,7 @@ class TimelineItemsFactory @Inject constructor( private suspend fun buildAndCacheItem( timelineItems: List, index: Int, - roomMembers: List?, + roomMembers: List, ): TimelineItem? { val timelineItemState = when (val currentTimelineItem = timelineItems[index]) { 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 cde3f32d95..4c9217a884 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 @@ -47,7 +47,7 @@ class TimelineItemEventFactory @Inject constructor( currentTimelineItem: MatrixTimelineItem.Event, index: Int, timelineItems: List, - roomMembers: List?, + roomMembers: List, ): TimelineItem.Event { val currentSender = currentTimelineItem.event.sender val groupPosition = @@ -132,10 +132,9 @@ class TimelineItemEventFactory @Inject constructor( } private fun MatrixTimelineItem.Event.computeReadReceiptState( - roomMembers: List?, + roomMembers: List, ): TimelineItemReadReceipts { - if (roomMembers == null) return TimelineItemReadReceipts.Hidden - return TimelineItemReadReceipts.ReadReceipts( + return TimelineItemReadReceipts( receipts = event.receipts .map { receipt -> val roomMember = roomMembers.find { it.userId == receipt.userId } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt index d9fc6c55af..dc5ae8289c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt @@ -18,25 +18,12 @@ package io.element.android.features.messages.impl.timeline.model import io.element.android.libraries.designsystem.components.avatar.AvatarData import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -sealed interface TimelineItemReadReceipts { - /** - * Value when the feature is disabled. - */ - data object Hidden : TimelineItemReadReceipts - - data class ReadReceipts( - val receipts: ImmutableList, - ) : TimelineItemReadReceipts -} +data class TimelineItemReadReceipts( + val receipts: ImmutableList, +) data class ReadReceiptData( val avatarData: AvatarData, val formattedDate: String, ) - -fun TimelineItemReadReceipts.receipts(): ImmutableList = when (this) { - TimelineItemReadReceipts.Hidden -> persistentListOf() - is TimelineItemReadReceipts.ReadReceipts -> receipts -} 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 91e1c2454e..68c80d6b80 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 @@ -53,7 +53,7 @@ internal fun aMessageEvent( sentTime = "", isMine = isMine, reactionsState = aTimelineItemReactions(count = 0), - readReceiptState = TimelineItemReadReceipts.ReadReceipts(emptyList().toImmutableList()), + readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, 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 14bd7e00e1..e8a8eb30cf 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 @@ -44,7 +44,7 @@ class TimelineItemGrouperTest { senderDisplayName = "", content = TimelineItemStateEventContent(body = "a state event"), reactionsState = aTimelineItemReactions(count = 0), - readReceiptState = TimelineItemReadReceipts.ReadReceipts(emptyList().toImmutableList()), + readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = LocalEventSendState.Sent(AN_EVENT_ID), inReplyTo = null, isThreaded = false,