mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Use TimelineMediaQuickLook in the MediaEventsTimelineScreen. (#3598)
This commit is contained in:
@@ -86,7 +86,8 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
mediaPlayerProvider: MediaPlayerProvider(),
|
||||
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
||||
appMediator: appMediator,
|
||||
emojiProvider: emojiProvider)
|
||||
emojiProvider: emojiProvider,
|
||||
userIndicatorController: userIndicatorController)
|
||||
|
||||
let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@ class TimelineMediaPreviewController: QLPreviewController, QLPreviewControllerDa
|
||||
|
||||
headerHostingController = UIHostingController(rootView: HeaderView(context: viewModel.context))
|
||||
headerHostingController.view.backgroundColor = .clear
|
||||
headerHostingController.sizingOptions = .intrinsicContentSize
|
||||
captionHostingController = UIHostingController(rootView: CaptionView(context: viewModel.context))
|
||||
captionHostingController.view.backgroundColor = .clear
|
||||
captionHostingController.sizingOptions = .intrinsicContentSize
|
||||
detailsHostingController = UIHostingController(rootView: TimelineMediaPreviewDetailsView(context: viewModel.context))
|
||||
detailsHostingController.view.backgroundColor = .compound.bgCanvasDefault
|
||||
|
||||
@@ -87,9 +89,7 @@ class TimelineMediaPreviewController: QLPreviewController, QLPreviewControllerDa
|
||||
|
||||
navigationBar?.topItem?.titleView = headerHostingController.view
|
||||
|
||||
if navigationBar?.topItem?.rightBarButtonItems?.count == 1 {
|
||||
navigationBar?.topItem?.rightBarButtonItems?.append(UIBarButtonItem(image: UIImage(systemSymbol: .infoCircle), style: .plain, target: self, action: #selector(presentMediaDetails)))
|
||||
}
|
||||
updateBarButtons()
|
||||
}
|
||||
|
||||
// MARK: QLPreviewControllerDataSource
|
||||
@@ -111,6 +111,21 @@ class TimelineMediaPreviewController: QLPreviewController, QLPreviewControllerDa
|
||||
|
||||
present(detailsHostingController, animated: true)
|
||||
}
|
||||
|
||||
private var detailsButtonIcon: UIImage {
|
||||
guard let bundle = Bundle(url: Bundle.main.bundleURL.appending(path: "CompoundDesignTokens_CompoundDesignTokens.bundle")) else {
|
||||
return UIImage(systemSymbol: .infoCircle)
|
||||
}
|
||||
|
||||
return UIImage(named: "info", in: bundle, compatibleWith: nil) ?? UIImage(systemSymbol: .infoCircle)
|
||||
}
|
||||
|
||||
private func updateBarButtons() {
|
||||
if navigationBar?.topItem?.rightBarButtonItems?.count == 1 {
|
||||
let button = UIBarButtonItem(image: detailsButtonIcon, style: .plain, target: self, action: #selector(presentMediaDetails))
|
||||
navigationBar?.topItem?.rightBarButtonItems?.append(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Subviews
|
||||
@@ -143,7 +158,21 @@ private struct CaptionView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(16)
|
||||
.background(.ultraThinMaterial)
|
||||
.background {
|
||||
BlurView(style: .systemChromeMaterial) // Darkest material available, matches the bottom bar when content is beneath.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct BlurView: 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ class TimelineMediaPreviewItem: NSObject, QLPreviewItem {
|
||||
// MARK: QLPreviewItem
|
||||
|
||||
var previewItemURL: URL? {
|
||||
fileHandle?.url
|
||||
// Falling back to a clear image allows the presentation animation to work when
|
||||
// the item is in the event cache and just needs to be loaded from the store.
|
||||
fileHandle?.url ?? Bundle.main.url(forResource: "clear", withExtension: "png")
|
||||
}
|
||||
|
||||
var previewItemTitle: String? {
|
||||
|
||||
+3
-1
@@ -17,6 +17,7 @@ struct MediaEventsTimelineScreenCoordinatorParameters {
|
||||
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
enum MediaEventsTimelineScreenCoordinatorAction { }
|
||||
@@ -59,7 +60,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
||||
filesTimelineViewModel: filesTimelineViewModel,
|
||||
mediaProvider: parameters.mediaProvider)
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
|
||||
+2
@@ -23,10 +23,12 @@ struct MediaEventsTimelineScreenViewState: BindableState {
|
||||
|
||||
struct MediaEventsTimelineScreenViewStateBindings {
|
||||
var screenMode: MediaEventsTimelineScreenMode
|
||||
var mediaPreviewViewModel: TimelineMediaPreviewViewModel?
|
||||
}
|
||||
|
||||
enum MediaEventsTimelineScreenViewAction {
|
||||
case changedScreenMode
|
||||
case oldestItemDidAppear
|
||||
case oldestItemDidDisappear
|
||||
case tappedItem(RoomTimelineItemViewState)
|
||||
}
|
||||
|
||||
+26
-1
@@ -13,6 +13,7 @@ typealias MediaEventsTimelineScreenViewModelType = StateStoreViewModel<MediaEven
|
||||
class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType, MediaEventsTimelineScreenViewModelProtocol {
|
||||
private let mediaTimelineViewModel: TimelineViewModelProtocol
|
||||
private let filesTimelineViewModel: TimelineViewModelProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private var isOldestItemVisible = false
|
||||
|
||||
@@ -33,9 +34,11 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
init(mediaTimelineViewModel: TimelineViewModelProtocol,
|
||||
filesTimelineViewModel: TimelineViewModelProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
screenMode: MediaEventsTimelineScreenMode = .media) {
|
||||
screenMode: MediaEventsTimelineScreenMode = .media,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.mediaTimelineViewModel = mediaTimelineViewModel
|
||||
self.filesTimelineViewModel = filesTimelineViewModel
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
super.init(initialViewState: .init(bindings: .init(screenMode: screenMode)), mediaProvider: mediaProvider)
|
||||
|
||||
@@ -73,6 +76,8 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
backPaginateIfNecessary(paginationStatus: activeTimelineViewModel.context.viewState.timelineState.paginationState.backward)
|
||||
case .oldestItemDidDisappear:
|
||||
isOldestItemVisible = false
|
||||
case .tappedItem(let item):
|
||||
handleItemTapped(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,4 +104,24 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
activeTimelineViewModel.context.send(viewAction: .paginateBackwards)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleItemTapped(_ item: RoomTimelineItemViewState) {
|
||||
let item: EventBasedMessageTimelineItemProtocol? = switch item.type {
|
||||
case .audio(let audioItem): audioItem
|
||||
case .file(let fileItem): fileItem
|
||||
case .image(let imageItem): imageItem
|
||||
case .video(let videoItem): videoItem
|
||||
default: nil
|
||||
}
|
||||
|
||||
guard let item, let mediaProvider = context.mediaProvider else {
|
||||
MXLog.error("Unexpected item type (or the media provider is missing).")
|
||||
return
|
||||
}
|
||||
|
||||
let viewModel = TimelineMediaPreviewViewModel(previewItems: [item],
|
||||
mediaProvider: mediaProvider,
|
||||
userIndicatorController: userIndicatorController)
|
||||
state.bindings.mediaPreviewViewModel = viewModel
|
||||
}
|
||||
}
|
||||
|
||||
+16
-9
@@ -30,6 +30,7 @@ struct MediaEventsTimelineScreen: View {
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
}
|
||||
.timelineMediaQuickLook(viewModel: $context.mediaPreviewViewModel)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -39,13 +40,17 @@ struct MediaEventsTimelineScreen: View {
|
||||
let columns = [GridItem(.adaptive(minimum: 80, maximum: 150), spacing: 1)]
|
||||
LazyVGrid(columns: columns, alignment: .center, spacing: 1) {
|
||||
ForEach(context.viewState.items) { item in
|
||||
Color.clear // Let the image aspect fill in place
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.overlay {
|
||||
viewForTimelineItem(item)
|
||||
}
|
||||
.clipped()
|
||||
.scaleEffect(.init(width: 1, height: -1))
|
||||
Button {
|
||||
context.send(viewAction: .tappedItem(item))
|
||||
} label: {
|
||||
Color.clear // Let the image aspect fill in place
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.overlay {
|
||||
viewForTimelineItem(item)
|
||||
}
|
||||
.clipped()
|
||||
.scaleEffect(.init(width: 1, height: -1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,12 +157,14 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let mediaViewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: timelineViewModel,
|
||||
filesTimelineViewModel: timelineViewModel,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
screenMode: .media)
|
||||
screenMode: .media,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
|
||||
static let filesViewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: timelineViewModel,
|
||||
filesTimelineViewModel: timelineViewModel,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
screenMode: .files)
|
||||
screenMode: .files,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
|
||||
Reference in New Issue
Block a user