Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework the presentation of the media browser quick look view to use SwiftUI. #3619

Merged
merged 6 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
coordinator.actions
.sink { [weak self] action in
switch action {
case .viewInRoomTimeline(let itemID):
self?.actionsSubject.send(.viewInRoomTimeline(itemID))
case .viewItem(let previewContext):
self?.presentMediaPreview(for: previewContext)
}
}
.store(in: &cancellables)
Expand All @@ -105,4 +105,26 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
self?.actionsSubject.send(.finished)
}
}

private func presentMediaPreview(for previewContext: TimelineMediaPreviewContext) {
let parameters = TimelineMediaPreviewCoordinatorParameters(context: previewContext,
mediaProvider: userSession.mediaProvider,
userIndicatorController: userIndicatorController)

let coordinator = TimelineMediaPreviewCoordinator(parameters: parameters)
coordinator.actionsPublisher
.sink { [weak self] action in
switch action {
case .viewInRoomTimeline(let itemID):
self?.navigationStackCoordinator.pop(animated: false)
self?.actionsSubject.send(.viewInRoomTimeline(itemID))
self?.navigationStackCoordinator.setFullScreenCoverCoordinator(nil)
case .dismiss:
self?.navigationStackCoordinator.setFullScreenCoverCoordinator(nil)
}
}
.store(in: &cancellables)

navigationStackCoordinator.setFullScreenCoverCoordinator(coordinator)
}
}
4 changes: 2 additions & 2 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1581,8 +1581,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
MXLog.error("Unable to present room timeline for event \(itemID)")
return
}
stateMachine.tryEvent(.dismissMediaEventsTimeline)
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: eventID, shouldSetPin: false))))
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: eventID, shouldSetPin: false))),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't we pass the animation directly in presentRoom as a variable?

userInfo: EventUserInfo(animated: false)) // No animation so the timeline visible when the preview animates away.
case .finished:
stateMachine.tryEvent(.dismissMediaEventsTimeline)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@ import SwiftUIIntrospect

extension PlatformViewVersionPredicate<TextFieldType, UITextField> {
static var supportedVersions: Self {
.iOS(.v16, .v17, .v18)
.iOS(.v17, .v18)
}
}

extension PlatformViewVersionPredicate<ScrollViewType, UIScrollView> {
static var supportedVersions: Self {
.iOS(.v16, .v17, .v18)
.iOS(.v17, .v18)
}
}

extension PlatformViewVersionPredicate<ViewControllerType, UIViewController> {
static var supportedVersions: Self {
.iOS(.v16, .v17, .v18)
.iOS(.v17, .v18)
}
}

extension PlatformViewVersionPredicate<NavigationStackType, UINavigationController> {
static var supportedVersions: Self {
.iOS(.v17, .v18)
}
}
30 changes: 30 additions & 0 deletions ElementX/Sources/Other/SwiftUI/Animation/ZoomTransition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import SwiftUI

extension View {
/// A convenience modifier to conditionally apply `.navigationTransition(.zoom(…))` when available.
@ViewBuilder
func zoomTransition(sourceID: some Hashable, in namespace: Namespace.ID) -> some View {
if #available(iOS 18.0, *) {
navigationTransition(.zoom(sourceID: sourceID, in: namespace))
} else {
self
}
}

/// A convenience modifier to conditionally apply `.matchedTransitionSource(…)` when available.
@ViewBuilder
func zoomTransitionSource(id: some Hashable, in namespace: Namespace.ID) -> some View {
if #available(iOS 18.0, *) {
matchedTransitionSource(id: id, in: namespace)
} else {
self
}
}
}
22 changes: 22 additions & 0 deletions ElementX/Sources/Other/SwiftUI/Views/BlurEffectView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import SwiftUI

/// A view that renders a `UIBlurEffect` as there is a larger range of
/// effects available compared to using SwiftUI's `Material` type.
struct BlurEffectView: UIViewRepresentable {
var style: UIBlurEffect.Style

func makeUIView(context: Context) -> UIVisualEffectView {
UIVisualEffectView(effect: UIBlurEffect(style: style))
}

func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
uiView.effect = UIBlurEffect(style: style)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ struct CallScreen: View {
Image(systemSymbol: .chevronBackward)
.fontWeight(.semibold)
}
// .padding(.leading, -8) // Fixes the button alignment, but harder to tap.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Foundation

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//

import Combine
import SwiftUI

struct TimelineMediaPreviewContext {
/// The initial item to preview from the provided timeline.
/// This item's `id` will be used as the navigation transition's `sourceID`.
let item: EventBasedMessageTimelineItemProtocol
/// The timeline that the preview comes from, to allow for swiping to other media.
let viewModel: TimelineViewModelProtocol
/// The namespace that the navigation transition's `sourceID` should be defined in.
let namespace: Namespace.ID
}

struct TimelineMediaPreviewCoordinatorParameters {
let context: TimelineMediaPreviewContext
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}

enum TimelineMediaPreviewCoordinatorAction {
case viewInRoomTimeline(TimelineItemIdentifier)
case dismiss
}

final class TimelineMediaPreviewCoordinator: CoordinatorProtocol {
private let parameters: TimelineMediaPreviewCoordinatorParameters
private let viewModel: TimelineMediaPreviewViewModel

private var cancellables = Set<AnyCancellable>()

private let actionsSubject: PassthroughSubject<TimelineMediaPreviewCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<TimelineMediaPreviewCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}

init(parameters: TimelineMediaPreviewCoordinatorParameters) {
self.parameters = parameters

viewModel = TimelineMediaPreviewViewModel(context: parameters.context,
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController)
}

func start() {
viewModel.actions.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")

guard let self else { return }
switch action {
case .viewInRoomTimeline(let itemID):
actionsSubject.send(.viewInRoomTimeline(itemID))
case .dismiss:
actionsSubject.send(.dismiss)
}
}
.store(in: &cancellables)
}

func toPresentable() -> AnyView {
AnyView(TimelineMediaPreviewView(context: viewModel.context))
}
}
Loading
Loading