Merge pull request #5669 from element-hq/fix/forward-events-from-pinned-media-timeline

Fix forward events from media viewer from pinned media timeline
This commit is contained in:
Benoit Marty
2025-11-04 09:52:01 +01:00
committed by GitHub
32 changed files with 148 additions and 71 deletions

View File

@@ -9,13 +9,14 @@ package io.element.android.x.di
import dev.zacsweers.metro.GraphExtension
import dev.zacsweers.metro.Provides
import io.element.android.appnav.di.TimelineBindings
import io.element.android.libraries.architecture.NodeFactoriesBindings
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
@GraphExtension(RoomScope::class)
interface RoomGraph : NodeFactoriesBindings {
interface RoomGraph : NodeFactoriesBindings, TimelineBindings {
@GraphExtension.Factory
interface Factory {
fun create(

View File

@@ -0,0 +1,16 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.appnav.di
import io.element.android.features.messages.api.pinned.PinnedEventsTimelineProvider
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
interface TimelineBindings {
val timelineProvider: TimelineProvider
val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider
}

View File

@@ -22,10 +22,10 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.appnav.di.TimelineBindings
import io.element.android.appnav.room.RoomNavigationTarget
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.api.MessagesEntryPointNode
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.architecture.BackstackView
@@ -47,8 +47,6 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import timber.log.Timber
@@ -136,8 +134,8 @@ class JoinedRoomLoadedFlowNode(
callback.handlePermalinkClick(data, pushToBackstack)
}
override fun startForwardEventFlow(eventId: EventId) {
backstack.push(NavTarget.ForwardEvent(eventId))
override fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) {
backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents))
}
}
return roomDetailsEntryPoint.createNode(
@@ -169,7 +167,11 @@ class JoinedRoomLoadedFlowNode(
createSpaceNode(buildContext)
}
is NavTarget.ForwardEvent -> {
val timelineProvider = { MutableStateFlow(inputs.room.liveTimeline).asStateFlow() }
val timelineProvider = if (navTarget.fromPinnedEvents) {
(graph as TimelineBindings).pinnedEventsTimelineProvider
} else {
(graph as TimelineBindings).timelineProvider
}
val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider)
val callback = object : ForwardEntryPoint.Callback {
override fun onDone(roomIds: List<RoomId>) {
@@ -228,8 +230,8 @@ class JoinedRoomLoadedFlowNode(
callback.handlePermalinkClick(data, pushToBackstack)
}
override fun forwardEvent(eventId: EventId) {
backstack.push(NavTarget.ForwardEvent(eventId))
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents))
}
override fun navigateToRoom(roomId: RoomId) {
@@ -266,7 +268,7 @@ class JoinedRoomLoadedFlowNode(
data class RoomMemberDetails(val userId: UserId) : NavTarget
@Parcelize
data class ForwardEvent(val eventId: EventId) : NavTarget
data class ForwardEvent(val eventId: EventId, val fromPinnedEvents: Boolean) : NavTarget
@Parcelize
data object RoomNotificationSettings : NavTarget
@@ -276,7 +278,7 @@ class JoinedRoomLoadedFlowNode(
val messageNode = waitForChildAttached<Node, NavTarget> { navTarget ->
navTarget is NavTarget.Messages
}
(messageNode as? MessagesEntryPointNode)?.attachThread(threadId, focusedEventId)
(messageNode as? MessagesEntryPoint.NodeProxy)?.attachThread(threadId, focusedEventId)
}
@Composable

View File

@@ -31,6 +31,7 @@ dependencies {
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.roomselect.api)
implementation(projects.libraries.uiStrings)
testCommonDependencies(libs, true)
testImplementation(projects.libraries.matrix.test)

View File

@@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.getActiveTimeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
@AssistedInject
class ForwardMessagesPresenter(
@@ -63,7 +64,11 @@ class ForwardMessagesPresenter(
roomIds: List<RoomId>,
) = launch {
suspend {
timelineProvider.getActiveTimeline().forwardEvent(eventId, roomIds).getOrThrow()
timelineProvider.getActiveTimeline().forwardEvent(eventId, roomIds)
.onFailure {
Timber.e(it, "Error while forwarding event")
}
.getOrThrow()
roomIds
}.runCatchingUpdatingState(forwardingActionState)
}

View File

@@ -8,11 +8,13 @@
package io.element.android.features.forward.impl
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun ForwardMessagesView(
@@ -24,6 +26,9 @@ fun ForwardMessagesView(
onSuccess = {
onForwardSuccess(it)
},
errorMessage = {
stringResource(id = CommonStrings.error_unknown)
},
onErrorDismiss = {
state.eventSink(ForwardMessagesEvents.ClearError)
},

View File

@@ -35,7 +35,7 @@ interface MessagesEntryPoint : FeatureEntryPoint {
fun navigateToRoomDetails()
fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
fun forwardEvent(eventId: EventId)
fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean)
fun navigateToRoom(roomId: RoomId)
}
@@ -47,8 +47,8 @@ interface MessagesEntryPoint : FeatureEntryPoint {
params: Params,
callback: Callback,
): Node
}
interface MessagesEntryPointNode {
suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?)
interface NodeProxy {
suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?)
}
}

View File

@@ -0,0 +1,12 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.api.pinned
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
interface PinnedEventsTimelineProvider : TimelineProvider

View File

@@ -32,10 +32,9 @@ import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.location.api.ShowLocationEntryPoint
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.api.MessagesEntryPointNode
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider
import io.element.android.features.messages.impl.pinned.list.PinnedMessagesListNode
import io.element.android.features.messages.impl.report.ReportMessageNode
import io.element.android.features.messages.impl.threads.ThreadedMessagesNode
@@ -115,7 +114,7 @@ class MessagesFlowNode(
private val roomNamesCache: RoomNamesCache,
private val mentionSpanUpdater: MentionSpanUpdater,
private val mentionSpanTheme: MentionSpanTheme,
private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider,
private val pinnedEventsTimelineProvider: DefaultPinnedEventsTimelineProvider,
private val timelineController: TimelineController,
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
private val dateFormatter: DateFormatter,
@@ -130,8 +129,7 @@ class MessagesFlowNode(
),
buildContext = buildContext,
plugins = plugins,
),
MessagesEntryPointNode {
), MessagesEntryPoint.NodeProxy {
sealed interface NavTarget : Parcelable {
@Parcelize
data class Messages(val focusedEventId: EventId?) : NavTarget
@@ -315,9 +313,9 @@ class MessagesFlowNode(
this@MessagesFlowNode.viewInTimeline(eventId)
}
override fun forwardEvent(eventId: EventId) {
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
// Need to go to the parent because of the overlay
callback.forwardEvent(eventId)
callback.forwardEvent(eventId, fromPinnedEvents)
}
}
mediaViewerEntryPoint.createNode(

View File

@@ -7,8 +7,9 @@
package io.element.android.features.messages.impl.pinned
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn
import io.element.android.features.messages.api.pinned.PinnedEventsTimelineProvider
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.mapState
@@ -17,7 +18,6 @@ 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.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,12 +29,12 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@SingleIn(RoomScope::class)
@Inject
class PinnedEventsTimelineProvider(
@ContributesBinding(RoomScope::class)
class DefaultPinnedEventsTimelineProvider(
private val room: JoinedRoom,
private val syncService: SyncService,
private val dispatchers: CoroutineDispatchers,
) : TimelineProvider {
) : PinnedEventsTimelineProvider {
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> =
MutableStateFlow(AsyncData.Uninitialized)

View File

@@ -18,7 +18,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.room.BaseRoom
@@ -35,7 +35,7 @@ import kotlinx.coroutines.flow.onEach
class PinnedMessagesBannerPresenter(
private val room: BaseRoom,
private val itemFactory: PinnedMessagesBannerItemFactory,
private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider,
private val pinnedEventsTimelineProvider: DefaultPinnedEventsTimelineProvider,
) : Presenter<PinnedMessagesBannerState> {
private val pinnedItems = mutableStateOf<AsyncData<ImmutableList<PinnedMessagesBannerItem>>>(AsyncData.Uninitialized)

View File

@@ -26,7 +26,7 @@ import io.element.android.features.messages.impl.UserEventPermissions
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
@@ -66,7 +66,7 @@ class PinnedMessagesListPresenter(
@Assisted private val navigator: PinnedMessagesListNavigator,
private val room: JoinedRoom,
timelineItemsFactoryCreator: TimelineItemsFactory.Creator,
private val timelineProvider: PinnedEventsTimelineProvider,
private val timelineProvider: DefaultPinnedEventsTimelineProvider,
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
private val linkPresenter: Presenter<LinkState>,
private val snackbarDispatcher: SnackbarDispatcher,

View File

@@ -90,7 +90,7 @@ class DefaultMessagesEntryPointTest {
override fun navigateToRoomDetails() = lambdaError()
override fun navigateToRoomMemberDetails(userId: UserId) = lambdaError()
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
override fun forwardEvent(eventId: EventId) = lambdaError()
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
override fun navigateToRoom(roomId: RoomId) = lambdaError()
}
val initialTarget = MessagesEntryPoint.InitialTarget.Messages(focusedEventId = AN_EVENT_ID)

View File

@@ -8,7 +8,7 @@
package io.element.android.features.messages.impl.pinned.banner
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider
import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.sync.SyncService
@@ -195,7 +195,7 @@ class PinnedMessagesBannerPresenterTest {
internal fun TestScope.createPinnedEventsTimelineProvider(
room: JoinedRoom = FakeJoinedRoom(),
syncService: SyncService = FakeSyncService(),
) = PinnedEventsTimelineProvider(
) = DefaultPinnedEventsTimelineProvider(
room = room,
syncService = syncService,
dispatchers = testCoroutineDispatchers(),

View File

@@ -13,7 +13,7 @@ import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
import io.element.android.features.messages.impl.link.aLinkState
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
@@ -300,7 +300,7 @@ class PinnedMessagesListPresenterTest {
analyticsService: AnalyticsService = FakeAnalyticsService(),
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
): PinnedMessagesListPresenter {
val timelineProvider = PinnedEventsTimelineProvider(
val timelineProvider = DefaultPinnedEventsTimelineProvider(
room = room,
syncService = syncService,
dispatchers = testCoroutineDispatchers(),

View File

@@ -40,7 +40,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
fun navigateToGlobalNotificationSettings()
fun navigateToRoom(roomId: RoomId, serverNames: List<String>)
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
fun startForwardEventFlow(eventId: EventId)
fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean)
}
fun createNode(

View File

@@ -299,7 +299,7 @@ class RoomDetailsFlowNode(
// Cannot happen
}
override fun forwardEvent(eventId: EventId) {
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
// Cannot happen
}
}
@@ -331,8 +331,8 @@ class RoomDetailsFlowNode(
callback.handlePermalinkClick(permalinkData, pushToBackstack = false)
}
override fun forward(eventId: EventId) {
callback.startForwardEventFlow(eventId)
override fun forward(eventId: EventId, fromPinnedEvents: Boolean) {
callback.startForwardEventFlow(eventId, fromPinnedEvents)
}
}
mediaGalleryEntryPoint.createNode(
@@ -358,8 +358,8 @@ class RoomDetailsFlowNode(
callback.handlePermalinkClick(data, pushToBackstack)
}
override fun forwardEvent(eventId: EventId) {
callback.startForwardEventFlow(eventId)
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
callback.startForwardEventFlow(eventId, fromPinnedEvents)
}
override fun navigateToRoom(roomId: RoomId) {

View File

@@ -64,7 +64,7 @@ class DefaultRoomDetailsEntryPointTest {
override fun navigateToGlobalNotificationSettings() = lambdaError()
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
override fun startForwardEventFlow(eventId: EventId) = lambdaError()
override fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
}
val params = RoomDetailsEntryPoint.Params(
initialElement = RoomDetailsEntryPoint.InitialTarget.RoomDetails,

View File

@@ -103,7 +103,7 @@ class UserProfileFlowNode(
// Cannot happen
}
override fun forwardEvent(eventId: EventId) {
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
// Cannot happen
}
}

View File

@@ -23,6 +23,6 @@ interface MediaGalleryEntryPoint : FeatureEntryPoint {
interface Callback : Plugin {
fun onBackClick()
fun viewInTimeline(eventId: EventId)
fun forward(eventId: EventId)
fun forward(eventId: EventId, fromPinnedEvents: Boolean)
}
}

View File

@@ -31,7 +31,7 @@ interface MediaViewerEntryPoint : FeatureEntryPoint {
interface Callback : Plugin {
fun onDone()
fun viewInTimeline(eventId: EventId)
fun forwardEvent(eventId: EventId)
fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean)
}
data class Params(

View File

@@ -85,7 +85,7 @@ class MediaGalleryFlowNode(
}
override fun forward(eventId: EventId) {
callback.forward(eventId)
callback.forward(eventId, fromPinnedEvents = false)
}
override fun showItem(item: MediaItem.Event) {
@@ -119,9 +119,9 @@ class MediaGalleryFlowNode(
callback.viewInTimeline(eventId)
}
override fun forwardEvent(eventId: EventId) {
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
// Need to go to the parent because of the overlay
callback.forward(eventId)
callback.forward(eventId, fromPinnedEvents)
}
}
mediaViewerEntryPoint.createNode(

View File

@@ -11,6 +11,6 @@ import io.element.android.libraries.matrix.api.core.EventId
interface MediaViewerNavigator {
fun onViewInTimelineClick(eventId: EventId)
fun onForwardClick(eventId: EventId)
fun onForwardClick(eventId: EventId, fromPinnedEvents: Boolean)
fun onItemDeleted()
}

View File

@@ -64,8 +64,8 @@ class MediaViewerNode(
callback.viewInTimeline(eventId)
}
override fun onForwardClick(eventId: EventId) {
callback.forwardEvent(eventId)
override fun onForwardClick(eventId: EventId, fromPinnedEvents: Boolean) {
callback.forwardEvent(eventId, fromPinnedEvents)
}
override fun onItemDeleted() {
@@ -81,11 +81,7 @@ class MediaViewerNode(
timelineMediaGalleryDataSource
} else {
// Can we use a specific timeline?
val timelineMode = when (val mode = inputs.mode) {
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> mode.timelineMode
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> mode.timelineMode
else -> null
}
val timelineMode = inputs.mode.getTimelineMode()
when (timelineMode) {
null -> timelineMediaGalleryDataSource
Timeline.Mode.Live,
@@ -149,3 +145,11 @@ class MediaViewerNode(
}
}
}
internal fun MediaViewerEntryPoint.MediaViewerMode.getTimelineMode(): Timeline.Mode? {
return when (this) {
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> timelineMode
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> timelineMode
else -> null
}
}

View File

@@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
@@ -119,7 +120,10 @@ class MediaViewerPresenter(
}
is MediaViewerEvents.Forward -> {
mediaBottomSheetState = MediaBottomSheetState.Hidden
navigator.onForwardClick(event.eventId)
navigator.onForwardClick(
eventId = event.eventId,
fromPinnedEvents = inputs.mode.getTimelineMode() == Timeline.Mode.PinnedEvents,
)
}
is MediaViewerEvents.OpenInfo -> coroutineScope.launch {
mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState(

View File

@@ -27,6 +27,7 @@ class SingleMediaGalleryDataSource(
override fun start() = Unit
override fun groupedMediaItemsFlow() = flowOf(AsyncData.Success(data))
override fun getLastData(): AsyncData<GroupedMediaItems> = AsyncData.Success(data)
override suspend fun loadMore(direction: Timeline.PaginationDirection) = Unit
override suspend fun deleteItem(eventId: EventId) = Unit

View File

@@ -40,7 +40,7 @@ class DefaultMediaGalleryEntryPointTest {
val callback = object : MediaGalleryEntryPoint.Callback {
override fun onBackClick() = lambdaError()
override fun viewInTimeline(eventId: EventId) = lambdaError()
override fun forward(eventId: EventId) = lambdaError()
override fun forward(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
}
val result = entryPoint.createNode(
parentNode = parentNode,

View File

@@ -72,7 +72,7 @@ class DefaultMediaViewerEntryPointTest {
val callback = object : MediaViewerEntryPoint.Callback {
override fun onDone() = lambdaError()
override fun viewInTimeline(eventId: EventId) = lambdaError()
override fun forwardEvent(eventId: EventId) = lambdaError()
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
}
val params = createMediaViewerEntryPointParams()
val result = entryPoint.createNode(
@@ -118,7 +118,7 @@ class DefaultMediaViewerEntryPointTest {
val callback = object : MediaViewerEntryPoint.Callback {
override fun onDone() = lambdaError()
override fun viewInTimeline(eventId: EventId) = lambdaError()
override fun forwardEvent(eventId: EventId) = lambdaError()
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
}
val params = entryPoint.createParamsForAvatar(
filename = "fn",

View File

@@ -12,15 +12,15 @@ import io.element.android.tests.testutils.lambda.lambdaError
class FakeMediaViewerNavigator(
private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() },
private val onForwardClickLambda: (EventId) -> Unit = { lambdaError() },
private val onForwardClickLambda: (EventId, Boolean) -> Unit = { _, _ -> lambdaError() },
private val onItemDeletedLambda: () -> Unit = { lambdaError() },
) : MediaViewerNavigator {
override fun onViewInTimelineClick(eventId: EventId) {
onViewInTimelineClickLambda(eventId)
}
override fun onForwardClick(eventId: EventId) {
onForwardClickLambda(eventId)
override fun onForwardClick(eventId: EventId, fromPinnedEvents: Boolean) {
onForwardClickLambda(eventId, fromPinnedEvents)
}
override fun onItemDeleted() {

View File

@@ -785,7 +785,7 @@ class MediaViewerPresenterTest {
@Test
fun `present - forward hides the bottom sheet and invokes the navigator`() = runTest {
val onForwardClickLambda = lambdaRecorder<EventId, Unit> { }
val onForwardClickLambda = lambdaRecorder<EventId, Boolean, Unit> { _, _ -> }
val navigator = FakeMediaViewerNavigator(
onForwardClickLambda = onForwardClickLambda,
)
@@ -804,7 +804,35 @@ class MediaViewerPresenterTest {
initialState.eventSink(MediaViewerEvents.Forward(AN_EVENT_ID))
val finalState = awaitItem()
assertThat(finalState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
onForwardClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
onForwardClickLambda.assertions().isCalledOnce()
.with(value(AN_EVENT_ID), value(false))
}
}
@Test
fun `present - forward from pinned events hides the bottom sheet and invokes the navigator`() = runTest {
val onForwardClickLambda = lambdaRecorder<EventId, Boolean, Unit> { _, _ -> }
val navigator = FakeMediaViewerNavigator(
onForwardClickLambda = onForwardClickLambda,
)
val presenter = createMediaViewerPresenter(
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.PinnedEvents),
localMediaFactory = localMediaFactory,
mediaViewerNavigator = navigator,
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(canRedactOwnResult = { Result.success(true) }),
),
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(MediaViewerEvents.OpenInfo(aMediaViewerPageData()))
val withBottomSheetState = awaitItem()
assertThat(withBottomSheetState.mediaBottomSheetState).isInstanceOf(MediaBottomSheetState.MediaDetailsBottomSheetState::class.java)
initialState.eventSink(MediaViewerEvents.Forward(AN_EVENT_ID))
val finalState = awaitItem()
assertThat(finalState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
onForwardClickLambda.assertions().isCalledOnce()
.with(value(AN_EVENT_ID), value(true))
}
}