Implements push notifications for LLS via MSC4505 (#5872)

* Render live location share start push notifications in the NSE

Live location sharing starts with a cleartext org.matrix.msc3672.beacon_info
STATE event, which the NSE previously rendered as the generic fallback
notification because NotificationContentBuilder only handled message-like
event content.

- Handle .state(.beaconInfo) in NotificationContentBuilder: sender display
  name as the title (same as other messages) with a new localized body,
  "Started sharing their live location".
- Let beaconInfo state events through NotificationHandler's preprocessing
  (all other state events are still discarded).
- Add notification_live_location_started_body, imported from Localazy.
- Unit tests for the new state-event notification paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* rename processMessageLike in processAsMessage

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Mauro Romito <mauro.romito@element.io>
This commit is contained in:
Matthew Hodgson
2026-07-27 15:31:47 +01:00
committed by GitHub
parent 5262350b40
commit a8c79fdd46
6 changed files with 97 additions and 27 deletions
@@ -8,6 +8,7 @@
import Dynamic
@testable import ElementX
import MatrixRustSDK
import MatrixRustSDKMocks
import Testing
import UserNotifications
@@ -221,6 +222,56 @@ nonisolated struct NotificationContentBuilderTests {
#expect(notificationContent.attachments == [])
}
@Test
mutating func liveLocationStartNotification() async {
let event = TimelineEventSDKMock()
event.eventIdReturnValue = UUID().uuidString
event.contentReturnValue = .state(content: .beaconInfo)
let notificationItem = NotificationItemProxyMock(.init(event: .timeline(event: event),
roomID: "!test:matrix.org",
receiverID: "@bob:matrix.org",
senderDisplayName: "Alice",
roomDisplayName: "Alice",
roomJoinedMembers: 2,
isRoomDirect: true,
isRoomPrivate: true,
isNoisy: true))
await notificationContentBuilder.process(notificationContent: &notificationContent,
notificationItem: notificationItem,
mediaProvider: mediaProvider)
let communicationContext = Dynamic(notificationContent, memberName: "communicationContext")
#expect(communicationContext.sender.displayName == "Alice")
#expect(notificationContent.body == L10n.notificationLiveLocationStartedBody)
#expect(notificationContent.categoryIdentifier == NotificationConstants.Category.message)
#expect(notificationContent.sound != nil)
}
@Test
mutating func otherStateEventNotification() async {
let event = TimelineEventSDKMock()
event.eventIdReturnValue = UUID().uuidString
event.contentReturnValue = .state(content: .roomName)
let notificationItem = NotificationItemProxyMock(.init(event: .timeline(event: event),
roomID: "!test:matrix.org",
receiverID: "@bob:matrix.org",
senderDisplayName: "Alice",
roomDisplayName: "Alice",
roomJoinedMembers: 2,
isRoomDirect: true,
isRoomPrivate: true,
isNoisy: true))
await notificationContentBuilder.process(notificationContent: &notificationContent,
notificationItem: notificationItem,
mediaProvider: mediaProvider)
#expect(notificationContent.body == L10n.notification)
}
@Test
mutating func roomMessageNotificationWithThreadAndMention() async {
let notificationItem = NotificationItemProxyMock(.init(roomID: "!testroom:matrix.org",