mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Revert back to UIKit for the presentation of the timeline media preview. (#3719)
* Revert back to UIKit for the presentation of the timeline media preview. * Fix a presentation issue where the media is clipped until the animation finishes. * Workaround for the preview controller replacing the info button when swiping. * Use a self-sizing detent on the media info sheet.
This commit is contained in:
+5
-4
@@ -21,7 +21,7 @@ struct MediaEventsTimelineScreenCoordinatorParameters {
|
||||
}
|
||||
|
||||
enum MediaEventsTimelineScreenCoordinatorAction {
|
||||
case viewItem(TimelineMediaPreviewContext)
|
||||
case viewInRoomTimeline(TimelineItemIdentifier)
|
||||
}
|
||||
|
||||
final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -63,13 +63,14 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
||||
filesTimelineViewModel: filesTimelineViewModel,
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
userIndicatorController: parameters.userIndicatorController,
|
||||
appMediator: parameters.appMediator)
|
||||
|
||||
viewModel.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
switch action {
|
||||
case .viewItem(let previewContext):
|
||||
self?.actionsSubject.send(.viewItem(previewContext))
|
||||
case .viewInRoomTimeline(let itemID):
|
||||
self?.actionsSubject.send(.viewInRoomTimeline(itemID))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
+3
-4
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
enum MediaEventsTimelineScreenViewModelAction {
|
||||
case viewItem(TimelineMediaPreviewContext)
|
||||
case viewInRoomTimeline(TimelineItemIdentifier)
|
||||
}
|
||||
|
||||
enum MediaEventsTimelineScreenMode {
|
||||
@@ -31,17 +31,16 @@ struct MediaEventsTimelineScreenViewState: BindableState {
|
||||
var activeTimelineContextProvider: (() -> TimelineViewModel.Context)!
|
||||
|
||||
var bindings: MediaEventsTimelineScreenViewStateBindings
|
||||
|
||||
var currentPreviewItemID: TimelineItemIdentifier?
|
||||
}
|
||||
|
||||
struct MediaEventsTimelineScreenViewStateBindings {
|
||||
var screenMode: MediaEventsTimelineScreenMode
|
||||
var mediaPreviewViewModel: TimelineMediaPreviewViewModel?
|
||||
}
|
||||
|
||||
enum MediaEventsTimelineScreenViewAction {
|
||||
case changedScreenMode
|
||||
case oldestItemDidAppear
|
||||
case oldestItemDidDisappear
|
||||
case tappedItem(item: RoomTimelineItemViewState, namespace: Namespace.ID)
|
||||
case tappedItem(item: RoomTimelineItemViewState)
|
||||
}
|
||||
|
||||
+27
-11
@@ -13,7 +13,9 @@ typealias MediaEventsTimelineScreenViewModelType = StateStoreViewModel<MediaEven
|
||||
class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType, MediaEventsTimelineScreenViewModelProtocol {
|
||||
private let mediaTimelineViewModel: TimelineViewModelProtocol
|
||||
private let filesTimelineViewModel: TimelineViewModelProtocol
|
||||
private let mediaProvider: MediaProviderProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
|
||||
private var isOldestItemVisible = false
|
||||
|
||||
@@ -37,10 +39,13 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
filesTimelineViewModel: TimelineViewModelProtocol,
|
||||
initialViewState: MediaEventsTimelineScreenViewState = .init(bindings: .init(screenMode: .media)),
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
appMediator: AppMediatorProtocol) {
|
||||
self.mediaTimelineViewModel = mediaTimelineViewModel
|
||||
self.filesTimelineViewModel = filesTimelineViewModel
|
||||
self.mediaProvider = mediaProvider
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.appMediator = appMediator
|
||||
|
||||
super.init(initialViewState: initialViewState, mediaProvider: mediaProvider)
|
||||
|
||||
@@ -84,8 +89,8 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
backPaginateIfNecessary(paginationStatus: activeTimelineViewModel.context.viewState.timelineState.paginationState.backward)
|
||||
case .oldestItemDidDisappear:
|
||||
isOldestItemVisible = false
|
||||
case .tappedItem(let item, let namespace):
|
||||
handleItemTapped(item, namespace: namespace)
|
||||
case .tappedItem(let item):
|
||||
handleItemTapped(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +146,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
}
|
||||
}
|
||||
|
||||
private func handleItemTapped(_ item: RoomTimelineItemViewState, namespace: Namespace.ID) {
|
||||
private func handleItemTapped(_ item: RoomTimelineItemViewState) {
|
||||
let item: EventBasedMessageTimelineItemProtocol? = switch item.type {
|
||||
case .audio(let audioItem): audioItem
|
||||
case .file(let fileItem): fileItem
|
||||
@@ -155,14 +160,25 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
return
|
||||
}
|
||||
|
||||
actionsSubject.send(.viewItem(.init(item: item,
|
||||
viewModel: activeTimelineViewModel,
|
||||
namespace: namespace) { [weak self] itemID in
|
||||
self?.state.currentPreviewItemID = itemID
|
||||
}))
|
||||
let viewModel = TimelineMediaPreviewViewModel(initialItem: item,
|
||||
timelineViewModel: activeTimelineViewModel,
|
||||
mediaProvider: mediaProvider,
|
||||
photoLibraryManager: PhotoLibraryManager(),
|
||||
userIndicatorController: userIndicatorController,
|
||||
appMediator: appMediator)
|
||||
viewModel.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .viewInRoomTimeline(let itemID):
|
||||
state.bindings.mediaPreviewViewModel = nil
|
||||
actionsSubject.send(.viewInRoomTimeline(itemID))
|
||||
case .dismiss:
|
||||
state.bindings.mediaPreviewViewModel = nil
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Set the current item in the next run loop so that (hopefully) the presentation will be ready before we flip the thumbnail.
|
||||
Task { state.currentPreviewItemID = item.id }
|
||||
state.bindings.mediaPreviewViewModel = viewModel
|
||||
}
|
||||
|
||||
private func titleForDate(_ date: Date) -> String {
|
||||
|
||||
+6
-17
@@ -11,8 +11,6 @@ import SwiftUI
|
||||
struct MediaEventsTimelineScreen: View {
|
||||
@ObservedObject var context: MediaEventsTimelineScreenViewModel.Context
|
||||
|
||||
@Namespace private var zoomTransition
|
||||
|
||||
var body: some View {
|
||||
mainContent
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
@@ -25,6 +23,7 @@ struct MediaEventsTimelineScreen: View {
|
||||
.onChange(of: context.screenMode) { _, _ in
|
||||
context.send(viewAction: .changedScreenMode)
|
||||
}
|
||||
.timelineMediaPreview(viewModel: $context.mediaPreviewViewModel)
|
||||
}
|
||||
|
||||
// The scale effects do the following:
|
||||
@@ -65,9 +64,8 @@ struct MediaEventsTimelineScreen: View {
|
||||
tappedItem(item)
|
||||
} label: {
|
||||
viewForTimelineItem(item)
|
||||
.scaleEffect(scale(for: item, isGridLayout: true))
|
||||
.scaleEffect(CGSize(width: -1, height: -1))
|
||||
}
|
||||
.zoomTransitionSource(id: item.identifier, in: zoomTransition)
|
||||
}
|
||||
} footer: {
|
||||
// Use a footer as the header because the scrollView is flipped
|
||||
@@ -92,9 +90,8 @@ struct MediaEventsTimelineScreen: View {
|
||||
tappedItem(item)
|
||||
} label: {
|
||||
viewForTimelineItem(item)
|
||||
.scaleEffect(scale(for: item, isGridLayout: false))
|
||||
.scaleEffect(CGSize(width: 1, height: -1))
|
||||
}
|
||||
.zoomTransitionSource(id: item.identifier, in: zoomTransition)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
@@ -216,16 +213,7 @@ struct MediaEventsTimelineScreen: View {
|
||||
}
|
||||
|
||||
func tappedItem(_ item: RoomTimelineItemViewState) {
|
||||
context.send(viewAction: .tappedItem(item: item, namespace: zoomTransition))
|
||||
}
|
||||
|
||||
func scale(for item: RoomTimelineItemViewState, isGridLayout: Bool) -> CGSize {
|
||||
if item.identifier == context.viewState.currentPreviewItemID, #available(iOS 18.0, *) {
|
||||
// Remove the flip when presenting a preview so that the zoom transition is the right way up 🙃
|
||||
CGSize(width: 1, height: 1)
|
||||
} else {
|
||||
CGSize(width: isGridLayout ? -1 : 1, height: -1)
|
||||
}
|
||||
context.send(viewAction: .tappedItem(item: item))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +253,8 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
||||
filesTimelineViewModel: makeTimelineViewModel(empty: empty),
|
||||
initialViewState: .init(bindings: .init(screenMode: screenMode)),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
appMediator: AppMediatorMock())
|
||||
}
|
||||
|
||||
private static func makeTimelineViewModel(empty: Bool) -> TimelineViewModel {
|
||||
|
||||
Reference in New Issue
Block a user