mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Show Encryption Authenticity warnings on messages in the timeline. (#3051)
* Initial implementation. * Add developer option for showing timeline item authenticity. * Refactor code to use new SendInfo.Status. --------- Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
@@ -330,6 +330,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
||||
.previewDisplayName("Replies")
|
||||
threads
|
||||
.previewDisplayName("Thread decorator")
|
||||
encryptionAuthenticity
|
||||
.previewDisplayName("Encryption Indicators")
|
||||
}
|
||||
|
||||
// These akwats include a reply
|
||||
@@ -477,4 +479,80 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
||||
}
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
|
||||
static var encryptionAuthenticity: some View {
|
||||
VStack(spacing: 0) {
|
||||
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||
timestamp: "10:42",
|
||||
isOutgoing: true,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "whoever"),
|
||||
content: .init(body: "A long message that should be on multiple lines."),
|
||||
properties: RoomTimelineItemProperties(encryptionAuthenticity: .unsignedDevice(color: .red))),
|
||||
groupStyle: .single))
|
||||
|
||||
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||
timestamp: "10:42",
|
||||
isOutgoing: true,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "whoever"),
|
||||
content: .init(body: "A long message that should be on multiple lines."),
|
||||
properties: RoomTimelineItemProperties(isEdited: true,
|
||||
encryptionAuthenticity: .unsignedDevice(color: .red))),
|
||||
groupStyle: .single))
|
||||
|
||||
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||
timestamp: "10:42",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "whoever"),
|
||||
content: .init(body: "Short message"),
|
||||
properties: RoomTimelineItemProperties(encryptionAuthenticity: .unknownDevice(color: .red))),
|
||||
groupStyle: .first))
|
||||
|
||||
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||
timestamp: "10:42",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "whoever"),
|
||||
content: .init(body: "Message goes Here"),
|
||||
properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray))),
|
||||
groupStyle: .last))
|
||||
|
||||
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .random,
|
||||
timestamp: "Now",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "Bob"),
|
||||
content: .init(body: "Some other image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
|
||||
|
||||
properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray))))
|
||||
|
||||
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||
timestamp: "10:42",
|
||||
isOutgoing: true,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: true,
|
||||
sender: .init(id: ""),
|
||||
content: .init(body: "audio.ogg",
|
||||
duration: 100,
|
||||
waveform: EstimatedWaveform.mockWaveform,
|
||||
source: nil,
|
||||
contentType: nil),
|
||||
properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray))),
|
||||
playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10, waveform: EstimatedWaveform.mockWaveform))
|
||||
}
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,17 @@ private struct TimelineItemSendInfoLabel: View {
|
||||
|
||||
var statusIcon: KeyPath<CompoundIcons, Image>? {
|
||||
switch sendInfo.status {
|
||||
case .sendingFailed: \.error
|
||||
case .unverifiedSession, .authenticityUnknown: \.admin
|
||||
case .unencrypted: \.keyOff
|
||||
case .none: nil
|
||||
case .sendingFailed:
|
||||
\.error
|
||||
case .encryptionAuthenticity(.notGuaranteed):
|
||||
\.infoSolid
|
||||
case .encryptionAuthenticity(.unknownDevice),
|
||||
.encryptionAuthenticity(.unsignedDevice),
|
||||
.encryptionAuthenticity(.unverifiedIdentity),
|
||||
.encryptionAuthenticity(.sentInClear):
|
||||
\.lockOff
|
||||
case .none:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +74,7 @@ private struct TimelineItemSendInfoLabel: View {
|
||||
case .sendingFailed: L10n.commonSendingFailed
|
||||
case .none: nil
|
||||
// Temporary testing strings.
|
||||
case .unverifiedSession: L10n.eventShieldReasonUnsignedDevice
|
||||
case .authenticityUnknown: L10n.eventShieldReasonAuthenticityNotGuaranteed
|
||||
case .unencrypted: UntranslatedL10n.sendInfoNotEncrypted
|
||||
case .encryptionAuthenticity(let authenticity): authenticity.message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +116,7 @@ private struct TimelineItemSendInfoLabel: View {
|
||||
|
||||
/// All the data needed to render a timeline item's send info label.
|
||||
private struct TimelineItemSendInfo {
|
||||
enum Status { case sendingFailed, unverifiedSession, authenticityUnknown, unencrypted }
|
||||
enum Status { case sendingFailed, encryptionAuthenticity(EncryptionAuthenticity) }
|
||||
|
||||
/// Describes how the content and the send info should be arranged inside a bubble
|
||||
enum LayoutType {
|
||||
@@ -126,9 +131,11 @@ private struct TimelineItemSendInfo {
|
||||
|
||||
var foregroundStyle: Color {
|
||||
switch status {
|
||||
case .sendingFailed, .unverifiedSession:
|
||||
case .sendingFailed:
|
||||
.compound.textCriticalPrimary
|
||||
case .authenticityUnknown, .unencrypted, .none:
|
||||
case .encryptionAuthenticity(let authenticity):
|
||||
authenticity.foregroundStyle
|
||||
case .none:
|
||||
.compound.textSecondary
|
||||
}
|
||||
}
|
||||
@@ -140,6 +147,8 @@ private extension TimelineItemSendInfo {
|
||||
|
||||
status = if adjustedDeliveryStatus == .sendingFailed {
|
||||
.sendingFailed
|
||||
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
|
||||
.encryptionAuthenticity(authenticity)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
@@ -161,6 +170,24 @@ private extension TimelineItemSendInfo {
|
||||
}
|
||||
}
|
||||
|
||||
private extension EncryptionAuthenticity {
|
||||
var foregroundStyle: SwiftUI.Color {
|
||||
switch self {
|
||||
case .notGuaranteed(let color),
|
||||
.unknownDevice(let color),
|
||||
.unsignedDevice(let color),
|
||||
.unverifiedIdentity(let color),
|
||||
.sentInClear(let color):
|
||||
switch color {
|
||||
case .red:
|
||||
.compound.textCriticalPrimary
|
||||
case .gray:
|
||||
.compound.textSecondary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
||||
@@ -172,14 +199,14 @@ struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
||||
status: .sendingFailed,
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .unverifiedSession,
|
||||
status: .encryptionAuthenticity(.unsignedDevice(color: .red)),
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .authenticityUnknown,
|
||||
layoutType: .horizontal()))
|
||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
status: .unencrypted,
|
||||
status: .encryptionAuthenticity(.notGuaranteed(color: .gray)),
|
||||
layoutType: .horizontal()))
|
||||
// TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
||||
// status: .unencrypted,
|
||||
// layoutType: .horizontal()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user