mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Add mechanism for opening up a threaded timeline
This commit is contained in:
committed by
Stefan Ceriu
parent
2f938d3cbf
commit
10fb2b5dda
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct ThreadTimelineScreenCoordinatorParameters {
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let timelineController: TimelineControllerProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let mediaPlayerProvider: MediaPlayerProviderProtocol
|
||||
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||
let clientProxy: ClientProxyProtocol
|
||||
}
|
||||
|
||||
enum ThreadTimelineScreenCoordinatorAction {
|
||||
case dismiss
|
||||
case displayUser(userID: String)
|
||||
case presentLocationViewer(geoURI: GeoURI, description: String?)
|
||||
case displayMessageForwarding(forwardingItem: MessageForwardingItem)
|
||||
case displayRoomScreenWithFocussedPin(eventID: String)
|
||||
}
|
||||
|
||||
final class ThreadTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: ThreadTimelineScreenCoordinatorParameters
|
||||
private let viewModel: ThreadTimelineScreenViewModelProtocol
|
||||
private let timelineViewModel: TimelineViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ThreadTimelineScreenCoordinatorAction, Never> = .init()
|
||||
var actions: AnyPublisher<ThreadTimelineScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: ThreadTimelineScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = ThreadTimelineScreenViewModel()
|
||||
timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||
timelineController: parameters.timelineController,
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
||||
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: parameters.appMediator,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: parameters.emojiProvider,
|
||||
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||
clientProxy: parameters.clientProxy)
|
||||
}
|
||||
|
||||
func start() {
|
||||
timelineViewModel.actions.sink { [weak self] action in
|
||||
MXLog.info("Coordinator: received timeline view model action: \(action)")
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .displaySenderDetails(let userID):
|
||||
actionsSubject.send(.displayUser(userID: userID))
|
||||
case .displayMessageForwarding(let forwardingItem):
|
||||
actionsSubject.send(.displayMessageForwarding(forwardingItem: forwardingItem))
|
||||
case .displayLocation(_, let geoURI, let description):
|
||||
actionsSubject.send(.presentLocationViewer(geoURI: geoURI, description: description))
|
||||
case .viewInRoomTimeline(let eventID):
|
||||
actionsSubject.send(.displayRoomScreenWithFocussedPin(eventID: eventID))
|
||||
// These other actions will not be handled in this view
|
||||
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaPreview,
|
||||
.displayMediaUploadPreviewScreen, .displayResolveSendFailure, .displayThread, .composer, .hasScrolled:
|
||||
// These actions are not handled in this coordinator
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
viewModel.stop()
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(ThreadTimelineScreen(context: viewModel.context, timelineContext: timelineViewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ThreadTimelineScreenViewModelAction { }
|
||||
|
||||
struct ThreadTimelineScreenViewState: BindableState {
|
||||
var bindings = ThreadTimelineScreenViewStateBindings()
|
||||
}
|
||||
|
||||
struct ThreadTimelineScreenViewStateBindings {
|
||||
/// The view model used to present a QuickLook media preview.
|
||||
var mediaPreviewViewModel: TimelineMediaPreviewViewModel?
|
||||
}
|
||||
|
||||
enum ThreadTimelineScreenViewAction { }
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias ThreadTimelineScreenViewModelType = StateStoreViewModel<ThreadTimelineScreenViewState, ThreadTimelineScreenViewAction>
|
||||
|
||||
class ThreadTimelineScreenViewModel: ThreadTimelineScreenViewModelType, ThreadTimelineScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<ThreadTimelineScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ThreadTimelineScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: ThreadTimelineScreenViewState())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: ThreadTimelineScreenViewAction) { }
|
||||
|
||||
func stop() {
|
||||
// Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier.
|
||||
state.bindings.mediaPreviewViewModel = nil
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ThreadTimelineScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ThreadTimelineScreenViewModelAction, Never> { get }
|
||||
var context: ThreadTimelineScreenViewModelType.Context { get }
|
||||
|
||||
func stop()
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct ThreadTimelineScreen: View {
|
||||
@ObservedObject var context: ThreadTimelineScreenViewModel.Context
|
||||
@ObservedObject var timelineContext: TimelineViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
content
|
||||
.navigationTitle("Thread")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.background(.compound.bgCanvasDefault)
|
||||
.interactiveDismissDisabled()
|
||||
.timelineMediaPreview(viewModel: $context.mediaPreviewViewModel)
|
||||
.sheet(item: $timelineContext.manageMemberViewModel) {
|
||||
ManageRoomMemberSheetView(context: $0.context)
|
||||
}
|
||||
.sheet(item: $timelineContext.debugInfo) { TimelineItemDebugView(info: $0) }
|
||||
.sheet(item: $timelineContext.actionMenuInfo) { info in
|
||||
let actions = TimelineItemMenuActionProvider(timelineItem: info.item,
|
||||
canCurrentUserRedactSelf: timelineContext.viewState.canCurrentUserRedactSelf,
|
||||
canCurrentUserRedactOthers: timelineContext.viewState.canCurrentUserRedactOthers,
|
||||
canCurrentUserPin: timelineContext.viewState.canCurrentUserPin,
|
||||
pinnedEventIDs: timelineContext.viewState.pinnedEventIDs,
|
||||
isDM: timelineContext.viewState.isDirectOneToOneRoom,
|
||||
isViewSourceEnabled: timelineContext.viewState.isViewSourceEnabled,
|
||||
timelineKind: timelineContext.viewState.timelineKind,
|
||||
emojiProvider: timelineContext.viewState.emojiProvider)
|
||||
.makeActions()
|
||||
if let actions {
|
||||
TimelineItemMenu(item: info.item, actions: actions)
|
||||
.environmentObject(timelineContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
TimelineView()
|
||||
.id(timelineContext.viewState.roomID)
|
||||
.environmentObject(timelineContext)
|
||||
.environment(\.focussedEventID, timelineContext.viewState.timelineState.focussedEvent?.eventID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user