mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Use TimelineMediaQuickLook in the MediaEventsTimelineScreen. (#3598)
This commit is contained in:
parent
ea0fa6b3b6
commit
e59a705459
@ -922,6 +922,7 @@
|
||||
BE8E5985771DF9137C6CE89A /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077B01C13BBA2996272C5FB5 /* ProcessInfo.swift */; };
|
||||
BEA646DF302711A753F0D420 /* MapTilerStyleBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */; };
|
||||
BEC6DFEA506085D3027E353C /* MediaEventsTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */; };
|
||||
BFDDAF1A36FBC7CF63DCB7DD /* clear.png in Resources */ = {isa = PBXBuildFile; fileRef = 17F7A723A46DF5C95BE15EBF /* clear.png */; };
|
||||
BFEB24336DFD5F196E6F3456 /* IntentionalMentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */; };
|
||||
C0090506A52A1991BAF4BA68 /* NotificationSettingsChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07579F9C29001E40715F3014 /* NotificationSettingsChatType.swift */; };
|
||||
C022284E2774A5E1EF683B4D /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
||||
@ -1408,6 +1409,7 @@
|
||||
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = "<group>"; };
|
||||
1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = "<group>"; };
|
||||
17A8AA0DFA06012A9DAB951E /* TimelineProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyMock.swift; sourceTree = "<group>"; };
|
||||
17F7A723A46DF5C95BE15EBF /* clear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = clear.png; sourceTree = "<group>"; };
|
||||
18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = "<group>"; };
|
||||
184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = "<group>"; };
|
||||
@ -2958,6 +2960,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
01C4C7DB37597D7D8379511A /* Assets.xcassets */,
|
||||
17F7A723A46DF5C95BE15EBF /* clear.png */,
|
||||
A0C06C0F6A8621B22BFAEB56 /* Localizations */,
|
||||
8AEA6A91159FA0D3EAFCCB0D /* Sounds */,
|
||||
);
|
||||
@ -6204,6 +6207,7 @@
|
||||
5FCD8AFA364206EE32B909A3 /* Settings.bundle in Resources */,
|
||||
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */,
|
||||
2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */,
|
||||
BFDDAF1A36FBC7CF63DCB7DD /* clear.png in Resources */,
|
||||
147597951DB07123A87AA1D1 /* landscape_test_image.jpg in Resources */,
|
||||
FDC67E8C0EDCB00ABC66C859 /* landscape_test_video.mov in Resources */,
|
||||
E67418DACEDBC29E988E6ACD /* message.caf in Resources */,
|
||||
|
BIN
ElementX/Resources/clear.png
Normal file
BIN
ElementX/Resources/clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -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? {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user