mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Content scanner also checks for thumbnails (#5911)
* scan also thumbnails for medias * pr suggestion
This commit is contained in:
@@ -137,17 +137,20 @@ class TimelineMediaPreviewViewModel: TimelineMediaPreviewViewModelType {
|
||||
}
|
||||
|
||||
/// Scans the media when a content scanner is configured, returning whether it's safe to be downloaded
|
||||
/// and previewed, reflecting the scan's progress and outcome in the current item.
|
||||
/// and previewed, reflecting the scan's progress and outcome in the current item. Both the media and
|
||||
/// its thumbnail are scanned as either being downloaded through the scanner can flag the media.
|
||||
private func checkSourceIsSafeIfNeeded(for mediaItem: TimelineMediaPreviewItem.Media, source: MediaSourceProxy) async -> Bool {
|
||||
guard let contentScannerService else { return true }
|
||||
|
||||
let sources = [source, mediaItem.thumbnailMediaSource].compactMap { $0 }
|
||||
|
||||
// Only reflect the scanning state when there's no cached verdict, so that
|
||||
// scanned items don't flash the scanning indicator when they're revisited.
|
||||
if contentScannerService.scanResultFromSource(source) == nil {
|
||||
if contentScannerService.scanResultFromSources(sources) == nil {
|
||||
setCurrentItem(.contentScan(.init(media: mediaItem, state: .scanning)))
|
||||
}
|
||||
|
||||
switch await contentScannerService.loadScanResultFromSource(source) {
|
||||
switch await contentScannerService.loadScanResultFromSources(sources) {
|
||||
case .success(true):
|
||||
finishScan(with: .media(mediaItem), for: mediaItem)
|
||||
return true
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ struct FileMediaEventsTimelineView: View {
|
||||
formattedCaption: timelineItem.content.formattedCaption,
|
||||
trailingReservedSize: timelineItem.trailingReservedSize,
|
||||
contentScannerService: context?.contentScannerService,
|
||||
mediaSource: timelineItem.content.source)
|
||||
mediaSource: timelineItem.content.source,
|
||||
thumbnailSource: timelineItem.content.thumbnailSource)
|
||||
.accessibilityLabel(L10n.commonFile)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.bubbleBackground(isOutgoing: timelineItem.isOutgoing,
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ struct ImageMediaEventsTimelineView: View {
|
||||
|
||||
var body: some View {
|
||||
ContentScanningView(contentScannerService: context?.contentScannerService,
|
||||
mediaSource: timelineItem.content.imageInfo.source) {
|
||||
mediaSource: timelineItem.content.imageInfo.source,
|
||||
thumbnailSource: timelineItem.content.thumbnailInfo?.source) {
|
||||
Color.clear // Let the image aspect fill in place
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.overlay {
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ struct VideoMediaEventsTimelineView: View {
|
||||
|
||||
var body: some View {
|
||||
ContentScanningView(contentScannerService: context?.contentScannerService,
|
||||
mediaSource: timelineItem.content.videoInfo.source) {
|
||||
mediaSource: timelineItem.content.videoInfo.source,
|
||||
thumbnailSource: timelineItem.content.thumbnailInfo?.source) {
|
||||
Color.clear // Let the image aspect fill in place
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.overlay {
|
||||
|
||||
@@ -25,6 +25,7 @@ struct TimelineReplyView: View {
|
||||
var body: some View {
|
||||
ContentScanningView(contentScannerService: timelineContext?.contentScannerService,
|
||||
mediaSource: scannedMediaSource,
|
||||
thumbnailSource: scannedThumbnailSource,
|
||||
containerShowsFailure: false) {
|
||||
content
|
||||
.roundedContainer(padding: 4,
|
||||
@@ -63,6 +64,21 @@ struct TimelineReplyView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// The thumbnail source validated alongside ``scannedMediaSource`` when the replied to message has one.
|
||||
private var scannedThumbnailSource: MediaSourceProxy? {
|
||||
guard case .loaded(_, _, let eventContent) = timelineItemReplyDetails,
|
||||
case .message(let message) = eventContent else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch message {
|
||||
case .file(let content): return content.thumbnailSource
|
||||
case .image(let content): return content.thumbnailInfo?.source
|
||||
case .video(let content): return content.thumbnailInfo?.source
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
if let timelineItemReplyDetails {
|
||||
|
||||
@@ -22,7 +22,8 @@ struct FileRoomTimelineView: View {
|
||||
trailingReservedSize: timelineItem.trailingReservedSize,
|
||||
shouldBoost: timelineItem.shouldBoost,
|
||||
contentScannerService: context?.contentScannerService,
|
||||
mediaSource: timelineItem.content.source) {
|
||||
mediaSource: timelineItem.content.source,
|
||||
thumbnailSource: timelineItem.content.thumbnailSource) {
|
||||
context?.send(viewAction: .mediaTapped(itemID: timelineItem.id))
|
||||
}
|
||||
.accessibilityLabel(L10n.commonFile)
|
||||
@@ -42,6 +43,7 @@ struct MediaFileRoomTimelineContent: View {
|
||||
var isAudioFile = false
|
||||
var contentScannerService: ContentScannerServiceProtocol?
|
||||
var mediaSource: MediaSourceProxy?
|
||||
var thumbnailSource: MediaSourceProxy?
|
||||
|
||||
private var fileDescription: String {
|
||||
var fileDescription = "\(filename.validatedFileExtension.uppercased())"
|
||||
@@ -60,7 +62,8 @@ struct MediaFileRoomTimelineContent: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ContentScanningView(contentScannerService: contentScannerService,
|
||||
mediaSource: mediaSource) {
|
||||
mediaSource: mediaSource,
|
||||
thumbnailSource: thumbnailSource) {
|
||||
if let onMediaTap {
|
||||
filePreview(isScanning: false)
|
||||
.onTapGesture(perform: onMediaTap)
|
||||
|
||||
+2
-1
@@ -24,7 +24,8 @@ struct ImageRoomTimelineView: View {
|
||||
// The caption sits 8pts below the content scanner failure placeholder, 4pts below the media.
|
||||
VStack(alignment: .leading, spacing: contentScanningFailure == nil ? 4 : 8) {
|
||||
ContentScanningView(contentScannerService: context?.contentScannerService,
|
||||
mediaSource: timelineItem.content.imageInfo.source) {
|
||||
mediaSource: timelineItem.content.imageInfo.source,
|
||||
thumbnailSource: timelineItem.content.thumbnailInfo?.source) {
|
||||
loadableImage
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(L10n.commonImage)
|
||||
|
||||
+2
-1
@@ -24,7 +24,8 @@ struct VideoRoomTimelineView: View {
|
||||
// The caption sits 8pts below the content scanner failure placeholder, 4pts below the media.
|
||||
VStack(alignment: .leading, spacing: contentScanningFailure == nil ? 4 : 8) {
|
||||
ContentScanningView(contentScannerService: context?.contentScannerService,
|
||||
mediaSource: timelineItem.content.videoInfo.source) {
|
||||
mediaSource: timelineItem.content.videoInfo.source,
|
||||
thumbnailSource: timelineItem.content.thumbnailInfo?.source) {
|
||||
thumbnail
|
||||
.timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo)
|
||||
.accessibilityElement(children: .ignore)
|
||||
|
||||
Reference in New Issue
Block a user