Fix "Sent" checkmark disappearing when the message is read and "Share presence" is disabled (#6985)

* Don't compute read receipts if presence sharing is disabled

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
bxdxnn
2026-06-08 14:21:34 +03:00
committed by GitHub
parent 165a8b776a
commit 6219378ad3
17 changed files with 43 additions and 69 deletions
@@ -208,7 +208,8 @@ class PinnedMessagesListPresenter(
combine(timelineItemsFlow, room.membersStateFlow) { items, membersState ->
timelineItemsFactory.replaceWith(
timelineItems = items,
roomMembers = membersState.roomMembers().orEmpty()
roomMembers = membersState.roomMembers().orEmpty(),
renderReadReceipts = false,
)
}.launchIn(this)
@@ -217,7 +217,6 @@ private fun PinnedMessagesListLoaded(
timelineItem = timelineItem,
timelineMode = Timeline.Mode.PinnedEvents,
timelineRoomInfo = state.timelineRoomInfo,
renderReadReceipts = false,
timelineProtectionState = state.timelineProtectionState,
isLastOutgoingMessage = false,
focusedEventId = null,
@@ -138,9 +138,6 @@ class TimelinePresenter(
val messageShieldDialogData: MutableState<MessageShieldData?> = remember { mutableStateOf(null) }
val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present()
val renderReadReceipts by remember {
sessionPreferencesStore.isRenderReadReceiptsEnabled()
}.collectAsState(initial = true)
val isLive by remember {
timelineController.isLive()
}.collectAsState(initial = true)
@@ -252,13 +249,18 @@ class TimelinePresenter(
}
.launchIn(this)
combine(timelineController.timelineItems(), room.membersStateFlow) { items, membersState ->
combine(
timelineController.timelineItems(),
room.membersStateFlow,
sessionPreferencesStore.isRenderReadReceiptsEnabled(),
) { items, membersState, renderReadReceipts ->
val parent = analyticsService.getLongRunningTransaction(DisplayFirstTimelineItems)
val transaction = parent?.startChild("timelineItemsFactory.replaceWith", "Processing timeline items")
transaction?.putExtraData(AnalyticsUserData.TIMELINE_ITEM_COUNT, items.count().toString())
timelineItemsFactory.replaceWith(
timelineItems = items,
roomMembers = membersState.roomMembers().orEmpty()
roomMembers = membersState.roomMembers().orEmpty(),
renderReadReceipts = renderReadReceipts,
)
transaction?.finish()
items
@@ -313,7 +315,6 @@ class TimelinePresenter(
timelineItems = timelineItems,
timelineMode = timelineMode,
timelineRoomInfo = timelineRoomInfo,
renderReadReceipts = renderReadReceipts,
newEventState = newEventState.value,
isLive = isLive,
focusRequestState = focusRequestState.value,
@@ -26,7 +26,6 @@ data class TimelineState(
val timelineItems: ImmutableList<TimelineItem>,
val timelineRoomInfo: TimelineRoomInfo,
val timelineMode: Timeline.Mode,
val renderReadReceipts: Boolean,
val newEventState: NewEventState,
val isLive: Boolean,
val focusRequestState: FocusRequestState,
@@ -51,7 +51,6 @@ import kotlin.random.Random
fun aTimelineState(
timelineItems: ImmutableList<TimelineItem> = persistentListOf(),
timelineMode: Timeline.Mode = Timeline.Mode.Live,
renderReadReceipts: Boolean = false,
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
focusedEventIndex: Int = -1,
isLive: Boolean = true,
@@ -70,7 +69,6 @@ fun aTimelineState(
timelineItems = timelineItems,
timelineMode = timelineMode,
timelineRoomInfo = timelineRoomInfo,
renderReadReceipts = renderReadReceipts,
newEventState = NewEventState.None,
isLive = isLive,
focusRequestState = focusRequestState,
@@ -171,7 +171,6 @@ fun TimelineView(
timelineMode = state.timelineMode,
timelineRoomInfo = state.timelineRoomInfo,
timelineProtectionState = timelineProtectionState,
renderReadReceipts = state.renderReadReceipts,
isLastOutgoingMessage = state.isLastOutgoingMessage(timelineItem.identifier()),
focusedEventId = state.focusedEventId,
displayThreadSummaries = state.displayThreadSummaries,
@@ -22,7 +22,6 @@ internal fun ATimelineItemEventRow(
event: TimelineItem.Event,
timelineMode: Timeline.Mode = Timeline.Mode.Live,
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
renderReadReceipts: Boolean = false,
isLastOutgoingMessage: Boolean = false,
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
displayThreadSummaries: Boolean = false,
@@ -30,7 +29,6 @@ internal fun ATimelineItemEventRow(
event = event,
timelineMode = timelineMode,
timelineRoomInfo = timelineRoomInfo,
renderReadReceipts = renderReadReceipts,
timelineProtectionState = timelineProtectionState,
isLastOutgoingMessage = isLastOutgoingMessage,
displayThreadSummaries = displayThreadSummaries,
@@ -52,7 +52,6 @@ internal fun TimelineItemCallNotifyView(
timelineRoomInfo: TimelineRoomInfo,
event: TimelineItem.Event,
content: TimelineItemRtcNotificationContent,
renderReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
onLongClick: (TimelineItem.Event) -> Unit,
onReadReceiptsClick: (TimelineItem.Event) -> Unit,
@@ -106,7 +105,6 @@ internal fun TimelineItemCallNotifyView(
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
renderReadReceipts = renderReadReceipts,
onReadReceiptsClick = { onReadReceiptsClick(event) },
modifier = Modifier.padding(top = 4.dp),
)
@@ -146,8 +144,10 @@ private fun getIcon(
@PreviewsDayNight
@Composable
internal fun TimelineItemCallNotifyViewPreview() = ElementPreview {
val readReceiptState = aTimelineItemReadReceipts(
receipts = List(3) { aReadReceiptData(it) },
val readReceiptState = mutableListOf(
aTimelineItemReadReceipts(
receipts = List(3) { aReadReceiptData(it) },
)
)
Column(modifier = Modifier.padding(bottom = 16.dp)) {
listOf(false, true).forEach { isDm ->
@@ -162,13 +162,10 @@ internal fun TimelineItemCallNotifyViewPreview() = ElementPreview {
timelineRoomInfo = aTimelineRoomInfo(isDm = isDm),
event = aTimelineItemEvent(
content = content,
readReceiptState = readReceiptState,
// Only display read receipts for the first item
readReceiptState = readReceiptState.removeFirstOrNull() ?: aTimelineItemReadReceipts(),
),
content = content,
// Render read receipts for the first item only
renderReadReceipts = !isDm &&
callIntent == CallIntent.AUDIO &&
state == RtcNotificationState.Started,
isLastOutgoingMessage = false,
onLongClick = {},
onReadReceiptsClick = {},
@@ -142,7 +142,6 @@ fun TimelineItemEventRow(
timelineMode: Timeline.Mode,
timelineRoomInfo: TimelineRoomInfo,
timelineProtectionState: TimelineProtectionState,
renderReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
displayThreadSummaries: Boolean,
onEventClick: () -> Unit,
@@ -296,7 +295,6 @@ fun TimelineItemEventRow(
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
renderReadReceipts = renderReadReceipts,
onReadReceiptsClick = { onReadReceiptClick(event) },
modifier = Modifier.padding(top = 4.dp)
)
@@ -37,7 +37,6 @@ internal fun TimelineItemEventRowWithRRPreview(
timelineItemReactions = aTimelineItemReactions(count = 0),
readReceiptState = TimelineItemReadReceipts(state.receipts),
),
renderReadReceipts = true,
isLastOutgoingMessage = false,
)
// A message from current user
@@ -49,7 +48,6 @@ internal fun TimelineItemEventRowWithRRPreview(
timelineItemReactions = aTimelineItemReactions(count = 0),
readReceiptState = TimelineItemReadReceipts(state.receipts),
),
renderReadReceipts = true,
isLastOutgoingMessage = false,
)
// Another message from current user
@@ -61,7 +59,6 @@ internal fun TimelineItemEventRowWithRRPreview(
timelineItemReactions = aTimelineItemReactions(count = 0),
readReceiptState = TimelineItemReadReceipts(state.receipts),
),
renderReadReceipts = true,
isLastOutgoingMessage = true,
)
}
@@ -43,7 +43,6 @@ fun TimelineItemGroupedEventsRow(
timelineMode: Timeline.Mode,
timelineRoomInfo: TimelineRoomInfo,
timelineProtectionState: TimelineProtectionState,
renderReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
focusedEventId: EventId?,
displayThreadSummaries: Boolean,
@@ -89,7 +88,6 @@ fun TimelineItemGroupedEventsRow(
timelineRoomInfo = timelineRoomInfo,
timelineProtectionState = timelineProtectionState,
focusedEventId = focusedEventId,
renderReadReceipts = renderReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
displayThreadSummaries = displayThreadSummaries,
onClick = onClick,
@@ -117,7 +115,6 @@ private fun TimelineItemGroupedEventsRowContent(
timelineRoomInfo: TimelineRoomInfo,
timelineProtectionState: TimelineProtectionState,
focusedEventId: EventId?,
renderReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
displayThreadSummaries: Boolean,
onClick: (TimelineItem.Event) -> Unit,
@@ -173,7 +170,6 @@ private fun TimelineItemGroupedEventsRowContent(
timelineItem = subGroupEvent,
timelineRoomInfo = timelineRoomInfo,
timelineProtectionState = timelineProtectionState,
renderReadReceipts = renderReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
focusedEventId = focusedEventId,
displayThreadSummaries = displayThreadSummaries,
@@ -193,14 +189,13 @@ private fun TimelineItemGroupedEventsRowContent(
)
}
}
} else if (renderReadReceipts) {
} else if (timelineItem.aggregatedReadReceipts.isNotEmpty()) {
TimelineItemReadReceiptView(
state = ReadReceiptViewState(
sendState = null,
isLastOutgoingMessage = false,
receipts = timelineItem.aggregatedReadReceipts,
),
renderReadReceipts = true,
onReadReceiptsClick = onExpandGroupClick
)
}
@@ -219,7 +214,6 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
timelineRoomInfo = aTimelineRoomInfo(),
timelineProtectionState = aTimelineProtectionState(),
focusedEventId = events.events.first().eventId,
renderReadReceipts = true,
isLastOutgoingMessage = false,
displayThreadSummaries = false,
onClick = {},
@@ -247,7 +241,6 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
timelineRoomInfo = aTimelineRoomInfo(),
timelineProtectionState = aTimelineProtectionState(),
focusedEventId = null,
renderReadReceipts = true,
isLastOutgoingMessage = false,
displayThreadSummaries = false,
onClick = {},
@@ -56,7 +56,6 @@ internal fun TimelineItemRow(
timelineItem: TimelineItem,
timelineMode: Timeline.Mode,
timelineRoomInfo: TimelineRoomInfo,
renderReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
timelineProtectionState: TimelineProtectionState,
focusedEventId: EventId?,
@@ -114,7 +113,6 @@ internal fun TimelineItemRow(
is TimelineItemStateContent, is TimelineItemLegacyCallInviteContent -> {
TimelineItemStateEventRow(
event = timelineItem,
renderReadReceipts = renderReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
onClick = { onContentClick(timelineItem) },
onReadReceiptsClick = onReadReceiptClick,
@@ -127,7 +125,6 @@ internal fun TimelineItemRow(
timelineRoomInfo = timelineRoomInfo,
event = timelineItem,
content = timelineItem.content,
renderReadReceipts = renderReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
onLongClick = onLongClick,
onReadReceiptsClick = onReadReceiptClick,
@@ -167,7 +164,6 @@ internal fun TimelineItemRow(
event = timelineItem,
timelineMode = timelineMode,
timelineRoomInfo = timelineRoomInfo,
renderReadReceipts = renderReadReceipts,
timelineProtectionState = timelineProtectionState,
isLastOutgoingMessage = isLastOutgoingMessage,
displayThreadSummaries = displayThreadSummaries,
@@ -196,7 +192,6 @@ internal fun TimelineItemRow(
timelineMode = timelineMode,
timelineRoomInfo = timelineRoomInfo,
timelineProtectionState = timelineProtectionState,
renderReadReceipts = renderReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
focusedEventId = focusedEventId,
displayThreadSummaries = displayThreadSummaries,
@@ -39,7 +39,6 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
fun TimelineItemStateEventRow(
event: TimelineItem.Event,
renderReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
onClick: () -> Unit,
onLongClick: () -> Unit,
@@ -86,7 +85,6 @@ fun TimelineItemStateEventRow(
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
renderReadReceipts = renderReadReceipts,
onReadReceiptsClick = { onReadReceiptsClick(event) },
)
}
@@ -104,7 +102,6 @@ internal fun TimelineItemStateEventRowPreview() = ElementPreview {
receipts = persistentListOf(aReadReceiptData(0)),
)
),
renderReadReceipts = true,
isLastOutgoingMessage = false,
onClick = {},
onLongClick = {},
@@ -53,27 +53,24 @@ import kotlinx.collections.immutable.ImmutableList
@Composable
fun TimelineItemReadReceiptView(
state: ReadReceiptViewState,
renderReadReceipts: Boolean,
onReadReceiptsClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (state.receipts.isNotEmpty()) {
if (renderReadReceipts) {
ReadReceiptsRow(
modifier = modifier.clearAndSetSemantics {
hideFromAccessibility()
}
) {
ReadReceiptsAvatars(
receipts = state.receipts,
modifier = Modifier
.clip(RoundedCornerShape(4.dp))
.clickable {
onReadReceiptsClick()
}
.padding(2.dp)
)
ReadReceiptsRow(
modifier = modifier.clearAndSetSemantics {
hideFromAccessibility()
}
) {
ReadReceiptsAvatars(
receipts = state.receipts,
modifier = Modifier
.clip(RoundedCornerShape(4.dp))
.clickable {
onReadReceiptsClick()
}
.padding(2.dp)
)
}
} else {
when (state.sendState) {
@@ -209,7 +206,6 @@ internal fun TimelineItemReadReceiptViewPreview(
) = ElementPreview {
TimelineItemReadReceiptView(
state = state,
renderReadReceipts = true,
onReadReceiptsClick = {},
)
}
@@ -64,22 +64,24 @@ class TimelineItemsFactory(
suspend fun replaceWith(
timelineItems: List<MatrixTimelineItem>,
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
) = withContext(dispatchers.computation) {
lock.withLock {
diffCacheUpdater.updateWith(timelineItems)
buildAndEmitTimelineItemStates(timelineItems, roomMembers)
buildAndEmitTimelineItemStates(timelineItems, roomMembers, renderReadReceipts)
}
}
private suspend fun buildAndEmitTimelineItemStates(
timelineItems: List<MatrixTimelineItem>,
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
) {
val newTimelineItemStates = ArrayList<TimelineItem>()
for (index in diffCache.indices().reversed()) {
val cacheItem = diffCache.get(index)
if (cacheItem == null) {
buildAndCacheItem(timelineItems, index, roomMembers)?.also { timelineItemState ->
buildAndCacheItem(timelineItems, index, roomMembers, renderReadReceipts)?.also { timelineItemState ->
newTimelineItemStates.add(timelineItemState)
}
} else {
@@ -87,7 +89,8 @@ class TimelineItemsFactory(
eventItemFactory.update(
timelineItem = cacheItem,
receivedMatrixTimelineItem = timelineItems[index] as MatrixTimelineItem.Event,
roomMembers = roomMembers
roomMembers = roomMembers,
renderReadReceipts = renderReadReceipts,
)
} else {
cacheItem
@@ -103,10 +106,11 @@ class TimelineItemsFactory(
timelineItems: List<MatrixTimelineItem>,
index: Int,
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
): TimelineItem? {
val timelineItem =
when (val currentTimelineItem = timelineItems[index]) {
is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems, roomMembers)
is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems, roomMembers, renderReadReceipts)
is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem)
MatrixTimelineItem.Other -> null
}
@@ -57,6 +57,7 @@ class TimelineItemEventFactory(
index: Int,
timelineItems: List<MatrixTimelineItem>,
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
): TimelineItem.Event {
val currentSender = currentTimelineItem.event.sender
val groupPosition =
@@ -116,7 +117,7 @@ class TimelineItemEventFactory(
sentDate = sentDate,
groupPosition = groupPosition,
reactionsState = currentTimelineItem.computeReactionsState(),
readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers),
readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers, renderReadReceipts),
localSendState = currentTimelineItem.event.localSendState,
inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser),
threadInfo = mappedThreadInfo,
@@ -133,9 +134,10 @@ class TimelineItemEventFactory(
timelineItem: TimelineItem.Event,
receivedMatrixTimelineItem: MatrixTimelineItem.Event,
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
): TimelineItem.Event {
return timelineItem.copy(
readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers)
readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers, renderReadReceipts)
)
}
@@ -180,8 +182,9 @@ class TimelineItemEventFactory(
private fun MatrixTimelineItem.Event.computeReadReceiptState(
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
): TimelineItemReadReceipts {
if (!config.computeReadReceipts) {
if (!config.computeReadReceipts || !renderReadReceipts) {
return TimelineItemReadReceipts(receipts = persistentListOf())
}
return TimelineItemReadReceipts(
@@ -238,7 +238,6 @@ class MessagesViewTest {
val eventsRecorder = EventsRecorder<ReadReceiptBottomSheetEvent>()
val state = aMessagesState(
timelineState = aTimelineState(
renderReadReceipts = true,
timelineItems = persistentListOf(
aTimelineItemEvent(
readReceiptState = aTimelineItemReadReceipts(