Fix forwarding an event not working in threads (#7743)

* Fix forwarding an event not working in threads

Add the `TimelineProvider` for the event to the navigation flow so the events can be fetched from the right timeline.
This commit is contained in:
Jorge Martin Espinosa
2026-09-22 10:59:12 +02:00
committed by GitHub
parent 4e47c6170e
commit bec3780c9c
12 changed files with 46 additions and 35 deletions
@@ -87,6 +87,7 @@ import io.element.android.libraries.matrix.api.room.alias.matches
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
import io.element.android.libraries.matrix.ui.messages.RoomNamesCache
@@ -104,6 +105,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import kotlin.time.Duration.Companion.milliseconds
@@ -180,7 +182,8 @@ class MessagesFlowNode(
@Parcelize
data class ForwardEvent(
val eventId: EventId,
val fromPinnedEvents: Boolean,
@IgnoredOnParcel
val timelineProvider: TimelineProvider? = null,
) : NavTarget
@Parcelize
@@ -303,8 +306,8 @@ class MessagesFlowNode(
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
}
override fun forwardEvent(eventId: EventId) {
backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents = false))
override fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider) {
backstack.push(NavTarget.ForwardEvent(eventId, timelineProvider))
}
override fun navigateToReportMessage(eventId: EventId, senderId: UserId) {
@@ -459,11 +462,8 @@ class MessagesFlowNode(
createNode<EventDebugInfoNode>(buildContext, listOf(inputs))
}
is NavTarget.ForwardEvent -> {
val timelineProvider = if (navTarget.fromPinnedEvents) {
pinnedEventsTimelineProvider
} else {
timelineController
}
// If no timeline provider is received, assume the live timeline should be used
val timelineProvider = navTarget.timelineProvider ?: timelineController
val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider)
val callback = object : ForwardEntryPoint.Callback {
override fun onDone(roomIds: List<RoomId>) {
@@ -550,8 +550,8 @@ class MessagesFlowNode(
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
}
override fun handleForwardEventClick(eventId: EventId) {
backstack.push(NavTarget.ForwardEvent(eventId = eventId, fromPinnedEvents = true))
override fun handleForwardEventClick(eventId: EventId, timelineProvider: TimelineProvider) {
backstack.push(NavTarget.ForwardEvent(eventId = eventId, timelineProvider = timelineProvider))
}
override fun navigateToThread(threadRootId: ThreadId) {
@@ -617,8 +617,8 @@ class MessagesFlowNode(
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
}
override fun handleForwardEventClick(eventId: EventId) {
backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents = false))
override fun handleForwardEventClick(eventId: EventId, timelineProvider: TimelineProvider) {
backstack.push(NavTarget.ForwardEvent(eventId, timelineProvider))
}
override fun navigateToReportMessage(eventId: EventId, senderId: UserId) {
@@ -13,12 +13,13 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import kotlinx.collections.immutable.ImmutableList
interface MessagesNavigator {
fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun forwardEvent(eventId: EventId)
fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider)
fun navigateToReportMessage(eventId: EventId, senderId: UserId)
fun navigateToEditPoll(eventId: EventId)
fun navigateToPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?)
@@ -68,6 +68,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.alias.matches
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.media.contentvalidation.EventContentValidationCache
import io.element.android.libraries.matrix.ui.media.contentvalidation.LocalEventContentValidationState
@@ -137,7 +138,7 @@ class MessagesNode(
fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData)
fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun forwardEvent(eventId: EventId)
fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider)
fun navigateToReportMessage(eventId: EventId, senderId: UserId)
fun navigateToSendLocation()
fun navigateToCreatePoll()
@@ -220,8 +221,8 @@ class MessagesNode(
callback.navigateToEventDebugInfo(eventId, debugInfo)
}
override fun forwardEvent(eventId: EventId) {
callback.forwardEvent(eventId)
override fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider) {
callback.forwardEvent(eventId, timelineProvider)
}
override fun navigateToReportMessage(eventId: EventId, senderId: UserId) {
@@ -615,7 +615,7 @@ class MessagesPresenter(
private fun handleForwardAction(event: TimelineItem.Event) {
if (event.eventId == null) return
navigator.forwardEvent(event.eventId)
navigator.forwardEvent(eventId = event.eventId, timelineProvider = timelineController)
}
private fun handleReportAction(event: TimelineItem.Event) {
@@ -10,11 +10,12 @@ package io.element.android.features.messages.impl.pinned.list
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
interface PinnedMessagesListNavigator {
fun viewInTimeline(eventId: EventId)
fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun forwardEvent(eventId: EventId)
fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider)
fun navigateToThread(threadRootId: ThreadId)
}
@@ -37,6 +37,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
@@ -60,7 +61,7 @@ class PinnedMessagesListNode(
fun viewInTimeline(eventId: EventId)
fun handlePermalinkClick(data: PermalinkData.RoomLink)
fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun handleForwardEventClick(eventId: EventId)
fun handleForwardEventClick(eventId: EventId, timelineProvider: TimelineProvider)
fun navigateToThread(threadRootId: ThreadId)
}
@@ -98,8 +99,8 @@ class PinnedMessagesListNode(
callback.navigateToEventDebugInfo(eventId, debugInfo)
}
override fun forwardEvent(eventId: EventId) {
callback.handleForwardEventClick(eventId)
override fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider) {
callback.handleForwardEventClick(eventId, timelineProvider)
}
override fun navigateToThread(threadRootId: ThreadId) {
@@ -181,7 +181,7 @@ class PinnedMessagesListPresenter(
}
TimelineItemAction.Forward -> {
targetEvent.eventId?.let { eventId ->
navigator.forwardEvent(eventId)
navigator.forwardEvent(eventId, timelineProvider)
}
}
TimelineItemAction.Unpin -> handleUnpinAction(targetEvent)
@@ -70,6 +70,7 @@ import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.alias.matches
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
@@ -142,7 +143,7 @@ class ThreadedMessagesNode(
fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData)
fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun handleForwardEventClick(eventId: EventId)
fun handleForwardEventClick(eventId: EventId, timelineProvider: TimelineProvider)
fun navigateToReportMessage(eventId: EventId, senderId: UserId)
fun navigateToSendLocation()
fun navigateToCreatePoll()
@@ -226,8 +227,8 @@ class ThreadedMessagesNode(
callback.navigateToEventDebugInfo(eventId, debugInfo)
}
override fun forwardEvent(eventId: EventId) {
callback.handleForwardEventClick(eventId)
override fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider) {
callback.handleForwardEventClick(eventId, timelineProvider)
}
override fun navigateToReportMessage(eventId: EventId, senderId: UserId) {
@@ -13,13 +13,14 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.tests.testutils.lambda.lambdaError
import kotlinx.collections.immutable.ImmutableList
class FakeMessagesNavigator(
private val onShowEventDebugInfoClickLambda: (eventId: EventId?, debugInfo: TimelineItemDebugInfo) -> Unit = { _, _ -> lambdaError() },
private val onForwardEventClickLambda: (eventId: EventId) -> Unit = { _ -> lambdaError() },
private val onForwardEventClickLambda: (eventId: EventId, timelineProvider: TimelineProvider) -> Unit = { _, _ -> lambdaError() },
private val onReportContentClickLambda: (eventId: EventId, senderId: UserId) -> Unit = { _, _ -> lambdaError() },
private val onEditPollClickLambda: (eventId: EventId) -> Unit = { _ -> lambdaError() },
private val onPreviewAttachmentLambda: (attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) -> Unit = { _, _ -> lambdaError() },
@@ -34,8 +35,8 @@ class FakeMessagesNavigator(
onShowEventDebugInfoClickLambda(eventId, debugInfo)
}
override fun forwardEvent(eventId: EventId) {
onForwardEventClickLambda(eventId)
override fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider) {
onForwardEventClickLambda(eventId, timelineProvider)
}
override fun navigateToReportMessage(eventId: EventId, senderId: UserId) {
@@ -71,6 +71,7 @@ import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
@@ -109,6 +110,7 @@ import io.element.android.tests.testutils.FakeLifecycleOwner
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.consumeItemsUntilTimeout
import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
@@ -288,7 +290,7 @@ class MessagesPresenterTest {
@Test
fun `present - handle action forward`() = runTest {
val onForwardEventClickLambda = lambdaRecorder<EventId, Unit> { }
val onForwardEventClickLambda = lambdaRecorder<EventId, TimelineProvider, Unit> { _, _ -> }
val navigator = FakeMessagesNavigator(
onForwardEventClickLambda = onForwardEventClickLambda,
)
@@ -297,7 +299,7 @@ class MessagesPresenterTest {
val initialState = awaitItem()
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
onForwardEventClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
onForwardEventClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), any())
}
}
@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.pinned.list
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
class FakePinnedMessagesListNavigator : PinnedMessagesListNavigator {
@@ -23,9 +24,9 @@ class FakePinnedMessagesListNavigator : PinnedMessagesListNavigator {
onShowEventDebugInfoClickLambda?.invoke(eventId, debugInfo)
}
var onForwardEventClickLambda: ((EventId) -> Unit)? = null
override fun forwardEvent(eventId: EventId) {
onForwardEventClickLambda?.invoke(eventId)
var onForwardEventClickLambda: ((EventId, TimelineProvider) -> Unit)? = null
override fun forwardEvent(eventId: EventId, timelineProvider: TimelineProvider) {
onForwardEventClickLambda?.invoke(eventId, timelineProvider)
}
var onOpenThreadLambda: ((ThreadId) -> Unit)? = null
@@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
@@ -47,6 +48,7 @@ import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
@@ -329,7 +331,7 @@ class PinnedMessagesListPresenterTest {
@Test
fun `present - forward event`() = runTest {
val onForwardEventClickLambda = lambdaRecorder { _: EventId -> }
val onForwardEventClickLambda = lambdaRecorder { _: EventId, _: TimelineProvider -> }
val navigator = FakePinnedMessagesListNavigator().apply {
this.onForwardEventClickLambda = onForwardEventClickLambda
}
@@ -352,7 +354,7 @@ class PinnedMessagesListPresenterTest {
cancelAndIgnoreRemainingEvents()
assert(onForwardEventClickLambda)
.isCalledOnce()
.with(value(AN_EVENT_ID))
.with(value(AN_EVENT_ID), any())
}
}