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
@@ -13,9 +13,11 @@ extension View {
/// Adds the send info (timestamp along indicators for edits and delivery/encryption issues) for the given timeline item to this view.
func timelineItemSendInfo(timelineItem: EventBasedTimelineItemProtocol,
adjustedDeliveryStatus: TimelineItemDeliveryStatus?,
hasContentScanningFailure: Bool = false,
context: TimelineViewModel.Context) -> some View {
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
adjustedDeliveryStatus: adjustedDeliveryStatus),
adjustedDeliveryStatus: adjustedDeliveryStatus,
hasContentScanningFailure: hasContentScanningFailure),
context: context))
}
}
@@ -148,7 +150,9 @@ private struct TimelineItemSendInfo {
}
private extension TimelineItemSendInfo {
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) {
init(timelineItem: EventBasedTimelineItemProtocol,
adjustedDeliveryStatus: TimelineItemDeliveryStatus?,
hasContentScanningFailure: Bool = false) {
itemID = timelineItem.id
localizedString = timelineItem.localizedSendInfo
@@ -162,29 +166,35 @@ private extension TimelineItemSendInfo {
nil
}
layoutType = switch timelineItem {
case is TextBasedRoomTimelineItem:
.overlay(capsuleStyle: false)
case let liveLocationTimelineItem as LiveLocationRoomTimelineItem:
liveLocationTimelineItem.layout
case let message as EventBasedMessageTimelineItemProtocol:
switch message {
case is ImageRoomTimelineItem, is VideoRoomTimelineItem:
.overlay(capsuleStyle: !message.hasMediaCaption)
case is AudioRoomTimelineItem, is FileRoomTimelineItem:
// swiftlint:disable:next void_function_in_ternary
message.hasMediaCaption ? .overlay(capsuleStyle: false) : .horizontal(spacing: 0) // No spacing as the content already contains it.
case let locationTimelineItem as LocationRoomTimelineItem:
.overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil)
layoutType = if hasContentScanningFailure {
// The content scanner failure placeholder replaces the media,
// so the send info is laid out like it is for a text message.
timelineItem.hasMediaCaption ? .overlay(capsuleStyle: false) : .horizontal()
} else {
switch timelineItem {
case is TextBasedRoomTimelineItem:
.overlay(capsuleStyle: false)
case let liveLocationTimelineItem as LiveLocationRoomTimelineItem:
liveLocationTimelineItem.layout
case let message as EventBasedMessageTimelineItemProtocol:
switch message {
case is ImageRoomTimelineItem, is VideoRoomTimelineItem:
.overlay(capsuleStyle: !message.hasMediaCaption)
case is AudioRoomTimelineItem, is FileRoomTimelineItem:
// swiftlint:disable:next void_function_in_ternary
message.hasMediaCaption ? .overlay(capsuleStyle: false) : .horizontal(spacing: 0) // No spacing as the content already contains it.
case let locationTimelineItem as LocationRoomTimelineItem:
.overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil)
default:
.horizontal()
}
case is StickerRoomTimelineItem:
.overlay(capsuleStyle: true)
case is PollRoomTimelineItem:
.vertical(spacing: 16)
default:
.horizontal()
}
case is StickerRoomTimelineItem:
.overlay(capsuleStyle: true)
case is PollRoomTimelineItem:
.vertical(spacing: 16)
default:
.horizontal()
}
}
}