From 60918a154778a9e627f9c8df97d243d4d8deac13 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 30 Jul 2024 17:21:29 +0100 Subject: [PATCH] Refactor code to use new SendInfo.Status. --- ElementX.xcodeproj/project.pbxproj | 10 +-- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../en.lproj/Untranslated.strings | 2 +- .../Generated/Strings+Untranslated.swift | 4 +- .../Style/TimelineItemBubbledStylerView.swift | 71 +++---------------- .../Style/TimelineItemSendInfoLabel.swift | 57 +++++++++++---- .../Fixtures/RoomTimelineItemFixtures.swift | 2 +- .../EncryptionAuthenticity.swift | 71 +++++++++++++++++++ .../TimelineItemContent/MessageShield.swift | 27 ------- .../RoomTimelineItemProperties.swift | 2 +- .../Services/Timeline/TimelineItemProxy.swift | 36 +--------- .../EventBasedTimelineItemProtocol.swift | 10 ++- .../RoomTimelineItemFactory.swift | 32 ++++++--- project.yml | 2 +- 14 files changed, 165 insertions(+), 165 deletions(-) create mode 100644 ElementX/Sources/Services/Timeline/TimelineItemContent/EncryptionAuthenticity.swift delete mode 100644 ElementX/Sources/Services/Timeline/TimelineItemContent/MessageShield.swift 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/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 3cf57ed4b7..3ebd637f28 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -6,7 +6,7 @@ // MARK: - Shields -"send_info_not_encrypted" = "Not encrypted"; +"event_shield_reason_sent_in_clear" = "Sent in clear"; // MARK: - Soft logout diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index b9029e983d..92d9d322dc 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -10,8 +10,8 @@ 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") } + /// Sent in clear + internal static var eventShieldReasonSentInClear: String { return UntranslatedL10n.tr("Untranslated", "event_shield_reason_sent_in_clear") } /// 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/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/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index eaf402704d..d7d0b1551e 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -40,7 +40,7 @@ enum RoomTimelineItemFixtures { 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"))), + properties: RoomTimelineItemProperties(encryptionAuthenticity: .unsignedDevice(color: .red))), 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..55fb9af149 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItemContent/EncryptionAuthenticity.swift @@ -0,0 +1,71 @@ +// +// 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 + +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: + UntranslatedL10n.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..aeb205d96d 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemContent/RoomTimelineItemProperties.swift @@ -27,5 +27,5 @@ struct RoomTimelineItemProperties: Hashable { /// The read receipts of the item, ordered from newest to oldest var orderedReadReceipts: [ReadReceipt] = [] /// Message shield for authenticity warnings - var shield: MessageShield? + 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..6c8bbf8b9a 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -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 shieldsEnabled else { return nil } + return shieldState.flatMap(EncryptionAuthenticity.init) + } + // MARK: - Message events content private func buildTextTimelineItemContent(_ messageContent: TextMessageContent) -> TextRoomTimelineItemContent { 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