mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +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:
@@ -239,6 +239,10 @@ class TimelineInteractionHandler {
|
||||
text = content.caption ?? ""
|
||||
htmlText = content.formattedCaptionHTMLString
|
||||
editType = text.isEmpty ? .addCaption : .editCaption
|
||||
case .gallery(let content):
|
||||
text = content.caption ?? ""
|
||||
htmlText = content.formattedCaptionHTMLString
|
||||
editType = text.isEmpty ? .addCaption : .editCaption
|
||||
default:
|
||||
text = messageTimelineItem.body
|
||||
}
|
||||
@@ -569,6 +573,16 @@ class TimelineInteractionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens a media preview scoped to a single gallery's attachments. The preview pages
|
||||
/// only between the items of that one gallery — siblings in the wider timeline aren't
|
||||
/// reachable from this entry point (use the regular media tap for that).
|
||||
func processGalleryItemTap(itemID: TimelineItemIdentifier, index: Int) -> TimelineControllerAction {
|
||||
guard let galleryItem = timelineController.timelineItems.firstUsingStableID(itemID) as? GalleryRoomTimelineItem else {
|
||||
return .none
|
||||
}
|
||||
return .displayGalleryPreview(galleryItem: galleryItem, initialIndex: index)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func buildReplyInfo(for item: EventBasedTimelineItemProtocol) -> ReplyInfo {
|
||||
|
||||
@@ -53,6 +53,7 @@ enum TimelineViewAction {
|
||||
case itemDisappeared(itemID: TimelineItemIdentifier)
|
||||
|
||||
case mediaTapped(itemID: TimelineItemIdentifier)
|
||||
case galleryItemTapped(itemID: TimelineItemIdentifier, index: Int)
|
||||
case itemSendInfoTapped(itemID: TimelineItemIdentifier)
|
||||
case toggleReaction(key: String, itemID: TimelineItemIdentifier)
|
||||
case sendReadReceiptIfNeeded(TimelineItemIdentifier)
|
||||
|
||||
@@ -159,6 +159,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
Task { await timelineController.processItemDisappearance(id) }
|
||||
case .mediaTapped(let id):
|
||||
Task { await handleMediaTapped(with: id) }
|
||||
case .galleryItemTapped(let id, let index):
|
||||
handleGalleryItemTapped(itemID: id, index: index)
|
||||
case .itemSendInfoTapped(let itemID):
|
||||
handleItemSendInfoTapped(itemID: itemID)
|
||||
case .toggleReaction(let emoji, let itemID):
|
||||
@@ -708,6 +710,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
|
||||
let mediaPreviewViewModel = makeMediaPreviewViewModel(item: item, timelineViewModelKind: timelineViewModelKind)
|
||||
actionsSubject.send(.displayMediaPreview(mediaPreviewViewModel))
|
||||
case .displayGalleryPreview:
|
||||
fatalError("Galleries open through the dedicated galleryItemTapped path, which carries the tapped index.")
|
||||
case .displayLocation(let location):
|
||||
actionsSubject.send(.displayLocation(location))
|
||||
case .displayLiveLocation(let sender, let initialLiveLocationShare):
|
||||
@@ -718,6 +722,18 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
state.showLoading = false
|
||||
}
|
||||
|
||||
private func handleGalleryItemTapped(itemID: TimelineItemIdentifier, index: Int) {
|
||||
if case let .displayGalleryPreview(galleryItem, initialIndex) = timelineInteractionHandler.processGalleryItemTap(itemID: itemID, index: index) {
|
||||
displayGalleryPreview(galleryItem: galleryItem, initialIndex: initialIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private func displayGalleryPreview(galleryItem: GalleryRoomTimelineItem, initialIndex: Int) {
|
||||
actionsSubject.send(.composer(action: .removeFocus))
|
||||
let mediaPreviewViewModel = makeGalleryPreviewViewModel(galleryItem: galleryItem, initialIndex: initialIndex)
|
||||
actionsSubject.send(.displayMediaPreview(mediaPreviewViewModel))
|
||||
}
|
||||
|
||||
private func handleItemSendInfoTapped(itemID: TimelineItemIdentifier) {
|
||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID) else {
|
||||
MXLog.warning("Couldn't find timeline item.")
|
||||
@@ -833,6 +849,17 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
appMediator: appMediator)
|
||||
}
|
||||
|
||||
private func makeGalleryPreviewViewModel(galleryItem: GalleryRoomTimelineItem,
|
||||
initialIndex: Int) -> TimelineMediaPreviewViewModel {
|
||||
TimelineMediaPreviewViewModel(galleryItem: galleryItem,
|
||||
initialIndex: initialIndex,
|
||||
timelineViewModel: self,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
photoLibraryManager: PhotoLibraryManager(),
|
||||
userIndicatorController: userIndicatorController,
|
||||
appMediator: appMediator)
|
||||
}
|
||||
|
||||
// MARK: - Timeline Item Building
|
||||
|
||||
private func buildTimelineViews(timelineItems: [RoomTimelineItemProtocol], isSwitchingTimelines: Bool = false) {
|
||||
|
||||
@@ -107,6 +107,7 @@ struct GalleryItemTileView: View {
|
||||
.font(.compound.headingSMSemibold)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
}
|
||||
.allowsHitTesting(false) // Let the tap fall through to the tile below.
|
||||
}
|
||||
|
||||
private func formatted(duration: TimeInterval) -> String {
|
||||
|
||||
+8
-6
@@ -27,15 +27,13 @@ struct GalleryRoomTimelineView: View {
|
||||
if usesListLayout {
|
||||
GalleryListView(items: timelineItem.content.items,
|
||||
mediaProvider: context?.mediaProvider,
|
||||
contentScannerService: context?.contentScannerService) { _ in
|
||||
// Tapping to open the full-screen preview lands in a follow-up.
|
||||
}
|
||||
contentScannerService: context?.contentScannerService,
|
||||
onItemTap: tap)
|
||||
} else {
|
||||
GalleryGridView(items: timelineItem.content.items,
|
||||
mediaProvider: context?.mediaProvider,
|
||||
contentScannerService: context?.contentScannerService) { _ in
|
||||
// Tapping to open the full-screen preview lands in a follow-up.
|
||||
}
|
||||
contentScannerService: context?.contentScannerService,
|
||||
onItemTap: tap)
|
||||
}
|
||||
|
||||
if hasMediaCaption {
|
||||
@@ -61,6 +59,10 @@ struct GalleryRoomTimelineView: View {
|
||||
boostFontSize: timelineItem.shouldBoost)
|
||||
}
|
||||
}
|
||||
|
||||
private func tap(index: Int) {
|
||||
context?.send(viewAction: .galleryItemTapped(itemID: timelineItem.id, index: index))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
Reference in New Issue
Block a user