mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Allow previews in the timeline to include filtered galleries (#5943)
* remove the usage of the timeline kind override in favour of getting it properly from the timeline view model * allow the previews to include gallery items filtered by the allowed types when started outside the gallery sss asa * fix for actions not being forwarded to the timeline right * pr suggestions
This commit is contained in:
@@ -44,12 +44,16 @@ class TimelineController: TimelineControllerProtocol {
|
||||
activeTimelineItemProvider.kind
|
||||
}
|
||||
|
||||
let allowedGalleryItemTypes: [TimelineAllowedGalleryItemType]?
|
||||
|
||||
init(roomProxy: JoinedRoomProxyProtocol,
|
||||
timelineProxy: TimelineProxyProtocol,
|
||||
initialFocussedEventID: String?,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
appSettings: AppSettings) {
|
||||
appSettings: AppSettings,
|
||||
allowedGalleryItemTypes: [TimelineAllowedGalleryItemType]? = nil) {
|
||||
self.allowedGalleryItemTypes = allowedGalleryItemTypes
|
||||
self.roomProxy = roomProxy
|
||||
liveTimelineItemProvider = timelineProxy.timelineItemProvider
|
||||
self.timelineItemFactory = timelineItemFactory
|
||||
|
||||
+2
-1
@@ -71,7 +71,8 @@ struct TimelineControllerFactory: TimelineControllerFactoryProtocol {
|
||||
initialFocussedEventID: nil,
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
mediaProvider: mediaProvider,
|
||||
appSettings: appSettings))
|
||||
appSettings: appSettings,
|
||||
allowedGalleryItemTypes: allowedMessageTypes.allowedGalleryItemTypes))
|
||||
case .failure(let error):
|
||||
return .failure(.roomProxyError(error))
|
||||
}
|
||||
|
||||
+4
-1
@@ -26,7 +26,7 @@ enum TimelineControllerAction {
|
||||
}
|
||||
|
||||
case displayMediaPreview(item: EventBasedMessageTimelineItemProtocol, timelineViewModel: TimelineViewModelKind)
|
||||
case displayGalleryPreview(galleryItem: GalleryRoomTimelineItem, initialIndex: Int)
|
||||
case displayGalleryPreview(galleryItem: GalleryRoomTimelineItem, timelineViewModel: TimelineViewModelKind)
|
||||
case displayLocation(StaticLocationData)
|
||||
case displayLiveLocation(sender: TimelineItemSender, initialLiveLocationShare: LiveLocationShare)
|
||||
case none
|
||||
@@ -47,6 +47,9 @@ enum TimelineControllerError: Error {
|
||||
protocol TimelineControllerProtocol: Sendable {
|
||||
var timelineKind: TimelineKind { get }
|
||||
|
||||
/// The gallery attachments this timeline includes, or `nil` when it isn't filtered.
|
||||
var allowedGalleryItemTypes: [TimelineAllowedGalleryItemType]? { get }
|
||||
|
||||
/// The currently known items, use only for setting up the intial state.
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
|
||||
|
||||
+15
-3
@@ -19,8 +19,9 @@ nonisolated struct GalleryRoomTimelineItemContent: Hashable {
|
||||
|
||||
/// Identifies a single attachment within a gallery message.
|
||||
nonisolated struct GalleryItemID: Hashable {
|
||||
/// The unique ID of the parent gallery timeline item.
|
||||
let timelineItemUniqueID: TimelineItemIdentifier.UniqueID
|
||||
/// The identifier of the parent gallery timeline item. The whole identifier is used so that an
|
||||
/// attachment remains identifiable across timelines, where the unique ID alone wouldn't be.
|
||||
let timelineItemID: TimelineItemIdentifier
|
||||
/// The item's position within the gallery.
|
||||
let mediaIndex: Int
|
||||
}
|
||||
@@ -152,13 +153,24 @@ nonisolated enum GalleryItem: Hashable, Identifiable {
|
||||
var hasThumbnail: Bool {
|
||||
isImage || thumbnailSource != nil
|
||||
}
|
||||
|
||||
/// The type this attachment is allowed by, or `nil` when its type is unknown.
|
||||
var allowedItemType: TimelineAllowedGalleryItemType? {
|
||||
switch self {
|
||||
case .image: .image
|
||||
case .video: .video
|
||||
case .audio: .audio
|
||||
case .file: .file
|
||||
case .other: nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
nonisolated extension GalleryItemID {
|
||||
static func mock(_ index: Int) -> GalleryItemID {
|
||||
.init(timelineItemUniqueID: .init("gallery-mock"), mediaIndex: index)
|
||||
.init(timelineItemID: .randomEvent, mediaIndex: index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ nonisolated struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
case .location(let content):
|
||||
.location(buildLocationTimelineItemContent(content))
|
||||
case .gallery(let content):
|
||||
.gallery(buildGalleryTimelineItemContent(content, uniqueID: .init(senderID)))
|
||||
.gallery(buildGalleryTimelineItemContent(content, timelineItemID: .randomEvent))
|
||||
case .other(_, let body):
|
||||
.text(.init(body: body))
|
||||
case .none:
|
||||
@@ -417,7 +417,7 @@ nonisolated struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildGalleryTimelineItemContent(galleryMessageContent, uniqueID: eventItemProxy.id.uniqueID),
|
||||
content: buildGalleryTimelineItemContent(galleryMessageContent, timelineItemID: eventItemProxy.id),
|
||||
properties: .init(replyDetails: buildTimelineItemReplyDetails(messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
threadSummary: buildTimelineItemThreadSummary(messageLikeContent.threadSummary),
|
||||
@@ -429,13 +429,13 @@ nonisolated struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
encryptionForwarder: eventItemProxy.forwarder))
|
||||
}
|
||||
|
||||
private func buildGalleryTimelineItemContent(_ messageContent: GalleryMessageContent, uniqueID: TimelineItemIdentifier.UniqueID) -> GalleryRoomTimelineItemContent {
|
||||
private func buildGalleryTimelineItemContent(_ messageContent: GalleryMessageContent, timelineItemID: TimelineItemIdentifier) -> GalleryRoomTimelineItemContent {
|
||||
let htmlCaption = messageContent.formatted?.format == .html ? messageContent.formatted?.body : nil
|
||||
let plainCaption = messageContent.formatted?.format != .html ? messageContent.formatted?.body : nil
|
||||
let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : (plainCaption.flatMap(attributedStringBuilder.fromPlain))
|
||||
|
||||
let items = messageContent.itemtypes.enumerated().map { index, itemType in
|
||||
buildGalleryItem(itemType, id: GalleryItemID(timelineItemUniqueID: uniqueID, mediaIndex: index))
|
||||
buildGalleryItem(itemType, id: GalleryItemID(timelineItemID: timelineItemID, mediaIndex: index))
|
||||
}
|
||||
|
||||
return GalleryRoomTimelineItemContent(body: messageContent.body,
|
||||
|
||||
@@ -94,7 +94,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
|
||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||
guard let content = timelineItemProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
|
||||
guard let content = timelineItemProvider.itemProxies.firstEventTimelineItem(matching: timelineItemID)?.content,
|
||||
case let .msgLike(messageLikeContent) = content,
|
||||
case let .message(messageContent) = messageLikeContent.kind else {
|
||||
return nil
|
||||
@@ -663,15 +663,19 @@ private extension MatrixRustSDK.PollKind {
|
||||
}
|
||||
|
||||
extension Array where Element == TimelineItemProxy {
|
||||
func firstEventTimelineItemUsingStableID(_ id: TimelineItemIdentifier) -> EventTimelineItem? {
|
||||
for item in self {
|
||||
if case let .event(eventTimelineItem) = item {
|
||||
if eventTimelineItem.id.uniqueID == id.uniqueID {
|
||||
return eventTimelineItem.item
|
||||
}
|
||||
}
|
||||
/// The event matching the given identifier, found by the event itself as that is the only part of
|
||||
/// the identifier that can be compared across timelines. Identifiers that don't represent an event
|
||||
/// fall back to the unique ID.
|
||||
func firstEventTimelineItem(matching id: TimelineItemIdentifier) -> EventTimelineItem? {
|
||||
let eventTimelineItems = compactMap { item -> EventTimelineItemProxy? in
|
||||
guard case let .event(eventTimelineItem) = item else { return nil }
|
||||
return eventTimelineItem
|
||||
}
|
||||
|
||||
return nil
|
||||
guard let eventOrTransactionID = id.eventOrTransactionID else {
|
||||
return eventTimelineItems.first { $0.id.uniqueID == id.uniqueID }?.item
|
||||
}
|
||||
|
||||
return eventTimelineItems.first { $0.id.eventOrTransactionID == eventOrTransactionID }?.item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ enum TimelineKind: Equatable {
|
||||
case pinned
|
||||
case thread(rootEventID: String)
|
||||
|
||||
enum MediaPresentation { case roomScreenLive, roomScreenDetached, pinnedEventsScreen, mediaFilesScreen }
|
||||
enum MediaPresentation { case roomScreenLive, roomScreenDetached, roomScreenThread, pinnedEventsScreen, mediaFilesScreen }
|
||||
case media(MediaPresentation)
|
||||
|
||||
var isThread: Bool {
|
||||
@@ -42,6 +42,34 @@ enum TimelineFocus {
|
||||
|
||||
enum TimelineAllowedMessageType {
|
||||
case audio, file, image, video, gallery
|
||||
|
||||
/// The gallery attachments this type allows, so that a gallery event's attachments can be
|
||||
/// filtered client side to match. A gallery can't contain another gallery, hence the `nil`.
|
||||
var allowedGalleryItemType: TimelineAllowedGalleryItemType? {
|
||||
switch self {
|
||||
case .audio: .audio
|
||||
case .file: .file
|
||||
case .image: .image
|
||||
case .video: .video
|
||||
case .gallery: nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The gallery attachments a timeline allows, mirroring the message types it is filtered to. The SDK can
|
||||
/// only filter whole events, so a gallery's attachments are filtered by these client side. Unlike a
|
||||
/// message type there's no gallery case, as a gallery can't contain another one.
|
||||
enum TimelineAllowedGalleryItemType {
|
||||
case audio, file, image, video
|
||||
}
|
||||
|
||||
extension [TimelineAllowedMessageType] {
|
||||
/// The gallery attachments these types allow, or `nil` when they place no restriction on them,
|
||||
/// which is the case for a timeline made up of galleries alone.
|
||||
var allowedGalleryItemTypes: [TimelineAllowedGalleryItemType]? {
|
||||
let types = compactMap(\.allowedGalleryItemType)
|
||||
return types.isEmpty ? nil : types
|
||||
}
|
||||
}
|
||||
|
||||
enum TimelineProxyError: Error {
|
||||
|
||||
Reference in New Issue
Block a user