Fix sharing gallery from pinned event timeline.

This commit is contained in:
Benoit Marty
2026-06-26 17:36:27 +02:00
parent a7e6d8af6e
commit b62242cdd2
9 changed files with 56 additions and 24 deletions
@@ -159,6 +159,7 @@ class MessagesFlowNode(
@Parcelize @Parcelize
data class GalleryViewer( data class GalleryViewer(
val fromPinnedMessages: Boolean,
val eventId: EventId?, val eventId: EventId?,
val galleryInfo: GalleryInfo, val galleryInfo: GalleryInfo,
val canUseOverlay: Boolean, val canUseOverlay: Boolean,
@@ -265,11 +266,13 @@ class MessagesFlowNode(
} }
override fun handleGalleryItemClick( override fun handleGalleryItemClick(
timelineMode: Timeline.Mode,
event: TimelineItem.Event, event: TimelineItem.Event,
galleryItemIndex: Int, galleryItemIndex: Int,
canUseOverlay: Boolean, canUseOverlay: Boolean,
): Boolean { ): Boolean {
return processGalleryEventClick( return processGalleryEventClick(
timelineMode = timelineMode,
event = event, event = event,
canUseOverlay = canUseOverlay, canUseOverlay = canUseOverlay,
galleryItemIndex = galleryItemIndex, galleryItemIndex = galleryItemIndex,
@@ -405,6 +408,7 @@ class MessagesFlowNode(
eventId = navTarget.eventId, eventId = navTarget.eventId,
galleryInfo = navTarget.galleryInfo, galleryInfo = navTarget.galleryInfo,
galleryItems = navTarget.galleryItems, galleryItems = navTarget.galleryItems,
fromPinnedMessages = navTarget.fromPinnedMessages,
) )
val callback = object : MediaViewerEntryPoint.Callback { val callback = object : MediaViewerEntryPoint.Callback {
override fun onDone() { override fun onDone() {
@@ -520,6 +524,7 @@ class MessagesFlowNode(
canUseOverlay: Boolean, canUseOverlay: Boolean,
) { ) {
processGalleryEventClick( processGalleryEventClick(
timelineMode = Timeline.Mode.PinnedEvents,
event = event, event = event,
galleryItemIndex = galleryItemIndex, galleryItemIndex = galleryItemIndex,
canUseOverlay = canUseOverlay, canUseOverlay = canUseOverlay,
@@ -574,11 +579,13 @@ class MessagesFlowNode(
} }
override fun handleGalleryItemClick( override fun handleGalleryItemClick(
timelineMode: Timeline.Mode,
event: TimelineItem.Event, event: TimelineItem.Event,
galleryItemIndex: Int, galleryItemIndex: Int,
canUseOverlay: Boolean, canUseOverlay: Boolean,
): Boolean { ): Boolean {
return processGalleryEventClick( return processGalleryEventClick(
timelineMode = timelineMode,
event = event, event = event,
canUseOverlay = canUseOverlay, canUseOverlay = canUseOverlay,
galleryItemIndex = galleryItemIndex, galleryItemIndex = galleryItemIndex,
@@ -792,6 +799,7 @@ class MessagesFlowNode(
} }
private fun processGalleryEventClick( private fun processGalleryEventClick(
timelineMode: Timeline.Mode,
event: TimelineItem.Event, event: TimelineItem.Event,
galleryItemIndex: Int, galleryItemIndex: Int,
canUseOverlay: Boolean, canUseOverlay: Boolean,
@@ -827,6 +835,7 @@ class MessagesFlowNode(
galleryInfo = galleryInfo, galleryInfo = galleryInfo,
canUseOverlay = canUseOverlay, canUseOverlay = canUseOverlay,
galleryItems = galleryItems, galleryItems = galleryItems,
fromPinnedMessages = timelineMode is Timeline.Mode.PinnedEvents
) )
} }
is TimelineItemAttachmentsContent -> { is TimelineItemAttachmentsContent -> {
@@ -859,6 +868,7 @@ class MessagesFlowNode(
galleryInfo = galleryInfo, galleryInfo = galleryInfo,
canUseOverlay = canUseOverlay, canUseOverlay = canUseOverlay,
galleryItems = galleryItems, galleryItems = galleryItems,
fromPinnedMessages = timelineMode is Timeline.Mode.PinnedEvents
) )
} }
else -> null else -> null
@@ -119,7 +119,7 @@ class MessagesNode(
interface Callback : Plugin { interface Callback : Plugin {
fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean
fun handleGalleryItemClick(event: TimelineItem.Event, galleryItemIndex: Int, canUseOverlay: Boolean): Boolean fun handleGalleryItemClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, galleryItemIndex: Int, canUseOverlay: Boolean): Boolean
fun navigateToPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) fun navigateToPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?)
fun navigateToRoomMemberDetails(userId: UserId) fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData) fun handlePermalinkClick(data: PermalinkData)
@@ -290,8 +290,17 @@ class MessagesNode(
} }
} }
}, },
onGalleryEventItemClick = { event, index -> onGalleryEventItemClick = { isLive, event, index ->
callback.handleGalleryItemClick(event, index, canUseOverlay) if (isLive) {
callback.handleGalleryItemClick(timelineController.mainTimelineMode(), event, index, canUseOverlay)
} else {
val detachedTimelineMode = timelineController.detachedTimelineMode()
if (detachedTimelineMode != null) {
callback.handleGalleryItemClick(detachedTimelineMode, event, index, canUseOverlay)
} else {
false
}
}
}, },
onUserDataClick = callback::navigateToRoomMemberDetails, onUserDataClick = callback::navigateToRoomMemberDetails,
onLinkClick = { url, customTab -> onLinkClick = { url, customTab ->
@@ -136,7 +136,7 @@ fun MessagesView(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onRoomDetailsClick: () -> Unit, onRoomDetailsClick: () -> Unit,
onEventContentClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean, onEventContentClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean,
onGalleryEventItemClick: (event: TimelineItem.Event, index: Int) -> Boolean, onGalleryEventItemClick: (isLive: Boolean, event: TimelineItem.Event, index: Int) -> Boolean,
onUserDataClick: (UserId) -> Unit, onUserDataClick: (UserId) -> Unit,
onLinkClick: (String, Boolean) -> Unit, onLinkClick: (String, Boolean) -> Unit,
onSendLocationClick: () -> Unit, onSendLocationClick: () -> Unit,
@@ -260,7 +260,11 @@ fun MessagesView(
state = state, state = state,
onContentClick = ::onContentClick, onContentClick = ::onContentClick,
onGalleryItemClick = { event, index -> onGalleryItemClick = { event, index ->
val hideKeyboard = onGalleryEventItemClick(event, index) val hideKeyboard = onGalleryEventItemClick(
state.timelineState.isLive,
event,
index,
)
if (hideKeyboard) { if (hideKeyboard) {
localView.hideKeyboard() localView.hideKeyboard()
} }
@@ -646,6 +650,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
onBackClick = {}, onBackClick = {},
onRoomDetailsClick = {}, onRoomDetailsClick = {},
onEventContentClick = { _, _ -> false }, onEventContentClick = { _, _ -> false },
onGalleryEventItemClick = { _, _, _ -> false },
onUserDataClick = {}, onUserDataClick = {},
onLinkClick = { _, _ -> }, onLinkClick = { _, _ -> },
onSendLocationClick = {}, onSendLocationClick = {},
@@ -655,7 +660,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
forceJumpToBottomVisibility = true, forceJumpToBottomVisibility = true,
knockRequestsBannerView = {}, knockRequestsBannerView = {},
onThreadsListClick = {}, onThreadsListClick = {},
onGalleryEventItemClick = { _, _ -> false },
) )
} }
@@ -702,6 +706,7 @@ internal fun MessagesViewA11yPreview() = ElementPreview {
onBackClick = {}, onBackClick = {},
onRoomDetailsClick = {}, onRoomDetailsClick = {},
onEventContentClick = { _, _ -> false }, onEventContentClick = { _, _ -> false },
onGalleryEventItemClick = { _, _, _ -> false },
onUserDataClick = {}, onUserDataClick = {},
onLinkClick = { _, _ -> }, onLinkClick = { _, _ -> },
onSendLocationClick = {}, onSendLocationClick = {},
@@ -709,7 +714,6 @@ internal fun MessagesViewA11yPreview() = ElementPreview {
onJoinCallClick = {}, onJoinCallClick = {},
onViewAllPinnedMessagesClick = {}, onViewAllPinnedMessagesClick = {},
onThreadsListClick = {}, onThreadsListClick = {},
onGalleryEventItemClick = { _, _ -> false },
forceJumpToBottomVisibility = true, forceJumpToBottomVisibility = true,
knockRequestsBannerView = {}, knockRequestsBannerView = {},
) )
@@ -35,6 +35,7 @@ internal fun MessagesViewWithIdentityChangePreview(
onBackClick = {}, onBackClick = {},
onRoomDetailsClick = {}, onRoomDetailsClick = {},
onEventContentClick = { _, _ -> false }, onEventContentClick = { _, _ -> false },
onGalleryEventItemClick = { _, _, _ -> false },
onUserDataClick = {}, onUserDataClick = {},
onLinkClick = { _, _ -> }, onLinkClick = { _, _ -> },
onSendLocationClick = {}, onSendLocationClick = {},
@@ -43,6 +44,5 @@ internal fun MessagesViewWithIdentityChangePreview(
onViewAllPinnedMessagesClick = {}, onViewAllPinnedMessagesClick = {},
knockRequestsBannerView = {}, knockRequestsBannerView = {},
onThreadsListClick = {}, onThreadsListClick = {},
onGalleryEventItemClick = { _, _ -> false },
) )
} }
@@ -126,7 +126,7 @@ class ThreadedMessagesNode(
interface Callback : Plugin { interface Callback : Plugin {
fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, canUseOverlay: Boolean): Boolean
fun handleGalleryItemClick(event: TimelineItem.Event, galleryItemIndex: Int, canUseOverlay: Boolean): Boolean fun handleGalleryItemClick(timelineMode: Timeline.Mode, event: TimelineItem.Event, galleryItemIndex: Int, canUseOverlay: Boolean): Boolean
fun navigateToPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) fun navigateToPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?)
fun navigateToRoomMemberDetails(userId: UserId) fun navigateToRoomMemberDetails(userId: UserId)
fun handlePermalinkClick(data: PermalinkData) fun handlePermalinkClick(data: PermalinkData)
@@ -290,8 +290,19 @@ class ThreadedMessagesNode(
} }
} == true } == true
}, },
onGalleryEventItemClick = { event, index -> onGalleryEventItemClick = { isLive, event, index ->
callback.handleGalleryItemClick(event, index, canUseOverlay) timelineController?.let { controller ->
if (isLive) {
callback.handleGalleryItemClick(controller.mainTimelineMode(), event, index, canUseOverlay)
} else {
val detachedTimelineMode = controller.detachedTimelineMode()
if (detachedTimelineMode != null) {
callback.handleGalleryItemClick(detachedTimelineMode, event, index, canUseOverlay)
} else {
false
}
}
} == true
}, },
onUserDataClick = callback::navigateToRoomMemberDetails, onUserDataClick = callback::navigateToRoomMemberDetails,
onLinkClick = { url, customTab -> onLinkClick = { url, customTab ->
@@ -48,6 +48,7 @@ interface MediaViewerEntryPoint : FeatureEntryPoint {
val eventId: EventId?, val eventId: EventId?,
val galleryInfo: GalleryInfo, val galleryInfo: GalleryInfo,
val galleryItems: List<GalleryItemData>, val galleryItems: List<GalleryItemData>,
val fromPinnedMessages: Boolean,
) : Params ) : Params
data class Avatar( data class Avatar(
@@ -59,7 +60,7 @@ interface MediaViewerEntryPoint : FeatureEntryPoint {
sealed interface MediaViewerMode : Parcelable { sealed interface MediaViewerMode : Parcelable {
@Parcelize @Parcelize
data object EventGallery : MediaViewerMode data class EventGallery(val fromPinnedMessages: Boolean) : MediaViewerMode
@Parcelize @Parcelize
data class TimelineImagesAndVideos(val timelineMode: Timeline.Mode) : MediaViewerMode data class TimelineImagesAndVideos(val timelineMode: Timeline.Mode) : MediaViewerMode
@@ -64,7 +64,7 @@ class MediaViewerDataSource(
private val galleryMode = when (mode) { private val galleryMode = when (mode) {
is MediaViewerMode.TimelineImagesAndVideos -> MediaGalleryMode.Images is MediaViewerMode.TimelineImagesAndVideos -> MediaGalleryMode.Images
is MediaViewerMode.TimelineFilesAndAudios -> MediaGalleryMode.Files is MediaViewerMode.TimelineFilesAndAudios -> MediaGalleryMode.Files
MediaViewerMode.EventGallery -> MediaGalleryMode.Images is MediaViewerMode.EventGallery -> MediaGalleryMode.Images
} }
// Map of sourceUrl to local media state // Map of sourceUrl to local media state
@@ -131,7 +131,7 @@ class MediaViewerNode(
is MediaViewerEntryPoint.Params.Avatar -> is MediaViewerEntryPoint.Params.Avatar ->
MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(Timeline.Mode.Media) MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(Timeline.Mode.Media)
is MediaViewerEntryPoint.Params.EventGallery -> is MediaViewerEntryPoint.Params.EventGallery ->
MediaViewerEntryPoint.MediaViewerMode.EventGallery MediaViewerEntryPoint.MediaViewerMode.EventGallery(fromPinnedMessages = inputs.fromPinnedMessages)
is MediaViewerEntryPoint.Params.RoomMedia -> is MediaViewerEntryPoint.Params.RoomMedia ->
inputs.mode inputs.mode
}, },
@@ -169,6 +169,6 @@ internal fun MediaViewerEntryPoint.MediaViewerMode.getTimelineMode(): Timeline.M
return when (this) { return when (this) {
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> timelineMode is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> timelineMode
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> timelineMode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> timelineMode
MediaViewerEntryPoint.MediaViewerMode.EventGallery -> null is MediaViewerEntryPoint.MediaViewerMode.EventGallery -> null
} }
} }
@@ -151,8 +151,11 @@ class MediaViewerPresenter(
mediaBottomSheetState = MediaBottomSheetState.Hidden mediaBottomSheetState = MediaBottomSheetState.Hidden
navigator.onForwardClick( navigator.onForwardClick(
eventId = event.eventId, eventId = event.eventId,
// TODO We can have a pinned gallery fromPinnedEvents = when (inputs) {
fromPinnedEvents = inputs.mode()?.getTimelineMode() == Timeline.Mode.PinnedEvents, is MediaViewerEntryPoint.Params.RoomMedia -> inputs.mode == Timeline.Mode.PinnedEvents
is MediaViewerEntryPoint.Params.EventGallery -> inputs.fromPinnedMessages
is MediaViewerEntryPoint.Params.Avatar -> false
},
) )
} }
is MediaViewerEvent.OpenInfo -> coroutineScope.launch { is MediaViewerEvent.OpenInfo -> coroutineScope.launch {
@@ -241,7 +244,7 @@ class MediaViewerPresenter(
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> R.string.screen_media_details_no_more_media_to_show is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> R.string.screen_media_details_no_more_media_to_show
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> R.string.screen_media_details_no_more_files_to_show is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> R.string.screen_media_details_no_more_files_to_show
// Should not happen // Should not happen
MediaViewerEntryPoint.MediaViewerMode.EventGallery -> R.string.screen_media_details_no_more_media_to_show is MediaViewerEntryPoint.MediaViewerMode.EventGallery -> R.string.screen_media_details_no_more_media_to_show
} }
val message = SnackbarMessage(messageResId) val message = SnackbarMessage(messageResId)
snackbarDispatcher.post(message) snackbarDispatcher.post(message)
@@ -319,9 +322,3 @@ private fun MediaViewerEntryPoint.Params.mediaSource() = when (this) {
is MediaViewerEntryPoint.Params.EventGallery -> null is MediaViewerEntryPoint.Params.EventGallery -> null
is MediaViewerEntryPoint.Params.RoomMedia -> mediaSource is MediaViewerEntryPoint.Params.RoomMedia -> mediaSource
} }
private fun MediaViewerEntryPoint.Params.mode() = when (this) {
is MediaViewerEntryPoint.Params.Avatar -> null
is MediaViewerEntryPoint.Params.EventGallery -> null
is MediaViewerEntryPoint.Params.RoomMedia -> mode
}