From c0cdcf170d8638e5ad52c2d6b1ebe7815650ca65 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 5 Apr 2019 10:59:18 +0200 Subject: [PATCH] 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]); }]; }