Repository: element-hq/element-x-android (language: kotlin)

Issue:
Mentions: Push notifications display user mentions differently
From element-x-ios created by [Velin92](https://github.com/Velin92): vector-im/element-x-ios#1825  

Mentions should be displayed with @username in the notification (however since the username is usually not available in the notification context, is also okay to prefix with @ the hyperlink text just to make it clear that is a mention from the notification)

Also if you have been mentioned in the notification we should prefix the notification body with:
"Username mentioned you"
or
"You have been mentioned" if the username of the sender is not available


Discussion hints:
nan

Pull request: Add special notifications for mentions
<!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
 
## Type of change

- [x] Feature
- [ ] Bugfix
- [ ] Technical
- [ ] Other :

## Content

- Sets a special type of notification message for received messages mentioning the current user.

## Motivation and context

Closes #1451 

## Screenshots / GIFs

|Before|After|
|-|-|
||<img width="484" alt="image" src="https://github.com/vector-im/element-x-android/assets/480955/aa1a98b5-5c3d-439b-85c6-e979883a7598">|

## Tests

<!-- Explain how you tested your development -->

- Receive a message mentioning you in a room you have notifications enabled.

## Tested devices

- [x] Physical
- [ ] Emulator
- OS version(s): 11

## Checklist

<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->

- [ ] Changes have been tested on an Android device or Android emulator with API 23
- [ ] UI change has been tested on both light and dark themes
- [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
- [x] Pull request is based on the develop branch
- [x] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#changelog
- [x] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off)
- [x] You've made a self review of your PR


Test command: ./gradlew :libraries:push:impl:testDebugUnitTest --tests "io.element.android.libraries.push.impl.notifications.NotifiableEventResolverTest"

Fix patch:
diff --git a/changelog.d/1451.feature b/changelog.d/1451.feature
new file mode 100644
index 00000000000..1869f0ecd50
--- /dev/null
+++ b/changelog.d/1451.feature
@@ -0,0 +1,1 @@
+Display different notifications for mentions.
diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt
index 9093b366151..26c8a7d55b6 100644
--- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt
+++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt
@@ -82,6 +82,11 @@ class NotifiableEventResolver @Inject constructor(
         return when (val content = this.content) {
             is NotificationContent.MessageLike.RoomMessage -> {
                 val messageBody = descriptionFromMessageContent(content, senderDisplayName ?: content.senderId.value)
+                val notificationBody = if (hasMention) {
+                    stringProvider.getString(R.string.notification_mentioned_you_body, messageBody)
+                } else {
+                    messageBody
+                }
                 buildNotifiableMessageEvent(
                     sessionId = userId,
                     senderId = content.senderId,
@@ -90,7 +95,7 @@ class NotifiableEventResolver @Inject constructor(
                     noisy = isNoisy,
                     timestamp = this.timestamp,
                     senderName = senderDisplayName,
-                    body = messageBody,
+                    body = notificationBody,
                     imageUriString = this.contentUrl,
                     roomName = roomDisplayName,
                     roomIsDirect = isDirect,
diff --git a/libraries/push/impl/src/main/res/values-cs/translations.xml b/libraries/push/impl/src/main/res/values-cs/translations.xml
index 84525a427ef..2b76d7b93e4 100644
--- a/libraries/push/impl/src/main/res/values-cs/translations.xml
+++ b/libraries/push/impl/src/main/res/values-cs/translations.xml
@@ -8,6 +8,7 @@
   <string name="notification_invitation_action_join">"Vstoupit"</string>
   <string name="notification_invitation_action_reject">"Odmítnout"</string>
   <string name="notification_invite_body">"Vás pozval(a) do chatu"</string>
+  <string name="notification_mentioned_you_body">"Zmínili vás: %1$s"</string>
   <string name="notification_new_messages">"Nové zprávy"</string>
   <string name="notification_reaction_body">"Reagoval(a) s %1$s"</string>
   <string name="notification_room_action_mark_as_read">"Označit jako přečtené"</string>
diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml
index fae900dd9c3..c816850f98b 100644
--- a/libraries/ui-strings/src/main/res/values/localazy.xml
+++ b/libraries/ui-strings/src/main/res/values/localazy.xml
@@ -227,6 +227,9 @@
     <item quantity="other">"%d votes"</item>
   </plurals>
   <string name="preference_rageshake">"Rageshake to report bug"</string>
+  <string name="screen_edit_poll_delete_confirmation">"Are you sure you want to delete this poll?"</string>
+  <string name="screen_edit_poll_delete_confirmation_title">"Delete Poll"</string>
+  <string name="screen_edit_poll_title">"Edit poll"</string>
   <string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
   <string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
   <string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>


Test patch:
diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt
index 819a1e94a23..6276983cc9d 100644
--- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt
+++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt
@@ -25,8 +25,10 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState
 import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
 import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
 import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
 import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
 import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
 import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
 import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
@@ -51,6 +53,7 @@ import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
 
 @RunWith(RobolectricTestRunner::class)
 class NotifiableEventResolverTest {
@@ -97,6 +100,71 @@ class NotifiableEventResolverTest {
         assertThat(result).isEqualTo(expectedResult)
     }
 
+    @Test
+    @Config(qualifiers = "en")
+    fun `resolve event message with mention`() = runTest {
+        val sut = createNotifiableEventResolver(
+            notificationResult = Result.success(
+                createNotificationData(
+                    content = NotificationContent.MessageLike.RoomMessage(
+                        senderId = A_USER_ID_2,
+                        messageType = TextMessageType(body = "Hello world", formatted = null)
+                    ),
+                    hasMention = true,
+                )
+            )
+        )
+        val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
+        val expectedResult = createNotifiableMessageEvent(body = "Mentioned you: Hello world")
+        assertThat(result).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun `resolve HTML formatted event message text takes plain text version`() = runTest {
+        val sut = createNotifiableEventResolver(
+            notificationResult = Result.success(
+                createNotificationData(
+                    content = NotificationContent.MessageLike.RoomMessage(
+                        senderId = A_USER_ID_2,
+                        messageType = TextMessageType(
+                            body = "Hello world!",
+                            formatted = FormattedBody(
+                                body = "<b>Hello world</b>",
+                                format = MessageFormat.HTML,
+                            )
+                        )
+                    )
+                )
+            )
+        )
+        val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
+        val expectedResult = createNotifiableMessageEvent(body = "Hello world")
+        assertThat(result).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun `resolve incorrectly formatted event message text uses fallback`() = runTest {
+        val sut = createNotifiableEventResolver(
+            notificationResult = Result.success(
+                createNotificationData(
+                    content = NotificationContent.MessageLike.RoomMessage(
+                        senderId = A_USER_ID_2,
+                        messageType = TextMessageType(
+                            body = "Hello world",
+                            formatted = FormattedBody(
+                                body = "???Hello world!???",
+                                format = MessageFormat.UNKNOWN,
+                            )
+                        )
+                    )
+                )
+            )
+        )
+        val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
+        val expectedResult = createNotifiableMessageEvent(body = "Hello world")
+        assertThat(result).isEqualTo(expectedResult)
+    }
+
     @Test
     fun `resolve event message audio`() = runTest {
         val sut = createNotifiableEventResolver(
@@ -430,6 +498,7 @@ class NotifiableEventResolverTest {
     private fun createNotificationData(
         content: NotificationContent,
         isDirect: Boolean = false,
+        hasMention: Boolean = false,
     ): NotificationData {
         return NotificationData(
             eventId = AN_EVENT_ID,
@@ -444,7 +513,7 @@ class NotifiableEventResolverTest {
             timestamp = A_TIMESTAMP,
             content = content,
             contentUrl = null,
-            hasMention = false,
+            hasMention = hasMention,
         )
     }