Detect the timeline start/end when swiping through media files. (#3714)

This commit is contained in:
Doug
2025-01-29 15:07:23 +00:00
committed by GitHub
parent df997ad251
commit d412c10352
12 changed files with 402 additions and 302 deletions
@@ -35,8 +35,11 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
self.userIndicatorController = userIndicatorController
self.appMediator = appMediator
super.init(initialViewState: TimelineMediaPreviewViewState(dataSource: .init(itemViewStates: timelineViewModel.context.viewState.timelineState.itemViewStates,
initialItem: context.item),
let timelineState = timelineViewModel.context.viewState.timelineState
super.init(initialViewState: TimelineMediaPreviewViewState(dataSource: .init(itemViewStates: timelineState.itemViewStates,
initialItem: context.item,
paginationState: timelineState.paginationState),
transitionNamespace: context.namespace),
mediaProvider: mediaProvider)
@@ -57,14 +60,19 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
self?.state.dataSource.updatePreviewItems(itemViewStates: itemViewStates)
}
.store(in: &cancellables)
timelineViewModel.context.$viewState.map(\.timelineState.paginationState)
.removeDuplicates()
.weakAssign(to: \.state.dataSource.paginationState, on: self)
.store(in: &cancellables)
}
override func process(viewAction: TimelineMediaPreviewViewAction) {
switch viewAction {
case .updateCurrentItem(let item):
Task { await updateCurrentItem(item) }
case .showCurrentItemDetails:
state.bindings.mediaDetailsItem = state.currentItem
case .showItemDetails(let mediaItem):
state.bindings.mediaDetailsItem = mediaItem
case .menuAction(let action, let item):
switch action {
case .viewInRoomTimeline:
@@ -78,28 +86,32 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
}
case .redactConfirmation(let item):
redactItem(item)
case .timelineEndReached:
showTimelineEndIndicator()
case .dismiss:
actionsSubject.send(.dismiss)
}
}
private func updateCurrentItem(_ previewItem: TimelineMediaPreviewItem?) async {
previewItem?.downloadError = nil // Clear any existing error.
private func updateCurrentItem(_ previewItem: TimelineMediaPreviewItem) async {
if case let .media(item) = previewItem {
item.downloadError = nil // Clear any existing error.
}
state.dataSource.updateCurrentItem(previewItem)
rebuildCurrentItemActions()
if let previewItem {
currentItemIDHandler?(previewItem.id)
if case let .media(mediaItem) = previewItem {
currentItemIDHandler?(mediaItem.id)
if previewItem.fileHandle == nil, let source = previewItem.mediaSource {
switch await mediaProvider.loadFileFromSource(source, filename: previewItem.filename) {
if mediaItem.fileHandle == nil, let source = mediaItem.mediaSource {
switch await mediaProvider.loadFileFromSource(source, filename: mediaItem.filename) {
case .success(let handle):
previewItem.fileHandle = handle
state.fileLoadedPublisher.send(previewItem.id)
mediaItem.fileHandle = handle
state.fileLoadedPublisher.send(mediaItem.id)
case .failure(let error):
MXLog.error("Failed loading media: \(error)")
context.objectWillChange.send() // Manually trigger the SwiftUI view update.
previewItem.downloadError = error
mediaItem.downloadError = error
}
}
}
@@ -107,8 +119,9 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
private func rebuildCurrentItemActions() {
let timelineContext = timelineViewModel.context
state.currentItemActions = if let currentItem = state.currentItem {
TimelineItemMenuActionProvider(timelineItem: currentItem.timelineItem,
state.currentItemActions = switch state.currentItem {
case .media(let mediaItem):
TimelineItemMenuActionProvider(timelineItem: mediaItem.timelineItem,
canCurrentUserRedactSelf: timelineContext.viewState.canCurrentUserRedactSelf,
canCurrentUserRedactOthers: timelineContext.viewState.canCurrentUserRedactOthers,
canCurrentUserPin: timelineContext.viewState.canCurrentUserPin,
@@ -118,13 +131,13 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
timelineKind: timelineContext.viewState.timelineKind,
emojiProvider: timelineContext.viewState.emojiProvider)
.makeActions()
} else {
case .loading:
nil
}
}
private func saveCurrentItem() async {
guard let currentItem = state.currentItem, let fileURL = currentItem.fileHandle?.url else {
guard case let .media(mediaItem) = state.currentItem, let fileURL = mediaItem.fileHandle?.url else {
MXLog.error("Unable to save an item without a URL, the button shouldn't be visible.")
return
}
@@ -133,7 +146,7 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
state.bindings.mediaDetailsItem = nil
do {
switch currentItem.timelineItem {
switch mediaItem.timelineItem {
case is AudioRoomTimelineItem, is FileRoomTimelineItem:
state.bindings.fileToExport = .init(url: fileURL)
return // Don't show the indicator.
@@ -158,7 +171,7 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
}
}
private func redactItem(_ item: TimelineMediaPreviewItem) {
private func redactItem(_ item: TimelineMediaPreviewItem.Media) {
timelineViewModel.context.send(viewAction: .handleTimelineItemMenuAction(itemID: item.id, action: .redact))
state.bindings.redactConfirmationItem = nil
state.bindings.mediaDetailsItem = nil
@@ -189,5 +202,11 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
iconName: "xmark"))
}
private func showTimelineEndIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: statusIndicatorID,
type: .toast,
title: L10n.screenMediaDetailsNoMoreMediaToShow))
}
private var statusIndicatorID: String { "\(Self.self)-Status" }
}