From 536c8cc3604a900922f90ea294167d1c12998f66 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 29 Dec 2017 13:04:57 +0100 Subject: [PATCH] Missing Push Notifications (#1696): Show a notification even if the app fails to sync with its hs to get all data. Build the string for the notification --- Riot/AppDelegate.m | 108 ++++++++++++++++++++--- Riot/Assets/en.lproj/Localizable.strings | 6 ++ 2 files changed, 102 insertions(+), 12 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index d576a99449..bb21b013b6 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -177,6 +177,14 @@ The current call view controller (if any). */ NSMutableDictionary *> *eventsToNotify; + /** + Cache for payloads received with incoming push notifications. + The key is the event id. The value, the payload. + Note: for the moment, objets in this dictionary are never removed but + the impact on memory is low. + */ + NSMutableDictionary *incomingPushPayloads; + /** Currently displayed "Call not supported" alert. */ @@ -395,6 +403,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( callEventsListeners = [NSMutableDictionary dictionary]; notificationListenerBlocks = [NSMutableDictionary dictionary]; eventsToNotify = [NSMutableDictionary dictionary]; + incomingPushPayloads = [NSMutableDictionary dictionary]; // To simplify navigation into the app, we retrieve here the main navigation controller and the tab bar controller. UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController; @@ -1085,6 +1094,7 @@ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UI 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 @@ -1165,6 +1175,9 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa { [array addObject:eventId]; } + + // Cache payload for further usage + incomingPushPayloads[eventId] = payload.dictionaryPayload; } else { @@ -1238,6 +1251,13 @@ - (void)handleLocalNotificationsForAccount:(MXKAccount*)account NSString *roomId = eventDict[@"room_id"]; BOOL checkReadEvent = YES; MXEvent *event; + + // Ignore event already notified to the user + if ([self displayedFailedSyncLocalNotificationForEvent:eventId andUser:account.mxCredentials.userId]) + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event already displayed in a failed sync notif. Event id: %@", eventId); + continue; + } if (eventId && roomId) { @@ -1303,7 +1323,11 @@ - (void)handleLocalNotificationsForAccount:(MXKAccount*)account UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; eventNotification.alertBody = notificationBody; - eventNotification.userInfo = @{ @"room_id" : event.roomId }; + eventNotification.userInfo = @{ + @"room_id": event.roomId, + @"event_id": event.eventId, + @"user_id": account.mxCredentials.userId + }; // Set sound name based on the value provided in action of MXPushRule for (MXPushRuleAction *action in rule.actions) @@ -1444,6 +1468,13 @@ - (nullable NSString *)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPus return notificationBody; } +/** + Display limited notifications for events the app was not able to get data + (because of /sync failure). + + @param mxSession the matrix session where the /sync failed. + @param events the list of events id we did not get data. + */ - (void)handleLocalNotificationsForFailedSync:(MXSession*)mxSession events:(NSArray *)events { NSString *userId = mxSession.matrixRestClient.credentials.userId; @@ -1466,13 +1497,27 @@ - (void)handleLocalNotificationsForFailedSync:(MXSession*)mxSession events:(NSAr continue; } - UILocalNotification *localNotificationForFailedSync = [[UILocalNotification alloc] init]; - localNotificationForFailedSync.userInfo = @{ - @"type": @"failed_sync", - @"event_id": eventId, - @"user_id": userId - }; + // Build notification user info + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"failed_sync", + @"event_id": eventId, + @"user_id": userId + }]; + // Add the room_id so that user will open the room when tapping on the notif + NSDictionary *payload = incomingPushPayloads[eventId]; + NSString *roomId = payload[@"room_id"]; + if (roomId) + { + userInfo[@"room_id"] = roomId; + } + else + { + 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 notificationBodyForFailedSyncEvent:eventId inMatrixSession:mxSession]; NSLog(@"[AppDelegate][Push] handleLocalNotificationsForFailedSync: Display notification for event %@", eventId); @@ -1480,6 +1525,50 @@ - (void)handleLocalNotificationsForFailedSync:(MXSession*)mxSession events:(NSAr } } +/** + Build the body of the "limited" notification to display to the user. + + @param eventId the id of the event the app failed to get data. + @param mxSession the matrix session where the /sync failed. + @return the string to display in the local notification. + */ +- (nullable NSString *)limited otificationBodyForEvent:(MXEvent *)event pushRule:(MXPushRule*)rule inAccount:(MXKAccount*)account +- (nullable NSString *)notificationBodyForFailedSyncEvent:(NSString *)eventId inMatrixSession:(MXSession*)mxSession +{ + NSString *notificationBody; + + NSString *roomDisplayName; + + NSDictionary *payload = incomingPushPayloads[eventId]; + NSString *roomId = payload[@"room_id"]; + if (roomId) + { + MXRoomSummary *roomSummary = [mxSession roomSummaryWithRoomId:roomId]; + if (roomSummary) + { + roomDisplayName = roomSummary.displayname; + } + } + + if (roomDisplayName.length) + { + [NSString stringWithFormat:NSLocalizedString(@"SINGLE_UNREAD_IN_ROOM", nil), roomDisplayName]; + } + else + { + [NSString stringWithFormat:NSLocalizedString(@"SINGLE_UNREAD", nil), roomDisplayName]; + } + + return notificationBody; +} + +/** + Return already displayed notification for a failed sync. + + @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. + @return the local notification if any. + */ - (UILocalNotification*)displayedFailedSyncLocalNotificationForEvent:(NSString*)eventId andUser:(NSString*)userId { UILocalNotification *localNotificationForFailedSync; @@ -1497,11 +1586,6 @@ - (UILocalNotification*)displayedFailedSyncLocalNotificationForEvent:(NSString*) return localNotificationForFailedSync; } -- (nullable NSString *)notificationBodyForFailedSyncEvent:(NSString *)eventId inMatrixSession:(MXSession*)mxSession -{ - return @"todo"; -} - - (void)refreshApplicationIconBadgeNumber { // Consider the total number of missed discussions including the invites. diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index 2c7af440a7..33e5d39f42 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -44,6 +44,12 @@ /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ posted a picture %@ in %@"; +/* 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"; + /** Coalesced messages **/ /* Multiple unread messages in a room */