mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Add a fullscreen button to TimelineMediaPreviewScreen and hook up swiping through the timeline. (#3638)
* Add a fullscreen button to media previews - Not ideal but the gestures conflict with the preview controller. * Don't un-flip the preview thumbnail until the preview has disappeared, and only do it on iOS 18. * Add all of the loaded items for previewing in the preview controller.
This commit is contained in:
parent
435dfb8e46
commit
e7cc807084
@ -126,8 +126,6 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
navigationStackCoordinator.setFullScreenCoverCoordinator(coordinator) {
|
navigationStackCoordinator.setFullScreenCoverCoordinator(coordinator)
|
||||||
previewContext.completion?()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,12 @@ struct TimelineMediaPreviewContext {
|
|||||||
let viewModel: TimelineViewModelProtocol
|
let viewModel: TimelineViewModelProtocol
|
||||||
/// The namespace that the navigation transition's `sourceID` should be defined in.
|
/// The namespace that the navigation transition's `sourceID` should be defined in.
|
||||||
let namespace: Namespace.ID
|
let namespace: Namespace.ID
|
||||||
/// A completion to be called immediately *after* the preview has been dismissed.
|
/// A closure to be called whenever a different preview item is shown. It should also
|
||||||
|
/// be called *after* the preview has been dismissed, with an ID of `nil`.
|
||||||
///
|
///
|
||||||
/// This helps work around a bug caused by the flipped scrollview where the zoomed
|
/// This helps work around a bug caused by the flipped scrollview where the zoomed
|
||||||
/// thumbnail starts off upside down while loading the preview screen.
|
/// thumbnail starts off upside down while loading the preview screen.
|
||||||
var completion: (() -> Void)?
|
var itemIDHandler: ((TimelineItemIdentifier?) -> Void)?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineMediaPreviewCoordinatorParameters {
|
struct TimelineMediaPreviewCoordinatorParameters {
|
||||||
@ -72,6 +73,9 @@ final class TimelineMediaPreviewCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(TimelineMediaPreviewScreen(context: viewModel.context))
|
// Calling the completion onDisappear isn't ideal, but we don't push away from the screen so it should be
|
||||||
|
// a good enough approximation of didDismiss, given that the only other option is our navigation callbacks
|
||||||
|
// which are essentially willDismiss callbacks and happen too early for this particular completion handler.
|
||||||
|
AnyView(TimelineMediaPreviewScreen(context: viewModel.context, itemIDHandler: parameters.context.itemIDHandler))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,19 @@ enum TimelineMediaPreviewViewModelAction: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineMediaPreviewViewState: BindableState {
|
struct TimelineMediaPreviewViewState: BindableState {
|
||||||
|
/// All of the items in the timeline that can be previewed.
|
||||||
var previewItems: [TimelineMediaPreviewItem]
|
var previewItems: [TimelineMediaPreviewItem]
|
||||||
|
/// The index of the initial item inside of `previewItems` that is to be shown.
|
||||||
|
let initialItemIndex: Int
|
||||||
|
|
||||||
|
/// The media item that is currently being previewed.
|
||||||
var currentItem: TimelineMediaPreviewItem
|
var currentItem: TimelineMediaPreviewItem
|
||||||
|
/// All of the available actions for the current item.
|
||||||
var currentItemActions: TimelineItemMenuActions?
|
var currentItemActions: TimelineItemMenuActions?
|
||||||
|
|
||||||
|
/// The namespace used for the zoom transition.
|
||||||
let transitionNamespace: Namespace.ID
|
let transitionNamespace: Namespace.ID
|
||||||
|
/// A publisher that the view model uses to signal to the QLPreviewController when the current item has been loaded.
|
||||||
let fileLoadedPublisher = PassthroughSubject<TimelineItemIdentifier, Never>()
|
let fileLoadedPublisher = PassthroughSubject<TimelineItemIdentifier, Never>()
|
||||||
|
|
||||||
var bindings = TimelineMediaPreviewViewStateBindings()
|
var bindings = TimelineMediaPreviewViewStateBindings()
|
||||||
@ -49,6 +57,21 @@ class TimelineMediaPreviewItem: NSObject, QLPreviewItem, Identifiable {
|
|||||||
self.timelineItem = timelineItem
|
self.timelineItem = timelineItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init?(roomTimelineItemViewState: RoomTimelineItemViewState) {
|
||||||
|
switch roomTimelineItemViewState.type {
|
||||||
|
case .audio(let audioRoomTimelineItem):
|
||||||
|
timelineItem = audioRoomTimelineItem
|
||||||
|
case .file(let fileRoomTimelineItem):
|
||||||
|
timelineItem = fileRoomTimelineItem
|
||||||
|
case .image(let imageRoomTimelineItem):
|
||||||
|
timelineItem = imageRoomTimelineItem
|
||||||
|
case .video(let videoRoomTimelineItem):
|
||||||
|
timelineItem = videoRoomTimelineItem
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Identifiable
|
// MARK: Identifiable
|
||||||
|
|
||||||
var id: TimelineItemIdentifier { timelineItem.id }
|
var id: TimelineItemIdentifier { timelineItem.id }
|
||||||
|
@ -12,6 +12,7 @@ typealias TimelineMediaPreviewViewModelType = StateStoreViewModel<TimelineMediaP
|
|||||||
|
|
||||||
class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
|
class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
|
||||||
private let timelineViewModel: TimelineViewModelProtocol
|
private let timelineViewModel: TimelineViewModelProtocol
|
||||||
|
private let currentItemIDHandler: ((TimelineItemIdentifier?) -> Void)?
|
||||||
private let mediaProvider: MediaProviderProtocol
|
private let mediaProvider: MediaProviderProtocol
|
||||||
private let photoLibraryManager: PhotoLibraryManagerProtocol
|
private let photoLibraryManager: PhotoLibraryManagerProtocol
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
@ -28,14 +29,18 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
|
|||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
appMediator: AppMediatorProtocol) {
|
appMediator: AppMediatorProtocol) {
|
||||||
timelineViewModel = context.viewModel
|
timelineViewModel = context.viewModel
|
||||||
|
currentItemIDHandler = context.itemIDHandler
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
self.photoLibraryManager = photoLibraryManager
|
self.photoLibraryManager = photoLibraryManager
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.appMediator = appMediator
|
self.appMediator = appMediator
|
||||||
|
|
||||||
let currentItem = TimelineMediaPreviewItem(timelineItem: context.item)
|
let previewItems = timelineViewModel.context.viewState.timelineState.itemViewStates.compactMap(TimelineMediaPreviewItem.init)
|
||||||
|
let initialItemIndex = previewItems.firstIndex { $0.id == context.item.id } ?? 0
|
||||||
|
let currentItem = previewItems[initialItemIndex]
|
||||||
|
|
||||||
super.init(initialViewState: TimelineMediaPreviewViewState(previewItems: [currentItem],
|
super.init(initialViewState: TimelineMediaPreviewViewState(previewItems: previewItems,
|
||||||
|
initialItemIndex: initialItemIndex,
|
||||||
currentItem: currentItem,
|
currentItem: currentItem,
|
||||||
transitionNamespace: context.namespace),
|
transitionNamespace: context.namespace),
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
@ -76,6 +81,7 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
|
|||||||
|
|
||||||
private func updateCurrentItem(_ previewItem: TimelineMediaPreviewItem) async {
|
private func updateCurrentItem(_ previewItem: TimelineMediaPreviewItem) async {
|
||||||
state.currentItem = previewItem
|
state.currentItem = previewItem
|
||||||
|
currentItemIDHandler?(previewItem.id)
|
||||||
rebuildCurrentItemActions()
|
rebuildCurrentItemActions()
|
||||||
|
|
||||||
if previewItem.fileHandle == nil, let source = previewItem.mediaSource {
|
if previewItem.fileHandle == nil, let source = previewItem.mediaSource {
|
||||||
|
@ -180,8 +180,11 @@ struct TimelineMediaPreviewDetailsView_Previews: PreviewProvider, TestablePrevie
|
|||||||
contentType: contentType))
|
contentType: contentType))
|
||||||
|
|
||||||
let timelineKind = TimelineKind.media(isPresentedOnRoomScreen ? .roomScreen : .mediaFilesScreen)
|
let timelineKind = TimelineKind.media(isPresentedOnRoomScreen ? .roomScreen : .mediaFilesScreen)
|
||||||
|
let timelineController = MockRoomTimelineController(timelineKind: timelineKind)
|
||||||
|
timelineController.timelineItems = [item]
|
||||||
return TimelineMediaPreviewViewModel(context: .init(item: item,
|
return TimelineMediaPreviewViewModel(context: .init(item: item,
|
||||||
viewModel: TimelineViewModel.mock(timelineKind: timelineKind),
|
viewModel: TimelineViewModel.mock(timelineKind: timelineKind,
|
||||||
|
timelineController: timelineController),
|
||||||
namespace: previewNamespace),
|
namespace: previewNamespace),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
photoLibraryManager: PhotoLibraryManagerMock(.init()),
|
photoLibraryManager: PhotoLibraryManagerMock(.init()),
|
||||||
|
@ -138,8 +138,11 @@ struct TimelineMediaPreviewRedactConfirmationView_Previews: PreviewProvider, Tes
|
|||||||
thumbnailInfo: .mockThumbnail,
|
thumbnailInfo: .mockThumbnail,
|
||||||
contentType: contentType))
|
contentType: contentType))
|
||||||
|
|
||||||
|
let timelineController = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen))
|
||||||
|
timelineController.timelineItems = [item]
|
||||||
return TimelineMediaPreviewViewModel(context: .init(item: item,
|
return TimelineMediaPreviewViewModel(context: .init(item: item,
|
||||||
viewModel: TimelineViewModel.mock,
|
viewModel: TimelineViewModel.mock(timelineKind: timelineController.timelineKind,
|
||||||
|
timelineController: timelineController),
|
||||||
namespace: previewNamespace),
|
namespace: previewNamespace),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
photoLibraryManager: PhotoLibraryManagerMock(.init()),
|
photoLibraryManager: PhotoLibraryManagerMock(.init()),
|
||||||
|
@ -12,18 +12,16 @@ import SwiftUI
|
|||||||
|
|
||||||
struct TimelineMediaPreviewScreen: View {
|
struct TimelineMediaPreviewScreen: View {
|
||||||
@ObservedObject var context: TimelineMediaPreviewViewModel.Context
|
@ObservedObject var context: TimelineMediaPreviewViewModel.Context
|
||||||
|
var itemIDHandler: ((TimelineItemIdentifier?) -> Void)?
|
||||||
|
|
||||||
|
@State private var isFullScreen = false
|
||||||
|
private var toolbarVisibility: Visibility { isFullScreen ? .hidden : .visible }
|
||||||
|
|
||||||
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem }
|
private var currentItem: TimelineMediaPreviewItem { context.viewState.currentItem }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Color.clear
|
quickLookPreview
|
||||||
.overlay { QuickLookView(viewModelContext: context).ignoresSafeArea() } // Overlay to stop QL hijacking the toolbar.
|
|
||||||
.toolbar { toolbar }
|
|
||||||
.toolbarBackground(.visible, for: .navigationBar) // The toolbar's scrollEdgeAppearance isn't aware of the quicklook view 🤷♂️
|
|
||||||
.toolbarBackground(.visible, for: .bottomBar)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.safeAreaInset(edge: .bottom, spacing: 0) { caption }
|
|
||||||
}
|
}
|
||||||
.introspect(.navigationStack, on: .supportedVersions) {
|
.introspect(.navigationStack, on: .supportedVersions) {
|
||||||
// Fixes a bug where the QuickLook view overrides the .toolbarBackground(.visible) after it loads the real item.
|
// Fixes a bug where the QuickLook view overrides the .toolbarBackground(.visible) after it loads the real item.
|
||||||
@ -39,12 +37,41 @@ struct TimelineMediaPreviewScreen: View {
|
|||||||
}
|
}
|
||||||
.alert(item: $context.alertInfo)
|
.alert(item: $context.alertInfo)
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
|
.onDisappear {
|
||||||
|
itemIDHandler?(nil)
|
||||||
|
}
|
||||||
.zoomTransition(sourceID: currentItem.id, in: context.viewState.transitionNamespace)
|
.zoomTransition(sourceID: currentItem.id, in: context.viewState.transitionNamespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var quickLookPreview: some View {
|
||||||
|
Color.clear // A completely clear view breaks any SwiftUI gestures (such as drag to dismiss).
|
||||||
|
.background { QuickLookView(viewModelContext: context).ignoresSafeArea() } // Not the root view to stop QL hijacking the toolbar.
|
||||||
|
.overlay(alignment: .topTrailing) { fullScreenButton }
|
||||||
|
.toolbar { toolbar }
|
||||||
|
.toolbar(toolbarVisibility, for: .navigationBar)
|
||||||
|
.toolbar(toolbarVisibility, for: .bottomBar)
|
||||||
|
.toolbarBackground(.visible, for: .navigationBar) // The toolbar's scrollEdgeAppearance isn't aware of the quicklook view 🤷♂️
|
||||||
|
.toolbarBackground(.visible, for: .bottomBar)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.safeAreaInset(edge: .bottom, spacing: 0) { caption }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var fullScreenButton: some View {
|
||||||
|
Button {
|
||||||
|
withAnimation { isFullScreen.toggle() }
|
||||||
|
} label: {
|
||||||
|
CompoundIcon(isFullScreen ? \.collapse : \.expand, size: .xSmall, relativeTo: .compound.bodyLG)
|
||||||
|
.padding(6)
|
||||||
|
.background(.thinMaterial, in: Circle())
|
||||||
|
}
|
||||||
|
.tint(.compound.textActionPrimary)
|
||||||
|
.padding(.top, 12)
|
||||||
|
.padding(.trailing, 14)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var caption: some View {
|
private var caption: some View {
|
||||||
if let caption = currentItem.caption {
|
if let caption = currentItem.caption, !isFullScreen {
|
||||||
Text(caption)
|
Text(caption)
|
||||||
.font(.compound.bodyLG)
|
.font(.compound.bodyLG)
|
||||||
.foregroundStyle(.compound.textPrimary)
|
.foregroundStyle(.compound.textPrimary)
|
||||||
@ -55,6 +82,7 @@ struct TimelineMediaPreviewScreen: View {
|
|||||||
.background {
|
.background {
|
||||||
BlurEffectView(style: .systemChromeMaterial) // Darkest material available, matches the bottom bar when content is beneath.
|
BlurEffectView(style: .systemChromeMaterial) // Darkest material available, matches the bottom bar when content is beneath.
|
||||||
}
|
}
|
||||||
|
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,12 +142,16 @@ struct TimelineMediaPreviewScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - QuickLook
|
||||||
|
|
||||||
private struct QuickLookView: UIViewControllerRepresentable {
|
private struct QuickLookView: UIViewControllerRepresentable {
|
||||||
let viewModelContext: TimelineMediaPreviewViewModel.Context
|
let viewModelContext: TimelineMediaPreviewViewModel.Context
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> PreviewController {
|
func makeUIViewController(context: Context) -> PreviewController {
|
||||||
PreviewController(coordinator: context.coordinator,
|
let fileLoadedPublisher = viewModelContext.viewState.fileLoadedPublisher.eraseToAnyPublisher()
|
||||||
fileLoadedPublisher: viewModelContext.viewState.fileLoadedPublisher.eraseToAnyPublisher())
|
let controller = PreviewController(coordinator: context.coordinator, fileLoadedPublisher: fileLoadedPublisher)
|
||||||
|
controller.currentPreviewItemIndex = viewModelContext.viewState.initialItemIndex
|
||||||
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: PreviewController, context: Context) { }
|
func updateUIViewController(_ uiViewController: PreviewController, context: Context) { }
|
||||||
@ -128,6 +160,8 @@ private struct QuickLookView: UIViewControllerRepresentable {
|
|||||||
Coordinator(viewModelContext: viewModelContext)
|
Coordinator(viewModelContext: viewModelContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Coordinator
|
||||||
|
|
||||||
class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate {
|
class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate {
|
||||||
private let viewModelContext: TimelineMediaPreviewViewModel.Context
|
private let viewModelContext: TimelineMediaPreviewViewModel.Context
|
||||||
|
|
||||||
@ -148,14 +182,12 @@ private struct QuickLookView: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: UIKit
|
||||||
|
|
||||||
class PreviewController: QLPreviewController {
|
class PreviewController: QLPreviewController {
|
||||||
let coordinator: Coordinator
|
|
||||||
|
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
init(coordinator: Coordinator, fileLoadedPublisher: AnyPublisher<TimelineItemIdentifier, Never>) {
|
init(coordinator: Coordinator, fileLoadedPublisher: AnyPublisher<TimelineItemIdentifier, Never>) {
|
||||||
self.coordinator = coordinator
|
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
dataSource = coordinator
|
dataSource = coordinator
|
||||||
@ -208,8 +240,12 @@ struct TimelineMediaPreviewScreen_Previews: PreviewProvider {
|
|||||||
thumbnailSource: nil,
|
thumbnailSource: nil,
|
||||||
contentType: .pdf))
|
contentType: .pdf))
|
||||||
|
|
||||||
|
let timelineController = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen))
|
||||||
|
timelineController.timelineItems = [item]
|
||||||
|
|
||||||
return TimelineMediaPreviewViewModel(context: .init(item: item,
|
return TimelineMediaPreviewViewModel(context: .init(item: item,
|
||||||
viewModel: TimelineViewModel.mock(timelineKind: .media(.mediaFilesScreen)),
|
viewModel: TimelineViewModel.mock(timelineKind: timelineController.timelineKind,
|
||||||
|
timelineController: timelineController),
|
||||||
namespace: namespace),
|
namespace: namespace),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
photoLibraryManager: PhotoLibraryManagerMock(.init()),
|
photoLibraryManager: PhotoLibraryManagerMock(.init()),
|
||||||
|
@ -157,8 +157,8 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
|||||||
|
|
||||||
actionsSubject.send(.viewItem(.init(item: item,
|
actionsSubject.send(.viewItem(.init(item: item,
|
||||||
viewModel: activeTimelineViewModel,
|
viewModel: activeTimelineViewModel,
|
||||||
namespace: namespace) { [weak self] in
|
namespace: namespace) { [weak self] itemID in
|
||||||
self?.state.currentPreviewItemID = nil
|
self?.state.currentPreviewItemID = itemID
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Set the current item in the next run loop so that (hopefully) the presentation will be ready before we flip the thumbnail.
|
// Set the current item in the next run loop so that (hopefully) the presentation will be ready before we flip the thumbnail.
|
||||||
|
@ -211,12 +211,12 @@ struct MediaEventsTimelineScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scale(for item: RoomTimelineItemViewState, isGridLayout: Bool) -> CGSize {
|
func scale(for item: RoomTimelineItemViewState, isGridLayout: Bool) -> CGSize {
|
||||||
guard item.identifier != context.viewState.currentPreviewItemID else {
|
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 🙃
|
// Remove the flip when presenting a preview so that the zoom transition is the right way up 🙃
|
||||||
return CGSize(width: 1, height: 1)
|
CGSize(width: 1, height: 1)
|
||||||
|
} else {
|
||||||
|
CGSize(width: isGridLayout ? -1 : 1, height: -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CGSize(width: isGridLayout ? -1 : 1, height: -1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,6 @@ class TimelineMediaPreviewViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(viewModel.state.currentItem.contentType, "JPEG image")
|
XCTAssertEqual(viewModel.state.currentItem.contentType, "JPEG image")
|
||||||
|
|
||||||
// When choosing to save the image.
|
// When choosing to save the image.
|
||||||
let item = context.viewState.currentItem
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||||
context.send(viewAction: .saveCurrentItem)
|
context.send(viewAction: .saveCurrentItem)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
@ -164,7 +163,7 @@ class TimelineMediaPreviewViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
private func loadInitialItem() async throws {
|
private func loadInitialItem() async throws {
|
||||||
let deferred = deferFulfillment(viewModel.state.fileLoadedPublisher) { _ in true }
|
let deferred = deferFulfillment(viewModel.state.fileLoadedPublisher) { _ in true }
|
||||||
context.send(viewAction: .updateCurrentItem(context.viewState.previewItems[0]))
|
context.send(viewAction: .updateCurrentItem(context.viewState.previewItems[context.viewState.initialItemIndex]))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user