From 6ed3866a7ed4f098e4cf8a29eb3c7ec5258df456 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Jul 2024 18:49:13 +0200 Subject: [PATCH 1/3] Initial implementation. --- ElementX.xcodeproj/project.pbxproj | 4 + .../Style/TimelineItemBubbledStylerView.swift | 129 ++++++++++++++++++ .../Fixtures/RoomTimelineItemFixtures.swift | 11 ++ .../TimelineItemContent/MessageShield.swift | 27 ++++ .../RoomTimelineItemProperties.swift | 2 + .../Services/Timeline/TimelineItemProxy.swift | 38 +++++- .../RoomTimelineItemFactory.swift | 18 ++- 7 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 4705d05ca7..220f07d50c 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */; }; 06F8EDF52E33A2D36BCC1161 /* AppLockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D6F88FE35A0979D2821E06 /* AppLockScreen.swift */; }; 071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; + 0746E133BCD0ED6BE997DC74 /* MessageShield.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B23FF90D0341B0F33A2D13E /* MessageShield.swift */; }; 07756D532EFE33DD1FA258E5 /* GeoURITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */; }; 077CB230153E072C94B1E6C3 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D65BCC659FD9087E49B3C25 /* AppAppearance.swift */; }; 07CC13C5729C24255348CBBD /* ElementCallWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */; }; @@ -1551,6 +1552,7 @@ 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreen.swift; sourceTree = ""; }; 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = ""; }; + 5B23FF90D0341B0F33A2D13E /* MessageShield.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageShield.swift; sourceTree = ""; }; 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = ""; }; 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = ""; }; 5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogProtocol.swift; sourceTree = ""; }; @@ -3516,6 +3518,7 @@ children = ( B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */, 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */, + 5B23FF90D0341B0F33A2D13E /* MessageShield.swift */, 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */, 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */, BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */, @@ -6347,6 +6350,7 @@ 695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */, F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */, C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */, + 0746E133BCD0ED6BE997DC74 /* MessageShield.swift in Sources */, C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */, 152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */, B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */, diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index b719c36c2f..94cfde14da 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -111,6 +111,14 @@ struct TimelineItemBubbledStylerView: View { context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id)) } + if timelineItem.shieldPosition == .outside { + e2eeShield + .background { + RoundedRectangle(cornerRadius: 12) + .fill(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) + } + } + if !timelineItem.properties.reactions.isEmpty { TimelineReactionsView(context: context, itemID: timelineItem.id, @@ -200,9 +208,48 @@ struct TimelineItemBubbledStylerView: View { content() .layoutPriority(TimelineBubbleLayout.Priority.regularText) .cornerRadius(timelineItem.contentCornerRadius) + + if timelineItem.shieldPosition == .inside { + e2eeShield + .padding(.horizontal, 4) + .padding(.bottom, -9) // 8 plus 1 extra for the timestamp padding. + .layoutPriority(TimelineBubbleLayout.Priority.regularText) + } } } + @ViewBuilder + var e2eeShield: some View { + if let shield = timelineItem.properties.shield { + Label { + Text(shield.message) + .foregroundColor(shield.color == ShieldColor.RED ? .compound.textCriticalPrimary : .compound.textSecondary) + .frame(maxWidth: 250, alignment: .leading) + .font(.compound.bodyXS) + } icon: { + if shield.color == ShieldColor.RED { + CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS) + .foregroundColor(.compound.iconCriticalPrimary) + .alignmentGuide(VerticalAlignment.top) { dimensions in + dimensions[.top] + } + } else { + CompoundIcon(\.infoSolid, size: .xSmall, relativeTo: .compound.bodyXS) + .foregroundColor(.compound.iconSecondary) + .alignmentGuide(VerticalAlignment.top) { dimensions in + dimensions[.top] + } + } + } + .labelStyle(.custom(spacing: 4, alignment: .top)) + .padding(4) + .background { + RoundedRectangle(cornerRadius: 12, style: .circular) + .stroke(shield.color == ShieldColor.RED ? .compound.borderCriticalSubtle : .compound.bgSubtlePrimary) + } + } + } + private var messageBubbleTopPadding: CGFloat { guard timelineItem.isOutgoing || isEncryptedOneToOneRoom else { return 0 } return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0 @@ -330,6 +377,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview .previewDisplayName("Replies") threads .previewDisplayName("Thread decorator") + shields + .previewDisplayName("E2E Shields") } // These akwats include a reply @@ -477,4 +526,84 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview } .environmentObject(viewModel.context) } + + static var shields: 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( + shield: MessageShield(color: .RED, message: "Encrypted by a device not verified by its owner."))), + 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, + shield: MessageShield(color: .RED, message: "Encrypted by a device not verified by its owner."))), + 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( + shield: MessageShield(color: .RED, message: "Encrypted by an unknown or deleted device."))), + 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( + shield: MessageShield(color: .GRAY, message: "The authenticity of this encrypted message can't be guaranteed on this device."))), + 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( + shield: MessageShield(color: .GRAY, message: "The authenticity of this encrypted message can't be guaranteed on this device.")))) + + 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( + shield: MessageShield(color: .GRAY, message: "The authenticity of this encrypted message can't be guaranteed on this device."))), playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10, waveform: EstimatedWaveform.mockWaveform)) + } + .environmentObject(viewModel.context) + } } diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 0d763b6cae..eaf402704d 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -30,6 +30,17 @@ enum RoomTimelineItemFixtures { sender: .init(id: "", displayName: "Jacob"), content: .init(body: "That looks so good!"), properties: RoomTimelineItemProperties(isEdited: true)), + + TextRoomTimelineItem(id: .init(timelineID: ".RoomTimelineItemFixtures.default.01", + eventID: "RoomTimelineItemFixtures.default.0"), + timestamp: "10:10 AM", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: false, + sender: .init(id: "", displayName: "Jacob"), + content: .init(body: "Some historical message"), + properties: RoomTimelineItemProperties(shield: MessageShield(color: .RED, message: "Encrypted by a device not verified by it's owner"))), TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.1", eventID: "RoomTimelineItemFixtures.default.1"), timestamp: "10:11 AM", diff --git a/ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift b/ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift new file mode 100644 index 0000000000..5169818a7a --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift @@ -0,0 +1,27 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct MessageShield: Hashable { + var color: ShieldColor + var message: String +} + +enum ShieldColor { + case RED + case GRAY +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift b/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift index ddbe6a044b..923a9b18f1 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift @@ -26,4 +26,6 @@ struct RoomTimelineItemProperties: Hashable { var deliveryStatus: TimelineItemDeliveryStatus? /// The read receipts of the item, ordered from newest to oldest var orderedReadReceipts: [ReadReceipt] = [] + /// Message shield for authenticity warnings + var shield: MessageShield? } diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index af68b34ade..93a52b776d 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -120,10 +120,46 @@ class EventTimelineItemProxy { let debugInfo = item.debugInfo() return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson) }() - + + lazy var shield: MessageShield? = { + if let shield = item.getShield(strict: false) { + return MessageShield.fromRustShield(shieldState: shield) + } + return nil + }() + lazy var readReceipts = item.readReceipts() } +extension MessageShield { + static func fromRustShield(shieldState: ShieldState) -> MessageShield? { + switch shieldState { + case .red(let message): + return MessageShield(color: .RED, message: translatableFromRawString(message)) + case .grey(let message): + return MessageShield(color: .GRAY, message: translatableFromRawString(message)) + default: break + } + return nil + } + + // There is no i18n in the rust sdk, so we have to do it here from the raw string. + private static func translatableFromRawString(_ message: String) -> String { + return switch message { + case "The authenticity of this encrypted message can't be guaranteed on this device.": + L10n.eventShieldReasonAuthenticityNotGuaranteed + case "Encrypted by an unknown or deleted device.": + L10n.eventShieldReasonUnknownDevice + case "Encrypted by a device not verified by its owner.": + L10n.eventShieldReasonUnsignedDevice + case "Encrypted by an unverified user.": + L10n.eventShieldReasonUnverifiedIdentity + // Default to the raw english string + default: message + } + } +} + struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible { let id = UUID() let model: String diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 1ef15d3229..da68220bb9 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -156,7 +156,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { blurhash: imageInfo.blurhash, properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + shield: eventItemProxy.shield)) } private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy, @@ -220,7 +221,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + shield: eventItemProxy.shield)) } private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -240,7 +242,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + shield: eventItemProxy.shield)) } private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -260,7 +263,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + shield: eventItemProxy.shield)) } private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -280,7 +284,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + shield: eventItemProxy.shield)) } private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -300,7 +305,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + shield: eventItemProxy.shield)) } private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy, From 72daf2d0ab6b094772f4c872b660e64d3daca7fe Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 17 Jul 2024 15:41:26 +0100 Subject: [PATCH 2/3] Add developer option for showing timeline item shields. --- ElementX/Sources/Application/AppSettings.swift | 4 ++++ .../FlowCoordinators/RoomFlowCoordinator.swift | 2 ++ .../DeveloperOptionsScreenModels.swift | 1 + .../View/DeveloperOptionsScreen.swift | 7 +++++++ .../TimelineItems/RoomTimelineItemFactory.swift | 15 +++++++++------ .../Sources/UITests/UITestsAppCoordinator.swift | 1 + UnitTests/Sources/TimelineItemFactoryTests.swift | 1 + 7 files changed, 25 insertions(+), 6 deletions(-) diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 2ac15cdd76..3c8240a0ef 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -47,6 +47,7 @@ final class AppSettings { case publicSearchEnabled case fuzzyRoomListSearchEnabled case pinningEnabled + case timelineItemShieldsEnabled } private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier @@ -284,6 +285,9 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.pinningEnabled, defaultValue: false, storageType: .userDefaults(store)) var pinningEnabled + + @UserPreference(key: UserDefaultsKeys.timelineItemShieldsEnabled, defaultValue: false, storageType: .userDefaults(store)) + var timelineItemShieldsEnabled #endif diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 9e3cdcb954..efbc3911eb 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -540,6 +540,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let userID = userSession.clientProxy.userID let timelineItemFactory = RoomTimelineItemFactory(userID: userID, + shieldsEnabled: appSettings.timelineItemShieldsEnabled, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) @@ -1033,6 +1034,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let userID = userSession.clientProxy.userID let timelineItemFactory = RoomTimelineItemFactory(userID: userID, + shieldsEnabled: appSettings.timelineItemShieldsEnabled, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index ba618ad297..0838cb782a 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -53,6 +53,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var elementCallBaseURLOverride: URL? { get set } var fuzzyRoomListSearchEnabled: Bool { get set } var pinningEnabled: Bool { get set } + var timelineItemShieldsEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 6462998924..9c57b2c787 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -50,6 +50,13 @@ struct DeveloperOptionsScreen: View { Text("Fuzzy searching") } } + + Section("Encryption") { + Toggle(isOn: $context.timelineItemShieldsEnabled) { + Text("Timeline item shields") + Text("Requires app reboot") + } + } Section("Element Call") { TextField(context.viewState.elementCallBaseURL.absoluteString, text: $elementCallBaseURLString) diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index da68220bb9..d33892ca95 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -24,11 +24,14 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { /// The Matrix ID of the current user. private let userID: String + private let shieldsEnabled: Bool init(userID: String, + shieldsEnabled: Bool, attributedStringBuilder: AttributedStringBuilderProtocol, stateEventStringBuilder: RoomStateEventStringBuilder) { self.userID = userID + self.shieldsEnabled = shieldsEnabled self.attributedStringBuilder = attributedStringBuilder self.stateEventStringBuilder = stateEventStringBuilder } @@ -157,7 +160,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: eventItemProxy.shield)) + shield: shieldsEnabled ? eventItemProxy.shield : nil)) } private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy, @@ -222,7 +225,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: eventItemProxy.shield)) + shield: shieldsEnabled ? eventItemProxy.shield : nil)) } private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -243,7 +246,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: eventItemProxy.shield)) + shield: shieldsEnabled ? eventItemProxy.shield : nil)) } private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -264,7 +267,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: eventItemProxy.shield)) + shield: shieldsEnabled ? eventItemProxy.shield : nil)) } private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -285,7 +288,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: eventItemProxy.shield)) + shield: shieldsEnabled ? eventItemProxy.shield : nil)) } private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -306,7 +309,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: eventItemProxy.shield)) + shield: shieldsEnabled ? eventItemProxy.shield : nil)) } private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy, diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 27d2bd2bb9..533a23e735 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -636,6 +636,7 @@ class MockScreen: Identifiable { let timelineController = RoomTimelineController(roomProxy: roomProxy, initialFocussedEventID: nil, timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org", + shieldsEnabled: true, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")), appSettings: ServiceLocator.shared.settings) diff --git a/UnitTests/Sources/TimelineItemFactoryTests.swift b/UnitTests/Sources/TimelineItemFactoryTests.swift index c7b8404106..2ba01035cc 100644 --- a/UnitTests/Sources/TimelineItemFactoryTests.swift +++ b/UnitTests/Sources/TimelineItemFactoryTests.swift @@ -24,6 +24,7 @@ class TimelineItemFactoryTests: XCTestCase { let senderUserID = "@bob:matrix.org" let factory = RoomTimelineItemFactory(userID: ownUserID, + shieldsEnabled: true, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: ownUserID)) From 93c13307a634e0583149e62cf03874d73e123fc0 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 30 Jul 2024 17:21:29 +0100 Subject: [PATCH 3/3] Refactor code to use new SendInfo.Status. --- ElementX.xcodeproj/project.pbxproj | 10 +-- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../en.lproj/Localizable.strings | 1 + .../en.lproj/Untranslated.strings | 4 - .../Sources/Application/AppSettings.swift | 6 +- .../RoomFlowCoordinator.swift | 4 +- .../Generated/Strings+Untranslated.swift | 2 - ElementX/Sources/Generated/Strings.swift | 2 + .../Style/TimelineItemBubbledStylerView.swift | 71 +++--------------- .../Style/TimelineItemSendInfoLabel.swift | 57 ++++++++++---- .../DeveloperOptionsScreenModels.swift | 2 +- .../View/DeveloperOptionsScreen.swift | 4 +- .../Fixtures/RoomTimelineItemFixtures.swift | 11 --- .../EncryptionAuthenticity.swift | 74 +++++++++++++++++++ .../TimelineItemContent/MessageShield.swift | 27 ------- .../RoomTimelineItemProperties.swift | 6 +- .../Services/Timeline/TimelineItemProxy.swift | 36 +-------- .../EventBasedTimelineItemProtocol.swift | 10 ++- .../RoomTimelineItemFactory.swift | 38 ++++++---- .../UITests/UITestsAppCoordinator.swift | 2 +- Package.resolved | 8 +- ...rView-iPad-en-GB.Encryption-Indicators.png | 3 + ...View-iPad-pseudo.Encryption-Indicators.png | 3 + ...-iPhone-15-en-GB.Encryption-Indicators.png | 3 + ...iPhone-15-pseudo.Encryption-Indicators.png | 3 + ...timelineItemSendInfoLabel-iPad-en-GB.1.png | 4 +- ...imelineItemSendInfoLabel-iPad-pseudo.1.png | 4 +- ...ineItemSendInfoLabel-iPhone-15-en-GB.1.png | 4 +- ...neItemSendInfoLabel-iPhone-15-pseudo.1.png | 4 +- .../Sources/TimelineItemFactoryTests.swift | 2 +- project.yml | 2 +- 31 files changed, 206 insertions(+), 205 deletions(-) create mode 100644 ElementX/Sources/Services/Timeline/TimelineItemContent/EncryptionAuthenticity.swift delete mode 100644 ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-en-GB.Encryption-Indicators.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-pseudo.Encryption-Indicators.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-en-GB.Encryption-Indicators.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-pseudo.Encryption-Indicators.png diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 220f07d50c..742e491716 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; }; 02F4FAE40AF63A1941FD3BBA /* NotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B7F8EE25775DE2A305CBB5 /* NotificationCenterProtocol.swift */; }; 037006FB6DF1374F94E4058D /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDCAC6CAAD65A2C24EA9C4B /* Dictionary.swift */; }; + 03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.swift */; }; 0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */; }; 044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */; }; 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; }; @@ -45,7 +46,6 @@ 06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */; }; 06F8EDF52E33A2D36BCC1161 /* AppLockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D6F88FE35A0979D2821E06 /* AppLockScreen.swift */; }; 071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; }; - 0746E133BCD0ED6BE997DC74 /* MessageShield.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B23FF90D0341B0F33A2D13E /* MessageShield.swift */; }; 07756D532EFE33DD1FA258E5 /* GeoURITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */; }; 077CB230153E072C94B1E6C3 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D65BCC659FD9087E49B3C25 /* AppAppearance.swift */; }; 07CC13C5729C24255348CBBD /* ElementCallWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */; }; @@ -1552,7 +1552,6 @@ 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreen.swift; sourceTree = ""; }; 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = ""; }; - 5B23FF90D0341B0F33A2D13E /* MessageShield.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageShield.swift; sourceTree = ""; }; 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = ""; }; 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = ""; }; 5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogProtocol.swift; sourceTree = ""; }; @@ -1773,6 +1772,7 @@ 94028A227645FA880B966211 /* WaveformSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformSource.swift; sourceTree = ""; }; 94D670124FC3E84F23A62CCF /* APNSPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNSPayload.swift; sourceTree = ""; }; 9501D11B4258DFA33BA3B40F /* ServerSelectionScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenModels.swift; sourceTree = ""; }; + 955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionAuthenticity.swift; sourceTree = ""; }; 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSRegularExpresion.swift; sourceTree = ""; }; 969694F67E844FCA51F7E051 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; }; 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomStringConvertible.swift; sourceTree = ""; }; @@ -3518,7 +3518,7 @@ children = ( B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */, 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */, - 5B23FF90D0341B0F33A2D13E /* MessageShield.swift */, + 955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.swift */, 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */, 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */, BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */, @@ -6201,6 +6201,7 @@ 8B1D5CE017EEC734CF5FE130 /* Encodable.swift in Sources */, 4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */, B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */, + 03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */, FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */, 46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */, 0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */, @@ -6350,7 +6351,6 @@ 695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */, F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */, C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */, - 0746E133BCD0ED6BE997DC74 /* MessageShield.swift in Sources */, C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */, 152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */, B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */, @@ -7557,7 +7557,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.0.30; + version = 1.0.31; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 19114cbe0e..3d793291f8 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "bc534e15fa0749d668b201b923ee57204afb868a", - "version" : "1.0.30" + "revision" : "8e2b4049fb492dcf5b0c796784b7aa7a3c099943", + "version" : "1.0.31" } }, { diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 53e7c97daa..bc19676b60 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -256,6 +256,7 @@ "error_some_messages_have_not_been_sent" = "Some messages have not been sent"; "error_unknown" = "Sorry, an error occurred"; "event_shield_reason_authenticity_not_guaranteed" = "The authenticity of this encrypted message can't be guaranteed on this device."; +"event_shield_reason_sent_in_clear" = "Sent in clear."; "event_shield_reason_unknown_device" = "Encrypted by an unknown or deleted device."; "event_shield_reason_unsigned_device" = "Encrypted by a device not verified by its owner."; "event_shield_reason_unverified_identity" = "Encrypted by an unverified user."; diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 3cf57ed4b7..2b31da559c 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -4,10 +4,6 @@ /* Used for testing */ "untranslated" = "Untranslated"; -// MARK: - Shields - -"send_info_not_encrypted" = "Not encrypted"; - // MARK: - Soft logout "soft_logout_signin_title" = "Sign in"; diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 3c8240a0ef..8e49a42c93 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -47,7 +47,7 @@ final class AppSettings { case publicSearchEnabled case fuzzyRoomListSearchEnabled case pinningEnabled - case timelineItemShieldsEnabled + case timelineItemAuthenticityEnabled } private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier @@ -286,8 +286,8 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.pinningEnabled, defaultValue: false, storageType: .userDefaults(store)) var pinningEnabled - @UserPreference(key: UserDefaultsKeys.timelineItemShieldsEnabled, defaultValue: false, storageType: .userDefaults(store)) - var timelineItemShieldsEnabled + @UserPreference(key: UserDefaultsKeys.timelineItemAuthenticityEnabled, defaultValue: false, storageType: .userDefaults(store)) + var timelineItemAuthenticityEnabled #endif diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index efbc3911eb..81619663ca 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -540,7 +540,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let userID = userSession.clientProxy.userID let timelineItemFactory = RoomTimelineItemFactory(userID: userID, - shieldsEnabled: appSettings.timelineItemShieldsEnabled, + encryptionAuthenticityEnabled: appSettings.timelineItemAuthenticityEnabled, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) @@ -1034,7 +1034,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let userID = userSession.clientProxy.userID let timelineItemFactory = RoomTimelineItemFactory(userID: userID, - shieldsEnabled: appSettings.timelineItemShieldsEnabled, + encryptionAuthenticityEnabled: appSettings.timelineItemAuthenticityEnabled, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index b9029e983d..55cfabb3ea 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -10,8 +10,6 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces internal enum UntranslatedL10n { - /// Not encrypted - internal static var sendInfoNotEncrypted: String { return UntranslatedL10n.tr("Untranslated", "send_info_not_encrypted") } /// Clear all data currently stored on this device? /// Sign in again to access your account data and messages. internal static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") } diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index cfee57a6cf..24b644e5a2 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -570,6 +570,8 @@ internal enum L10n { internal static var errorUnknown: String { return L10n.tr("Localizable", "error_unknown") } /// The authenticity of this encrypted message can't be guaranteed on this device. internal static var eventShieldReasonAuthenticityNotGuaranteed: String { return L10n.tr("Localizable", "event_shield_reason_authenticity_not_guaranteed") } + /// Sent in clear. + internal static var eventShieldReasonSentInClear: String { return L10n.tr("Localizable", "event_shield_reason_sent_in_clear") } /// Encrypted by an unknown or deleted device. internal static var eventShieldReasonUnknownDevice: String { return L10n.tr("Localizable", "event_shield_reason_unknown_device") } /// Encrypted by a device not verified by its owner. diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index 94cfde14da..06ddbd5098 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -111,14 +111,6 @@ struct TimelineItemBubbledStylerView: View { context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id)) } - if timelineItem.shieldPosition == .outside { - e2eeShield - .background { - RoundedRectangle(cornerRadius: 12) - .fill(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) - } - } - if !timelineItem.properties.reactions.isEmpty { TimelineReactionsView(context: context, itemID: timelineItem.id, @@ -208,48 +200,9 @@ struct TimelineItemBubbledStylerView: View { content() .layoutPriority(TimelineBubbleLayout.Priority.regularText) .cornerRadius(timelineItem.contentCornerRadius) - - if timelineItem.shieldPosition == .inside { - e2eeShield - .padding(.horizontal, 4) - .padding(.bottom, -9) // 8 plus 1 extra for the timestamp padding. - .layoutPriority(TimelineBubbleLayout.Priority.regularText) - } } } - @ViewBuilder - var e2eeShield: some View { - if let shield = timelineItem.properties.shield { - Label { - Text(shield.message) - .foregroundColor(shield.color == ShieldColor.RED ? .compound.textCriticalPrimary : .compound.textSecondary) - .frame(maxWidth: 250, alignment: .leading) - .font(.compound.bodyXS) - } icon: { - if shield.color == ShieldColor.RED { - CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyXS) - .foregroundColor(.compound.iconCriticalPrimary) - .alignmentGuide(VerticalAlignment.top) { dimensions in - dimensions[.top] - } - } else { - CompoundIcon(\.infoSolid, size: .xSmall, relativeTo: .compound.bodyXS) - .foregroundColor(.compound.iconSecondary) - .alignmentGuide(VerticalAlignment.top) { dimensions in - dimensions[.top] - } - } - } - .labelStyle(.custom(spacing: 4, alignment: .top)) - .padding(4) - .background { - RoundedRectangle(cornerRadius: 12, style: .circular) - .stroke(shield.color == ShieldColor.RED ? .compound.borderCriticalSubtle : .compound.bgSubtlePrimary) - } - } - } - private var messageBubbleTopPadding: CGFloat { guard timelineItem.isOutgoing || isEncryptedOneToOneRoom else { return 0 } return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0 @@ -377,8 +330,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview .previewDisplayName("Replies") threads .previewDisplayName("Thread decorator") - shields - .previewDisplayName("E2E Shields") + encryptionAuthenticity + .previewDisplayName("Encryption Indicators") } // These akwats include a reply @@ -527,7 +480,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview .environmentObject(viewModel.context) } - static var shields: some View { + static var encryptionAuthenticity: some View { VStack(spacing: 0) { RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), timestamp: "10:42", @@ -537,8 +490,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview isThreaded: false, sender: .init(id: "whoever"), content: .init(body: "A long message that should be on multiple lines."), - properties: RoomTimelineItemProperties( - shield: MessageShield(color: .RED, message: "Encrypted by a device not verified by its owner."))), + properties: RoomTimelineItemProperties(encryptionAuthenticity: .unsignedDevice(color: .red))), groupStyle: .single)) RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), @@ -550,7 +502,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview sender: .init(id: "whoever"), content: .init(body: "A long message that should be on multiple lines."), properties: RoomTimelineItemProperties(isEdited: true, - shield: MessageShield(color: .RED, message: "Encrypted by a device not verified by its owner."))), + encryptionAuthenticity: .unsignedDevice(color: .red))), groupStyle: .single)) RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), @@ -561,8 +513,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview isThreaded: false, sender: .init(id: "whoever"), content: .init(body: "Short message"), - properties: RoomTimelineItemProperties( - shield: MessageShield(color: .RED, message: "Encrypted by an unknown or deleted device."))), + properties: RoomTimelineItemProperties(encryptionAuthenticity: .unknownDevice(color: .red))), groupStyle: .first)) RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), @@ -573,8 +524,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview isThreaded: false, sender: .init(id: "whoever"), content: .init(body: "Message goes Here"), - properties: RoomTimelineItemProperties( - shield: MessageShield(color: .GRAY, message: "The authenticity of this encrypted message can't be guaranteed on this device."))), + properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray))), groupStyle: .last)) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .random, @@ -586,8 +536,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview sender: .init(id: "Bob"), content: .init(body: "Some other image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil), - properties: RoomTimelineItemProperties( - shield: MessageShield(color: .GRAY, message: "The authenticity of this encrypted message can't be guaranteed on this device.")))) + properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray)))) VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""), timestamp: "10:42", @@ -601,8 +550,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview waveform: EstimatedWaveform.mockWaveform, source: nil, contentType: nil), - properties: RoomTimelineItemProperties( - shield: MessageShield(color: .GRAY, message: "The authenticity of this encrypted message can't be guaranteed on this device."))), playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10, waveform: EstimatedWaveform.mockWaveform)) + properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray))), + playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10, waveform: EstimatedWaveform.mockWaveform)) } .environmentObject(viewModel.context) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemSendInfoLabel.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemSendInfoLabel.swift index 0c020d4ec8..834fc0d62e 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemSendInfoLabel.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemSendInfoLabel.swift @@ -55,10 +55,17 @@ private struct TimelineItemSendInfoLabel: View { var statusIcon: KeyPath? { 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, - layoutType: .horizontal())) - TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM", - status: .authenticityUnknown, + status: .encryptionAuthenticity(.unsignedDevice(color: .red)), 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())) } } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 0838cb782a..a51b15b96b 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -53,7 +53,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var elementCallBaseURLOverride: URL? { get set } var fuzzyRoomListSearchEnabled: Bool { get set } var pinningEnabled: Bool { get set } - var timelineItemShieldsEnabled: Bool { get set } + var timelineItemAuthenticityEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 9c57b2c787..18be31e8be 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -52,8 +52,8 @@ struct DeveloperOptionsScreen: View { } Section("Encryption") { - Toggle(isOn: $context.timelineItemShieldsEnabled) { - Text("Timeline item shields") + Toggle(isOn: $context.timelineItemAuthenticityEnabled) { + Text("Message authenticity warnings") Text("Requires app reboot") } } diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index eaf402704d..0d763b6cae 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -30,17 +30,6 @@ enum RoomTimelineItemFixtures { sender: .init(id: "", displayName: "Jacob"), content: .init(body: "That looks so good!"), properties: RoomTimelineItemProperties(isEdited: true)), - - TextRoomTimelineItem(id: .init(timelineID: ".RoomTimelineItemFixtures.default.01", - eventID: "RoomTimelineItemFixtures.default.0"), - timestamp: "10:10 AM", - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: "", displayName: "Jacob"), - content: .init(body: "Some historical message"), - properties: RoomTimelineItemProperties(shield: MessageShield(color: .RED, message: "Encrypted by a device not verified by it's owner"))), TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.1", eventID: "RoomTimelineItemFixtures.default.1"), timestamp: "10:11 AM", diff --git a/ElementX/Sources/Services/Timeline/TimelineItemContent/EncryptionAuthenticity.swift b/ElementX/Sources/Services/Timeline/TimelineItemContent/EncryptionAuthenticity.swift new file mode 100644 index 0000000000..fef91f098e --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItemContent/EncryptionAuthenticity.swift @@ -0,0 +1,74 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixRustSDK + +/// Represents and issue with a timeline item's authenticity such as coming from an +/// unsigned session or being sent unencrypted in an encrypted room. See Rust's +/// `ShieldStateCode` for more information about the meaning of the cases. +enum EncryptionAuthenticity: Hashable { + enum Color { case red, gray } + + case notGuaranteed(color: Color) + case unknownDevice(color: Color) + case unsignedDevice(color: Color) + case unverifiedIdentity(color: Color) + case sentInClear(color: Color) + + var message: String { + switch self { + case .notGuaranteed: + L10n.eventShieldReasonAuthenticityNotGuaranteed + case .unknownDevice: + L10n.eventShieldReasonUnknownDevice + case .unsignedDevice: + L10n.eventShieldReasonUnsignedDevice + case .unverifiedIdentity: + L10n.eventShieldReasonUnverifiedIdentity + case .sentInClear: + L10n.eventShieldReasonSentInClear + } + } +} + +extension EncryptionAuthenticity { + init?(shieldState: ShieldState) { + switch shieldState { + case .red(let code, _): + self.init(shieldStateCode: code, color: .red) + case .grey(let code, _): + self.init(shieldStateCode: code, color: .gray) + case .none: + return nil + } + } + + init(shieldStateCode: ShieldStateCode, color: EncryptionAuthenticity.Color) { + switch shieldStateCode { + case .authenticityNotGuaranteed: + self = .notGuaranteed(color: color) + case .unknownDevice: + self = .unknownDevice(color: color) + case .unsignedDevice: + self = .unsignedDevice(color: color) + case .unverifiedIdentity: + self = .unverifiedIdentity(color: color) + case .sentInClear: + self = .sentInClear(color: color) + } + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift b/ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift deleted file mode 100644 index 5169818a7a..0000000000 --- a/ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright 2024 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -struct MessageShield: Hashable { - var color: ShieldColor - var message: String -} - -enum ShieldColor { - case RED - case GRAY -} diff --git a/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift b/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift index 923a9b18f1..07b971bdbf 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift @@ -24,8 +24,8 @@ struct RoomTimelineItemProperties: Hashable { var reactions: [AggregatedReaction] = [] /// The delivery status for this item. If a sent message is echoed the value is nil. var deliveryStatus: TimelineItemDeliveryStatus? - /// The read receipts of the item, ordered from newest to oldest + /// The read receipts of the item, ordered from newest to oldest. var orderedReadReceipts: [ReadReceipt] = [] - /// Message shield for authenticity warnings - var shield: MessageShield? + /// Authenticity warnings for item's sent in encrypted rooms. + var encryptionAuthenticity: EncryptionAuthenticity? } diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index 93a52b776d..f162ae6c45 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -121,45 +121,11 @@ class EventTimelineItemProxy { return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson) }() - lazy var shield: MessageShield? = { - if let shield = item.getShield(strict: false) { - return MessageShield.fromRustShield(shieldState: shield) - } - return nil - }() + lazy var shieldState = item.getShield(strict: false) lazy var readReceipts = item.readReceipts() } -extension MessageShield { - static func fromRustShield(shieldState: ShieldState) -> MessageShield? { - switch shieldState { - case .red(let message): - return MessageShield(color: .RED, message: translatableFromRawString(message)) - case .grey(let message): - return MessageShield(color: .GRAY, message: translatableFromRawString(message)) - default: break - } - return nil - } - - // There is no i18n in the rust sdk, so we have to do it here from the raw string. - private static func translatableFromRawString(_ message: String) -> String { - return switch message { - case "The authenticity of this encrypted message can't be guaranteed on this device.": - L10n.eventShieldReasonAuthenticityNotGuaranteed - case "Encrypted by an unknown or deleted device.": - L10n.eventShieldReasonUnknownDevice - case "Encrypted by a device not verified by its owner.": - L10n.eventShieldReasonUnsignedDevice - case "Encrypted by an unverified user.": - L10n.eventShieldReasonUnverifiedIdentity - // Default to the raw english string - default: message - } - } -} - struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible { let id = UUID() let model: String diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift index 22e4291e6a..d017409f54 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift @@ -50,7 +50,11 @@ extension EventBasedTimelineItemProtocol { var pollIfAvailable: Poll? { (self as? PollRoomTimelineItem)?.poll } - + + var hasStatusIcon: Bool { + hasFailedToSend || properties.encryptionAuthenticity != nil + } + var hasFailedToSend: Bool { properties.deliveryStatus == .sendingFailed } @@ -74,8 +78,8 @@ extension EventBasedTimelineItemProtocol { whiteSpaces += 1 } - // To account for the extra spacing created by the alert icon - if hasFailedToSend { + // To account for the extra spacing created by the status icon + if hasStatusIcon { whiteSpaces += 3 } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index d33892ca95..f48a273e99 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -24,14 +24,14 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { /// The Matrix ID of the current user. private let userID: String - private let shieldsEnabled: Bool + private let encryptionAuthenticityEnabled: Bool init(userID: String, - shieldsEnabled: Bool, + encryptionAuthenticityEnabled: Bool, attributedStringBuilder: AttributedStringBuilderProtocol, stateEventStringBuilder: RoomStateEventStringBuilder) { self.userID = userID - self.shieldsEnabled = shieldsEnabled + self.encryptionAuthenticityEnabled = encryptionAuthenticityEnabled self.attributedStringBuilder = attributedStringBuilder self.stateEventStringBuilder = stateEventStringBuilder } @@ -160,7 +160,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: shieldsEnabled ? eventItemProxy.shield : nil)) + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy, @@ -225,7 +225,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: shieldsEnabled ? eventItemProxy.shield : nil)) + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -246,7 +246,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: shieldsEnabled ? eventItemProxy.shield : nil)) + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -267,7 +267,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: shieldsEnabled ? eventItemProxy.shield : nil)) + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -288,7 +288,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: shieldsEnabled ? eventItemProxy.shield : nil)) + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -309,7 +309,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), - shield: shieldsEnabled ? eventItemProxy.shield : nil)) + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -329,7 +329,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -349,7 +350,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -369,7 +371,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy, @@ -389,7 +392,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } // swiftlint:disable:next function_parameter_count @@ -438,7 +442,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(isEdited: edited, reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, - orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts))) + orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), + encryptionAuthenticity: authenticity(eventItemProxy.shieldState))) } private func buildCallInviteTimelineItem(for eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol { @@ -497,6 +502,11 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { } } + private func authenticity(_ shieldState: ShieldState?) -> EncryptionAuthenticity? { + guard encryptionAuthenticityEnabled else { return nil } + return shieldState.flatMap(EncryptionAuthenticity.init) + } + // MARK: - Message events content private func buildTextTimelineItemContent(_ messageContent: TextMessageContent) -> TextRoomTimelineItemContent { diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 533a23e735..e138a50843 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -636,7 +636,7 @@ class MockScreen: Identifiable { let timelineController = RoomTimelineController(roomProxy: roomProxy, initialFocussedEventID: nil, timelineItemFactory: RoomTimelineItemFactory(userID: "@alice:matrix.org", - shieldsEnabled: true, + encryptionAuthenticityEnabled: true, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")), appSettings: ServiceLocator.shared.settings) diff --git a/Package.resolved b/Package.resolved index 2c7d7e93e9..8ff67e001a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", - "version" : "1.4.0" + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" } }, { @@ -22,8 +22,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams", "state" : { - "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", - "version" : "5.1.2" + "revision" : "3036ba9d69cf1fd04d433527bc339dc0dc75433d", + "version" : "5.1.3" } } ], diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-en-GB.Encryption-Indicators.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-en-GB.Encryption-Indicators.png new file mode 100644 index 0000000000..489f113889 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-en-GB.Encryption-Indicators.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfcaff9f0d5680fc4dbb30c906b8ce84213b6a41f6b4fb52c2ec256902fce350 +size 437852 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-pseudo.Encryption-Indicators.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-pseudo.Encryption-Indicators.png new file mode 100644 index 0000000000..e97cd1fe45 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPad-pseudo.Encryption-Indicators.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec285cfbd7793fa8feca3e8869b776f64214b3cfd116bb6e77d9f4149732625a +size 440028 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-en-GB.Encryption-Indicators.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-en-GB.Encryption-Indicators.png new file mode 100644 index 0000000000..39476b5fa5 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-en-GB.Encryption-Indicators.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb717b5c2f4c5e5dc9f0a473934bb237eebad19e401ea08353a62d5567d99fc3 +size 343142 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-pseudo.Encryption-Indicators.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-pseudo.Encryption-Indicators.png new file mode 100644 index 0000000000..2334be92ff --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemBubbledStylerView-iPhone-15-pseudo.Encryption-Indicators.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8613ae1955fd57f8536f51e46b87913b0c6f156d8cff7db6a3e6f7f18846896 +size 344422 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-en-GB.1.png index 6736fd1f16..47a1d61850 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f04ababd10ae20834830511223418d25b49030ed0be58cb649c437acd4ed7b9 -size 81479 +oid sha256:d8a4a63fd7a948b134d02a405070c038116405d6b19f7ab45930d8d8fe25d66c +size 78530 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-pseudo.1.png index 6736fd1f16..47a1d61850 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f04ababd10ae20834830511223418d25b49030ed0be58cb649c437acd4ed7b9 -size 81479 +oid sha256:d8a4a63fd7a948b134d02a405070c038116405d6b19f7ab45930d8d8fe25d66c +size 78530 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-en-GB.1.png index c5054a9f11..132c59a02e 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1b0ecbae3765e60c67f4a468d61586652c31a5d0f4dcdc32dd35de886694288 -size 38900 +oid sha256:67533013d6b66f5256e00fbb7ee684f4ec364f640d10a9209dc14f067aa31905 +size 36782 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-pseudo.1.png index c5054a9f11..132c59a02e 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_timelineItemSendInfoLabel-iPhone-15-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1b0ecbae3765e60c67f4a468d61586652c31a5d0f4dcdc32dd35de886694288 -size 38900 +oid sha256:67533013d6b66f5256e00fbb7ee684f4ec364f640d10a9209dc14f067aa31905 +size 36782 diff --git a/UnitTests/Sources/TimelineItemFactoryTests.swift b/UnitTests/Sources/TimelineItemFactoryTests.swift index 2ba01035cc..9b75a6516a 100644 --- a/UnitTests/Sources/TimelineItemFactoryTests.swift +++ b/UnitTests/Sources/TimelineItemFactoryTests.swift @@ -24,7 +24,7 @@ class TimelineItemFactoryTests: XCTestCase { let senderUserID = "@bob:matrix.org" let factory = RoomTimelineItemFactory(userID: ownUserID, - shieldsEnabled: true, + encryptionAuthenticityEnabled: true, attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: ownUserID)) diff --git a/project.yml b/project.yml index 8b3ec05f93..91f61e8ea4 100644 --- a/project.yml +++ b/project.yml @@ -60,7 +60,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 1.0.30 + exactVersion: 1.0.31 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios