From f60021adde85c7789a9b303160fc9afab2268540 Mon Sep 17 00:00:00 2001 From: James Xabregas Date: Mon, 19 Jul 2021 02:46:57 +1000 Subject: [PATCH 1/2] Added configurable repeatInterval (minute, hour, day, week, month, year) parameter for addNotificationRequest(). Also fixes bug with current implementation where notification would not repeat daily with repeats= true as per the documentation. --- README.md | 3 +- index.d.ts | 4 ++ ios/RCTConvert+Notification.m | 96 +++++++++++++++++++++++++---------- js/types.js | 4 ++ 4 files changed, 78 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1ac090d68..dc9568255 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,8 @@ request is an object containing: - `body` : The message displayed in the notification alert. - `badge` The number to display as the app's icon badge. Setting the number to 0 removes the icon badge. - `fireDate` : The date and time when the system should deliver the notification. -- `repeats` : Sets notification to repeat daily. Must be used with fireDate. +- `repeats` : Sets notification to repeat (default daily). Must be used with fireDate. Use repeatInterval to modify repeat behaviour. +- `repeatInterval`: The interval to repeat as a string. Possible values: `minute`, `hour`, `day`, `week`, `month`, `year` (optional). Default `day` if `repeats` is true. - `sound` : The sound played when the notification is fired. - `category` : The category of this notification, required for actionable notifications. - `isSilent` : If true, the notification will appear without sound. diff --git a/index.d.ts b/index.d.ts index d59ba22a4..7920d83a4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -174,6 +174,10 @@ export type NotificationRequest = { * Must be used with fireDate. */ repeats?: boolean; + /** + * The interval to repeat as a string. Possible values: minute, hour, day, week, month, year. + */ + repeatInterval?: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; /** * Sets notification to be silent */ diff --git a/ios/RCTConvert+Notification.m b/ios/RCTConvert+Notification.m index 434ef80a4..7f63f476e 100644 --- a/ios/RCTConvert+Notification.m +++ b/ios/RCTConvert+Notification.m @@ -62,14 +62,14 @@ + (UILocalNotification *)UILocalNotification:(id)json + (NSDictionary *)RCTFormatLocalNotification:(UILocalNotification *)notification { NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; - + if (notification.fireDate) { NSDateFormatter *formatter = [NSDateFormatter new]; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; NSString *fireDateString = [formatter stringFromDate:notification.fireDate]; formattedLocalNotification[@"fireDate"] = fireDateString; } - + formattedLocalNotification[@"alertAction"] = RCTNullIfNil(notification.alertAction); formattedLocalNotification[@"alertTitle"] = RCTNullIfNil(notification.alertTitle); formattedLocalNotification[@"alertBody"] = RCTNullIfNil(notification.alertBody); @@ -93,11 +93,11 @@ @implementation RCTConvert (UNNotificationRequest) + (UNNotificationRequest *)UNNotificationRequest:(id)json { NSDictionary *details = [self NSDictionary:json]; - + BOOL isSilent = [RCTConvert BOOL:details[@"isSilent"]]; NSString* identifier = [RCTConvert NSString:details[@"id"]]; - - + + UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; content.title= [RCTConvert NSString:details[@"title"]]; content.subtitle= [RCTConvert NSString:details[@"subtitle"]]; @@ -117,13 +117,53 @@ + (UNNotificationRequest *)UNNotificationRequest:(id)json NSDate* fireDate = [RCTConvert NSDate:details[@"fireDate"]]; BOOL repeats = [RCTConvert BOOL:details[@"repeats"]]; - NSDateComponents *triggerDate = fireDate ? [[NSCalendar currentCalendar] - components:NSCalendarUnitYear + - NSCalendarUnitMonth + NSCalendarUnitDay + - NSCalendarUnitHour + NSCalendarUnitMinute + - NSCalendarUnitSecond + NSCalendarUnitTimeZone - fromDate:fireDate] : nil; + NSString* repeatInterval = [RCTConvert NSString:details[@"repeatInterval"]]; + + NSDateComponents *triggerDate = nil; + if (repeats && fireDate) { + if ([repeatInterval isEqualToString:@"year"]) { + triggerDate = [[NSCalendar currentCalendar] + components: + NSCalendarUnitMonth + NSCalendarUnitDay + + NSCalendarUnitHour + NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate]; + } else if ([repeatInterval isEqualToString:@"month"]) { + triggerDate = fireDate ? [[NSCalendar currentCalendar] + components: NSCalendarUnitDay + + NSCalendarUnitHour + NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate] : nil; + + } else if ([repeatInterval isEqualToString:@"week"]) { + triggerDate = fireDate ? [[NSCalendar currentCalendar] + components: NSCalendarUnitWeekday + + NSCalendarUnitHour + NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate] : nil; + + } else if ([repeatInterval isEqualToString:@"hour"]) { + + triggerDate = fireDate ? [[NSCalendar currentCalendar] + components: NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate] : nil; + + } else if ([repeatInterval isEqualToString:@"minute"]) { + + triggerDate = fireDate ? [[NSCalendar currentCalendar] + components: NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate] : nil; + } else { //Default to "day" if repeatInterval not specifed or invalid + triggerDate = fireDate ? [[NSCalendar currentCalendar] + components: + NSCalendarUnitHour + NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate] : nil; + } + } + UNCalendarNotificationTrigger* trigger = triggerDate ? [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:triggerDate repeats:repeats] : nil; UNNotificationRequest* notification = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger]; @@ -136,9 +176,9 @@ + (UNNotificationRequest *)UNNotificationRequest:(id)json + (NSDictionary *)RCTFormatUNNotificationRequest:(UNNotificationRequest*)request { NSMutableDictionary *formattedRequest = [NSMutableDictionary dictionary]; - + formattedRequest[@"id"] = RCTNullIfNil(request.identifier); - + UNNotificationContent *content = request.content; formattedRequest[@"title"] = RCTNullIfNil(content.title); formattedRequest[@"subtitle"] = RCTNullIfNil(content.subtitle); @@ -148,7 +188,7 @@ + (NSDictionary *)RCTFormatUNNotificationRequest:(UNNotificationRequest*)request formattedRequest[@"category"] = RCTNullIfNil(content.categoryIdentifier); formattedRequest[@"thread-id"] = RCTNullIfNil(content.threadIdentifier); formattedRequest[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); - + if (request.trigger) { UNCalendarNotificationTrigger* trigger = (UNCalendarNotificationTrigger*)request.trigger; NSDateFormatter *formatter = [NSDateFormatter new]; @@ -195,11 +235,11 @@ + (UNNotificationAction *)UNNotificationAction:(id)json NSDictionary *details = [self NSDictionary:json]; NSString* identifier = [RCTConvert NSString:details[@"id"]]; NSString* title = [RCTConvert NSString:details[@"title"]]; - - + + UNNotificationActionOptions options = [RCTConvert UNNotificationActionOptions:details[@"options"]]; UNNotificationAction* action = details[@"textInput"] ? [UNTextInputNotificationAction actionWithIdentifier:identifier title:title options:options textInputButtonTitle:details[@"textInput"][@"buttonTitle"] textInputPlaceholder:details[@"textInput"][@"placeholder"]] : [UNNotificationAction actionWithIdentifier:identifier title:title options:options]; - + return action; } @@ -213,15 +253,15 @@ @implementation RCTConvert (UNNotificationCategory) + (UNNotificationCategory *)UNNotificationCategory:(id)json { NSDictionary *details = [self NSDictionary:json]; - + NSString* identifier = [RCTConvert NSString:details[@"id"]]; NSMutableArray* actions = [NSMutableArray new]; for (NSDictionary* action in [RCTConvert NSArray:details[@"actions"]]) { [actions addObject:[RCTConvert UNNotificationAction:action]]; } - + UNNotificationCategory* category = [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:@[] options:UNNotificationCategoryOptionNone]; - + return category; } @@ -237,21 +277,21 @@ + (NSDictionary *)RCTFormatUNNotificationResponse:(UNNotificationResponse *)resp UNNotification* notification = response.notification; NSMutableDictionary *formattedResponse = [[RCTConvert RCTFormatUNNotification:notification] mutableCopy]; UNNotificationContent *content = notification.request.content; - + NSMutableDictionary *userInfo = [content.userInfo mutableCopy]; userInfo[@"userInteraction"] = [NSNumber numberWithInt:1]; userInfo[@"actionIdentifier"] = response.actionIdentifier; - + formattedResponse[@"badge"] = RCTNullIfNil(content.badge); formattedResponse[@"sound"] = RCTNullIfNil(content.sound); formattedResponse[@"userInfo"] = RCTNullIfNil(RCTJSONClean(userInfo)); formattedResponse[@"actionIdentifier"] = RCTNullIfNil(response.actionIdentifier); - + NSString* userText = [response isKindOfClass:[UNTextInputNotificationResponse class]] ? ((UNTextInputNotificationResponse *)response).userText : nil; if (userText) { formattedResponse[@"userText"] = RCTNullIfNil(userText); } - + return formattedResponse; } @end @@ -265,16 +305,16 @@ + (NSDictionary *)RCTFormatUNNotification:(UNNotification *)notification { NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary]; UNNotificationContent *content = notification.request.content; - + formattedNotification[@"identifier"] = notification.request.identifier; - + if (notification.date) { NSDateFormatter *formatter = [NSDateFormatter new]; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; NSString *dateString = [formatter stringFromDate:notification.date]; formattedNotification[@"date"] = dateString; } - + formattedNotification[@"title"] = RCTNullIfNil(content.title); formattedNotification[@"subtitle"] = RCTNullIfNil(content.subtitle); formattedNotification[@"body"] = RCTNullIfNil(content.body); @@ -283,7 +323,7 @@ + (NSDictionary *)RCTFormatUNNotification:(UNNotification *)notification formattedNotification[@"category"] = RCTNullIfNil(content.categoryIdentifier); formattedNotification[@"thread-id"] = RCTNullIfNil(content.threadIdentifier); formattedNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); - + return formattedNotification; } diff --git a/js/types.js b/js/types.js index c373e0de8..bd2584c7c 100644 --- a/js/types.js +++ b/js/types.js @@ -45,6 +45,10 @@ export type NotificationRequest = {| * Must be used with fireDate. */ repeats?: boolean, + /** + * The interval to repeat as a string. Possible values: minute, hour, day, week, month, year. + */ + repeatInterval?: string, /** * Sets notification to be silent */ From b159cb60acf127ff1cd536d554eed58d200284a7 Mon Sep 17 00:00:00 2001 From: James Xabregas Date: Tue, 20 Jul 2021 13:00:52 +1000 Subject: [PATCH 2/2] Additional modifications to local notification repeating logic to ensure function of all use cases. Supports repeating by year, month, week, day, hour, minute, non-repeating, and immediate delivery. The "repeats" parameter in addNotificationRequest() is effectively redundant and replaced by "repeatInterval" however the documented behaviour of "repeats" has been retained to ensure backwards compatibility, even though it didn't previously work. --- ios/RCTConvert+Notification.m | 38 ++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/ios/RCTConvert+Notification.m b/ios/RCTConvert+Notification.m index 7f63f476e..75326c5b2 100644 --- a/ios/RCTConvert+Notification.m +++ b/ios/RCTConvert+Notification.m @@ -116,12 +116,19 @@ + (UNNotificationRequest *)UNNotificationRequest:(id)json } NSDate* fireDate = [RCTConvert NSDate:details[@"fireDate"]]; - BOOL repeats = [RCTConvert BOOL:details[@"repeats"]]; NSString* repeatInterval = [RCTConvert NSString:details[@"repeatInterval"]]; + //For backward compatability with existing request interface. + //If repeats param is set to true and no repeatInterval set then use "day" as the default + BOOL repeatsParam = [RCTConvert BOOL:details[@"repeats"]]; + if (repeatsParam && repeatInterval.length == 0) { + repeatInterval = @"day"; + } + + BOOL repeats = TRUE; NSDateComponents *triggerDate = nil; - - if (repeats && fireDate) { + + if (fireDate) { if ([repeatInterval isEqualToString:@"year"]) { triggerDate = [[NSCalendar currentCalendar] components: @@ -135,32 +142,35 @@ + (UNNotificationRequest *)UNNotificationRequest:(id)json NSCalendarUnitHour + NSCalendarUnitMinute + NSCalendarUnitSecond + NSCalendarUnitTimeZone fromDate:fireDate] : nil; - } else if ([repeatInterval isEqualToString:@"week"]) { triggerDate = fireDate ? [[NSCalendar currentCalendar] components: NSCalendarUnitWeekday + NSCalendarUnitHour + NSCalendarUnitMinute + NSCalendarUnitSecond + NSCalendarUnitTimeZone fromDate:fireDate] : nil; - + } else if ([repeatInterval isEqualToString:@"day"]) { + triggerDate = fireDate ? [[NSCalendar currentCalendar] + components: + NSCalendarUnitHour + NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate] : nil; } else if ([repeatInterval isEqualToString:@"hour"]) { - triggerDate = fireDate ? [[NSCalendar currentCalendar] components: NSCalendarUnitMinute + NSCalendarUnitSecond + NSCalendarUnitTimeZone fromDate:fireDate] : nil; - } else if ([repeatInterval isEqualToString:@"minute"]) { - triggerDate = fireDate ? [[NSCalendar currentCalendar] components: NSCalendarUnitSecond + NSCalendarUnitTimeZone fromDate:fireDate] : nil; - } else { //Default to "day" if repeatInterval not specifed or invalid - triggerDate = fireDate ? [[NSCalendar currentCalendar] - components: - NSCalendarUnitHour + NSCalendarUnitMinute + - NSCalendarUnitSecond + NSCalendarUnitTimeZone - fromDate:fireDate] : nil; + } else { //If no valid repeat interval, set repeats to false and create non-repeating trigger date + repeats = FALSE; + triggerDate = [[NSCalendar currentCalendar] + components: NSCalendarUnitYear + + NSCalendarUnitMonth + NSCalendarUnitDay + + NSCalendarUnitHour + NSCalendarUnitMinute + + NSCalendarUnitSecond + NSCalendarUnitTimeZone + fromDate:fireDate]; } }