Content scanner (Element Pro): scan timeline events for unsafe content (#7159)

* Simplify how event content with captions are displayed

The caption component will now be added in `TimelineItemEventContentView` instead of separately in each `TimelineItem__View` component

* Make sure `CoilMediaFetcher` methods to fetch media throw errors

We need to catch them later to check if the content scanner marked some thumbnails or medias as invalid/unsafe in `AsyncImage` instances

* Add a `ContentScannerService` that allows us to manually scan a list of medias in an event and then update and store the results

The results take the form of `ContentValidationValue`, stored in a `ContentValidationState` instance tied to the event

* Create `EventContentValidationCache` to keep the `ContentValidationStates` for events in a room

* Expose `EventContentValidationCache` to Compose using `LocalEventContentValidationState`

Also add a couple of helper functions to avoid having to manually remember the states

* Use the new components to display invalid content in the timeline items

* Add `blurhash` to most media items so we can use it while validating the thumbnails/medias

* Fix custom layout issues with screenshot and UI tests

* Also check the validation state for replied-to events in the composer

* Fix and add tests

* Handle unrecoverable content validation errors too: display a 'not found' UI

* Improve the performance of `EqualWidthColumn` for the single item case

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa
2026-07-16 09:59:51 +02:00
committed by GitHub
parent 56cf00560e
commit 0d03472c60
241 changed files with 2881 additions and 828 deletions
@@ -34,6 +34,7 @@ class KonsistArchitectureTest {
.classes()
.withNameEndingWith("State")
.withoutName(
"NoopContentValidationState",
"CameraPositionState",
"CustomSheetState",
)
@@ -145,7 +145,8 @@ class KonsistPreviewTest {
"TextComposerVoiceNotEncryptedPreview",
"TextComposerVoicePreview",
"TextFieldDialogWithErrorPreview",
"TimelineImageWithCaptionRowPreview",
"TimelineItemAttachmentsViewScanningContentFailedPreview",
"TimelineItemAudioViewScanningContentPreview",
"TimelineItemEventRowForDirectRoomPreview",
"TimelineItemEventRowShieldPreview",
"TimelineItemEventRowTimestampPreview",
@@ -155,13 +156,21 @@ class KonsistPreviewTest {
"TimelineItemEventRowWithRRPreview",
"TimelineItemEventRowWithReplyPreview",
"TimelineItemEventRowWithThreadSummaryPreview",
"TimelineItemFileViewScanningContentPreview",
"TimelineItemGalleryViewScanningContentFailedPreview",
"TimelineItemGroupedEventsRowContentCollapsePreview",
"TimelineItemGroupedEventsRowContentExpandedPreview",
"TimelineItemImageViewHideMediaContentPreview",
"TimelineItemImageViewScanningContentPreview",
"TimelineItemRedactedMessagesGroupPreview",
"TimelineItemScanningContentFailedPreview",
"TimelineItemScanningContentWithInvalidRepliesPreview",
"TimelineItemScanningContentWithRepliesFailedPreview",
"TimelineItemStickerViewScanningContentPreview",
"TimelineItemVideoViewHideMediaContentPreview",
"TimelineItemVideoViewScanningContentPreview",
"TimelineItemVoiceViewScanningContentPreview",
"TimelineItemVoiceViewUnifiedPreview",
"TimelineVideoWithCaptionRowPreview",
"TimelineViewMessageShieldPreview",
"TimelineViewWithReadMarkerBothIndicatorsPreview",
"TimelineViewWithReadMarkerJumpToUnreadIndicatorOnlyPreview",