Merge pull request #2347 from fridtjof/riot_2337

Add notification titles and group them properly on iOS 10+
This commit is contained in:
SBiOSoftWhare 2019-04-03 10:39:19 +02:00 committed by GitHub
commit 55e6cb7cd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 208 additions and 27 deletions

View file

@ -2,6 +2,8 @@ Changes in 0.8.5 (2019-xx-xx)
=============================================== ===============================================
Improvements: Improvements:
* Added titles to notifications on iOS 10+ (#2347).
* Implemented notification grouping (#2347).
* Use UserNotifications framework for local notifications (iOS 10+), thanks to @fridtjof (PR #2207). * Use UserNotifications framework for local notifications (iOS 10+), thanks to @fridtjof (PR #2207).
Bug fix: Bug fix:

View file

@ -1154,9 +1154,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setNotificationCategories:[[NSSet alloc] initWithArray:@[quickReplyCategory]]]; [center setNotificationCategories:[[NSSet alloc] initWithArray:@[quickReplyCategory]]];
[center setDelegate:self]; // commenting this out will fall back to using the same AppDelegate methods as the iOS 9 way of doing this [center setDelegate:self]; // commenting this out will fall back to using the same AppDelegate methods as the iOS 9 way of doing this
UNAuthorizationOptions authorizationOptions = (UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge); UNAuthorizationOptions authorizationOptions = (UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge);
// FIXME: Uncomment lines below when issue https://github.com/matrix-org/matrix-ios-kit/issues/533 will be done. // FIXME: Uncomment lines below when issue https://github.com/matrix-org/matrix-ios-kit/issues/533 will be done.
// // Authorize sending notifications without explicit permission (iOS 12+). // // Authorize sending notifications without explicit permission (iOS 12+).
// // User can still disable Riot notifications later in settings or directly from a Riot notification. // // User can still disable Riot notifications later in settings or directly from a Riot notification.
@ -1164,7 +1164,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
// { // {
// authorizationOptions = authorizationOptions | UNAuthorizationOptionProvisional; // authorizationOptions = authorizationOptions | UNAuthorizationOptionProvisional;
// } // }
[center requestAuthorizationWithOptions:authorizationOptions [center requestAuthorizationWithOptions:authorizationOptions
completionHandler:^(BOOL granted, NSError *error) completionHandler:^(BOOL granted, NSError *error)
{ // code here is equivalent to self:application:didRegisterUserNotificationSettings: { // code here is equivalent to self:application:didRegisterUserNotificationSettings:
@ -1232,31 +1232,31 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
UNNotificationContent *content = notification.request.content; UNNotificationContent *content = notification.request.content;
NSString *actionIdentifier = [response actionIdentifier]; NSString *actionIdentifier = [response actionIdentifier];
NSString *roomId = content.userInfo[@"room_id"]; NSString *roomId = content.userInfo[@"room_id"];
if ([actionIdentifier isEqualToString:@"inline-reply"]) if ([actionIdentifier isEqualToString:@"inline-reply"])
{ {
if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) if ([response isKindOfClass:[UNTextInputNotificationResponse class]])
{ {
UNTextInputNotificationResponse *textInputNotificationResponse = (UNTextInputNotificationResponse *)response; UNTextInputNotificationResponse *textInputNotificationResponse = (UNTextInputNotificationResponse *)response;
NSString *responseText = [textInputNotificationResponse userText]; NSString *responseText = [textInputNotificationResponse userText];
[self handleNotificationInlineReplyForRoomId:roomId withResponseText:responseText success:^(NSString *eventId) { [self handleNotificationInlineReplyForRoomId:roomId withResponseText:responseText success:^(NSString *eventId) {
completionHandler(); completionHandler();
} failure:^(NSError *error) { } failure:^(NSError *error) {
UNMutableNotificationContent *failureNotificationContent = [[UNMutableNotificationContent alloc] init]; UNMutableNotificationContent *failureNotificationContent = [[UNMutableNotificationContent alloc] init];
failureNotificationContent.userInfo = content.userInfo; failureNotificationContent.userInfo = content.userInfo;
failureNotificationContent.body = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); failureNotificationContent.body = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil);
failureNotificationContent.threadIdentifier = roomId; failureNotificationContent.threadIdentifier = roomId;
NSString *uuid = [[NSUUID UUID] UUIDString]; NSString *uuid = [[NSUUID UUID] UUIDString];
UNNotificationRequest *failureNotificationRequest = [UNNotificationRequest requestWithIdentifier:uuid UNNotificationRequest *failureNotificationRequest = [UNNotificationRequest requestWithIdentifier:uuid
content:failureNotificationContent content:failureNotificationContent
trigger:nil]; trigger:nil];
[center addNotificationRequest:failureNotificationRequest withCompletionHandler:nil]; [center addNotificationRequest:failureNotificationRequest withCompletionHandler:nil];
NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: error sending text message: %@", error); NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: error sending text message: %@", error);
completionHandler(); completionHandler();
}]; }];
} }
@ -1283,21 +1283,23 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler - (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
{ {
NSString* roomId = notification.userInfo[@"room_id"]; NSString* roomId = notification.userInfo[@"room_id"];
NSString* roomId = notification.userInfo[@"room_id"];
if ([identifier isEqualToString: @"inline-reply"]) if ([identifier isEqualToString: @"inline-reply"])
{ {
NSString* responseText = responseInfo[UIUserNotificationActionResponseTypedTextKey]; NSString* responseText = responseInfo[UIUserNotificationActionResponseTypedTextKey];
[self handleNotificationInlineReplyForRoomId:roomId withResponseText:responseText success:^(NSString *eventId) { [self handleNotificationInlineReplyForRoomId:roomId withResponseText:responseText success:^(NSString *eventId) {
completionHandler(); completionHandler();
} failure:^(NSError *error) { } failure:^(NSError *error) {
UILocalNotification* failureNotification = [[UILocalNotification alloc] init]; UILocalNotification* failureNotification = [[UILocalNotification alloc] init];
failureNotification.alertBody = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); failureNotification.alertBody = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil);
failureNotification.userInfo = notification.userInfo; failureNotification.userInfo = notification.userInfo;
[[UIApplication sharedApplication] scheduleLocalNotification: failureNotification]; [[UIApplication sharedApplication] scheduleLocalNotification: failureNotification];
NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: error sending text message: %@", error); NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: error sending text message: %@", error);
completionHandler(); completionHandler();
}]; }];
} }
@ -1488,7 +1490,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
NSMutableArray *eventsArray = eventsToNotify[@(account.mxSession.hash)]; NSMutableArray *eventsArray = eventsToNotify[@(account.mxSession.hash)];
NSMutableArray<NSString*> *redactedEventIds = [NSMutableArray array]; NSMutableArray<NSString*> *redactedEventIds = [NSMutableArray array];
// Display a local notification for each event retrieved by the bg sync. // Display a local notification for each event retrieved by the bg sync.
for (NSUInteger index = 0; index < eventsArray.count; index++) for (NSUInteger index = 0; index < eventsArray.count; index++)
{ {
@ -1602,19 +1604,19 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
} }
} }
} }
[self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody) [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody)
{ {
if (notificationBody) if (notificationBody)
{ {
NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId);
// Printf style escape characters are stripped from the string prior to display; // Printf style escape characters are stripped from the string prior to display;
// to include a percent symbol (%) in the message, use two percent symbols (%%). // to include a percent symbol (%) in the message, use two percent symbols (%%).
// TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc // TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc
// use this - maybe not necessary to replace %s // use this - maybe not necessary to replace %s
NSString *fixedNotificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; NSString *fixedNotificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
if (@available(iOS 10, *)) if (@available(iOS 10, *))
{ {
UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init];
@ -1626,11 +1628,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
{ {
notificationContent.sound = [UNNotificationSound soundNamed:soundName]; notificationContent.sound = [UNNotificationSound soundNamed:soundName];
} }
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:event.eventId UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:event.eventId
content:notificationContent content:notificationContent
trigger:nil]; trigger:nil];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil];
} }
else else
@ -1640,10 +1642,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
eventNotification.userInfo = notificationUserInfo; eventNotification.userInfo = notificationUserInfo;
eventNotification.category = categoryIdentifier; eventNotification.category = categoryIdentifier;
eventNotification.soundName = soundName; eventNotification.soundName = soundName;
[[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification];
} }
scheduledNotifications++; scheduledNotifications++;
} }
else else
@ -1653,14 +1655,14 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
}]; }];
} }
} }
if (@available(iOS 10, *)) if (@available(iOS 10, *))
{ {
// Remove possible pending and delivered notifications having a redacted event id // Remove possible pending and delivered notifications having a redacted event id
if (redactedEventIds.count) if (redactedEventIds.count)
{ {
NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Remove possible notification with redacted event ids: %@", redactedEventIds); NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Remove possible notification with redacted event ids: %@", redactedEventIds);
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:redactedEventIds]; [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:redactedEventIds];
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:redactedEventIds]; [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:redactedEventIds];
} }
@ -1797,6 +1799,159 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
}]; }];
} }
// iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete:, except with more features
- (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule inAccount:(MXKAccount *)account onComplete:(void (^)(UNMutableNotificationContent * _Nullable notificationContent))onComplete;
{
if (!event.content || !event.content.count)
{
NSLog(@"[AppDelegate][Push] notificationContentForEvent: empty event content");
onComplete (nil);
return;
}
MXRoom *room = [account.mxSession roomWithRoomId:event.roomId];
if (!room)
{
NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Unknown room");
onComplete (nil);
return;
}
[room state:^(MXRoomState *roomState) {
NSString *notificationTitle;
NSString *notificationBody;
NSString *threadIdentifier = room.roomId;
NSString *eventSenderName = [roomState.members memberName:event.sender];
if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted)
{
if (room.isMentionsOnly)
{
// A local notification will be displayed only for highlighted notification.
BOOL isHighlighted = NO;
// Check whether is there an highlight tweak on it
for (MXPushRuleAction *ruleAction in rule.actions)
{
if (ruleAction.actionType == MXPushRuleActionTypeSetTweak)
{
if ([ruleAction.parameters[@"set_tweak"] isEqualToString:@"highlight"])
{
// Check the highlight tweak "value"
// If not present, highlight. Else check its value before highlighting
if (nil == ruleAction.parameters[@"value"] || YES == [ruleAction.parameters[@"value"] boolValue])
{
isHighlighted = YES;
break;
}
}
}
}
if (!isHighlighted)
{
// Ignore this notif.
NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Ignore non highlighted notif in mentions only room");
onComplete(nil);
return;
}
}
NSString *msgType = event.content[@"msgtype"];
NSString *messageContent = event.content[@"body"];
if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications)
{
// Hide the content
msgType = nil;
}
NSString *roomDisplayName = room.summary.displayname;
// Display the room name only if it is different than the sender name
if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName])
{
notificationTitle = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_TITLE", nil), eventSenderName, roomDisplayName];
if ([msgType isEqualToString:@"m.text"])
notificationBody = messageContent;
else if ([msgType isEqualToString:@"m.emote"])
{
notificationTitle = roomDisplayName;
notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, messageContent];
}
else if ([msgType isEqualToString:@"m.image"])
notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil);
else
// Encrypted messages falls here
notificationBody = NSLocalizedString(@"MSG_TEXT_WITH_TITLE", nil);
}
else
{
notificationTitle = eventSenderName;
if ([msgType isEqualToString:@"m.text"])
notificationBody = messageContent;
else if ([msgType isEqualToString:@"m.emote"])
{
notificationTitle = eventSenderName;
notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION", nil), messageContent];
}
else if ([msgType isEqualToString:@"m.image"])
notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil);
else
// Encrypted messages falls here
notificationBody = NSLocalizedString(@"MSG_TEXT_WITH_TITLE", nil);
}
}
else if (event.eventType == MXEventTypeCallInvite)
{
NSString *sdp = event.content[@"offer"][@"sdp"];
BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound;
notificationTitle = eventSenderName;
if (!isVideoCall)
notificationBody = NSLocalizedString(@"VOICE_CALL", nil);
else
notificationBody = NSLocalizedString(@"VIDEO_CALL", nil);
// call notifications should stand out from normal messages, so we don't stack them
threadIdentifier = nil;
}
else if (event.eventType == MXEventTypeRoomMember)
{
NSString *roomDisplayName = room.summary.displayname;
notificationTitle = roomDisplayName;
if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName])
notificationBody = [NSString stringWithFormat:NSLocalizedString(@"INVITE_BY_USER_TO_ROOM", nil), eventSenderName];
else
notificationBody = NSLocalizedString(@"INVITE_TO_CHAT", nil);
}
else if (event.eventType == MXEventTypeSticker)
{
NSString *roomDisplayName = room.summary.displayname;
if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName])
notificationTitle = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_TITLE", nil), eventSenderName, roomDisplayName];
else
notificationTitle = eventSenderName;
notificationBody = NSLocalizedString(@"STICKER_TEXT_WITH_TITLE", nil);
}
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
[content setTitle:notificationTitle];
[content setBody:notificationBody];
[content setThreadIdentifier:threadIdentifier];
onComplete(content);
}];
}
/** /**
Display "limited" notifications for events the app was not able to get data Display "limited" notifications for events the app was not able to get data
(because of /sync failure). (because of /sync failure).
@ -1963,11 +2118,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
failure(nil); failure(nil);
return; return;
} }
NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts; NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
MXKRoomDataSourceManager* manager; MXKRoomDataSourceManager* manager;
for (MXKAccount* account in mxAccounts) for (MXKAccount* account in mxAccounts)
{ {
MXRoom* room = [account.mxSession roomWithRoomId:roomId]; MXRoom* room = [account.mxSession roomWithRoomId:roomId];
@ -1980,7 +2135,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
} }
} }
} }
if (manager == nil) if (manager == nil)
{ {
NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: room with id %@ not found", roomId); NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: room with id %@ not found", roomId);

View file

@ -22,6 +22,9 @@
/* New message from a specific person in a named room */ /* New message from a specific person in a named room */
"MSG_FROM_USER_IN_ROOM" = "%@ posted in %@"; "MSG_FROM_USER_IN_ROOM" = "%@ posted in %@";
/* New message when a notification title is used */
"MSG_TEXT_WITH_TITLE" = "Message";
/** Single, unencrypted messages (where we can include the content */ /** Single, unencrypted messages (where we can include the content */
/* New message from a specific person, not referencing a room. Content included. */ /* New message from a specific person, not referencing a room. Content included. */
@ -36,6 +39,9 @@
/* New action message from a specific person in a named room. */ /* New action message from a specific person in a named room. */
"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@";
/* New action message, sender is specified in notification title */
"ACTION" = "* %@";
/** Image Messages **/ /** Image Messages **/
/* New action message from a specific person, not referencing a room. */ /* New action message from a specific person, not referencing a room. */
@ -44,12 +50,18 @@
/* New action message from a specific person in a named room. */ /* New action message from a specific person in a named room. */
"IMAGE_FROM_USER_IN_ROOM" = "%@ posted a picture %@ in %@"; "IMAGE_FROM_USER_IN_ROOM" = "%@ posted a picture %@ in %@";
/* New action message, but the sender (and room) are already in the notification title */
"IMAGE_TEXT_WITH_TITLE" = "📷 Picture";
/* A single unread message in a room */ /* A single unread message in a room */
"SINGLE_UNREAD_IN_ROOM" = "You received a message in %@"; "SINGLE_UNREAD_IN_ROOM" = "You received a message in %@";
/* A single unread message */ /* A single unread message */
"SINGLE_UNREAD" = "You received a message"; "SINGLE_UNREAD" = "You received a message";
/* Sticker, but with the sender (and room) already in the title */
"STICKER_TEXT_WITH_TITLE" = "💟 Sticker";
/** Coalesced messages **/ /** Coalesced messages **/
/* Multiple unread messages in a room */ /* Multiple unread messages in a room */
@ -84,14 +96,24 @@
/* A user has invited you to a named room */ /* A user has invited you to a named room */
"USER_INVITE_TO_NAMED_ROOM" = "%@ has invited you to %@"; "USER_INVITE_TO_NAMED_ROOM" = "%@ has invited you to %@";
/* Same as USER_INVITE_TO_CHAT but the username is already displayed in the notification title */
"INVITE_TO_CHAT" = "You were invited to chat";
/* Same as USER_INVITE_TO_NAMED_ROOM but the room name is already displayed in the notification title */
"INVITE_BY_USER_TO_ROOM" = "You were invited by %@";
/** Calls **/ /** Calls **/
/* Incoming one-to-one voice call */ /* Incoming one-to-one voice call */
"VOICE_CALL_FROM_USER" = "Call from %@"; "VOICE_CALL_FROM_USER" = "Call from %@";
"VOICE_CALL" = "📞 Call";
/* Incoming one-to-one video call */ /* Incoming one-to-one video call */
"VIDEO_CALL_FROM_USER" = "Video call from %@"; "VIDEO_CALL_FROM_USER" = "Video call from %@";
"VIDEO_CALL" = "📹 Video call";
/* Incoming unnamed voice conference invite from a specific person */ /* Incoming unnamed voice conference invite from a specific person */
"VOICE_CONF_FROM_USER" = "Group call from %@"; "VOICE_CONF_FROM_USER" = "Group call from %@";
@ -103,3 +125,5 @@
/* Incoming named video conference invite from a specific person */ /* Incoming named video conference invite from a specific person */
"VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'"; "VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'";
"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@";