Repository: matrix-org/matrix-rust-sdk (Rust)

Issue:
Historical messages that cannot be decrypted displays a red padlock saying the message is not encrypted

## Step to reproduce

Join a room with some history that you don't have access to on EXA for example.


## Actual:

<img width="643" alt="image" src="https://github.com/user-attachments/assets/b5293206-9b95-4364-a291-b6dea6276fc3">

English text is `You don't have access to this message`
On tap the red padlock says:
<img width="281" alt="image" src="https://github.com/user-attachments/assets/e240d299-a251-4efd-bfd9-c7e2f12ccd20">

## Expected

There should not be a red padlock icon on this message. It is encrypted (and you don't have the key to decrypt)


Discussion:

Patch:
diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md
index 6945684010a..55f9cb79440 100644
--- a/crates/matrix-sdk-ui/CHANGELOG.md
+++ b/crates/matrix-sdk-ui/CHANGELOG.md
@@ -12,6 +12,8 @@ Bug fixes:
 
 - `UtdHookManager` no longer re-reports UTD events as late decryptions.
   ([#3480](https://github.com/matrix-org/matrix-rust-sdk/pull/3480))
+- Messages that we were unable to decrypt no longer display a red padlock.
+  ([#3956](https://github.com/matrix-org/matrix-rust-sdk/issues/3956))
 
 Other changes:
 
diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
index 3a8e7033d10..d742dc3be0e 100644
--- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
+++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
@@ -381,6 +381,11 @@ impl EventTimelineItem {
             return None;
         }
 
+        // An unable-to-decrypt message has no authenticity shield.
+        if let TimelineItemContent::UnableToDecrypt(_) = self.content() {
+            return None;
+        }
+
         match self.encryption_info() {
             Some(info) => {
                 if strict {


Test patch:
diff --git a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs
index 73e2798f6c6..ad86b21c913 100644
--- a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs
+++ b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs
@@ -4,7 +4,15 @@ use matrix_sdk_base::deserialized_responses::{ShieldState, ShieldStateCode};
 use matrix_sdk_test::{async_test, sync_timeline_event, ALICE};
 use ruma::{
     event_id,
-    events::{room::message::RoomMessageEventContent, AnyMessageLikeEventContent},
+    events::{
+        room::{
+            encrypted::{
+                EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
+            },
+            message::RoomMessageEventContent,
+        },
+        AnyMessageLikeEventContent,
+    },
 };
 use stream_assert::assert_next_matches;
 
@@ -111,3 +119,44 @@ async fn test_local_sent_in_clear_shield() {
         Some(ShieldState::Red { code: ShieldStateCode::SentInClear, message: "Not encrypted." })
     );
 }
+
+#[async_test]
+/// Test a bug that was causing unable to decrypt messages to have a `message
+/// sent in clear` red warning.
+async fn test_utd_shield() {
+    // Given we are in an encrypted room
+    let timeline = TestTimeline::with_is_room_encrypted(true);
+    let mut stream = timeline.subscribe().await;
+
+    let f = &timeline.factory;
+
+    // When we receive a message that we can't decrypt
+    timeline
+        .handle_live_event(
+            f.event(RoomEncryptedEventContent::new(
+                EncryptedEventScheme::MegolmV1AesSha2(
+                    MegolmV1AesSha2ContentInit {
+                        ciphertext: "\
+                            AwgAEpABNOd7Rxpc/98gaaOanApQ/h40uNyYE/aiFd8PKeQPH65bwuxBy/glodmteryH\
+                            4t5d0cKSPjb+996yK90+A8YUevQKBuC+/+4iRF2CSqMNvArdOCnFHJdZBuCyRP6W82DZ\
+                            sR1w5X/tKGs/A9egJdxomLCzMRZarayTXUlgMT8Kj7E9zKOgyLEZGki6Y9IPybfrU3+S\
+                            b4VbF7RKY395/lIZFiLvJ5hUT+Ao1k13opeTE9GHtdOK0GzQPVFLnN61pRa3K/vV9Otk\
+                            D0QbVS/4mE3C29+yIC1lEkwA"
+                            .to_owned(),
+                        sender_key: "peI8cfSKqZvTOAfY0Od2e7doDpJ1cxdBsOhSceTLU3E".to_owned(),
+                        device_id: "KDCTEHOVSS".into(),
+                        session_id: "C25PoE+4MlNidQD0YU5ibZqHawV0zZ/up7R8vYJBYTY".into(),
+                    }
+                    .into(),
+                ),
+                None,
+            ))
+            .sender(&ALICE),
+        )
+        .await;
+
+    // Then the message is displayed with no shield
+    let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
+    let shield = item.as_event().unwrap().get_shield(false);
+    assert!(shield.is_none());
+}