Skip to content

Commit

Permalink
Hook up the actions in the media details sheet. (#3607)
Browse files Browse the repository at this point in the history
  • Loading branch information
pixlwave authored Dec 12, 2024
1 parent 114255c commit 88b5426
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Combine
import Foundation

enum MediaEventsTimelineFlowCoordinatorAction {
case viewInRoomTimeline(TimelineItemIdentifier)
case finished
}

Expand Down Expand Up @@ -91,6 +92,15 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {

let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)

coordinator.actions
.sink { [weak self] action in
switch action {
case .viewInRoomTimeline(let itemID):
self?.actionsSubject.send(.viewInRoomTimeline(itemID))
}
}
.store(in: &cancellables)

navigationStackCoordinator.push(coordinator) { [weak self] in
self?.actionsSubject.send(.finished)
}
Expand Down
7 changes: 7 additions & 0 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,13 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }

switch action {
case .viewInRoomTimeline(let itemID):
guard let eventID = itemID.eventID else {
MXLog.error("Unable to present room timeline for event \(itemID)")
return
}
stateMachine.tryEvent(.dismissMediaEventsTimeline)
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: eventID, shouldSetPin: false))))
case .finished:
stateMachine.tryEvent(.dismissMediaEventsTimeline)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ class TimelineMediaPreviewController: QLPreviewController, QLPreviewControllerDa
switch action {
case .loadedMediaFile:
self?.refreshCurrentPreviewItem()
case .viewInTimeline:
case .viewInRoomTimeline, .dismiss:
self?.dismiss(animated: true) // Dismiss the details sheet.
// Errrr, hmmmmm, do something else here.
// And let the view model handle the rest.
}
}
.store(in: &cancellables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import QuickLook

enum TimelineMediaPreviewViewModelAction {
enum TimelineMediaPreviewViewModelAction: Equatable {
case loadedMediaFile
case viewInTimeline
case viewInRoomTimeline(TimelineItemIdentifier)
case dismiss
}

struct TimelineMediaPreviewViewState: BindableState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
case .menuAction(let action):
switch action {
case .viewInRoomTimeline:
actionsSubject.send(.viewInTimeline)
actionsSubject.send(.viewInRoomTimeline(state.currentItem.id))
case .redact:
state.bindings.isPresentingRedactConfirmation = true
default:
MXLog.error("Received unexpected action: \(action)")
}
case .redactConfirmation:
break // Do it here??
timelineViewModel.context.send(viewAction: .handleTimelineItemMenuAction(itemID: state.currentItem.id, action: .redact))
state.bindings.isPresentingRedactConfirmation = false
actionsSubject.send(.dismiss) // Will dismiss the details sheet and the QuickLook view.
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ struct MediaEventsTimelineScreenCoordinatorParameters {
let userIndicatorController: UserIndicatorControllerProtocol
}

enum MediaEventsTimelineScreenCoordinatorAction { }
enum MediaEventsTimelineScreenCoordinatorAction {
case viewInRoomTimeline(TimelineItemIdentifier)
}

final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
private let parameters: MediaEventsTimelineScreenCoordinatorParameters
Expand Down Expand Up @@ -62,6 +64,15 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
filesTimelineViewModel: filesTimelineViewModel,
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController)

viewModel.actionsPublisher
.sink { [weak self] action in
switch action {
case .viewInRoomTimeline(let itemID):
self?.actionsSubject.send(.viewInRoomTimeline(itemID))
}
}
.store(in: &cancellables)
}

func toPresentable() -> AnyView {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

import Foundation

enum MediaEventsTimelineScreenViewModelAction { }
enum MediaEventsTimelineScreenViewModelAction {
case viewInRoomTimeline(TimelineItemIdentifier)
}

enum MediaEventsTimelineScreenMode {
case media
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
}
}

private var mediaPreviewCancellable: AnyCancellable?

private let actionsSubject: PassthroughSubject<MediaEventsTimelineScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<MediaEventsTimelineScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
Expand Down Expand Up @@ -154,6 +156,21 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
timelineViewModel: activeTimelineViewModel,
mediaProvider: mediaProvider,
userIndicatorController: userIndicatorController)

mediaPreviewCancellable = 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
case .loadedMediaFile:
break // Handled by the preview controller
}
}

state.bindings.mediaPreviewViewModel = viewModel
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ struct MediaEventsTimelineScreen: View {
ProgressView()
.padding()
.opacity(context.viewState.isBackPaginating ? 1 : 0)
.scaleEffect(.init(width: 1, height: -1)) // Make sure it spins the right way around 🙃

Rectangle()
.frame(height: 1)
Expand Down
4 changes: 2 additions & 2 deletions ElementX/Sources/Screens/Timeline/TimelineViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -870,10 +870,10 @@ private extension RoomInfoProxy {
extension TimelineViewModel {
static let mock = mock(timelineKind: .live)

static func mock(timelineKind: TimelineKind = .live) -> TimelineViewModel {
static func mock(timelineKind: TimelineKind = .live, timelineController: MockRoomTimelineController? = nil) -> TimelineViewModel {
TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
focussedEventID: nil,
timelineController: MockRoomTimelineController(timelineKind: timelineKind),
timelineController: timelineController ?? MockRoomTimelineController(timelineKind: timelineKind),
mediaProvider: MediaProviderMock(configuration: .init()),
mediaPlayerProvider: MediaPlayerProviderMock(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {

func removeCaption(_ eventOrTransactionID: EventOrTransactionId) async { }

func redact(_ eventOrTransactionID: EventOrTransactionId) async { }
private(set) var redactCalled = false
func redact(_ eventOrTransactionID: EventOrTransactionId) async {
redactCalled = true
}

func pin(eventID: String) async { }

Expand Down
46 changes: 44 additions & 2 deletions UnitTests/Sources/TimelineMediaPreviewViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,57 @@ class TimelineMediaPreviewViewModelTests: XCTestCase {
var viewModel: TimelineMediaPreviewViewModel!
var context: TimelineMediaPreviewViewModel.Context { viewModel.context }
var mediaProvider: MediaProviderMock!
var timelineController: MockRoomTimelineController!

func testLoadingItem() async throws {
func testLoadingItem() async {
// Given a fresh view model.
setupViewModel()
XCTAssertFalse(mediaProvider.loadFileFromSourceFilenameCalled)
XCTAssertEqual(context.viewState.currentItem, context.viewState.previewItems[0])
XCTAssertNotNil(context.viewState.currentItemActions)

// When the preview controller sets the current item.
await viewModel.updateCurrentItem(context.viewState.previewItems[0])

// Then the view model should load the item and update its view state.
XCTAssertTrue(mediaProvider.loadFileFromSourceFilenameCalled)
XCTAssertEqual(context.viewState.currentItem, context.viewState.previewItems[0])
XCTAssertNotNil(context.viewState.currentItemActions)
}

func testViewInRoomTimeline() async throws {
// Given a view model with a loaded item.
await testLoadingItem()

// When choosing to view the current item in the timeline.
let currentItemID = context.viewState.currentItem.id
let deferred = deferFulfillment(viewModel.actions) { $0 == .viewInRoomTimeline(currentItemID) }
context.send(viewAction: .menuAction(.viewInRoomTimeline))

// Then the action should be sent upwards to make this happen.
try await deferred.fulfill()
}

func testRedactConfirmation() async throws {
// Given a view model with a loaded item.
await testLoadingItem()
XCTAssertFalse(context.isPresentingRedactConfirmation)
XCTAssertFalse(timelineController.redactCalled)

// When choosing to redact the current item.
context.send(viewAction: .menuAction(.redact))

// Then the confirmation sheet should be presented.
XCTAssertTrue(context.isPresentingRedactConfirmation)
XCTAssertFalse(timelineController.redactCalled)

// When confirming the redaction.
let deferred = deferFulfillment(viewModel.actions) { $0 == .dismiss }
context.send(viewAction: .redactConfirmation)

// Then the item should be redacted and the view should be dismissed.
try await deferred.fulfill()
XCTAssertTrue(timelineController.redactCalled)
}

// MARK: - Helpers
Expand All @@ -46,9 +84,13 @@ class TimelineMediaPreviewViewModelTests: XCTestCase {
imageInfo: .mockImage,
thumbnailInfo: .mockThumbnail))

timelineController = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen))
timelineController.timelineItems = [item]

mediaProvider = MediaProviderMock(configuration: .init())
viewModel = TimelineMediaPreviewViewModel(initialItem: item,
timelineViewModel: TimelineViewModel.mock,
timelineViewModel: TimelineViewModel.mock(timelineKind: .media(.mediaFilesScreen),
timelineController: timelineController),
mediaProvider: mediaProvider,
userIndicatorController: UserIndicatorControllerMock())
}
Expand Down

0 comments on commit 88b5426

Please sign in to comment.