Use the new preview screen when tapping media on the room and pinned events screens. (#3736)

* Use the new TimelineMediaPreview modifier on the room and pinned timeline screens.

* Use the same presentation logic for all timeline media previews.

* Fix a bug with the detection of the timeline end.

* Send pagination requests from the media preview screen.

* Add SwiftLint to the Danger workflow (it is no longer installed on the runner).

* Put SwiftLint back on all of the GitHub runners too.

* Set the function_parameter_count lint rule to 10.

* Make sure to clean-up any previews when the coordinator is done.

* Handle the viewInRoomTimeline action more appropriately.
This commit is contained in:
Doug
2025-02-05 13:27:23 +00:00
committed by GitHub
parent 921d1c627d
commit cfaa1b455a
45 changed files with 490 additions and 202 deletions
@@ -41,9 +41,16 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
previewItems = itemViewStates.compactMap(TimelineMediaPreviewItem.Media.init)
self.initialItem = initialItem
let initialItemArrayIndex = previewItems.firstIndex { $0.id == initialItem.id } ?? 0
initialItemIndex = initialItemArrayIndex + initialPadding
currentItem = .media(previewItems[initialItemArrayIndex])
if let initialItemArrayIndex = previewItems.firstIndex(where: { $0.id == initialItem.id.eventOrTransactionID }) {
initialItemIndex = initialItemArrayIndex + initialPadding
currentItem = .media(previewItems[initialItemArrayIndex])
} else {
// The timeline hasn't loaded the initial item yet, so replace the whatever was loaded with
// the item the user wants to preview.
initialItemIndex = initialPadding
previewItems = [.init(timelineItem: initialItem)]
currentItem = .media(previewItems[0])
}
backwardPadding = initialPadding
forwardPadding = initialPadding
@@ -83,10 +90,18 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
hasPaginated = true
}
} else {
// Do nothing! Not ideal but if we reload the data source the current item will
// also be, reloaded resetting any interaction the user has made with it. If we
// ignore the pagination, then the next time they swipe they'll land on a different
// When the timeline is loading items from the store and the initial item is the only
// preview in the array, we don't want to wipe it out, so if the existing items aren't
// found within the new items then let's ignore the update for now. This comes with a
// tradeoff that when a media gets redacted, no more previews will be added to the viewer.
//
// Note for the future if anyone wants to fix the redaction issue: Reloading the data source,
// will also reload the current item resetting any interaction the user has made with it.
// If you ignore the pagination, then the next time they swipe they'll land on a different
// media but this is probably less jarring overall. I hate QLPreviewController!
MXLog.info("Ignoring update: unable to find existing preview items range.")
return
}
previewItems = newItems
@@ -109,9 +124,9 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
let arrayIndex = index - backwardPadding
if index < firstPreviewItemIndex {
return paginationState.backward == .timelineEndReached ? TimelineMediaPreviewItem.Loading.timelineStart : .paginating
return paginationState.backward == .timelineEndReached ? TimelineMediaPreviewItem.Loading.timelineStart : .paginatingBackwards
} else if index > lastPreviewItemIndex {
return paginationState.forward == .timelineEndReached ? TimelineMediaPreviewItem.Loading.timelineEnd : .paginating
return paginationState.forward == .timelineEndReached ? TimelineMediaPreviewItem.Loading.timelineEnd : .paginatingForwards
} else {
return previewItems[arrayIndex]
}
@@ -151,7 +166,15 @@ enum TimelineMediaPreviewItem: Equatable {
// MARK: Identifiable
var id: TimelineItemIdentifier { timelineItem.id }
/// The timeline item's event or transaction ID.
///
/// We're identifying items by this to ensure that all matching is made using only this part of the identifier. This is
/// because the unique ID will be different across timelines so when the initial item comes from a regular timeline and
/// we build a filtered timeline to fetch the other media items, it is impossible to match by the `TimelineItemIdentifier`.
var id: TimelineItemIdentifier.EventOrTransactionID {
guard let id = timelineItem.id.eventOrTransactionID else { fatalError("Virtual items cannot be previewed.") }
return id
}
// MARK: QLPreviewItem
@@ -274,11 +297,12 @@ enum TimelineMediaPreviewItem: Equatable {
}
class Loading: NSObject, QLPreviewItem {
static let paginating = Loading(state: .paginating)
static let paginatingBackwards = Loading(state: .paginating(.backwards))
static let paginatingForwards = Loading(state: .paginating(.forwards))
static let timelineStart = Loading(state: .timelineStart)
static let timelineEnd = Loading(state: .timelineEnd)
enum State { case paginating, timelineStart, timelineEnd }
enum State { case paginating(PaginationDirection), timelineStart, timelineEnd }
let state: State
let previewItemURL: URL? = nil