From 4f67a99678473556dbb7de3dda1e3f5bacec3f82 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Wed, 30 Jan 2019 17:36:00 +0100 Subject: [PATCH 01/30] Use the non-deprecated UNUserNotification framework on iOS 10 and up --- Riot/AppDelegate.m | 62 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 7f67cc7b0..dc2c6ff84 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -71,6 +71,8 @@ #endif #ifdef CALL_STACK_JINGLE #import +#import + #endif #define CALL_STATUS_BAR_HEIGHT 44 @@ -1076,23 +1078,55 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { if (!isPushRegistered) { - NSMutableSet* notificationCategories = [NSMutableSet set]; + if (@available(iOS 10, *)) { + UNTextInputNotificationAction *quickReply = [UNTextInputNotificationAction + actionWithIdentifier:@"inline-reply" + title:NSLocalizedStringFromTable(@"room_message_short_placeholder", @"Vector", nil) + options:UNNotificationActionOptionAuthenticationRequired + ]; - UIMutableUserNotificationAction* quickReply = [[UIMutableUserNotificationAction alloc] init]; - quickReply.title = NSLocalizedStringFromTable(@"room_message_short_placeholder", @"Vector", nil); - quickReply.identifier = @"inline-reply"; - quickReply.activationMode = UIUserNotificationActivationModeBackground; - quickReply.authenticationRequired = true; - quickReply.behavior = UIUserNotificationActionBehaviorTextInput; + UNNotificationCategory *quickReplyCategory = [UNNotificationCategory + categoryWithIdentifier:@"QUICK_REPLY" + actions:@[quickReply] + intentIdentifiers:NULL + options:UNNotificationCategoryOptionNone]; - UIMutableUserNotificationCategory* quickReplyCategory = [[UIMutableUserNotificationCategory alloc] init]; - quickReplyCategory.identifier = @"QUICK_REPLY"; - [quickReplyCategory setActions:@[quickReply] forContext:UIUserNotificationActionContextDefault]; - [notificationCategories addObject:quickReplyCategory]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center setNotificationCategories:[[NSSet alloc] initWithArray:@[quickReplyCategory]]]; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) + completionHandler:^(BOOL granted, NSError *error) + { // code here is equivalent to self:application:didRegisterUserNotificationSettings: + if (granted) { + [self registerForRemoteNotificationsWithCompletion:nil]; + } + else + { + // Clear existing token + MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; + [accountManager setPushDeviceToken:nil withPushOptions:nil]; + } + }]; + } + else + { + NSMutableSet *notificationCategories = [NSMutableSet set]; - // Registration on iOS 8 and later - UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound |UIUserNotificationTypeAlert) categories:notificationCategories]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + UIMutableUserNotificationAction *quickReply = [[UIMutableUserNotificationAction alloc] init]; + quickReply.title = NSLocalizedStringFromTable(@"room_message_short_placeholder", @"Vector", nil); + quickReply.identifier = @"inline-reply"; + quickReply.activationMode = UIUserNotificationActivationModeBackground; + quickReply.authenticationRequired = true; + quickReply.behavior = UIUserNotificationActionBehaviorTextInput; + + UIMutableUserNotificationCategory *quickReplyCategory = [[UIMutableUserNotificationCategory alloc] init]; + quickReplyCategory.identifier = @"QUICK_REPLY"; + [quickReplyCategory setActions:@[quickReply] forContext:UIUserNotificationActionContextDefault]; + [notificationCategories addObject:quickReplyCategory]; + + // Registration on iOS 8 and later + UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories:notificationCategories]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + } } } From 8402106bba1f1f0eff038f28286c2cba5ba6f60a Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Wed, 30 Jan 2019 22:01:55 +0100 Subject: [PATCH 02/30] Implement UNUserNotificationCenterDelegate methods --- Riot/AppDelegate.h | 3 +- Riot/AppDelegate.m | 116 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index 811161010..df985fa13 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -17,6 +17,7 @@ #import #import +#import #import "MasterTabBarController.h" #import "JitsiViewController.h" @@ -37,7 +38,7 @@ extern NSString *const kAppDelegateDidTapStatusBarNotification; */ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification; -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder { BOOL isPushRegistered; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index dc2c6ff84..a1cf1684c 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1093,6 +1093,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [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 requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError *error) { // code here is equivalent to self:application:didRegisterUserNotificationSettings: @@ -1107,7 +1108,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } }]; } - else + else // DEPRECATED, for iOS 9 { NSMutableSet *notificationCategories = [NSMutableSet set]; @@ -1139,6 +1140,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } +// DEPRECATED, for iOS 9 - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { // Register for remote notifications only if user provide access to notification feature @@ -1154,6 +1156,70 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } +// iOS 10+, see application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler: +- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler +{ + UNNotification *notification = response.notification; + UNNotificationContent *content = notification.request.content; + if ([[response actionIdentifier] isEqualToString:@"inline-reply"]) { + UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *) response; + NSString* roomId = content.userInfo[@"room_id"]; + + if (roomId.length) { + NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts; + + MXKRoomDataSourceManager* manager; + for (MXKAccount* account in mxAccounts) + { + MXRoom* room = [account.mxSession roomWithRoomId:roomId]; + if (room) + { + manager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:account.mxSession]; + if (manager) + { + break; + } + } + } + if (manager == nil) + { + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: room with id %@ not found", roomId); + } + else + { + [manager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + NSString* responseText = [textResponse userText]; + if (responseText != nil && responseText.length != 0) + { + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: sending message to room: %@", roomId); + [roomDataSource sendTextMessage:responseText success:^(NSString* eventId) {} failure:^(NSError* error) { + UNMutableNotificationContent *failureNotificationContent = [[UNMutableNotificationContent alloc] init]; + failureNotificationContent.userInfo = content.userInfo; + failureNotificationContent.body = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); + + UNNotificationRequest *failureNotificationRequest = [UNNotificationRequest + requestWithIdentifier:@"failureNotification" + content:failureNotificationContent + trigger:nil]; + + [center addNotificationRequest:failureNotificationRequest withCompletionHandler:nil]; + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: error sending text message: %@", error); + }]; + } + + completionHandler(); + }]; + } + } + } + else + { + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: unhandled identifier %@", [response actionIdentifier]); + } + completionHandler(); +} + +// DEPRECATED, for iOS 9 // "This block is not a prototype" - don't fix this, or it won't match Apple's definition - (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler { @@ -1210,6 +1276,54 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN completionHandler(); } +// iOS 10+, see application:didReceiveLocalNotification: +- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler +{ + NSLog(@"[AppDelegate][Push] willPresentNotification: applicationState: %@", @([UIApplication sharedApplication].applicationState)); + + NSString* roomId = notification.request.content.userInfo[@"room_id"]; + if (roomId.length) + { + // TODO retrieve the right matrix session + // We can use the "user_id" value in notification.userInfo + + //************** + // Patch consider the first session which knows the room id + MXKAccount *dedicatedAccount = nil; + + NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts; + + if (mxAccounts.count == 1) + { + dedicatedAccount = mxAccounts.firstObject; + } + else + { + for (MXKAccount *account in mxAccounts) + { + if ([account.mxSession roomWithRoomId:roomId]) + { + dedicatedAccount = account; + break; + } + } + } + + // sanity checks + if (dedicatedAccount && dedicatedAccount.mxSession) + { + NSLog(@"[AppDelegate][Push] willPresentNotification: open the roomViewController %@", roomId); + + [self showRoom:roomId andEventId:nil withMatrixSession:dedicatedAccount.mxSession]; + } + else + { + NSLog(@"[AppDelegate][Push] willPresentNotification : no linked session / account has been found."); + } + } +} + +// DEPRECATED, for iOS 9 - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { NSLog(@"[AppDelegate][Push] didReceiveLocalNotification: applicationState: %@", @(application.applicationState)); From 54dc486541f67b9fdaf5ceda0c5cae7dd11b2550 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Wed, 30 Jan 2019 23:01:59 +0100 Subject: [PATCH 03/30] add iOS 10+ alternatives to all uses of the old notification system --- Riot/AppDelegate.m | 183 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 35 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index a1cf1684c..0d509b8fa 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1500,7 +1500,15 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN MXEvent *event; // Ignore event already notified to the user - if ([self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) + if (@available(iOS 10, *)) + { + if ([self displayedNotificationRequestForEvent:eventId andUser:account.mxCredentials.userId type:nil]) + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); + continue; + } + } + else if ([self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; @@ -1569,41 +1577,88 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // to include a percent symbol (%) in the message, use two percent symbols (%%). notificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; - eventNotification.alertBody = notificationBody; - eventNotification.userInfo = @{ - @"type": @"full", - @"room_id": event.roomId, - @"event_id": event.eventId, - @"user_id": account.mxCredentials.userId - }; - - BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; - - if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) + if (@available(iOS 10, *)) { - eventNotification.category = @"QUICK_REPLY"; - } + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; - // Set sound name based on the value provided in action of MXPushRule - for (MXPushRuleAction *action in rule.actions) - { - if (action.actionType == MXPushRuleActionTypeSetTweak) + content.body = notificationBody; + content.userInfo = @{ + @"type": @"full", + @"room_id": event.roomId, + @"event_id": event.eventId, + @"user_id": account.mxCredentials.userId + }; + + BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + + UNNotificationRequest *request = [UNNotificationRequest + requestWithIdentifier:event.eventId + content:content + trigger:nil]; + + if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) { - if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) - { - NSString *soundName = action.parameters[@"value"]; - if ([soundName isEqualToString:@"default"]) - soundName = @"message.mp3"; + content.categoryIdentifier = @"QUICK_REPLY"; + } - eventNotification.soundName = soundName; + // Set sound name based on the value provided in action of MXPushRule + for (MXPushRuleAction *action in rule.actions) + { + if (action.actionType == MXPushRuleActionTypeSetTweak) + { + if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + { + NSString *soundName = action.parameters[@"value"]; + if ([soundName isEqualToString:@"default"]) + soundName = @"message.mp3"; + + content.sound = [UNNotificationSound soundNamed:soundName]; + } } } - } - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); - [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; - scheduledNotifications++; + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); + [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; + scheduledNotifications++; + } + else // DEPRECATED, for iOS 9 + { + UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; + eventNotification.alertBody = notificationBody; + eventNotification.userInfo = @{ + @"type": @"full", + @"room_id": event.roomId, + @"event_id": event.eventId, + @"user_id": account.mxCredentials.userId + }; + + BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + + if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) + { + eventNotification.category = @"QUICK_REPLY"; + } + + // Set sound name based on the value provided in action of MXPushRule + for (MXPushRuleAction *action in rule.actions) + { + if (action.actionType == MXPushRuleActionTypeSetTweak) + { + if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + { + NSString *soundName = action.parameters[@"value"]; + if ([soundName isEqualToString:@"default"]) + soundName = @"message.mp3"; + + eventNotification.soundName = soundName; + } + } + } + + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); + [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; + scheduledNotifications++; + } } else { @@ -1770,7 +1825,15 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN for (NSString *eventId in events) { // Ignore event already notified to the user - if ([self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) + if (@available(iOS 10, *)) + { + if ([self displayedNotificationRequestForEvent:eventId andUser:userId type:nil]) + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); + continue; + } + } + else if ([self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; @@ -1795,12 +1858,26 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSLog(@"[AppDelegate][Push] handleLocalNotificationsForFailedSync: room_id is missing for event %@ in payload %@", eventId, payload); } - UILocalNotification *localNotificationForFailedSync = [[UILocalNotification alloc] init]; - localNotificationForFailedSync.userInfo = userInfo; - localNotificationForFailedSync.alertBody = [self limitedNotificationBodyForEvent:eventId inMatrixSession:mxSession]; + if (@available(iOS 10, *)) + { + UNMutableNotificationContent *localNotificationContentForFailedSync = [[UNMutableNotificationContent alloc] init]; + localNotificationContentForFailedSync.userInfo = userInfo; + localNotificationContentForFailedSync.body = [self limitedNotificationBodyForEvent:eventId inMatrixSession:mxSession]; - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForFailedSync: Display notification for event %@", eventId); - [[UIApplication sharedApplication] scheduleLocalNotification:localNotificationForFailedSync]; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:eventId content:localNotificationContentForFailedSync trigger:nil]; + + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForFailedSync: Display notification for event %@", eventId); + [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; + } + else // DEPRECATED, for iOS 9 + { + UILocalNotification *localNotificationForFailedSync = [[UILocalNotification alloc] init]; + localNotificationForFailedSync.userInfo = userInfo; + localNotificationForFailedSync.alertBody = [self limitedNotificationBodyForEvent:eventId inMatrixSession:mxSession]; + + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForFailedSync: Display notification for event %@", eventId); + [[UIApplication sharedApplication] scheduleLocalNotification:localNotificationForFailedSync]; + } } } @@ -1840,6 +1917,41 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN return notificationBody; } +/** + Return the already displayed notification for an event. + + @param eventId the id of the event attached to the notification to find. + @param userId the id of the user attached to the notification to find. + @param type the type of notification. @"full" or @"limited". nil for any type. + @return the local notification request if any. + */ +// iOS 10+ only! +- (UNNotificationRequest *)displayedNotificationRequestForEvent:(NSString *)eventId andUser:(NSString *)userId type:(NSString*)type +{ + __block UNNotificationRequest *foundRequest; + [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray *requests) + { + NSLog(@"[AppDelegate] displayedNotificationRequestForEvent: %@ andUser: %@. Current delivered notifications: %@", eventId, userId, requests); + + for (UNNotificationRequest *request in requests) + { + UNNotificationContent *content = request.content; + NSLog(@" - %@", content.userInfo); + + if ([content.userInfo[@"event_id"] isEqualToString:eventId] + && [content.userInfo[@"user_id"] isEqualToString:userId] + && (!type || [content.userInfo[@"type"] isEqualToString:type])) + { + foundRequest = request; + break; + } + } + }]; + + NSLog(@"[AppDelegate] displayedNotificationRequestForEvent: found: %@", foundRequest); + return foundRequest; +} + /** Return the already displayed notification for an event. @@ -1848,6 +1960,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN @param type the type of notification. @"full" or @"limited". nil for any type. @return the local notification if any. */ +// DEPRECATED, for iOS 9 // TODO: This method does not work: [[UIApplication sharedApplication] scheduledLocalNotifications] is not reliable - (UILocalNotification*)displayedLocalNotificationForEvent:(NSString*)eventId andUser:(NSString*)userId type:(NSString*)type { From 2ba91d6f991124101edb3d9d822d95b00a773522 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 31 Jan 2019 02:02:09 +0100 Subject: [PATCH 04/30] specify empty array instead of NULL for intentIdentifiers --- Riot/AppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 0d509b8fa..f2cb20d3e 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1088,7 +1088,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNNotificationCategory *quickReplyCategory = [UNNotificationCategory categoryWithIdentifier:@"QUICK_REPLY" actions:@[quickReply] - intentIdentifiers:NULL + intentIdentifiers:@[] options:UNNotificationCategoryOptionNone]; UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; From 6806953a815cc490ae19a17e9967ce2127450115 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 31 Jan 2019 02:08:03 +0100 Subject: [PATCH 05/30] added changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 17139e6d7..013e7384a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,7 @@ Improvements: * RoomVC: Remove the beta warning modal when opening an e2e room (#2239). * RoomVC: `Redact` has been renamed to `Remove` to match riot/web (#2134). * Clean up iOS version checking (#2190). + * Use newer notification mechanisms where possible (#2207). * Key backup: Implement setup screen (#2198). * Key backup: Implement recover screen (#2196). * Key backup: Add a dedicated section to settings (#2193). From b75c25c047a615558b1400dd45f9d4cdd1246f01 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Fri, 8 Feb 2019 12:42:55 +0100 Subject: [PATCH 06/30] separate notification content generation for iOS 10+ - this will make using iOS 10+ features easier --- Riot/AppDelegate.m | 183 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 162 insertions(+), 21 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index f2cb20d3e..37c277825 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1569,20 +1569,18 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Prepare the local notification MXPushRule *rule = eventDict[@"push_rule"]; - [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString * _Nullable notificationBody) { - - if (notificationBody) + if (@available(iOS 10, *)) { + [self notificationContentForEvent:event pushRule:rule inAccount:account onComplete:^(UNMutableNotificationContent * _Nullable notificationContent) { - // Printf style escape characters are stripped from the string prior to display; - // to include a percent symbol (%) in the message, use two percent symbols (%%). - notificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - - if (@available(iOS 10, *)) + if (notificationContent) { - UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + // Printf style escape characters are stripped from the string prior to display; + // to include a percent symbol (%) in the message, use two percent symbols (%%). + // TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc + // use this - maybe not necessary to replace %s + notificationContent.body = [notificationContent.body stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - content.body = notificationBody; - content.userInfo = @{ + notificationContent.userInfo = @{ @"type": @"full", @"room_id": event.roomId, @"event_id": event.eventId, @@ -1593,12 +1591,12 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:event.eventId - content:content + content:notificationContent trigger:nil]; if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) { - content.categoryIdentifier = @"QUICK_REPLY"; + notificationContent.categoryIdentifier = @"QUICK_REPLY"; } // Set sound name based on the value provided in action of MXPushRule @@ -1612,7 +1610,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if ([soundName isEqualToString:@"default"]) soundName = @"message.mp3"; - content.sound = [UNNotificationSound soundNamed:soundName]; + notificationContent.sound = [UNNotificationSound soundNamed:soundName]; } } } @@ -1621,8 +1619,22 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; scheduledNotifications++; } - else // DEPRECATED, for iOS 9 + else { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationContent. Event id: %@", event.eventId); + } + }]; + } + else + { + [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody) + { + if (notificationBody) + { + // Printf style escape characters are stripped from the string prior to display; + // to include a percent symbol (%) in the message, use two percent symbols (%%). + notificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; + UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; eventNotification.alertBody = notificationBody; eventNotification.userInfo = @{ @@ -1658,13 +1670,12 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; scheduledNotifications++; + } else + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); } - } - else - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); - } - }]; + }]; + } } } @@ -1799,6 +1810,136 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN }]; } +// iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete: for now +- (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 *notificationBody; + 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 *content = 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]) + { + if ([msgType isEqualToString:@"m.text"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; + else if ([msgType isEqualToString:@"m.emote"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; + else if ([msgType isEqualToString:@"m.image"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; + else + // Encrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + } + else + { + if ([msgType isEqualToString:@"m.text"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; + else if ([msgType isEqualToString:@"m.emote"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; + else if ([msgType isEqualToString:@"m.image"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; + else + // Encrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; + } + } + else if (event.eventType == MXEventTypeCallInvite) + { + NSString *sdp = event.content[@"offer"][@"sdp"]; + BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; + + if (!isVideoCall) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; + } + else if (event.eventType == MXEventTypeRoomMember) + { + NSString *roomDisplayName = room.summary.displayname; + + if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; + } + else if (event.eventType == MXEventTypeSticker) + { + NSString *roomDisplayName = room.summary.displayname; + + if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; + } + + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + [content setBody:notificationBody]; + + onComplete(content); + }]; +} + /** Display "limited" notifications for events the app was not able to get data (because of /sync failure). From 43cc6145664aa6767cdc2942ed8a2ef21b32ba26 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 00:23:43 +0100 Subject: [PATCH 07/30] more accurately match the old notification action handler in the iOS 10+ code --- Riot/AppDelegate.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 37c277825..c79c85272 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1209,6 +1209,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN completionHandler(); }]; + return; } } } From c807fa7273600df9a496135a952bca38d27cb137 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 00:35:36 +0100 Subject: [PATCH 08/30] don't bother ignoring notifications on iOS 10+ --- Riot/AppDelegate.m | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index c79c85272..38135e758 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1501,15 +1501,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN MXEvent *event; // Ignore event already notified to the user - if (@available(iOS 10, *)) - { - if ([self displayedNotificationRequestForEvent:eventId andUser:account.mxCredentials.userId type:nil]) - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); - continue; - } - } - else if ([self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) + // only necessary on iOS 9, iOS 10 will just overwrite notifications with identical IDs + if (!@available(iOS 10, *) && [self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; From cefd63d8042e82feb814f3856b3300b703857dae Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 00:49:02 +0100 Subject: [PATCH 09/30] don't bother ignoring notifications on iOS 10+ - no 2 --- Riot/AppDelegate.m | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 38135e758..4683ea9ee 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1960,15 +1960,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN for (NSString *eventId in events) { // Ignore event already notified to the user - if (@available(iOS 10, *)) - { - if ([self displayedNotificationRequestForEvent:eventId andUser:userId type:nil]) - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); - continue; - } - } - else if ([self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) + // only necessary on iOS 9, iOS 10 will just overwrite notifications with identical IDs + if (!@available(iOS 10, *) && [self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; From 0580a0713b750b09e76a1765793582df123bd826 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 00:50:04 +0100 Subject: [PATCH 10/30] factor out common code --- Riot/AppDelegate.m | 98 ++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 59 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 4683ea9ee..fa5e2d1e6 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1563,6 +1563,39 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Prepare the local notification MXPushRule *rule = eventDict[@"push_rule"]; + NSDictionary *notificationUserInfo = @{ + @"type": @"full", + @"room_id": event.roomId, + @"event_id": event.eventId, + @"user_id": account.mxCredentials.userId + }; + + BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + + NSString *categoryIdentifier; + + if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) + { + categoryIdentifier = @"QUICK_REPLY"; + } + + + NSString *soundName; + + // Set sound name based on the value provided in action of MXPushRule + for (MXPushRuleAction *action in rule.actions) + { + if (action.actionType == MXPushRuleActionTypeSetTweak) + { + if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + { + soundName = action.parameters[@"value"]; + if ([soundName isEqualToString:@"default"]) + soundName = @"message.mp3"; + } + } + } + if (@available(iOS 10, *)) { [self notificationContentForEvent:event pushRule:rule inAccount:account onComplete:^(UNMutableNotificationContent * _Nullable notificationContent) { @@ -1573,42 +1606,15 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc // use this - maybe not necessary to replace %s notificationContent.body = [notificationContent.body stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - - notificationContent.userInfo = @{ - @"type": @"full", - @"room_id": event.roomId, - @"event_id": event.eventId, - @"user_id": account.mxCredentials.userId - }; - - BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + notificationContent.userInfo = notificationUserInfo; + notificationContent.categoryIdentifier = categoryIdentifier; + notificationContent.sound = [UNNotificationSound soundNamed:soundName]; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:event.eventId content:notificationContent trigger:nil]; - if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) - { - notificationContent.categoryIdentifier = @"QUICK_REPLY"; - } - - // Set sound name based on the value provided in action of MXPushRule - for (MXPushRuleAction *action in rule.actions) - { - if (action.actionType == MXPushRuleActionTypeSetTweak) - { - if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) - { - NSString *soundName = action.parameters[@"value"]; - if ([soundName isEqualToString:@"default"]) - soundName = @"message.mp3"; - - notificationContent.sound = [UNNotificationSound soundNamed:soundName]; - } - } - } - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; scheduledNotifications++; @@ -1631,35 +1637,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; eventNotification.alertBody = notificationBody; - eventNotification.userInfo = @{ - @"type": @"full", - @"room_id": event.roomId, - @"event_id": event.eventId, - @"user_id": account.mxCredentials.userId - }; - - BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; - - if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) - { - eventNotification.category = @"QUICK_REPLY"; - } - - // Set sound name based on the value provided in action of MXPushRule - for (MXPushRuleAction *action in rule.actions) - { - if (action.actionType == MXPushRuleActionTypeSetTweak) - { - if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) - { - NSString *soundName = action.parameters[@"value"]; - if ([soundName isEqualToString:@"default"]) - soundName = @"message.mp3"; - - eventNotification.soundName = soundName; - } - } - } + eventNotification.userInfo = notificationUserInfo; + eventNotification.category = categoryIdentifier; + eventNotification.soundName = soundName; NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; From a9ded46d085e1aad2807cfe156afb28dd1a18318 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 00:53:41 +0100 Subject: [PATCH 11/30] don't display notifications when in foreground --- Riot/AppDelegate.m | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index fa5e2d1e6..efab4d20e 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1277,51 +1277,12 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN completionHandler(); } -// iOS 10+, see application:didReceiveLocalNotification: +// iOS 10+, this is called when a notification is about to display in foreground. - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { NSLog(@"[AppDelegate][Push] willPresentNotification: applicationState: %@", @([UIApplication sharedApplication].applicationState)); - NSString* roomId = notification.request.content.userInfo[@"room_id"]; - if (roomId.length) - { - // TODO retrieve the right matrix session - // We can use the "user_id" value in notification.userInfo - - //************** - // Patch consider the first session which knows the room id - MXKAccount *dedicatedAccount = nil; - - NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts; - - if (mxAccounts.count == 1) - { - dedicatedAccount = mxAccounts.firstObject; - } - else - { - for (MXKAccount *account in mxAccounts) - { - if ([account.mxSession roomWithRoomId:roomId]) - { - dedicatedAccount = account; - break; - } - } - } - - // sanity checks - if (dedicatedAccount && dedicatedAccount.mxSession) - { - NSLog(@"[AppDelegate][Push] willPresentNotification: open the roomViewController %@", roomId); - - [self showRoom:roomId andEventId:nil withMatrixSession:dedicatedAccount.mxSession]; - } - else - { - NSLog(@"[AppDelegate][Push] willPresentNotification : no linked session / account has been found."); - } - } + completionHandler(UNNotificationPresentationOptionNone); } // DEPRECATED, for iOS 9 From 2da9e08a592f90143defb225b010083829859d95 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 00:57:09 +0100 Subject: [PATCH 12/30] remove displayedNotificationRequestForEvent:andUser:type: (unused as of c807fa7 and cefd63d) --- Riot/AppDelegate.m | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index efab4d20e..e74673909 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1986,41 +1986,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN return notificationBody; } -/** - Return the already displayed notification for an event. - - @param eventId the id of the event attached to the notification to find. - @param userId the id of the user attached to the notification to find. - @param type the type of notification. @"full" or @"limited". nil for any type. - @return the local notification request if any. - */ -// iOS 10+ only! -- (UNNotificationRequest *)displayedNotificationRequestForEvent:(NSString *)eventId andUser:(NSString *)userId type:(NSString*)type -{ - __block UNNotificationRequest *foundRequest; - [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray *requests) - { - NSLog(@"[AppDelegate] displayedNotificationRequestForEvent: %@ andUser: %@. Current delivered notifications: %@", eventId, userId, requests); - - for (UNNotificationRequest *request in requests) - { - UNNotificationContent *content = request.content; - NSLog(@" - %@", content.userInfo); - - if ([content.userInfo[@"event_id"] isEqualToString:eventId] - && [content.userInfo[@"user_id"] isEqualToString:userId] - && (!type || [content.userInfo[@"type"] isEqualToString:type])) - { - foundRequest = request; - break; - } - } - }]; - - NSLog(@"[AppDelegate] displayedNotificationRequestForEvent: found: %@", foundRequest); - return foundRequest; -} - /** Return the already displayed notification for an event. From d0b4c56a3941a875c0f2dbc44b48b440df7d8326 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Thu, 14 Mar 2019 01:05:30 +0100 Subject: [PATCH 13/30] navigate to the room when a notification is tapped on iOS 10 --- Riot/AppDelegate.m | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index e74673909..d2be8d38e 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1213,6 +1213,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } } + else if ([[response actionIdentifier] isEqualToString:UNNotificationDefaultActionIdentifier]) + { + NSString *roomId = content.userInfo[@"room_id"]; + [self navigateToRoomById:roomId]; + } else { NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: unhandled identifier %@", [response actionIdentifier]); @@ -1291,17 +1296,22 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSLog(@"[AppDelegate][Push] didReceiveLocalNotification: applicationState: %@", @(application.applicationState)); NSString* roomId = notification.userInfo[@"room_id"]; + [self navigateToRoomById:roomId]; +} + +- (void)navigateToRoomById:(NSString *)roomId +{ if (roomId.length) { // TODO retrieve the right matrix session // We can use the "user_id" value in notification.userInfo - + //************** // Patch consider the first session which knows the room id MXKAccount *dedicatedAccount = nil; - + NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts; - + if (mxAccounts.count == 1) { dedicatedAccount = mxAccounts.firstObject; @@ -1317,17 +1327,17 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } } - + // sanity checks if (dedicatedAccount && dedicatedAccount.mxSession) { - NSLog(@"[AppDelegate][Push] didReceiveLocalNotification: open the roomViewController %@", roomId); - + NSLog(@"[AppDelegate][Push] navigateToRoomById: open the roomViewController %@", roomId); + [self showRoom:roomId andEventId:nil withMatrixSession:dedicatedAccount.mxSession]; } else { - NSLog(@"[AppDelegate][Push] didReceiveLocalNotification : no linked session / account has been found."); + NSLog(@"[AppDelegate][Push] navigateToRoomById : no linked session / account has been found."); } } } From 5d56fd791f841d39249c0de43e997e65f74a6f7a Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Fri, 22 Mar 2019 00:40:27 +0100 Subject: [PATCH 14/30] add a threadIdentifier to notifications and implement titles --- Riot/AppDelegate.m | 51 ++++++++++++++++++------ Riot/Assets/en.lproj/Localizable.strings | 11 +++++ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 7985de290..c6a01d15a 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1815,7 +1815,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN }]; } -// iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete: for now +// 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) @@ -1835,7 +1835,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [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) @@ -1873,7 +1876,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } NSString *msgType = event.content[@"msgtype"]; - NSString *content = event.content[@"body"]; + NSString *messageContent = event.content[@"body"]; if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications) { @@ -1886,27 +1889,44 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // 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 = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; + notificationBody = messageContent; else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; + { + notificationTitle = nil; + // TODO: how should this look? /me style messages don't look right with a title + // maybe like this: + // title = roomDisplayName + // body = * eventSenderName messageContent + + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, messageContent]; + } else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; + notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil); else // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + notificationBody = NSLocalizedString(@"MSG_TEXT_WITH_TITLE", nil); } else { + notificationTitle = eventSenderName; if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; + notificationBody = messageContent; else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; + { + notificationTitle = nil; + // TODO: how should this look? /me style messages look weird with a title + // maybe like this: + // title = eventSenderName + // body = * messageContent + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, messageContent]; + } else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; + notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil); else // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; + notificationBody = NSLocalizedString(@"MSG_TEXT_WITH_TITLE", nil); } } else if (event.eventType == MXEventTypeCallInvite) @@ -1918,6 +1938,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; else notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; + + threadIdentifier = nil; // call notifications should probably stand out from normal messages } else if (event.eventType == MXEventTypeRoomMember) { @@ -1933,13 +1955,18 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *roomDisplayName = room.summary.displayname; if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + notificationTitle = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_TITLE", nil), eventSenderName, roomDisplayName]; else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; + 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); }]; diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index 33e5d39f4..53b8a579c 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -22,6 +22,9 @@ /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ posted in %@"; +/* New message when a notification title is used */ +"MSG_TEXT_WITH_TITLE" = "Encrypted message"; + /** Single, unencrypted messages (where we can include the content */ /* New message from a specific person, not referencing a room. Content included. */ @@ -44,12 +47,18 @@ /* New action message from a specific person in a named room. */ "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 */ "SINGLE_UNREAD_IN_ROOM" = "You received a message in %@"; /* A single unread 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 **/ /* Multiple unread messages in a room */ @@ -103,3 +112,5 @@ /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'"; + +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; \ No newline at end of file From 3f21c790bf827140014528740a384a3127950a02 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Fri, 22 Mar 2019 01:10:57 +0100 Subject: [PATCH 15/30] add additional notification titles --- Riot/AppDelegate.m | 19 ++++++++++++------- Riot/Assets/en.lproj/Localizable.strings | 12 ++++++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index c6a01d15a..ed80fd1d2 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1934,21 +1934,26 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *sdp = event.content[@"offer"][@"sdp"]; BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; - if (!isVideoCall) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; + notificationTitle = eventSenderName; - threadIdentifier = nil; // call notifications should probably stand out from normal messages + 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(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"INVITE_BY_USER_TO_ROOM", nil), eventSenderName]; else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; + notificationBody = NSLocalizedString(@"INVITE_TO_CHAT", nil); } else if (event.eventType == MXEventTypeSticker) { diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index 53b8a579c..38fca6c83 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -23,7 +23,7 @@ "MSG_FROM_USER_IN_ROOM" = "%@ posted in %@"; /* New message when a notification title is used */ -"MSG_TEXT_WITH_TITLE" = "Encrypted message"; +"MSG_TEXT_WITH_TITLE" = "Message"; /** Single, unencrypted messages (where we can include the content */ @@ -57,7 +57,7 @@ "SINGLE_UNREAD" = "You received a message"; /* Sticker, but with the sender (and room) already in the title */ -"STICKER_TEXT_WITH_TITLE" = "Sticker"; +"STICKER_TEXT_WITH_TITLE" = "💟 Sticker"; /** Coalesced messages **/ @@ -93,14 +93,22 @@ /* A user has invited you to a named room */ "USER_INVITE_TO_NAMED_ROOM" = "%@ has invited you to %@"; +"INVITE_TO_CHAT" = "You were invited to chat"; + +"INVITE_BY_USER_TO_ROOM" = "You were invited by %@"; + /** Calls **/ /* Incoming one-to-one voice call */ "VOICE_CALL_FROM_USER" = "Call from %@"; +"VOICE_CALL" = "📞 Call"; + /* Incoming one-to-one video call */ "VIDEO_CALL_FROM_USER" = "Video call from %@"; +"VIDEO_CALL" = "📹 Video call"; + /* Incoming unnamed voice conference invite from a specific person */ "VOICE_CONF_FROM_USER" = "Group call from %@"; From 689d05259023944f39257fb6fc817f4fa8eb1140 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Fri, 22 Mar 2019 01:20:50 +0100 Subject: [PATCH 16/30] finalize emote style notifications --- Riot/AppDelegate.m | 17 ++++------------- Riot/Assets/en.lproj/Localizable.strings | 5 +++++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index ed80fd1d2..1bd28e594 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1894,13 +1894,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN notificationBody = messageContent; else if ([msgType isEqualToString:@"m.emote"]) { - notificationTitle = nil; - // TODO: how should this look? /me style messages don't look right with a title - // maybe like this: - // title = roomDisplayName - // body = * eventSenderName messageContent - - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, messageContent]; + 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); @@ -1915,12 +1910,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN notificationBody = messageContent; else if ([msgType isEqualToString:@"m.emote"]) { - notificationTitle = nil; - // TODO: how should this look? /me style messages look weird with a title - // maybe like this: - // title = eventSenderName - // body = * messageContent - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, messageContent]; + notificationTitle = eventSenderName; + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION", nil), messageContent]; } else if ([msgType isEqualToString:@"m.image"]) notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil); diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index 38fca6c83..b387bfa82 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -39,6 +39,9 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; +/* New action message, sender is specified in notification title */ +"ACTION" = "* %@"; + /** Image Messages **/ /* New action message from a specific person, not referencing a room. */ @@ -93,8 +96,10 @@ /* A user has invited you to a named room */ "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 **/ From b8a314d7dbe0c7194cb3adc994e3556bfd74bd83 Mon Sep 17 00:00:00 2001 From: fridtjof <2780577+fridtjof@users.noreply.github.com> Date: Fri, 22 Mar 2019 01:30:20 +0100 Subject: [PATCH 17/30] update CHANGES.rst --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f060e739d..fc8ea25b4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,8 @@ Changes in 0.8.5 (2019-xx-xx) =============================================== Improvements: + * Added titles to notifications on iOS 10+ (#2347). + * Implemented notification grouping (#2347). Bug fix: From d4f9dfef4d890c9d22057845a5fd92d17707ef3c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 22 Mar 2019 10:21:16 +0100 Subject: [PATCH 18/30] Factorize and fix some issues with local notifications code --- Riot/AppDelegate.m | 458 +++++++++++++++++---------------------------- 1 file changed, 168 insertions(+), 290 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 7985de290..7c5486524 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1163,8 +1163,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN else { // Clear existing token - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setPushDeviceToken:nil withPushOptions:nil]; + [self clearPushNotificationToken]; } }]; } @@ -1211,8 +1210,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN else { // Clear existing token - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setPushDeviceToken:nil withPushOptions:nil]; + [self clearPushNotificationToken]; } } @@ -1221,125 +1219,81 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { UNNotification *notification = response.notification; UNNotificationContent *content = notification.request.content; - if ([[response actionIdentifier] isEqualToString:@"inline-reply"]) { - UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *) response; - NSString* roomId = content.userInfo[@"room_id"]; - - if (roomId.length) { - NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts; - - MXKRoomDataSourceManager* manager; - for (MXKAccount* account in mxAccounts) - { - MXRoom* room = [account.mxSession roomWithRoomId:roomId]; - if (room) - { - manager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:account.mxSession]; - if (manager) - { - break; - } - } - } - if (manager == nil) - { - NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: room with id %@ not found", roomId); - } - else - { - [manager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { - NSString* responseText = [textResponse userText]; - if (responseText != nil && responseText.length != 0) - { - NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: sending message to room: %@", roomId); - [roomDataSource sendTextMessage:responseText success:^(NSString* eventId) {} failure:^(NSError* error) { - UNMutableNotificationContent *failureNotificationContent = [[UNMutableNotificationContent alloc] init]; - failureNotificationContent.userInfo = content.userInfo; - failureNotificationContent.body = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); - - UNNotificationRequest *failureNotificationRequest = [UNNotificationRequest - requestWithIdentifier:@"failureNotification" - content:failureNotificationContent - trigger:nil]; - - [center addNotificationRequest:failureNotificationRequest withCompletionHandler:nil]; - NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: error sending text message: %@", error); - }]; - } - - completionHandler(); - }]; - return; - } + NSString *actionIdentifier = [response actionIdentifier]; + NSString *roomId = content.userInfo[@"room_id"]; + + if ([actionIdentifier isEqualToString:@"inline-reply"]) + { + if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) + { + UNTextInputNotificationResponse *textInputNotificationResponse = (UNTextInputNotificationResponse *)response; + NSString *responseText = [textInputNotificationResponse userText]; + + [self handleNotificationInlineReplyForRoomId:roomId withResponseText:responseText success:^(NSString *eventId) { + completionHandler(); + } failure:^(NSError *error) { + + UNMutableNotificationContent *failureNotificationContent = [[UNMutableNotificationContent alloc] init]; + failureNotificationContent.userInfo = content.userInfo; + failureNotificationContent.body = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); + + NSString *uuid = [[NSUUID UUID] UUIDString]; + UNNotificationRequest *failureNotificationRequest = [UNNotificationRequest requestWithIdentifier:uuid + content:failureNotificationContent + trigger:nil]; + + [center addNotificationRequest:failureNotificationRequest withCompletionHandler:nil]; + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: error sending text message: %@", error); + + completionHandler(); + }]; + } + else + { + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: error, expect a response of type UNTextInputNotificationResponse"); + completionHandler(); } } - else if ([[response actionIdentifier] isEqualToString:UNNotificationDefaultActionIdentifier]) + else if ([actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { - NSString *roomId = content.userInfo[@"room_id"]; [self navigateToRoomById:roomId]; + completionHandler(); } else { NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: unhandled identifier %@", [response actionIdentifier]); + completionHandler(); } - completionHandler(); } // DEPRECATED, for iOS 9 // "This block is not a prototype" - don't fix this, or it won't match Apple's definition - (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler { + NSString* roomId = notification.userInfo[@"room_id"]; + if ([identifier isEqualToString: @"inline-reply"]) { - NSString* roomId = notification.userInfo[@"room_id"]; - if (roomId.length) - { - NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts; - MXKRoomDataSource* roomDataSource = nil; - MXKRoomDataSourceManager* manager; - for (MXKAccount* account in mxAccounts) - { - MXRoom* room = [account.mxSession roomWithRoomId:roomId]; - if (room) - { - manager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:account.mxSession]; - if (manager) - { - break; - } - } - } - if (manager == nil) - { - NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: room with id %@ not found", roomId); - } - else - { - [manager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { - NSString* responseText = responseInfo[UIUserNotificationActionResponseTypedTextKey]; - if (responseText != nil && responseText.length != 0) - { - NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: sending message to room: %@", roomId); - [roomDataSource sendTextMessage:responseText success:^(NSString* eventId) {} failure:^(NSError* error) { - UILocalNotification* failureNotification = [[UILocalNotification alloc] init]; - failureNotification.alertBody = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); - failureNotification.userInfo = notification.userInfo; - [[UIApplication sharedApplication] scheduleLocalNotification: failureNotification]; - NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: error sending text message: %@", error); - }]; - } - - completionHandler(); - }]; - return; - } - } + NSString* responseText = responseInfo[UIUserNotificationActionResponseTypedTextKey]; + + [self handleNotificationInlineReplyForRoomId:roomId withResponseText:responseText success:^(NSString *eventId) { + completionHandler(); + } failure:^(NSError *error) { + + UILocalNotification* failureNotification = [[UILocalNotification alloc] init]; + failureNotification.alertBody = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); + failureNotification.userInfo = notification.userInfo; + [[UIApplication sharedApplication] scheduleLocalNotification: failureNotification]; + NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: error sending text message: %@", error); + + completionHandler(); + }]; } else { NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: unhandled identifier %@", identifier); + completionHandler(); } - completionHandler(); } // iOS 10+, this is called when a notification is about to display in foreground. @@ -1423,8 +1377,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type { - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setPushDeviceToken:nil withPushOptions:nil]; + [self clearPushNotificationToken]; } - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type @@ -1533,7 +1486,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Ignore event already notified to the user // only necessary on iOS 9, iOS 10 will just overwrite notifications with identical IDs - if (!@available(iOS 10, *) && [self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) + if (@available(iOS 10, *)) {} + else if ([self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; @@ -1626,61 +1580,54 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } } - - if (@available(iOS 10, *)) { - [self notificationContentForEvent:event pushRule:rule inAccount:account onComplete:^(UNMutableNotificationContent * _Nullable notificationContent) - { - if (notificationContent) - { - // Printf style escape characters are stripped from the string prior to display; - // to include a percent symbol (%) in the message, use two percent symbols (%%). - // TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc - // use this - maybe not necessary to replace %s - notificationContent.body = [notificationContent.body stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - notificationContent.userInfo = notificationUserInfo; - notificationContent.categoryIdentifier = categoryIdentifier; - notificationContent.sound = [UNNotificationSound soundNamed:soundName]; - - UNNotificationRequest *request = [UNNotificationRequest - requestWithIdentifier:event.eventId - content:notificationContent - trigger:nil]; - - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); - [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; - scheduledNotifications++; - } - else - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationContent. Event id: %@", event.eventId); - } - }]; - } - else - { - [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody) - { - if (notificationBody) - { - // Printf style escape characters are stripped from the string prior to display; - // to include a percent symbol (%) in the message, use two percent symbols (%%). - notificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - - UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; - eventNotification.alertBody = notificationBody; - eventNotification.userInfo = notificationUserInfo; - eventNotification.category = categoryIdentifier; - eventNotification.soundName = soundName; - - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); - [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; - scheduledNotifications++; - } else - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); - } - }]; - } + + [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody) + { + if (notificationBody) + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); + + // Printf style escape characters are stripped from the string prior to display; + // to include a percent symbol (%) in the message, use two percent symbols (%%). + // TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc + // use this - maybe not necessary to replace %s + NSString *fixedNotificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; + + if (@available(iOS 10, *)) + { + UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; + notificationContent.body = fixedNotificationBody; + notificationContent.userInfo = notificationUserInfo; + notificationContent.categoryIdentifier = categoryIdentifier; + if (soundName) + { + notificationContent.sound = [UNNotificationSound soundNamed:soundName]; + } + + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:event.eventId + content:notificationContent + trigger:nil]; + + [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; + } + else + { + UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; + eventNotification.alertBody = fixedNotificationBody; + eventNotification.userInfo = notificationUserInfo; + eventNotification.category = categoryIdentifier; + eventNotification.soundName = soundName; + + [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; + } + + scheduledNotifications++; + } + else + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); + } + }]; } } @@ -1815,136 +1762,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN }]; } -// iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete: for now -- (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 *notificationBody; - 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 *content = 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]) - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; - else - // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - } - else - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; - else - // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - } - else if (event.eventType == MXEventTypeCallInvite) - { - NSString *sdp = event.content[@"offer"][@"sdp"]; - BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; - - if (!isVideoCall) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeRoomMember) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeSticker) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - - UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; - [content setBody:notificationBody]; - - onComplete(content); - }]; -} - /** Display "limited" notifications for events the app was not able to get data (because of /sync failure). @@ -1972,7 +1789,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { // Ignore event already notified to the user // only necessary on iOS 9, iOS 10 will just overwrite notifications with identical IDs - if (!@available(iOS 10, *) && [self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) + if (@available(iOS 10, *)) {} + else if ([self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; @@ -2099,6 +1917,66 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [UIApplication sharedApplication].applicationIconBadgeNumber = count; } +- (void)handleNotificationInlineReplyForRoomId:(NSString*)roomId + withResponseText:(NSString*)responseText + success:(void(^)(NSString *eventId))success + failure:(void(^)(NSError *error))failure +{ + if (!roomId.length) + { + failure(nil); + return; + } + + NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts; + + MXKRoomDataSourceManager* manager; + + for (MXKAccount* account in mxAccounts) + { + MXRoom* room = [account.mxSession roomWithRoomId:roomId]; + if (room) + { + manager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:account.mxSession]; + if (manager) + { + break; + } + } + } + + if (manager == nil) + { + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: room with id %@ not found", roomId); + failure(nil); + } + else + { + [manager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + if (responseText != nil && responseText.length != 0) + { + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: sending message to room: %@", roomId); + [roomDataSource sendTextMessage:responseText success:^(NSString* eventId) { + success(eventId); + } failure:^(NSError* error) { + failure(error); + }]; + } + else + { + failure(nil); + } + }]; + } +} + +- (void)clearPushNotificationToken +{ + // Clear existing token + MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; + [accountManager setPushDeviceToken:nil withPushOptions:nil]; +} + #pragma mark - Universal link - (BOOL)handleUniversalLink:(NSUserActivity*)userActivity From 0679e0af8d35ead2777e84323747b8d0722a730b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 22 Mar 2019 11:06:15 +0100 Subject: [PATCH 19/30] Remove pending and delivered notifications containing a redacted event (Fix #1725) --- Riot/AppDelegate.m | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 7c5486524..9df3ccaf9 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1475,6 +1475,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSMutableArray *eventsArray = eventsToNotify[@(account.mxSession.hash)]; + NSMutableArray *redactedEventIds = [NSMutableArray array]; + // Display a local notification for each event retrieved by the bg sync. for (NSUInteger index = 0; index < eventsArray.count; index++) { @@ -1500,10 +1502,18 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if (event) { - // Ignore redacted event. if (event.isRedactedEvent) { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip redacted event. Event id: %@", event.eventId); + if (@available(iOS 10, *)) + { + // Collect redacted event ids to remove possible delivered redacted notifications + [redactedEventIds addObject:eventId]; + } + else + { + // Ignore redacted event. + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip redacted event. Event id: %@", event.eventId); + } continue; } @@ -1630,6 +1640,18 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN }]; } } + + if (@available(iOS 10, *)) + { + // Remove possible pending and delivered notifications having a redacted event id + if (redactedEventIds.count) + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Remove possible notification with redacted event ids: %@", redactedEventIds); + + [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:redactedEventIds]; + [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:redactedEventIds]; + } + } NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Sent %tu local notifications for %tu events", scheduledNotifications, eventsArray.count); From 5514dbeecd6b548dbc94fd164bfff8c38876d06d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 22 Mar 2019 11:18:07 +0100 Subject: [PATCH 20/30] Handle iOS 12 grouped notifications. Group by room id. --- Riot/AppDelegate.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 9df3ccaf9..39e8a8a98 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1236,6 +1236,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNMutableNotificationContent *failureNotificationContent = [[UNMutableNotificationContent alloc] init]; failureNotificationContent.userInfo = content.userInfo; failureNotificationContent.body = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); + failureNotificationContent.threadIdentifier = roomId; NSString *uuid = [[NSUUID UUID] UUIDString]; UNNotificationRequest *failureNotificationRequest = [UNNotificationRequest requestWithIdentifier:uuid @@ -1609,6 +1610,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN notificationContent.body = fixedNotificationBody; notificationContent.userInfo = notificationUserInfo; notificationContent.categoryIdentifier = categoryIdentifier; + notificationContent.threadIdentifier = roomId; if (soundName) { notificationContent.sound = [UNNotificationSound soundNamed:soundName]; @@ -1842,6 +1844,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNMutableNotificationContent *localNotificationContentForFailedSync = [[UNMutableNotificationContent alloc] init]; localNotificationContentForFailedSync.userInfo = userInfo; localNotificationContentForFailedSync.body = [self limitedNotificationBodyForEvent:eventId inMatrixSession:mxSession]; + localNotificationContentForFailedSync.threadIdentifier = roomId; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:eventId content:localNotificationContentForFailedSync trigger:nil]; From c3af90be0030662042661f2cf03675e68c8e2fef Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 22 Mar 2019 11:49:20 +0100 Subject: [PATCH 21/30] Update changes --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index f060e739d..26dc93173 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,7 @@ Changes in 0.8.5 (2019-xx-xx) =============================================== Improvements: + * Use UserNotifications framework for local notifications (iOS 10+), thanks to @fridtjof (PR #2207). Bug fix: @@ -74,7 +75,6 @@ Improvements: * RoomVC: Remove the beta warning modal when opening an e2e room (#2239). * RoomVC: `Redact` has been renamed to `Remove` to match riot/web (#2134). * Clean up iOS version checking (#2190). - * Use newer notification mechanisms where possible (#2207). * Key backup: Implement setup screen (#2198). * Key backup: Implement recover screen (#2196). * Key backup: Add a dedicated section to settings (#2193). From 9babadfb85ab77cc79443c69e79197fbbde8b198 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 22 Mar 2019 16:00:24 +0100 Subject: [PATCH 22/30] Prepare iOS 12 implicit notifications permission --- Riot/AppDelegate.m | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 39e8a8a98..dc8e1b5ad 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1154,7 +1154,18 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [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 requestAuthorizationWithOptions:(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. +// // Authorize sending notifications without explicit permission (iOS 12+). +// // User can still disable Riot notifications later in settings or directly from a Riot notification. +// if (@available(iOS 12.0, *)) +// { +// authorizationOptions = authorizationOptions | UNAuthorizationOptionProvisional; +// } + + [center requestAuthorizationWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error) { // code here is equivalent to self:application:didRegisterUserNotificationSettings: if (granted) { From 5645dfa43f458c330eee7f9bca9fc602303c0538 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 4 Apr 2019 17:45:13 +0200 Subject: [PATCH 23/30] Update notification strings for notification titles --- Riot/Assets/en.lproj/Localizable.strings | 32 ++++++------------------ 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index b387bfa82..fc5cc015a 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -14,17 +14,17 @@ limitations under the License. */ +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ 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 */ -"MSG_FROM_USER" = "Message from %@"; +"MSG_FROM_USER" = "%@ sent a message"; /* New message from a specific person in a named room */ "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 */ /* New message from a specific person, not referencing a room. Content included. */ @@ -39,28 +39,22 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; -/* New action message, sender is specified in notification title */ -"ACTION" = "* %@"; - /** Image Messages **/ /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ sent you a picture %@"; +"IMAGE_FROM_USER" = "%@ sent a picture %@"; /* New action message from a specific person in a named room. */ "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 */ "SINGLE_UNREAD_IN_ROOM" = "You received a message in %@"; /* A single unread message */ "SINGLE_UNREAD" = "You received a message"; -/* Sticker, but with the sender (and room) already in the title */ -"STICKER_TEXT_WITH_TITLE" = "💟 Sticker"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ sent a sticker"; /** Coalesced messages **/ @@ -96,24 +90,14 @@ /* A user has invited you to a named room */ "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 **/ /* Incoming one-to-one voice call */ "VOICE_CALL_FROM_USER" = "Call from %@"; -"VOICE_CALL" = "📞 Call"; - /* Incoming one-to-one video call */ "VIDEO_CALL_FROM_USER" = "Video call from %@"; -"VIDEO_CALL" = "📹 Video call"; - /* Incoming unnamed voice conference invite from a specific person */ "VOICE_CONF_FROM_USER" = "Group call from %@"; @@ -125,5 +109,3 @@ /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'"; - -"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; \ No newline at end of file From 374b45fead0385ee8ed79573d2bf6530ab14bdef Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 10:59:18 +0200 Subject: [PATCH 24/30] Update iOS 10+ notification titles --- Riot/AppDelegate.m | 303 +++++++++++++++++++++++++++------------------ 1 file changed, 183 insertions(+), 120 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 892acd89d..ba281a040 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1478,19 +1478,21 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)handleLocalNotificationsForAccount:(MXKAccount*)account { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: %@", account.mxCredentials.userId); + NSString *userId = account.mxCredentials.userId; + + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: %@", userId); NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: eventsToNotify: %@", eventsToNotify[@(account.mxSession.hash)]); NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: incomingPushEventIds: %@", self.incomingPushEventIds[@(account.mxSession.hash)]); - + __block NSUInteger scheduledNotifications = 0; - + // The call invite are handled here only when the callkit is not active. BOOL isCallKitActive = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled; NSMutableArray *eventsArray = eventsToNotify[@(account.mxSession.hash)]; NSMutableArray *redactedEventIds = [NSMutableArray array]; - + // Display a local notification for each event retrieved by the bg sync. for (NSUInteger index = 0; index < eventsArray.count; index++) { @@ -1499,11 +1501,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *roomId = eventDict[@"room_id"]; BOOL checkReadEvent = YES; MXEvent *event; - + // Ignore event already notified to the user // only necessary on iOS 9, iOS 10 will just overwrite notifications with identical IDs if (@available(iOS 10, *)) {} - else if ([self displayedLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId type:nil]) + else if ([self displayedLocalNotificationForEvent:eventId andUser:userId type:nil]) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a notification. Event id: %@", eventId); continue; @@ -1526,7 +1528,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN else { // Ignore redacted event. - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip redacted event. Event id: %@", event.eventId); + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip redacted event. Event id: %@", eventId); } continue; } @@ -1537,7 +1539,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Ignore call invite when callkit is active. if (isCallKitActive) { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip call event. Event id: %@", event.eventId); + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip call event. Event id: %@", eventId); continue; } else @@ -1557,13 +1559,13 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if (checkReadEvent) { // Ignore event which has been read on another device. - MXReceiptData *readReceipt = [account.mxSession.store getReceiptInRoom:roomId forUserId:account.mxCredentials.userId]; + MXReceiptData *readReceipt = [account.mxSession.store getReceiptInRoom:roomId forUserId:userId]; if (readReceipt) { MXEvent *readReceiptEvent = [account.mxSession.store eventWithEventId:readReceipt.eventId inRoom:roomId]; if (event.originServerTs <= readReceiptEvent.originServerTs) { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip already read event. Event id: %@", event.eventId); + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip already read event. Event id: %@", eventId); continue; } } @@ -1571,108 +1573,137 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Prepare the local notification MXPushRule *rule = eventDict[@"push_rule"]; - - NSDictionary *notificationUserInfo = @{ - @"type": @"full", - @"room_id": event.roomId, - @"event_id": event.eventId, - @"user_id": account.mxCredentials.userId - }; - - BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; - - NSString *categoryIdentifier; - - if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) + + if (@available(iOS 10, *)) { - categoryIdentifier = @"QUICK_REPLY"; - } - - - NSString *soundName; - - // Set sound name based on the value provided in action of MXPushRule - for (MXPushRuleAction *action in rule.actions) - { - if (action.actionType == MXPushRuleActionTypeSetTweak) - { - if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + [self notificationContentForEvent:event pushRule:rule inAccount:account onComplete:^(UNNotificationContent * _Nullable notificationContent) { + + if (notificationContent) { - soundName = action.parameters[@"value"]; - if ([soundName isEqualToString:@"default"]) - soundName = @"message.mp3"; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:eventId + content:notificationContent + trigger:nil]; + + [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + + if (error) + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Fail to display notification for event %@ with error: %@", eventId, error); + } + else + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", eventId); + } + }]; + + scheduledNotifications++; } - } + else + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated content. Event id: %@", eventId); + } + }]; } - - [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody) - { - if (notificationBody) + else + { + [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString *_Nullable notificationBody) { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); - - // Printf style escape characters are stripped from the string prior to display; - // to include a percent symbol (%) in the message, use two percent symbols (%%). - // TODO: https://developer.apple.com/documentation/foundation/nsstring/1649585-localizedusernotificationstringf?language=objc - // use this - maybe not necessary to replace %s - NSString *fixedNotificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - - if (@available(iOS 10, *)) - { - UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; - notificationContent.body = fixedNotificationBody; - notificationContent.userInfo = notificationUserInfo; - notificationContent.categoryIdentifier = categoryIdentifier; - notificationContent.threadIdentifier = roomId; - if (soundName) - { - notificationContent.sound = [UNNotificationSound soundNamed:soundName]; - } - - UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:event.eventId - content:notificationContent - trigger:nil]; - - [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; - } - else + if (notificationBody) { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", eventId); + + // Printf style escape characters are stripped from the string prior to display; + // to include a percent symbol (%) in the message, use two percent symbols (%%). + NSString *fixedNotificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; + + NSDictionary *notificationUserInfo = [self notificationUserInfoForEvent:event andUserId:userId]; + NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; + NSString *soundName = [self notificationSoundNameFromPushRule:rule]; + UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; eventNotification.alertBody = fixedNotificationBody; eventNotification.userInfo = notificationUserInfo; eventNotification.category = categoryIdentifier; eventNotification.soundName = soundName; - + [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; + + scheduledNotifications++; } - - scheduledNotifications++; - } - else - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); - } - }]; + else + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", eventId); + } + }]; + } } } - + if (@available(iOS 10, *)) { // Remove possible pending and delivered notifications having a redacted event id if (redactedEventIds.count) { NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Remove possible notification with redacted event ids: %@", redactedEventIds); - + [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:redactedEventIds]; [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:redactedEventIds]; } } - + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Sent %tu local notifications for %tu events", scheduledNotifications, eventsArray.count); - + [eventsArray removeAllObjects]; } +- (NSString*)notificationSoundNameFromPushRule:(MXPushRule*)pushRule +{ + NSString *soundName; + + // Set sound name based on the value provided in action of MXPushRule + for (MXPushRuleAction *action in pushRule.actions) + { + if (action.actionType == MXPushRuleActionTypeSetTweak) + { + if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + { + soundName = action.parameters[@"value"]; + if ([soundName isEqualToString:@"default"]) + { + soundName = @"message.mp3"; + } + } + } + } + + return soundName; +} + +- (NSString*)notificationCategoryIdentifierForEvent:(MXEvent*)event +{ + BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + + NSString *categoryIdentifier; + + if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) + { + categoryIdentifier = @"QUICK_REPLY"; + } + + return categoryIdentifier; +} + +- (NSDictionary*)notificationUserInfoForEvent:(MXEvent*)event andUserId:(NSString*)userId +{ + NSDictionary *notificationUserInfo = @{ + @"type": @"full", + @"room_id": event.roomId, + @"event_id": event.eventId, + @"user_id": userId + }; + return notificationUserInfo; +} + - (void)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPushRule*)rule inAccount:(MXKAccount*)account onComplete:(void (^)(NSString * _Nullable notificationBody))onComplete; { if (!event.content || !event.content.count) @@ -1800,7 +1831,7 @@ 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; +- (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule inAccount:(MXKAccount *)account onComplete:(void (^)(UNNotificationContent * _Nullable notificationContent))onComplete; { if (!event.content || !event.content.count) { @@ -1858,97 +1889,129 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN 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]; + notificationTitle = [NSString localizedUserNotificationStringForKey:@"MSG_FROM_USER_IN_ROOM_TITLE" arguments:@[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]; + notificationBody = [NSString localizedUserNotificationStringForKey:@"ACTION_FROM_USER" arguments:@[eventSenderName, messageContent]]; } else if ([msgType isEqualToString:@"m.image"]) - notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil); + { + notificationBody = [NSString localizedUserNotificationStringForKey:@"IMAGE_FROM_USER" arguments:@[eventSenderName, messageContent]]; + } else + { // Encrypted messages falls here - notificationBody = NSLocalizedString(@"MSG_TEXT_WITH_TITLE", nil); + notificationBody = [NSString localizedUserNotificationStringForKey:@"MSG_FROM_USER" arguments:@[eventSenderName]]; + } } 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]; + notificationBody = [NSString localizedUserNotificationStringForKey:@"ACTION_FROM_USER" arguments:@[eventSenderName, messageContent]]; } else if ([msgType isEqualToString:@"m.image"]) - notificationBody = NSLocalizedString(@"IMAGE_TEXT_WITH_TITLE", nil); + { + notificationBody = [NSString localizedUserNotificationStringForKey:@"IMAGE_FROM_USER" arguments:@[eventSenderName, messageContent]]; + } else + { // Encrypted messages falls here - notificationBody = NSLocalizedString(@"MSG_TEXT_WITH_TITLE", nil); + notificationBody = [NSString localizedUserNotificationStringForKey:@"MSG_FROM_USER" arguments:@[eventSenderName]]; + } } } 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); + { + notificationBody = [NSString localizedUserNotificationStringForKey:@"VOICE_CALL_FROM_USER" arguments:@[eventSenderName]]; + } else - notificationBody = NSLocalizedString(@"VIDEO_CALL", nil); - + { + notificationBody = [NSString localizedUserNotificationStringForKey:@"VIDEO_CALL_FROM_USER" arguments:@[eventSenderName]]; + } + // 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]; + { + notificationBody = [NSString localizedUserNotificationStringForKey:@"USER_INVITE_TO_NAMED_ROOM" arguments:@[eventSenderName, roomDisplayName]]; + } else - notificationBody = NSLocalizedString(@"INVITE_TO_CHAT", nil); + { + notificationBody = [NSString localizedUserNotificationStringForKey:@"USER_INVITE_TO_CHAT" arguments:@[eventSenderName]]; + } } 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]; + { + notificationTitle = [NSString localizedUserNotificationStringForKey:@"MSG_FROM_USER_IN_ROOM_TITLE" arguments:@[eventSenderName, roomDisplayName]]; + } else + { notificationTitle = eventSenderName; - - notificationBody = NSLocalizedString(@"STICKER_TEXT_WITH_TITLE", nil); + } + + notificationBody = [NSString localizedUserNotificationStringForKey:@"STICKER_FROM_USER" arguments:@[eventSenderName]]; } - - UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; - - [content setTitle:notificationTitle]; - [content setBody:notificationBody]; - [content setThreadIdentifier:threadIdentifier]; - - onComplete(content); + + UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; + + NSDictionary *notificationUserInfo = [self notificationUserInfoForEvent:event andUserId:account.mxCredentials.userId]; + NSString *notificationSoundName = [self notificationSoundNameFromPushRule:rule]; + NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; + + notificationContent.title = notificationTitle; + notificationContent.subtitle = notificationSubTitle; + notificationContent.body = notificationBody; + notificationContent.threadIdentifier = threadIdentifier; + notificationContent.userInfo = notificationUserInfo; + notificationContent.categoryIdentifier = categoryIdentifier; + + if (notificationSoundName) + { + notificationContent.sound = [UNNotificationSound soundNamed:notificationSoundName]; + } + + onComplete([notificationContent copy]); }]; } From 73f00428f0f05b9de8b0d1bae564737efe997e5d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 14:51:56 +0200 Subject: [PATCH 25/30] When navigate to room, remove associated delivered notifications. --- Riot/AppDelegate.m | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index ba281a040..a6d335fb9 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1273,7 +1273,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } else { - NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: unhandled identifier %@", [response actionIdentifier]); + NSLog(@"[AppDelegate][Push] didReceiveNotificationResponse: unhandled identifier %@", actionIdentifier); completionHandler(); } } @@ -2231,6 +2231,34 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [accountManager setPushDeviceToken:nil withPushOptions:nil]; } +// Remove delivred notifications for a given room id except call notifications +- (void)removeDeliveredNotificationsWithRoomId:(NSString*)roomId completion:(dispatch_block_t)completion +{ + NSMutableArray *notificationRequestIdentifiersToRemove = [NSMutableArray new]; + + UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + + [notificationCenter getDeliveredNotificationsWithCompletionHandler:^(NSArray * _Nonnull notifications) { + + for (UNNotification *notification in notifications) + { + NSString *threadIdentifier = notification.request.content.threadIdentifier; + + if ([threadIdentifier isEqualToString:roomId]) + { + [notificationRequestIdentifiersToRemove addObject:notification.request.identifier]; + } + } + + [notificationCenter removeDeliveredNotificationsWithIdentifiers:notificationRequestIdentifiersToRemove]; + + if (completion) + { + completion(); + } + }]; +} + #pragma mark - Universal link - (BOOL)handleUniversalLink:(NSUserActivity*)userActivity @@ -3710,8 +3738,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [self restoreInitialDisplay:^{ // Select room to display its details (dispatch this action in order to let TabBarController end its refresh) - [_masterTabBarController selectRoomWithId:roomId andEventId:eventId inMatrixSession:mxSession]; - + [_masterTabBarController selectRoomWithId:roomId andEventId:eventId inMatrixSession:mxSession completion:^{ + // Remove delivered notifications for this room + [self removeDeliveredNotificationsWithRoomId:roomId completion:nil]; + }]; }]; } From 5e0f7121a96aebe37c886d8ff0987f380226e81f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 14:57:55 +0200 Subject: [PATCH 26/30] Remove commented code about notification provisional authorization. --- Riot/AppDelegate.m | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index a6d335fb9..13cf6fd99 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1157,14 +1157,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN UNAuthorizationOptions authorizationOptions = (UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge); - // 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+). -// // User can still disable Riot notifications later in settings or directly from a Riot notification. -// if (@available(iOS 12.0, *)) -// { -// authorizationOptions = authorizationOptions | UNAuthorizationOptionProvisional; -// } - [center requestAuthorizationWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error) { // code here is equivalent to self:application:didRegisterUserNotificationSettings: From a963a92b055c7a78a0e52d90fa3f93c9fb9621a2 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 15:11:50 +0200 Subject: [PATCH 27/30] Update changes --- CHANGES.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 97f51f2e4..e0053a560 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,9 +2,10 @@ Changes in 0.8.5 (2019-xx-xx) =============================================== 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). + * Notifications: Use UserNotifications framework for local notifications (iOS 10+), thanks to @fridtjof (PR #2207). + * Notifications: Added titles to notifications on iOS 10+, thanks to @fridtjof (PR #2347). + * iOS 12 Notification: Group them by room (#2337 and PR #2347 thanks to @fridtjof). + * Notifications: When navigate to a room, remove associated delivered notifications (#2337). Bug fix: From 3daa0fa7abcdd246407396e1ff675fca943d1606 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 5 Apr 2019 15:55:05 +0200 Subject: [PATCH 28/30] Update Riot/AppDelegate.m Co-Authored-By: SBiOSoftWhare --- Riot/AppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 13cf6fd99..c8268ba4a 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1896,7 +1896,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Display the room name only if it is different than the sender name if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) { - notificationTitle = [NSString localizedUserNotificationStringForKey:@"MSG_FROM_USER_IN_ROOM_TITLE" arguments:@[eventSenderName, roomDisplayName]];// + notificationTitle = [NSString localizedUserNotificationStringForKey:@"MSG_FROM_USER_IN_ROOM_TITLE" arguments:@[eventSenderName, roomDisplayName]]; if ([msgType isEqualToString:@"m.text"]) { From 74c6e746b2bbf61895b5ac73a3353b06b889297f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 16:03:27 +0200 Subject: [PATCH 29/30] Add logs on related push methods --- Riot/AppDelegate.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index c8268ba4a..42aa818b8 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2218,6 +2218,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)clearPushNotificationToken { + NSLog(@"[AppDelegate][Push] clearPushNotificationToken: Clear existing token"); + // Clear existing token MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; [accountManager setPushDeviceToken:nil withPushOptions:nil]; @@ -2226,6 +2228,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Remove delivred notifications for a given room id except call notifications - (void)removeDeliveredNotificationsWithRoomId:(NSString*)roomId completion:(dispatch_block_t)completion { + NSLog(@"[AppDelegate][Push] removeDeliveredNotificationsWithRoomId: Remove potential delivered notifications for room id: %@", roomId); + NSMutableArray *notificationRequestIdentifiersToRemove = [NSMutableArray new]; UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; From c5df3cd5cb803592412f24cbb7993b31c1abd9e8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 16:48:24 +0200 Subject: [PATCH 30/30] Fix an issue in AppDelegate --- Riot/AppDelegate.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index bfec8e010..fddfe1bd9 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2000,8 +2000,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *notificationSoundName = [self notificationSoundNameFromPushRule:rule]; NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; - notificationContent.title = notificationTitle; - notificationContent.subtitle = notificationSubTitle; + notificationContent.title = notificationTitle; notificationContent.body = notificationBody; notificationContent.threadIdentifier = threadIdentifier; notificationContent.userInfo = notificationUserInfo;