Display a badge for messages decrypted using shared keys. (#4937)

* feat: Show info badge for messages decrypted with shared keys

* tests: Add snapshots and tests for forwarder icon.

* fix: Update `EventBasedTimelineItemProtocol.hasStatusIcon`

* fix: Address review comments.

* fix: Use two translations for key forwarded dialog content.

* refactor: Rename `TimelineItemForwarder` to `TimelineItemKeyForwarder`.

Additionally adds a `[weak self]` guard to `encryptionForwarder` alert
handler.

* tests: Add `previewLayout` directive to `encryptionForwarder` previews

* refactor: Remove unused translations, use short-circuiting condition.
This commit is contained in:
Skye Elliot
2026-01-19 14:27:49 +00:00
committed by GitHub
parent 20e15f0e3e
commit a30ae24d16
19 changed files with 282 additions and 24 deletions
@@ -322,11 +322,18 @@ private extension View {
}
}
private extension TimelineItemKeyForwarder {
static var test: TimelineItemKeyForwarder {
TimelineItemKeyForwarder(id: "@alice:matrix.org", displayName: "alice")
}
}
// MARK: - Previews
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
static let viewModel: TimelineViewModel = {
let appSettings = AppSettings()
appSettings.enableKeyShareOnInvite = true
appSettings.threadsEnabled = true
let roomProxy = JoinedRoomProxyMock(.init())
@@ -385,6 +392,9 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
.padding(.bottom, 20)
encryptionAuthenticity
.previewDisplayName("Encryption Indicators")
encryptionForwarder
.previewLayout(.sizeThatFits)
.previewDisplayName("Encryption Forwarder Info")
pinned
.previewDisplayName("Pinned messages")
.previewLayout(.fixed(width: 390, height: 1150))
@@ -540,6 +550,82 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
.environmentObject(viewModel.context)
.environment(\.timelineContext, viewModel.context)
}
static var encryptionForwarder: some View {
VStack(spacing: 0) {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .randomEvent,
timestamp: .mock,
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "whoever"),
content: .init(body: "A long message that should be on multiple lines."),
properties: RoomTimelineItemProperties(isEdited: true, encryptionForwarder: .test)),
groupStyle: .single))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .randomEvent,
timestamp: .mock,
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "whoever"),
content: .init(body: "A long message that should be on multiple lines."),
properties: RoomTimelineItemProperties(encryptionForwarder: .test)),
groupStyle: .single))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .randomEvent,
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "whoever"),
content: .init(body: "Short message"),
properties: RoomTimelineItemProperties(encryptionForwarder: .test)),
groupStyle: .first))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .randomEvent,
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "whoever"),
content: .init(body: "Message goes Here"),
properties: RoomTimelineItemProperties(encryptionForwarder: .test)),
groupStyle: .last))
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .randomEvent,
timestamp: .mock,
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: "Bob"),
content: .init(filename: "other.png",
imageInfo: .mockImage,
thumbnailInfo: nil),
properties: RoomTimelineItemProperties(encryptionForwarder: .test)))
VoiceMessageRoomTimelineView(timelineItem: .init(id: .randomEvent,
timestamp: .mock,
isOutgoing: true,
isEditable: false,
canBeRepliedTo: true,
sender: .init(id: ""),
content: .init(filename: "audio.ogg",
duration: 100,
waveform: EstimatedWaveform.mockWaveform,
source: nil,
fileSize: nil,
contentType: nil),
properties: RoomTimelineItemProperties(isThreaded: true,
encryptionForwarder: .test)),
playerState: AudioPlayerState(id: .timelineItemIdentifier(.randomEvent),
title: L10n.commonVoiceMessage,
duration: 10,
waveform: EstimatedWaveform.mockWaveform))
}
.environmentObject(viewModel.context)
.environment(\.timelineContext, viewModel.context)
}
}
private struct MockTimelineContent: View {