Content Scanning View (#5831)

* implement a MediaView to delegate at the view level the usage of the content scanner service, to determine how to render the media.

* fix for a small design mismatch

* fix for greedy error state

* updated naming

* just making some screenshot that have been affected by a string change on Localazy

* pr suggestions + updated strings
This commit is contained in:
Mauro
2026-07-06 16:15:27 +02:00
committed by GitHub
parent 0a77ba6c90
commit a2c1ca06dd
95 changed files with 786 additions and 219 deletions
@@ -15,18 +15,27 @@ struct StickerRoomTimelineView: View {
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
LoadableImage(mediaSource: timelineItem.imageInfo.source,
mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID),
blurhash: timelineItem.blurhash,
size: timelineItem.imageInfo.size,
mediaProvider: context?.mediaProvider) {
ContentScanningView(contentScannerService: context?.contentScannerService,
mediaSource: timelineItem.imageInfo.source) {
LoadableImage(mediaSource: timelineItem.imageInfo.source,
mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID),
blurhash: timelineItem.blurhash,
size: timelineItem.imageInfo.size,
mediaProvider: context?.mediaProvider) {
placeholder
}
.timelineMediaFrame(imageInfo: timelineItem.imageInfo)
.accessibilityElement(children: .ignore)
.accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)")
.onTapGesture {
context?.send(viewAction: .mediaTapped(itemID: timelineItem.id))
}
} scanningContent: {
placeholder
}
.timelineMediaFrame(imageInfo: timelineItem.imageInfo)
.accessibilityElement(children: .ignore)
.accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)")
.onTapGesture {
context?.send(viewAction: .mediaTapped(itemID: timelineItem.id))
.overlay { ProgressView() }
.timelineMediaFrame(imageInfo: timelineItem.imageInfo)
} unsafeContent: { failure in
ContentScanningFailureView(failure: failure)
}
}
}
@@ -40,29 +49,41 @@ struct StickerRoomTimelineView: View {
struct StickerRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static let viewModel = TimelineViewModel.mock
static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil)))
static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false)))
static var previews: some View {
VStack(spacing: 20.0) {
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .randomEvent,
body: "Some image",
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "Bob"),
imageInfo: .mockImage))
StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Some image"))
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .randomEvent,
body: "Blurhashed image",
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "Bob"),
imageInfo: .mockImage,
blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))
StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Blurhashed image",
blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))
}
.environmentObject(viewModel.context)
.environment(\.timelineContext, viewModel.context)
VStack(spacing: 20.0) {
StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Scanning image"))
.environmentObject(scanningViewModel.context)
.environment(\.timelineContext, scanningViewModel.context)
StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Unsafe image"))
.environmentObject(unsafeViewModel.context)
.environment(\.timelineContext, unsafeViewModel.context)
}
.environmentObject(viewModel.context)
.previewDisplayName("Content Scanner")
}
private static func makeTimelineItem(body: String, blurhash: String? = nil) -> StickerRoomTimelineItem {
StickerRoomTimelineItem(id: .randomEvent,
body: body,
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "Bob"),
imageInfo: .mockImage,
blurhash: blurhash)
}
}