Fix read receipt behavior when the timeline is opened. (#4679)

* Use Timber instead of println

* Fix ReadReceipt not sent when new event is received:
- filter Virtual item regarding typing to compute NewEventState
- always scroll, since the latest event is always the typing notification (but with 0 height). This triggers the sending of RR.

* Improve the fix to fix regression detected by unit tests.

* Remove unnecessary parenthesis

* Document parameter.
This commit is contained in:
Benoit Marty 2025-05-06 14:41:34 +02:00 committed by GitHub
parent c61ee59528
commit 3603ed6830
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 17 additions and 7 deletions

View file

@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.factories.TimelineItem
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
import io.element.android.features.messages.impl.timeline.model.NewEventState
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel
import io.element.android.features.messages.impl.typing.TypingNotificationState
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
import io.element.android.features.poll.api.actions.EndPollAction
@ -131,7 +132,7 @@ class TimelinePresenter @AssistedInject constructor(
if (event.firstIndex == 0) {
newEventState.value = NewEventState.None
}
println("## sendReadReceiptIfNeeded firstVisibleIndex: ${event.firstIndex}")
Timber.d("## sendReadReceiptIfNeeded firstVisibleIndex: ${event.firstIndex}")
appScope.sendReadReceiptIfNeeded(
firstVisibleIndex = event.firstIndex,
timelineItems = timelineItems,
@ -278,7 +279,10 @@ class TimelinePresenter @AssistedInject constructor(
if (newEventState.value == NewEventState.FromMe) {
return@withContext
}
val newMostRecentItem = timelineItems.firstOrNull()
val newMostRecentItem = timelineItems.firstOrNull {
// Ignore typing item
(it as? TimelineItem.Virtual)?.model !is TimelineItemTypingNotificationModel
}
val prevMostRecentItemIdValue = prevMostRecentItemId.value
val newMostRecentItemId = newMostRecentItem?.identifier()
val hasNewEvent = prevMostRecentItemIdValue != null &&

View file

@ -286,11 +286,16 @@ private fun BoxScope.TimelineScrollHelper(
}
var jumpToLiveHandled by remember { mutableStateOf(true) }
fun scrollToBottom() {
/**
* @param force If true, scroll to the bottom even if the user is already seeing the most recent item.
* This fixes the issue where the user is seeing typing notification and so the read receipt is not sent
* when a new message comes in.
*/
fun scrollToBottom(force: Boolean) {
coroutineScope.launch {
if (lazyListState.firstVisibleItemIndex > 10) {
lazyListState.scrollToItem(0)
} else if (lazyListState.firstVisibleItemIndex != 0) {
} else if (force || lazyListState.firstVisibleItemIndex != 0) {
lazyListState.animateScrollToItem(0)
}
}
@ -298,7 +303,7 @@ private fun BoxScope.TimelineScrollHelper(
fun jumpToBottom() {
if (isLive) {
scrollToBottom()
scrollToBottom(force = false)
} else {
jumpToLiveHandled = false
onJumpToLive()
@ -321,9 +326,10 @@ private fun BoxScope.TimelineScrollHelper(
}
LaunchedEffect(canAutoScroll, newEventState) {
val shouldScrollToBottom = isScrollFinished && (canAutoScroll || newEventState == NewEventState.FromMe)
val shouldScrollToBottom = isScrollFinished &&
(canAutoScroll && newEventState == NewEventState.FromOther || newEventState == NewEventState.FromMe)
if (shouldScrollToBottom) {
scrollToBottom()
scrollToBottom(force = true)
}
}