Skip to content

Commit

Permalink
Missing Push Notifications (#1696): Show a notification even if the a…
Browse files Browse the repository at this point in the history
…pp fails to sync with its hs to get all data.

Build the string for the notification
  • Loading branch information
manuroe committed Dec 29, 2017
1 parent f734096 commit 536c8cc
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 12 deletions.
108 changes: 96 additions & 12 deletions Riot/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ The current call view controller (if any).
*/
NSMutableDictionary <NSNumber *, NSMutableArray <NSDictionary *> *> *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 <NSString*, NSDictionary*> *incomingPushPayloads;

/**
Currently displayed "Call not supported" alert.
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1165,6 +1175,9 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
{
[array addObject:eventId];
}

// Cache payload for further usage
incomingPushPayloads[eventId] = payload.dictionaryPayload;
}
else
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<NSString *> *)events
{
NSString *userId = mxSession.matrixRestClient.credentials.userId;
Expand All @@ -1466,20 +1497,78 @@ - (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);
[[UIApplication sharedApplication] scheduleLocalNotification:localNotificationForFailedSync];
}
}

/**
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;
Expand All @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions Riot/Assets/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down

0 comments on commit 536c8cc

Please sign in to comment.