mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
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:
@@ -270,6 +270,7 @@
|
|||||||
2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */; };
|
2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */; };
|
||||||
2D38D39B1789B91AE69F477F /* PhotoLibraryManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD955A0380C287C418F1A74D /* PhotoLibraryManagerMock.swift */; };
|
2D38D39B1789B91AE69F477F /* PhotoLibraryManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD955A0380C287C418F1A74D /* PhotoLibraryManagerMock.swift */; };
|
||||||
2D45A04699BB6BA3B3A0CB9A /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; };
|
2D45A04699BB6BA3B3A0CB9A /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; };
|
||||||
|
2D76463CE5A9238B5BB5F393 /* TimelineItemKeyForwarder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F137B69DCB59353E37B80 /* TimelineItemKeyForwarder.swift */; };
|
||||||
2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */; };
|
2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */; };
|
||||||
2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; };
|
2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; };
|
||||||
2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */; };
|
2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */; };
|
||||||
@@ -2734,6 +2735,7 @@
|
|||||||
DADECBBB672497BCD4822468 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
|
DADECBBB672497BCD4822468 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
|
||||||
DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreen.swift; sourceTree = "<group>"; };
|
DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreen.swift; sourceTree = "<group>"; };
|
||||||
DB08D1F7C27A8C24EF81073C /* MapURLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapURLs.swift; sourceTree = "<group>"; };
|
DB08D1F7C27A8C24EF81073C /* MapURLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapURLs.swift; sourceTree = "<group>"; };
|
||||||
|
DB6F137B69DCB59353E37B80 /* TimelineItemKeyForwarder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemKeyForwarder.swift; sourceTree = "<group>"; };
|
||||||
DBEDCEC9D908C19C63D24395 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
DBEDCEC9D908C19C63D24395 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
|
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
|
||||||
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -4615,6 +4617,7 @@
|
|||||||
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */,
|
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */,
|
||||||
5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */,
|
5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */,
|
||||||
2F926D08EB3D622A480BCA71 /* TimelineEventContent.swift */,
|
2F926D08EB3D622A480BCA71 /* TimelineEventContent.swift */,
|
||||||
|
DB6F137B69DCB59353E37B80 /* TimelineItemKeyForwarder.swift */,
|
||||||
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */,
|
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */,
|
||||||
98C6A082F2B2A15E1B9BE280 /* TimelineItemThreadSummary.swift */,
|
98C6A082F2B2A15E1B9BE280 /* TimelineItemThreadSummary.swift */,
|
||||||
);
|
);
|
||||||
@@ -8674,6 +8677,7 @@
|
|||||||
79959F8E45C3749997482A7F /* TimelineItemBubbledStylerView.swift in Sources */,
|
79959F8E45C3749997482A7F /* TimelineItemBubbledStylerView.swift in Sources */,
|
||||||
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */,
|
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */,
|
||||||
877D3CE8680536DB430DE6A2 /* TimelineItemIdentifier.swift in Sources */,
|
877D3CE8680536DB430DE6A2 /* TimelineItemIdentifier.swift in Sources */,
|
||||||
|
2D76463CE5A9238B5BB5F393 /* TimelineItemKeyForwarder.swift in Sources */,
|
||||||
C0B97FFEC0083F3A36609E61 /* TimelineItemMacContextMenu.swift in Sources */,
|
C0B97FFEC0083F3A36609E61 /* TimelineItemMacContextMenu.swift in Sources */,
|
||||||
6C98153D60FF9B648C166C27 /* TimelineItemMenu.swift in Sources */,
|
6C98153D60FF9B648C166C27 /* TimelineItemMenu.swift in Sources */,
|
||||||
AE07F215EBC2B9CBF17AA54B /* TimelineItemMenuAction.swift in Sources */,
|
AE07F215EBC2B9CBF17AA54B /* TimelineItemMenuAction.swift in Sources */,
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ struct TimelineViewState: BindableState {
|
|||||||
var linkMetadataProvider: LinkMetadataProviderProtocol?
|
var linkMetadataProvider: LinkMetadataProviderProtocol?
|
||||||
|
|
||||||
var mapTilerConfiguration: MapTilerConfiguration
|
var mapTilerConfiguration: MapTilerConfiguration
|
||||||
|
|
||||||
|
var enableKeyShareOnInvite: Bool
|
||||||
|
|
||||||
var bindings: TimelineViewStateBindings
|
var bindings: TimelineViewStateBindings
|
||||||
}
|
}
|
||||||
@@ -196,6 +198,7 @@ enum TimelineAlertInfoType: Hashable {
|
|||||||
case pollEndConfirmation(String)
|
case pollEndConfirmation(String)
|
||||||
case sendingFailed
|
case sendingFailed
|
||||||
case encryptionAuthenticity(String)
|
case encryptionAuthenticity(String)
|
||||||
|
case encryptionForwarder(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomMemberState {
|
struct RoomMemberState {
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
linkMetadataProvider: hideTimelineMedia ? nil : linkMetadataProvider,
|
linkMetadataProvider: hideTimelineMedia ? nil : linkMetadataProvider,
|
||||||
mapTilerConfiguration: appSettings.mapTilerConfiguration,
|
mapTilerConfiguration: appSettings.mapTilerConfiguration,
|
||||||
|
enableKeyShareOnInvite: appSettings.enableKeyShareOnInvite,
|
||||||
bindings: .init(reactionsCollapsed: [:])),
|
bindings: .init(reactionsCollapsed: [:])),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
@@ -674,6 +675,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
actionsSubject.send(.displayResolveSendFailure(failure: failure,
|
actionsSubject.send(.displayResolveSendFailure(failure: failure,
|
||||||
sendHandle: sendHandle))
|
sendHandle: sendHandle))
|
||||||
|
|
||||||
|
} else if let forwarderMessage = eventTimelineItem.properties.encryptionForwarder?.message {
|
||||||
|
displayAlert(.encryptionForwarder(forwarderMessage))
|
||||||
} else if let authenticityMessage = eventTimelineItem.properties.encryptionAuthenticity?.message {
|
} else if let authenticityMessage = eventTimelineItem.properties.encryptionAuthenticity?.message {
|
||||||
displayAlert(.encryptionAuthenticity(authenticityMessage))
|
displayAlert(.encryptionAuthenticity(authenticityMessage))
|
||||||
}
|
}
|
||||||
@@ -1003,6 +1006,14 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
state.bindings.alertInfo = .init(id: type,
|
state.bindings.alertInfo = .init(id: type,
|
||||||
title: message,
|
title: message,
|
||||||
primaryButton: .init(title: L10n.actionOk, action: nil))
|
primaryButton: .init(title: L10n.actionOk, action: nil))
|
||||||
|
case .encryptionForwarder(let message):
|
||||||
|
state.bindings.alertInfo = .init(id: type,
|
||||||
|
title: message,
|
||||||
|
primaryButton: .init(title: L10n.actionOk, action: nil),
|
||||||
|
secondaryButton: .init(title: L10n.actionLearnMore) { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
appMediator.open(appSettings.historySharingDetailsURL)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,11 +322,18 @@ private extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension TimelineItemKeyForwarder {
|
||||||
|
static var test: TimelineItemKeyForwarder {
|
||||||
|
TimelineItemKeyForwarder(id: "@alice:matrix.org", displayName: "alice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
|
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel: TimelineViewModel = {
|
static let viewModel: TimelineViewModel = {
|
||||||
let appSettings = AppSettings()
|
let appSettings = AppSettings()
|
||||||
|
appSettings.enableKeyShareOnInvite = true
|
||||||
appSettings.threadsEnabled = true
|
appSettings.threadsEnabled = true
|
||||||
|
|
||||||
let roomProxy = JoinedRoomProxyMock(.init())
|
let roomProxy = JoinedRoomProxyMock(.init())
|
||||||
@@ -385,6 +392,9 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
.padding(.bottom, 20)
|
.padding(.bottom, 20)
|
||||||
encryptionAuthenticity
|
encryptionAuthenticity
|
||||||
.previewDisplayName("Encryption Indicators")
|
.previewDisplayName("Encryption Indicators")
|
||||||
|
encryptionForwarder
|
||||||
|
.previewLayout(.sizeThatFits)
|
||||||
|
.previewDisplayName("Encryption Forwarder Info")
|
||||||
pinned
|
pinned
|
||||||
.previewDisplayName("Pinned messages")
|
.previewDisplayName("Pinned messages")
|
||||||
.previewLayout(.fixed(width: 390, height: 1150))
|
.previewLayout(.fixed(width: 390, height: 1150))
|
||||||
@@ -540,6 +550,82 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.environment(\.timelineContext, 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 {
|
private struct MockTimelineContent: View {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ extension View {
|
|||||||
adjustedDeliveryStatus: TimelineItemDeliveryStatus?,
|
adjustedDeliveryStatus: TimelineItemDeliveryStatus?,
|
||||||
context: TimelineViewModel.Context) -> some View {
|
context: TimelineViewModel.Context) -> some View {
|
||||||
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
|
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
|
||||||
adjustedDeliveryStatus: adjustedDeliveryStatus),
|
adjustedDeliveryStatus: adjustedDeliveryStatus,
|
||||||
|
enableKeyShareOnInvite: context.viewState.enableKeyShareOnInvite),
|
||||||
context: context))
|
context: context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +61,7 @@ private struct TimelineItemSendInfoLabel: View {
|
|||||||
switch sendInfo.status {
|
switch sendInfo.status {
|
||||||
case .sendingFailed: \.errorSolid
|
case .sendingFailed: \.errorSolid
|
||||||
case .encryptionAuthenticity(let authenticity): authenticity.icon
|
case .encryptionAuthenticity(let authenticity): authenticity.icon
|
||||||
|
case .encryptionForwarder: \.info
|
||||||
case .none: nil
|
case .none: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,6 +70,7 @@ private struct TimelineItemSendInfoLabel: View {
|
|||||||
switch sendInfo.status {
|
switch sendInfo.status {
|
||||||
case .sendingFailed: L10n.commonSendingFailed
|
case .sendingFailed: L10n.commonSendingFailed
|
||||||
case .encryptionAuthenticity(let authenticity): authenticity.message
|
case .encryptionAuthenticity(let authenticity): authenticity.message
|
||||||
|
case .encryptionForwarder(let forwarder): forwarder.message
|
||||||
case .none: nil
|
case .none: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +114,11 @@ private struct TimelineItemSendInfoLabel: View {
|
|||||||
|
|
||||||
/// All the data needed to render a timeline item's send info label.
|
/// All the data needed to render a timeline item's send info label.
|
||||||
private struct TimelineItemSendInfo {
|
private struct TimelineItemSendInfo {
|
||||||
enum Status { case sendingFailed, encryptionAuthenticity(EncryptionAuthenticity) }
|
enum Status {
|
||||||
|
case sendingFailed
|
||||||
|
case encryptionAuthenticity(EncryptionAuthenticity)
|
||||||
|
case encryptionForwarder(TimelineItemKeyForwarder)
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes how the content and the send info should be arranged inside a bubble
|
/// Describes how the content and the send info should be arranged inside a bubble
|
||||||
enum LayoutType {
|
enum LayoutType {
|
||||||
@@ -131,6 +138,8 @@ private struct TimelineItemSendInfo {
|
|||||||
.compound.textCriticalPrimary
|
.compound.textCriticalPrimary
|
||||||
case .encryptionAuthenticity(let authenticity):
|
case .encryptionAuthenticity(let authenticity):
|
||||||
authenticity.foregroundStyle
|
authenticity.foregroundStyle
|
||||||
|
case .encryptionForwarder:
|
||||||
|
.compound.textSecondary
|
||||||
case .none:
|
case .none:
|
||||||
.compound.textSecondary
|
.compound.textSecondary
|
||||||
}
|
}
|
||||||
@@ -138,7 +147,7 @@ private struct TimelineItemSendInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension TimelineItemSendInfo {
|
private extension TimelineItemSendInfo {
|
||||||
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) {
|
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?, enableKeyShareOnInvite: Bool) {
|
||||||
itemID = timelineItem.id
|
itemID = timelineItem.id
|
||||||
localizedString = timelineItem.localizedSendInfo
|
localizedString = timelineItem.localizedSendInfo
|
||||||
|
|
||||||
@@ -146,6 +155,8 @@ private extension TimelineItemSendInfo {
|
|||||||
.sendingFailed
|
.sendingFailed
|
||||||
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
|
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
|
||||||
.encryptionAuthenticity(authenticity)
|
.encryptionAuthenticity(authenticity)
|
||||||
|
} else if enableKeyShareOnInvite, let forwarder = timelineItem.properties.encryptionForwarder {
|
||||||
|
.encryptionForwarder(forwarder)
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
@@ -184,6 +195,12 @@ private extension EncryptionAuthenticity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension TimelineItemKeyForwarder {
|
||||||
|
static var test: TimelineItemKeyForwarder {
|
||||||
|
TimelineItemKeyForwarder(id: "@alice:matrix.org", displayName: "alice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
||||||
@@ -208,6 +225,10 @@ struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
|||||||
localizedString: "09:47 AM",
|
localizedString: "09:47 AM",
|
||||||
status: .encryptionAuthenticity(.sentInClear(color: .red)),
|
status: .encryptionAuthenticity(.sentInClear(color: .red)),
|
||||||
layoutType: .horizontal()))
|
layoutType: .horizontal()))
|
||||||
|
TimelineItemSendInfoLabel(sendInfo: .init(itemID: .randomEvent,
|
||||||
|
localizedString: "09:47 AM",
|
||||||
|
status: .encryptionForwarder(.test),
|
||||||
|
layoutType: .horizontal()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -26,4 +26,6 @@ struct RoomTimelineItemProperties: Hashable {
|
|||||||
var orderedReadReceipts: [ReadReceipt] = []
|
var orderedReadReceipts: [ReadReceipt] = []
|
||||||
/// Authenticity warnings for item's sent in encrypted rooms.
|
/// Authenticity warnings for item's sent in encrypted rooms.
|
||||||
var encryptionAuthenticity: EncryptionAuthenticity?
|
var encryptionAuthenticity: EncryptionAuthenticity?
|
||||||
|
/// Information about the forwarder of the keys used to decrypt this message.
|
||||||
|
var encryptionForwarder: TimelineItemKeyForwarder?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2026 Element Creations Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import MatrixRustSDK
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TimelineItemKeyForwarder: Identifiable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let displayName: String?
|
||||||
|
|
||||||
|
init(id: String, displayName: String? = nil) {
|
||||||
|
self.id = id
|
||||||
|
self.displayName = displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
init(forwarderID: String, forwarderProfile: ProfileDetails) {
|
||||||
|
switch forwarderProfile {
|
||||||
|
case let .ready(displayName, _, _):
|
||||||
|
self.init(id: forwarderID,
|
||||||
|
displayName: displayName)
|
||||||
|
default:
|
||||||
|
self.init(id: forwarderID,
|
||||||
|
displayName: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var message: String {
|
||||||
|
if let displayName {
|
||||||
|
L10n.cryptoEventKeyForwardedKnownProfileDialogContent(displayName, id)
|
||||||
|
} else {
|
||||||
|
L10n.cryptoEventKeyForwardedUnknownProfileDialogContent(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,6 +110,13 @@ class EventTimelineItemProxy {
|
|||||||
|
|
||||||
lazy var sender = TimelineItemSender(senderID: item.sender, senderProfile: item.senderProfile)
|
lazy var sender = TimelineItemSender(senderID: item.sender, senderProfile: item.senderProfile)
|
||||||
|
|
||||||
|
lazy var forwarder: TimelineItemKeyForwarder? = {
|
||||||
|
guard let forwarderID = item.forwarder, let forwarderProfile = item.forwarderProfile else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return TimelineItemKeyForwarder(forwarderID: forwarderID, forwarderProfile: forwarderProfile)
|
||||||
|
}()
|
||||||
|
|
||||||
lazy var timestamp = Date(timeIntervalSince1970: TimeInterval(item.timestamp / 1000))
|
lazy var timestamp = Date(timeIntervalSince1970: TimeInterval(item.timestamp / 1000))
|
||||||
|
|
||||||
lazy var debugInfo: TimelineItemDebugInfo = {
|
lazy var debugInfo: TimelineItemDebugInfo = {
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ extension EventBasedTimelineItemProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hasStatusIcon: Bool {
|
var hasStatusIcon: Bool {
|
||||||
hasFailedToSend || properties.encryptionAuthenticity != nil
|
hasFailedToSend || properties.encryptionAuthenticity != nil || properties.encryptionForwarder != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasFailedToSend: Bool {
|
var hasFailedToSend: Bool {
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -149,7 +150,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -172,7 +174,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -195,7 +198,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -217,7 +221,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -240,7 +245,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -262,7 +268,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -284,7 +291,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -306,7 +314,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildGalleryTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
private func buildGalleryTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -329,7 +338,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -355,7 +365,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildPollTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
private func buildPollTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||||
@@ -415,7 +426,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState),
|
||||||
|
encryptionForwarder: eventItemProxy.forwarder))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:55908ffa46570a989039410f9e056afdb8211ce421247d3e19104b7218293b93
|
||||||
|
size 1203308
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6d91e86dae36030cd61951adbd820e87f0b928ba7a050975972e9a6ecd9a0812
|
||||||
|
size 1205155
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:022c8ee2dc977a2f9e8bdac90432dfc65d6ac23e5b2712f402753aeb1a0d0ff3
|
||||||
|
size 613179
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0ef7c7d66a44a77fb0cd155b09e4fd83d7f0f360ed2aa511f22b914be83ad9dd
|
||||||
|
size 615896
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c9d1903ddcd00dc637ebee2d55038c6b9415341bc0f1ef46d2432c47ecd2ae2b
|
oid sha256:d7848594961700544347776b4ec346e87dff5848a0f99e468e0a0df7b173ccfc
|
||||||
size 81888
|
size 85032
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c9d1903ddcd00dc637ebee2d55038c6b9415341bc0f1ef46d2432c47ecd2ae2b
|
oid sha256:d7848594961700544347776b4ec346e87dff5848a0f99e468e0a0df7b173ccfc
|
||||||
size 81888
|
size 85032
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ae635e987f00becec9a2c643f37a217ff5d188fa368e71358efb2d5118d7069b
|
oid sha256:ebd41dc454e46f31a4c1b84bd5309191f52b2f0d181f9c77f9bf2b584ac6d7b7
|
||||||
size 39296
|
size 42173
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ae635e987f00becec9a2c643f37a217ff5d188fa368e71358efb2d5118d7069b
|
oid sha256:ebd41dc454e46f31a4c1b84bd5309191f52b2f0d181f9c77f9bf2b584ac6d7b7
|
||||||
size 39296
|
size 42173
|
||||||
|
|||||||
@@ -527,6 +527,36 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Tap Actions
|
||||||
|
|
||||||
|
func testTapSendInfoEncryptionAuthentictyDisplaysAlert() {
|
||||||
|
// Given a room with an event whose authenticity could not be verified
|
||||||
|
let items = [TextRoomTimelineItem(eventID: "t1", encryptionAuthenticity: .verificationViolation(color: .red))]
|
||||||
|
let timelineController = MockTimelineController()
|
||||||
|
timelineController.timelineItems = items
|
||||||
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
|
XCTAssertNil(viewModel.state.bindings.alertInfo)
|
||||||
|
|
||||||
|
viewModel.process(viewAction: .itemSendInfoTapped(itemID: items[0].id))
|
||||||
|
|
||||||
|
XCTAssertEqual(viewModel.state.bindings.alertInfo?.title, "Encrypted by a previously-verified user.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTapSendInfoEncryptionForwarderDisplaysAlert() {
|
||||||
|
// Given a room with an event whose key was forwarded
|
||||||
|
let items = [TextRoomTimelineItem(eventID: "t1", keyForwarder: .test)]
|
||||||
|
let timelineController = MockTimelineController()
|
||||||
|
timelineController.timelineItems = items
|
||||||
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
|
XCTAssertNil(viewModel.state.bindings.alertInfo)
|
||||||
|
|
||||||
|
viewModel.process(viewAction: .itemSendInfoTapped(itemID: items[0].id))
|
||||||
|
|
||||||
|
XCTAssertEqual(viewModel.state.bindings.alertInfo?.title, "alice (@alice:matrix.org) shared this message since you were not in the room when it was sent.")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func makeViewModel(roomProxy: JoinedRoomProxyProtocol? = nil,
|
private func makeViewModel(roomProxy: JoinedRoomProxyProtocol? = nil,
|
||||||
@@ -579,6 +609,32 @@ private extension TextRoomTimelineItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension TextRoomTimelineItem {
|
||||||
|
init(eventID: String, keyForwarder: TimelineItemKeyForwarder) {
|
||||||
|
self.init(id: .event(uniqueID: .init(UUID().uuidString), eventOrTransactionID: .eventID(eventID)),
|
||||||
|
timestamp: .mock,
|
||||||
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
canBeRepliedTo: true,
|
||||||
|
sender: .init(id: ""),
|
||||||
|
content: .init(body: "Hello, World!"),
|
||||||
|
properties: RoomTimelineItemProperties(encryptionForwarder: keyForwarder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TextRoomTimelineItem {
|
||||||
|
init(eventID: String, encryptionAuthenticity: EncryptionAuthenticity) {
|
||||||
|
self.init(id: .event(uniqueID: .init(UUID().uuidString), eventOrTransactionID: .eventID(eventID)),
|
||||||
|
timestamp: .mock,
|
||||||
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
canBeRepliedTo: true,
|
||||||
|
sender: .init(id: ""),
|
||||||
|
content: .init(body: "Hello, World!"),
|
||||||
|
properties: RoomTimelineItemProperties(encryptionAuthenticity: encryptionAuthenticity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extension TimelineItemSender {
|
private extension TimelineItemSender {
|
||||||
init(with proxy: RoomMemberProxyMock) {
|
init(with proxy: RoomMemberProxyMock) {
|
||||||
self.init(id: proxy.userID,
|
self.init(id: proxy.userID,
|
||||||
@@ -587,3 +643,9 @@ private extension TimelineItemSender {
|
|||||||
avatarURL: proxy.avatarURL)
|
avatarURL: proxy.avatarURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension TimelineItemKeyForwarder {
|
||||||
|
static var test: TimelineItemKeyForwarder {
|
||||||
|
TimelineItemKeyForwarder(id: "@alice:matrix.org", displayName: "alice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user