mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-05 23:47:44 +02:00
Merge pull request #4662 from vector-im/doug/4132_improve_notification_content
Improve notification content
This commit is contained in:
@@ -41,6 +41,9 @@ class CommonConfiguration: NSObject, Configurable {
|
||||
// Enable lazy loading
|
||||
settings.syncWithLazyLoadOfRoomMembers = true
|
||||
|
||||
// Customize the default notification content
|
||||
settings.notificationBodyLocalizationKey = "NOTIFICATION"
|
||||
|
||||
settings.messageDetailsAllowSharing = BuildSettings.messageDetailsAllowShare
|
||||
settings.messageDetailsAllowSaving = BuildSettings.messageDetailsAllowSave
|
||||
settings.messageDetailsAllowCopyingMedia = BuildSettings.messageDetailsAllowCopyMedia
|
||||
|
||||
@@ -14,9 +14,21 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/** General **/
|
||||
|
||||
"NOTIFICATION" = "Notification";
|
||||
|
||||
/** Titles **/
|
||||
|
||||
/* Message title for a specific person in a named room */
|
||||
"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@";
|
||||
|
||||
/* New message reply from a specific person, not referencing a room. */
|
||||
"REPLY_FROM_USER_TITLE" = "%@ replied";
|
||||
|
||||
/* New message reply from a specific person in a named room. */
|
||||
"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ replied in %@";
|
||||
|
||||
/** Single, end-to-end encrypted messages (ie. we don't know what they say) */
|
||||
|
||||
/* New message from a specific person, not referencing a room */
|
||||
@@ -39,14 +51,26 @@
|
||||
/* New action message from a specific person in a named room. */
|
||||
"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@";
|
||||
|
||||
/** Image Messages **/
|
||||
/** Media Messages **/
|
||||
|
||||
/* New action message from a specific person, not referencing a room. */
|
||||
"IMAGE_FROM_USER" = "%@ sent a picture %@";
|
||||
/* New image message from a specific person, not referencing a room. */
|
||||
"PICTURE_FROM_USER" = "%@ sent a picture";
|
||||
|
||||
/* New action message from a specific person in a named room. */
|
||||
/* New image message from a specific person in a named room. */
|
||||
"IMAGE_FROM_USER_IN_ROOM" = "%@ posted a picture %@ in %@";
|
||||
|
||||
/* New video message from a specific person, not referencing a room. */
|
||||
"VIDEO_FROM_USER" = "%@ sent a video";
|
||||
|
||||
/* New audio message from a specific person, not referencing a room. */
|
||||
"AUDIO_FROM_USER" = "%@ sent an audio file %@";
|
||||
|
||||
/* New voice message from a specific person, not referencing a room. */
|
||||
"VOICE_MESSAGE_FROM_USER" = "%@ sent a voice message";
|
||||
|
||||
/* New file message from a specific person, not referencing a room. */
|
||||
"FILE_FROM_USER" = "%@ sent a file %@";
|
||||
|
||||
/* A single unread message in a room */
|
||||
"SINGLE_UNREAD_IN_ROOM" = "You received a message in %@";
|
||||
|
||||
@@ -67,6 +91,7 @@
|
||||
/* New message indicator on a room */
|
||||
"MESSAGE_IN_X" = "Message in %@";
|
||||
|
||||
/* New message with hidden content due to PIN enabled */
|
||||
"MESSAGE_PROTECTED" = "New Message";
|
||||
|
||||
/** Coalesced messages **/
|
||||
@@ -92,6 +117,14 @@
|
||||
/* Look, stuff's happened, alright? Just open the app. */
|
||||
"MSGS_IN_TWO_PLUS_ROOMS" = "%@ new messages in %@, %@ and others";
|
||||
|
||||
/** Reactions **/
|
||||
|
||||
/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */
|
||||
"REACTION_FROM_USER" = "%@ reacted %@";
|
||||
|
||||
/* A user has reacted to a message, but the reaction content is unknown */
|
||||
"GENERIC_REACTION_FROM_USER" = "%@ sent a reaction";
|
||||
|
||||
/** Invites **/
|
||||
|
||||
/* A user has invited you to a chat */
|
||||
@@ -103,6 +136,20 @@
|
||||
/* A user has invited you to a named room */
|
||||
"USER_INVITE_TO_NAMED_ROOM" = "%@ has invited you to %@";
|
||||
|
||||
/** Membership Updates **/
|
||||
|
||||
/* A user has change their name to a new name */
|
||||
"USER_UPDATED_DISPLAYNAME" = "%@ changed their name to %@";
|
||||
|
||||
/* A user has change their name to a new name which we don't know */
|
||||
"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ changed their name";
|
||||
|
||||
/* A user has change their avatar */
|
||||
"USER_UPDATED_AVATAR" = "%@ changed their avatar";
|
||||
|
||||
/* A user's membership has updated in an unknown way */
|
||||
"USER_MEMBERSHIP_UPDATED" = "%@ updated their profile";
|
||||
|
||||
/** Calls **/
|
||||
|
||||
/* Incoming one-to-one voice call */
|
||||
|
||||
@@ -186,13 +186,13 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
MXLog.debug("[NotificationService] preprocessPayload: Do not preprocess because app protection is set")
|
||||
return
|
||||
}
|
||||
|
||||
// If a room summary is available, use the displayname for the best attempt title.
|
||||
guard let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId) else { return }
|
||||
guard let roomDisplayName = roomSummary.displayname else { return }
|
||||
if roomSummary.isDirect == true {
|
||||
bestAttemptContents[eventId]?.body = NSString.localizedUserNotificationString(forKey: "MESSAGE_FROM_X", arguments: [roomDisplayName as Any])
|
||||
} else {
|
||||
bestAttemptContents[eventId]?.body = NSString.localizedUserNotificationString(forKey: "MESSAGE_IN_X", arguments: [roomDisplayName as Any])
|
||||
}
|
||||
bestAttemptContents[eventId]?.title = roomDisplayName
|
||||
|
||||
// At this stage we don't know the message type, so leave the body as set in didReceive.
|
||||
}
|
||||
|
||||
private func fetchEvent(withEventId eventId: String, roomId: String, allowSync: Bool = true) {
|
||||
@@ -240,10 +240,10 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
isUnwantedNotification = true
|
||||
}
|
||||
|
||||
// modify the best attempt content, to be able to use in future
|
||||
if self.ongoingVoIPPushRequests[event.eventId] == true {
|
||||
// modify the best attempt content, to be able to use in the future
|
||||
self.bestAttemptContents[event.eventId] = content
|
||||
|
||||
if self.ongoingVoIPPushRequests[event.eventId] == true {
|
||||
// There is an ongoing VoIP Push request for this event, wait for it to be completed.
|
||||
// When it completes, it'll continue with the bestAttemptContent.
|
||||
return
|
||||
@@ -326,7 +326,10 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
} else {
|
||||
MXLog.debug("[NotificationService] notificationContent: Do not attempt to send a VoIP push, there is not enough time to process it.")
|
||||
}
|
||||
case .roomMessage, .roomEncrypted:
|
||||
case .roomEncrypted:
|
||||
// If unable to decrypt the event, use the fallback.
|
||||
break
|
||||
case .roomMessage:
|
||||
if isRoomMentionsOnly {
|
||||
// A local notification will be displayed only for highlighted notification.
|
||||
var isHighlighted = false
|
||||
@@ -345,6 +348,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
|
||||
if !isHighlighted {
|
||||
#warning("In practice, this only hides the notification's content. An empty notification may be less useful in this instance?")
|
||||
// Ignore this notif.
|
||||
MXLog.debug("[NotificationService] notificationContentForEvent: Ignore non highlighted notif in mentions only room")
|
||||
onComplete(nil)
|
||||
@@ -352,62 +356,105 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
|
||||
var msgType = event.content["msgtype"] as? String
|
||||
let msgType = event.content["msgtype"] as? String
|
||||
let messageContent = event.content["body"] as? String
|
||||
let isReply = event.isReply()
|
||||
|
||||
if isReply {
|
||||
notificationTitle = self.replyTitle(for: eventSenderName, in: roomDisplayName)
|
||||
} else {
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
}
|
||||
|
||||
if event.isEncrypted && !self.showDecryptedContentInNotifications {
|
||||
// Hide the content
|
||||
msgType = nil
|
||||
}
|
||||
|
||||
// Display the room name only if it is different than the sender name
|
||||
if roomDisplayName != nil && roomDisplayName != eventSenderName {
|
||||
notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any])
|
||||
|
||||
if msgType == kMXMessageTypeText {
|
||||
notificationBody = messageContent
|
||||
} else if msgType == kMXMessageTypeEmote {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any])
|
||||
} else if msgType == kMXMessageTypeImage {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any])
|
||||
} else {
|
||||
// Encrypted messages falls here
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: [])
|
||||
break
|
||||
}
|
||||
} else {
|
||||
notificationTitle = eventSenderName
|
||||
|
||||
switch msgType {
|
||||
case kMXMessageTypeText:
|
||||
notificationBody = messageContent
|
||||
break
|
||||
case kMXMessageTypeEmote:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any])
|
||||
break
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName, messageContent as Any])
|
||||
case kMXMessageTypeImage:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any])
|
||||
break
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "PICTURE_FROM_USER", arguments: [eventSenderName])
|
||||
case kMXMessageTypeVideo:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "VIDEO_FROM_USER", arguments: [eventSenderName])
|
||||
case kMXMessageTypeAudio:
|
||||
if event.isVoiceMessage() {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "VOICE_MESSAGE_FROM_USER", arguments: [eventSenderName])
|
||||
} else {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "AUDIO_FROM_USER", arguments: [eventSenderName, messageContent as Any])
|
||||
}
|
||||
case kMXMessageTypeFile:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "FILE_FROM_USER", arguments: [eventSenderName, messageContent as Any])
|
||||
|
||||
// All other message types such as text, notice, server notice etc
|
||||
default:
|
||||
// Encrypted messages falls here
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: [])
|
||||
break
|
||||
if event.isReply() {
|
||||
let parser = MXReplyEventParser()
|
||||
let replyParts = parser.parse(event)
|
||||
notificationBody = replyParts.bodyParts.replyText
|
||||
} else {
|
||||
notificationBody = messageContent
|
||||
}
|
||||
}
|
||||
break
|
||||
case .roomMember:
|
||||
if roomDisplayName != nil && roomDisplayName != eventSenderName {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_NAMED_ROOM", arguments: [eventSenderName as Any, roomDisplayName as Any])
|
||||
// If the current user is already joined, display updated displayname/avatar events.
|
||||
// This is an unexpected path, but has been seen in some circumstances.
|
||||
if NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId)?.membership == .join {
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
|
||||
// If the sender's membership is join and hasn't changed.
|
||||
if let newContent = MXRoomMemberEventContent(fromJSON: event.content),
|
||||
let prevContentDict = event.prevContent,
|
||||
let oldContent = MXRoomMemberEventContent(fromJSON: prevContentDict),
|
||||
newContent.membership == kMXMembershipStringJoin,
|
||||
oldContent.membership == kMXMembershipStringJoin {
|
||||
|
||||
// Check for display name changes
|
||||
if newContent.displayname != oldContent.displayname {
|
||||
// If there was a change, use the sender's userID if one was blank and show the change.
|
||||
if let oldDisplayname = oldContent.displayname ?? event.sender,
|
||||
let displayname = newContent.displayname ?? event.sender {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_UPDATED_DISPLAYNAME", arguments: [oldDisplayname, displayname])
|
||||
} else {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_CHAT", arguments: [eventSenderName as Any])
|
||||
// Should never be reached as the event should always have a sender.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "GENERIC_USER_UPDATED_DISPLAYNAME", arguments: [eventSenderName])
|
||||
}
|
||||
case .sticker:
|
||||
if roomDisplayName != nil && roomDisplayName != eventSenderName {
|
||||
notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any])
|
||||
} else {
|
||||
notificationTitle = eventSenderName
|
||||
// If the display name hasn't changed, handle as an avatar change.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_UPDATED_AVATAR", arguments: [eventSenderName])
|
||||
}
|
||||
} else {
|
||||
// No known reports of having reached this situation for a membership notification
|
||||
// So use a generic membership updated fallback.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_MEMBERSHIP_UPDATED", arguments: [eventSenderName])
|
||||
}
|
||||
// Otherwise treat the notification as an invite.
|
||||
// This is the expected notification content for a membership event.
|
||||
} else {
|
||||
if roomDisplayName != nil && roomDisplayName != eventSenderName {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_NAMED_ROOM", arguments: [eventSenderName, roomDisplayName as Any])
|
||||
} else {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_CHAT", arguments: [eventSenderName])
|
||||
}
|
||||
}
|
||||
|
||||
case .sticker:
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "STICKER_FROM_USER", arguments: [eventSenderName as Any])
|
||||
|
||||
// Reactions are unexpected notification types, but have been seen in some circumstances.
|
||||
case .reaction:
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
if let reactionKey = event.relatesTo?.key {
|
||||
// Try to show the reaction key in the notification.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "REACTION_FROM_USER", arguments: [eventSenderName, reactionKey])
|
||||
} else {
|
||||
// Otherwise show a generic reaction.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "GENERIC_REACTION_FROM_USER", arguments: [eventSenderName])
|
||||
}
|
||||
|
||||
case .custom:
|
||||
if (event.type == kWidgetMatrixEventTypeString || event.type == kWidgetModularEventTypeString),
|
||||
let type = event.content?["type"] as? String,
|
||||
@@ -457,6 +504,29 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the default title for message notifications.
|
||||
/// - Parameters:
|
||||
/// - eventSenderName: The displayname of the sender.
|
||||
/// - roomDisplayName: The displayname of the room the message was sent in.
|
||||
/// - Returns: A string to be used for the notification's title.
|
||||
private func messageTitle(for eventSenderName: String, in roomDisplayName: String?) -> String {
|
||||
// Display the room name only if it is different than the sender name
|
||||
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
||||
return NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName, roomDisplayName])
|
||||
} else {
|
||||
return eventSenderName
|
||||
}
|
||||
}
|
||||
|
||||
private func replyTitle(for eventSenderName: String, in roomDisplayName: String?) -> String {
|
||||
// Display the room name only if it is different than the sender name
|
||||
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
||||
return NSString.localizedUserNotificationString(forKey: "REPLY_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName, roomDisplayName])
|
||||
} else {
|
||||
return NSString.localizedUserNotificationString(forKey: "REPLY_FROM_USER_TITLE", arguments: [eventSenderName])
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the context of an event.
|
||||
/// - Parameters:
|
||||
/// - event: the event
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Notifications: Replace "Message" fallback with "Notification" as the event may not be a message.
|
||||
@@ -0,0 +1 @@
|
||||
Notifications: Show the body of all message event types.
|
||||
@@ -0,0 +1 @@
|
||||
Notifications: Replies now hide the referenced content.
|
||||
Reference in New Issue
Block a user