mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
unified the filtering of the gallery items
This commit is contained in:
@@ -449,12 +449,8 @@ private extension RoomTimelineItemViewState {
|
|||||||
return [TimelineMediaPreviewItem.Media(roomTimelineItemViewState: self)].compactMap { $0 }
|
return [TimelineMediaPreviewItem.Media(roomTimelineItemViewState: self)].compactMap { $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
return galleryItem.content.items.filter { item in
|
return galleryItem.content.attachments(allowedTypes: allowedGalleryItemTypes).map {
|
||||||
guard let allowedItemType = item.allowedItemType else {
|
TimelineMediaPreviewItem.Media(galleryParent: galleryItem, item: $0.item)
|
||||||
return false // Nothing to preview, and unlike a tile there's no index to line up with.
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return allowedGalleryItemTypes?.contains(allowedItemType) ?? true
|
|
||||||
}.map { TimelineMediaPreviewItem.Media(galleryParent: galleryItem, item: $0) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ enum MediaEventsTimelineScreenViewModelAction {
|
|||||||
case viewInRoomTimeline(TimelineItemIdentifier)
|
case viewInRoomTimeline(TimelineItemIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated enum MediaEventsTimelineScreenMode {
|
enum MediaEventsTimelineScreenMode {
|
||||||
case media
|
case media
|
||||||
case files
|
case files
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-13
@@ -204,7 +204,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
|||||||
return [itemViewState]
|
return [itemViewState]
|
||||||
case .gallery(let galleryItem):
|
case .gallery(let galleryItem):
|
||||||
// A gallery's attachments are displayed flattened, listed individually.
|
// A gallery's attachments are displayed flattened, listed individually.
|
||||||
let flattenedItems = galleryItem.itemsAsIndividualMessages(for: state.screenMode)
|
let flattenedItems = galleryItem.itemsAsIndividualMessages(allowedTypes: timelineViewState.allowedGalleryItemTypes)
|
||||||
for (mediaIndex, item) in flattenedItems {
|
for (mediaIndex, item) in flattenedItems {
|
||||||
newGalleryItemLookup[item.id.uniqueID] = .init(parentID: galleryItem.id, mediaIndex: mediaIndex)
|
newGalleryItemLookup[item.id.uniqueID] = .init(parentID: galleryItem.id, mediaIndex: mediaIndex)
|
||||||
}
|
}
|
||||||
@@ -285,32 +285,31 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension GalleryRoomTimelineItem {
|
private extension GalleryRoomTimelineItem {
|
||||||
/// Represents the gallery's attachments as though each had been sent as an individual message, so
|
/// Represents the gallery's attachments of the given types as though each had been sent as an
|
||||||
/// that the screen can list them alongside the room's other media, keeping only those that belong
|
/// individual message, so that the screen can list them alongside the room's other media. Each one
|
||||||
/// in the given mode. Each one keeps the gallery's event ID but is given its own unique ID so that
|
/// keeps the gallery's event ID but is given its own unique ID so that they remain distinct.
|
||||||
/// the attachments remain distinct within the list.
|
func itemsAsIndividualMessages(allowedTypes: [TimelineAllowedGalleryItemType]?) -> [(mediaIndex: Int, item: EventBasedMessageTimelineItemProtocol)] {
|
||||||
func itemsAsIndividualMessages(for screenMode: MediaEventsTimelineScreenMode) -> [(mediaIndex: Int, item: EventBasedMessageTimelineItemProtocol)] {
|
|
||||||
guard let eventOrTransactionID = id.eventOrTransactionID else { return [] }
|
guard let eventOrTransactionID = id.eventOrTransactionID else { return [] }
|
||||||
|
|
||||||
return content.items.enumerated().compactMap { mediaIndex, galleryItem in
|
return content.attachments(allowedTypes: allowedTypes).compactMap { mediaIndex, galleryItem in
|
||||||
let itemID = TimelineItemIdentifier.event(uniqueID: .init("\(id.uniqueID.value)-\(mediaIndex)"),
|
let itemID = TimelineItemIdentifier.event(uniqueID: .init("\(id.uniqueID.value)-\(mediaIndex)"),
|
||||||
eventOrTransactionID: eventOrTransactionID)
|
eventOrTransactionID: eventOrTransactionID)
|
||||||
|
|
||||||
let item: EventBasedMessageTimelineItemProtocol? = switch galleryItem {
|
let item: EventBasedMessageTimelineItemProtocol? = switch galleryItem {
|
||||||
case .image(_, let content) where screenMode == .media:
|
case .image(_, let content):
|
||||||
ImageRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
ImageRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
||||||
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
||||||
case .video(_, let content) where screenMode == .media:
|
case .video(_, let content):
|
||||||
VideoRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
VideoRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
||||||
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
||||||
case .audio(_, let content) where screenMode == .files:
|
case .audio(_, let content):
|
||||||
AudioRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
AudioRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
||||||
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
||||||
case .file(_, let content) where screenMode == .files:
|
case .file(_, let content):
|
||||||
FileRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
FileRoomTimelineItem(id: itemID, timestamp: timestamp, isOutgoing: isOutgoing, isEditable: isEditable,
|
||||||
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
canBeRepliedTo: canBeRepliedTo, sender: sender, content: content, properties: properties)
|
||||||
default:
|
case .other:
|
||||||
nil // Attachments of an unknown type, or that belong in the other mode.
|
nil // Filtered out above, as there's nothing to show for it.
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let item else { return nil }
|
guard let item else { return nil }
|
||||||
|
|||||||
+9
-1
@@ -361,7 +361,15 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
timelineItems.append(contentsOf: [makeFileItem(url: .mockMXCScanning), makeFileItem(url: .mockMXCUnsafe)])
|
timelineItems.append(contentsOf: [makeFileItem(url: .mockMXCScanning), makeFileItem(url: .mockMXCUnsafe)])
|
||||||
}
|
}
|
||||||
|
|
||||||
return TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: timelineItems))
|
// Mirrors the message types that the flow coordinator filters each timeline to.
|
||||||
|
let allowedGalleryItemTypes: [TimelineAllowedGalleryItemType] = switch screenMode {
|
||||||
|
case .media: [.image, .video]
|
||||||
|
case .files: [.file, .audio]
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen),
|
||||||
|
timelineItems: timelineItems,
|
||||||
|
allowedGalleryItemTypes: allowedGalleryItemTypes))
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func makeImageItem(url: URL) -> ImageRoomTimelineItem {
|
private static func makeImageItem(url: URL) -> ImageRoomTimelineItem {
|
||||||
|
|||||||
+15
@@ -15,6 +15,21 @@ nonisolated struct GalleryRoomTimelineItemContent: Hashable {
|
|||||||
/// The original textual representation of the formatted caption directly from the event (usually HTML code)
|
/// The original textual representation of the formatted caption directly from the event (usually HTML code)
|
||||||
var formattedCaptionHTMLString: String?
|
var formattedCaptionHTMLString: String?
|
||||||
let items: [GalleryItem]
|
let items: [GalleryItem]
|
||||||
|
|
||||||
|
/// The attachments of the given types, paired with their position in the gallery so that they remain
|
||||||
|
/// identifiable once separated from it. Attachments of an unknown type are never included as there's
|
||||||
|
/// nothing to show for them.
|
||||||
|
/// - Parameter allowedTypes: The types to include, or `nil` to include them all.
|
||||||
|
func attachments(allowedTypes: [TimelineAllowedGalleryItemType]?) -> [(mediaIndex: Int, item: GalleryItem)] {
|
||||||
|
items.enumerated().compactMap { mediaIndex, item in
|
||||||
|
guard let allowedItemType = item.allowedItemType,
|
||||||
|
allowedTypes?.contains(allowedItemType) ?? true else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mediaIndex, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies a single attachment within a gallery message.
|
/// Identifies a single attachment within a gallery message.
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import Testing
|
|||||||
struct MediaEventsTimelineScreenViewModelTests {
|
struct MediaEventsTimelineScreenViewModelTests {
|
||||||
@Test
|
@Test
|
||||||
func changingScreenModeSwitchesTimelines() {
|
func changingScreenModeSwitchesTimelines() {
|
||||||
let mediaTimelineViewModel = makeTimelineViewModel()
|
let mediaTimelineViewModel = makeTimelineViewModel(allowedGalleryItemTypes: [.image, .video])
|
||||||
let filesTimelineViewModel = makeTimelineViewModel()
|
let filesTimelineViewModel = makeTimelineViewModel(allowedGalleryItemTypes: [.file, .audio])
|
||||||
|
|
||||||
let viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
let viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
||||||
filesTimelineViewModel: filesTimelineViewModel,
|
filesTimelineViewModel: filesTimelineViewModel,
|
||||||
@@ -45,10 +45,12 @@ struct MediaEventsTimelineScreenViewModelTests {
|
|||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
// Both timelines are given the same mixed chunk of items so that the mode based filtering is what's under test.
|
// Both timelines are given the same mixed chunk of items, filtered as they are in the app, so that
|
||||||
private func makeTimelineViewModel() -> TimelineViewModel {
|
// the filtering of the items is what's under test.
|
||||||
|
private func makeTimelineViewModel(allowedGalleryItemTypes: [TimelineAllowedGalleryItemType]) -> TimelineViewModel {
|
||||||
let timelineController = TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen),
|
let timelineController = TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen),
|
||||||
timelineItems: [TimelineFixtures.separator] + TimelineFixtures.mediaChunk))
|
timelineItems: [TimelineFixtures.separator] + TimelineFixtures.mediaChunk,
|
||||||
|
allowedGalleryItemTypes: allowedGalleryItemTypes))
|
||||||
return TimelineViewModel.mock(timelineKind: .media(.mediaFilesScreen), timelineController: timelineController)
|
return TimelineViewModel.mock(timelineKind: .media(.mediaFilesScreen), timelineController: timelineController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user