mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Open a full-screen preview when tapping a gallery item
Tapping a gallery tile/row opens the existing media preview scoped to that gallery's attachments, paging between them. Introduces a typed MediaPreviewItemID (replacing the stringly-typed preview identifier) and a gallery-scoped TimelineMediaPreviewDataSource; the tap routes through TimelineInteractionHandler/TimelineViewModel. Non-previewable (.other) items are skipped without shifting the tapped index. Co-authored-by: svetoslav.mitov <svetoslav.mitov@sportinno.com> just use id instead of previewID removed horrible multiline ternary pr suggestions remove redundant code remove empty caption display + gallery uses the appropriate media timeline actions
This commit is contained in:
@@ -29,6 +29,62 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
try assertInitialDataSource()
|
||||
}
|
||||
|
||||
@Test
|
||||
func galleryPreview() throws {
|
||||
// Given a gallery message with three previewable attachments.
|
||||
let items = (0..<3).map { index in
|
||||
GalleryItem.mockImage(index: index, filename: "image-\(index).jpg")
|
||||
}
|
||||
let gallery = GalleryRoomTimelineItem(id: .randomEvent,
|
||||
timestamp: .mock,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
sender: .init(id: "Bob"),
|
||||
content: .init(body: "Gallery", caption: nil, items: items),
|
||||
properties: .init())
|
||||
|
||||
// When opening the preview scoped to that gallery on the second attachment.
|
||||
let dataSource = TimelineMediaPreviewDataSource(galleryItem: gallery, initialIndex: 1)
|
||||
|
||||
// Then it contains exactly the gallery's attachments with no surrounding pagination…
|
||||
#expect(dataSource.previewItems.count == 3)
|
||||
#expect(dataSource.numberOfPreviewItems(in: previewController) == 3)
|
||||
#expect(dataSource.initialItemIndex == 1)
|
||||
|
||||
// …and the initial item is the tapped attachment.
|
||||
let media = try #require(dataSource.currentItem.mediaItem, "The current item should be a media item.")
|
||||
#expect(media.id == .galleryItem(items[1].id))
|
||||
}
|
||||
|
||||
@Test
|
||||
func galleryPreviewIncludesNonPreviewableItems() throws {
|
||||
// Given a gallery whose first attachment is an unknown type (QuickLook shows its default screen).
|
||||
let items: [GalleryItem] = [
|
||||
.other(id: .mock(0), filename: "unknown.bin"),
|
||||
.mockImage(index: 1, filename: "image-1.jpg"),
|
||||
.mockImage(index: 2, filename: "image-2.jpg")
|
||||
]
|
||||
let gallery = GalleryRoomTimelineItem(id: .randomEvent,
|
||||
timestamp: .mock,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
sender: .init(id: "Bob"),
|
||||
content: .init(body: "Gallery", caption: nil, items: items),
|
||||
properties: .init())
|
||||
|
||||
// When tapping the first image (index 1 in the full items array).
|
||||
let dataSource = TimelineMediaPreviewDataSource(galleryItem: gallery, initialIndex: 1)
|
||||
|
||||
// Then every attachment is kept — indices line up 1:1 — and the unknown item has no media source.
|
||||
#expect(dataSource.previewItems.count == 3)
|
||||
#expect(dataSource.previewItems[0].id == .galleryItem(items[0].id))
|
||||
#expect(dataSource.previewItems[0].mediaSource == nil)
|
||||
let media = try #require(dataSource.currentItem.mediaItem, "The current item should be a media item.")
|
||||
#expect(media.id == .galleryItem(items[1].id))
|
||||
}
|
||||
|
||||
@Test
|
||||
func currentUpdateItem() throws {
|
||||
// Given a data source built with the initial items.
|
||||
@@ -69,8 +125,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
|
||||
let previewItemCount = dataSource.numberOfPreviewItems(in: previewController)
|
||||
let displayedItem = try #require(dataSource.previewController(previewController, previewItemAt: dataSource.initialItemIndex) as? TimelineMediaPreviewItem.Media)
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should not change.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should not change.")
|
||||
|
||||
#expect(dataSource.previewItems.count == initialMediaViewStates.count, "The number of items should not change.")
|
||||
#expect(previewItemCount == initialMediaViewStates.count + (2 * initialPadding), "The padded number of items should not change.")
|
||||
@@ -93,8 +149,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
|
||||
var previewItemCount = dataSource.numberOfPreviewItems(in: previewController)
|
||||
var displayedItem = try #require(dataSource.previewController(previewController, previewItemAt: dataSource.initialItemIndex) as? TimelineMediaPreviewItem.Media)
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should not change.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should not change.")
|
||||
#expect(previewItemCount == initialMediaViewStates.count + (2 * initialPadding), "The number of items should not change")
|
||||
|
||||
// When more items are loaded in a forward pagination or sync.
|
||||
@@ -109,8 +165,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
|
||||
previewItemCount = dataSource.numberOfPreviewItems(in: previewController)
|
||||
displayedItem = try #require(dataSource.previewController(previewController, previewItemAt: dataSource.initialItemIndex) as? TimelineMediaPreviewItem.Media)
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should not change.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should not change.")
|
||||
#expect(previewItemCount == initialMediaViewStates.count + (2 * initialPadding), "The number of items should not change")
|
||||
}
|
||||
|
||||
@@ -133,8 +189,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
|
||||
var previewItemCount = dataSource.numberOfPreviewItems(in: previewController)
|
||||
var displayedItem = try #require(dataSource.previewController(previewController, previewItemAt: dataSource.initialItemIndex) as? TimelineMediaPreviewItem.Media)
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should not change.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should not change.")
|
||||
#expect(previewItemCount == initialMediaViewStates.count + (2 * initialPadding), "The number of items should not change")
|
||||
|
||||
// When paginating forwards by more than the available padding.
|
||||
@@ -149,8 +205,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
|
||||
previewItemCount = dataSource.numberOfPreviewItems(in: previewController)
|
||||
displayedItem = try #require(dataSource.previewController(previewController, previewItemAt: dataSource.initialItemIndex) as? TimelineMediaPreviewItem.Media)
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should not change.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should not change.")
|
||||
#expect(previewItemCount == initialMediaViewStates.count + (2 * initialPadding), "The number of items should not change")
|
||||
}
|
||||
|
||||
@@ -173,8 +229,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
#expect(previewItemCount == 1 + (2 * initialPadding), "The initial item count should be padded for the preview controller.")
|
||||
#expect(dataSource.initialItemIndex == initialPadding, "The initial item index should be padded for the preview controller.")
|
||||
|
||||
#expect(displayedItem.id == initialItem.id.eventOrTransactionID, "The displayed item should be the initial item.")
|
||||
#expect(dataSource.currentMediaItemID == initialItem.id.eventOrTransactionID, "The current item should also be the initial item.")
|
||||
#expect(displayedItem.id == initialItem.previewID, "The displayed item should be the initial item.")
|
||||
#expect(dataSource.currentMediaItemID == initialItem.previewID, "The current item should also be the initial item.")
|
||||
|
||||
// When the timeline loads the initial items.
|
||||
let deferred = deferFulfillment(dataSource.previewItemsPaginationPublisher) { _ in true }
|
||||
@@ -191,8 +247,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
#expect(previewItemCount == 1 + (2 * initialPadding), "The item count should not change as the padding will be reduced.")
|
||||
#expect(dataSource.initialItemIndex == initialPadding, "The item index should not change.")
|
||||
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should not change.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should not change.")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -214,8 +270,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
#expect(previewItemCount == 1 + (2 * initialPadding), "The initial item count should be padded for the preview controller.")
|
||||
#expect(dataSource.initialItemIndex == initialPadding, "The initial item index should be padded for the preview controller.")
|
||||
|
||||
#expect(displayedItem.id == initialItem.id.eventOrTransactionID, "The displayed item should be the initial item.")
|
||||
#expect(dataSource.currentMediaItemID == initialItem.id.eventOrTransactionID, "The current item should also be the initial item.")
|
||||
#expect(displayedItem.id == initialItem.previewID, "The displayed item should be the initial item.")
|
||||
#expect(dataSource.currentMediaItemID == initialItem.previewID, "The current item should also be the initial item.")
|
||||
|
||||
// When the timeline loads more items but still doesn't include the initial item.
|
||||
let failure = deferFailure(dataSource.previewItemsPaginationPublisher, timeout: .seconds(1)) { _ in true }
|
||||
@@ -232,8 +288,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
#expect(previewItemCount == 1 + (2 * initialPadding), "The initial item count should not change.")
|
||||
#expect(dataSource.initialItemIndex == initialPadding, "The initial item index should not change.")
|
||||
|
||||
#expect(displayedItem.id == initialItem.id.eventOrTransactionID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialItem.id.eventOrTransactionID, "The current item not change.")
|
||||
#expect(displayedItem.id == initialItem.previewID, "The displayed item should not change.")
|
||||
#expect(dataSource.currentMediaItemID == initialItem.previewID, "The current item not change.")
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
@@ -259,8 +315,8 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
|
||||
// Then the preview controller should be showing the initial item and the data source should reflect this.
|
||||
#expect(dataSource.initialItemIndex == initialItemIndex + initialPadding, "The initial item index should be padded for the preview controller.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The displayed item should be the initial item.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].id.eventOrTransactionID, "The current item should also be the initial item.")
|
||||
#expect(displayedItem.id == initialMediaItems[initialItemIndex].previewID, "The displayed item should be the initial item.")
|
||||
#expect(dataSource.currentMediaItemID == initialMediaItems[initialItemIndex].previewID, "The current item should also be the initial item.")
|
||||
|
||||
#expect(dataSource.previewItems.count == initialMediaViewStates.count, "The initial count of preview items should be correct.")
|
||||
#expect(previewItemCount == initialMediaViewStates.count + (2 * initialPadding), "The initial item count should be padded for the preview controller.")
|
||||
@@ -270,7 +326,15 @@ struct TimelineMediaPreviewDataSourceTests {
|
||||
}
|
||||
|
||||
private extension TimelineMediaPreviewDataSource {
|
||||
var currentMediaItemID: TimelineItemIdentifier.EventOrTransactionID? {
|
||||
var currentMediaItemID: MediaPreviewItemID? {
|
||||
currentItem.mediaItem?.id
|
||||
}
|
||||
}
|
||||
|
||||
private extension EventBasedMessageTimelineItemProtocol {
|
||||
/// Test helper that derives the same preview ID used by `TimelineMediaPreviewItem.Media` —
|
||||
/// avoids reaching into the private encoding from the tests.
|
||||
var previewID: MediaPreviewItemID {
|
||||
TimelineMediaPreviewItem.Media(timelineItem: self).id
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user