Quellcode durchsuchen

Merge branch 'master' of https://github.com/katzer/cordova-plugin-local-notifications

PKnittel vor 11 Jahren
Ursprung
Commit
bafe8b5e0e

+ 5 - 2
CHANGELOG.md

@@ -1,11 +1,14 @@
 ## ChangeLog
 #### Version 0.8.0 (not yet released)
+- [feature:] New method `hasPermission` to ask if the user has granted to display local notifications.
+- [feature:] New method `registerPermission` to register permission to display local notifications.
+- [feature:] New Android specific `led:` flag.
+- [feature:] Add `isTriggered` & `getTriggeredIds` methods.
+- [enhancement:] iOS8 support.
 - [enhancement:] Android 2.x (SDK >= 7) support (Thanks to **khizarsonu**)
 - [enhancement:] Scope parameter for `isScheduled` and `getScheduledIds`
 - [enhancement:] Callbacks for `add`, `cancel` & `cancelAll`
 - [enhancement:] `image:` accepts remote URLs and local URIs (Android)
-- [feature:] New Android specific `led:` flag.
-- [feature:] Add `isTriggered` & `getTriggeredIds` methods.
 
 #### Version 0.7.4 (22.03.2014)
 - [bugfix:] Platform specific properties were ignored.

+ 46 - 15
README.md

@@ -19,7 +19,7 @@ The purpose of the plugin is to create an platform independent javascript interf
 
 
 ## Supported Platforms
-- **iOS**<br>
+- **iOS** _(up to iOS8)_<br>
 See [Local and Push Notification Programming Guide][ios_notification_guide] for detailed informations and screenshots.
 
 - **Android** *(SDK >=7)*<br>
@@ -70,12 +70,15 @@ More informations can be found [here][PGB_plugin].
 
 ## ChangeLog
 #### Version 0.8.0 (not yet released)
+- [feature:] New method `hasPermission` to ask if the user has granted to display local notifications.
+- [feature:] New method `registerPermission` to register permission to display local notifications.
+- [feature:] New Android specific `led:` flag.
+- [feature:] Add `isTriggered` & `getTriggeredIds` methods.
+- [enhancement:] iOS8 support.
 - [enhancement:] Android 2.x (SDK >= 7) support (Thanks to **khizarsonu**)
 - [enhancement:] Scope parameter for `isScheduled` and `getScheduledIds`
 - [enhancement:] Callbacks for `add`, `cancel` & `cancelAll`
 - [enhancement:] `image:` accepts remote URLs and local URIs (Android)
-- [feature:] New Android specific `led:` flag
-- [feature:] Add `isTriggered` & `getTriggeredIds` methods.
 
 #### Further informations
 - See [CHANGELOG.md][changelog] to get the full changelog for the plugin.
@@ -85,18 +88,20 @@ More informations can be found [here][PGB_plugin].
 ## Using the plugin
 The plugin creates the object ```window.plugin.notification.local``` with the following methods:
 
-1. [notification.local.add][add]
-2. [notification.local.cancel][cancel]
-3. [notification.local.cancelAll][cancelall]
-4. [notification.local.isScheduled][isscheduled]
-5. [notification.local.getScheduledIds][getscheduledids]
-6. [notification.local.isTriggered][istriggered]
-7. [notification.local.getDefaults][getdefaults]
-8. [notification.local.setDefaults][setDefaults]
-9. [notification.local.onadd][onadd]
-10. [notification.local.ontrigger][ontrigger]
-11. [notification.local.onclick][onclick]
-12. [notification.local.oncancel][oncancel]
+1. [notification.local.hasPermission][has_permission]
+2. [notification.local.registerPermission][register_permission]
+3. [notification.local.add][add]
+4. [notification.local.cancel][cancel]
+5. [notification.local.cancelAll][cancelall]
+6. [notification.local.isScheduled][isscheduled]
+7. [notification.local.getScheduledIds][getscheduledids]
+8. [notification.local.isTriggered][istriggered]
+9. [notification.local.getDefaults][getdefaults]
+10. [notification.local.setDefaults][setdefaults]
+11. [notification.local.onadd][onadd]
+12. [notification.local.ontrigger][ontrigger]
+13. [notification.local.onclick][onclick]
+14. [notification.local.oncancel][oncancel]
 
 ### Plugin initialization
 The plugin and its methods are not available before the *deviceready* event has been fired.
@@ -107,6 +112,30 @@ document.addEventListener('deviceready', function () {
 }, false);
 ```
 
+### Determine if the app does have the permission to show local notifications
+If the permission has been granted through the user can be retrieved through the `notification.local.hasPermission` interface.<br/>
+The method takes a callback function as its argument which will be called with a boolean value. Optional the scope of the callback function ca be defined through a second argument.
+
+#### Further informations
+- The method is supported on each platform, however its only relevant for iOS8 and above.
+
+```javascript
+window.plugin.notification.local.hasPermission(function (granted) {
+    // console.log('Permission has been granted: ' + granted);
+});
+```
+
+### Register permission for local notifications
+Required permissions can be registered through the `notification.local.registerPermission` interface.
+
+#### Further informations
+- The method is supported on each platform, however its only relevant for iOS8 and above.
+- The user will only get a prompt dialog for the first time. Later its only possible to change the setting via the notification center.
+
+```javascript
+window.plugin.notification.local.registerPermission();
+```
+
 ### Schedule local notifications
 Local notifications can be scheduled through the `notification.local.add` interface.<br>
 The method takes a hash as an argument to specify the notification's properties and returns the ID for the notification.<br>
@@ -533,6 +562,8 @@ This software is released under the [Apache 2.0 License][apache2_license].
 [oncancel]: #get-notified-when-a-local-notification-has-been-canceled
 [ontrigger]: #get-notified-when-a-local-notification-has-been-triggered
 [platform-specific-properties]: #platform-specifics
+[has_permission]: #determine-if-the-app-does-have-the-permission-to-show-local-notifications
+[register_permission]: #register-permission-for-local-notifications
 [add]: #schedule-local-notifications
 [cancel]: #cancel-scheduled-local-notifications
 [cancelall]: #cancel-all-scheduled-local-notifications

+ 9 - 0
plugin.xml

@@ -35,6 +35,15 @@
 
         <header-file src="src/ios/APPLocalNotification.h" />
         <source-file src="src/ios/APPLocalNotification.m" />
+
+        <header-file src="src/ios/APPLocalNotificationOptions.h" />
+        <source-file src="src/ios/APPLocalNotificationOptions.m" />
+
+        <source-file src="src/ios/UIApplication+APPLocalNotification.h" />
+        <source-file src="src/ios/UIApplication+APPLocalNotification.m" />
+
+        <header-file src="src/ios/UILocalNotification+APPLocalNotification.h" />
+        <source-file src="src/ios/UILocalNotification+APPLocalNotification.m" />
     </platform>
 
     <!-- android -->

+ 4 - 0
src/ios/APPLocalNotification.h

@@ -36,5 +36,9 @@
 - (void) isScheduled:(CDVInvokedUrlCommand*)command;
 // Retrieves a list of ids from all currently pending notifications
 - (void) getScheduledIds:(CDVInvokedUrlCommand*)command;
+// Informs if the app has the permission to show notifications
+- (void) hasPermission:(CDVInvokedUrlCommand*)command;
+// Registers permission to show notifications
+- (void) registerPermission:(CDVInvokedUrlCommand*)command;
 
 @end

+ 196 - 372
src/ios/APPLocalNotification.m

@@ -20,11 +20,14 @@
  */
 
 #import "APPLocalNotification.h"
+#import "APPLocalNotificationOptions.h"
+#import "UIApplication+APPLocalNotification.h"
+#import "UILocalNotification+APPLocalNotification.h"
+
+#import <Availability.h>
 
 @interface APPLocalNotification ()
 
-// Retrieves all scheduled notifications
-@property (readonly, getter=scheduledNotifications) NSArray* scheduledNotifications;
 // Retrieves the application state
 @property (readonly, getter=applicationState) NSString* applicationState;
 // All events will be queued until deviceready has been fired
@@ -36,13 +39,13 @@
 
 @implementation APPLocalNotification
 
-@synthesize deviceready, eventQueue, applicationState, scheduledNotifications;
+@synthesize deviceready, eventQueue;
 
 #pragma mark -
-#pragma mark Plugin interface methods
+#pragma mark Interface
 
 /**
- * Executes all queued events.
+ * Execute all queued events.
  */
 - (void) deviceready:(CDVInvokedUrlCommand*)command
 {
@@ -56,29 +59,24 @@
 }
 
 /**
- * Schedules a new local notification.
+ * Schedule a new local notification.
  *
- * @param {NSMutableDictionary} properties
- *      The properties of the notification
+ * @param properties
+ *      A dict of properties
  */
 - (void) add:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* arguments = [command arguments];
-        NSMutableDictionary* properties = [arguments objectAtIndex:0];
-
-        NSString* id = [properties objectForKey:@"id"];
+        NSDictionary* options = [[command arguments]
+                                 objectAtIndex:0];
 
-        if ([self isNotificationScheduledWithId:id]) {
-            UILocalNotification* notification = [self notificationWithId:id];
+        UILocalNotification* notification;
 
-            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC),
-                           dispatch_get_main_queue(), ^{
-                               [self cancelNotification:notification fireEvent:NO];
-                           });
-        }
+        notification = [[UILocalNotification alloc]
+                        initWithOptions:options];
 
-        [self scheduleNotificationWithProperties:properties];
+        [self scheduleLocalNotification:notification];
+        [self fireEvent:@"add" localNotification:notification];
         [self execCallback:command];
     }];
 }
@@ -86,21 +84,22 @@
 /**
  * Cancels a given local notification.
  *
- * @param {NSString} id
+ * @param id
  *      The ID of the local notification
  */
 - (void) cancel:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* arguments = [command arguments];
-        NSString* id       = [arguments objectAtIndex:0];
+        NSString* id = [[command arguments]
+                        objectAtIndex:0];
 
-        UILocalNotification* notification = [self notificationWithId:id];
+        UILocalNotification* notification;
 
-        if (notification) {
-            [self cancelNotification:notification fireEvent:YES];
-        }
+        notification = [[UIApplication sharedApplication]
+                        scheduledLocalNotificationWithId:id];
 
+        [self cancelLocalNotification:notification];
+        [self fireEvent:@"cancel" localNotification:notification];
         [self execCallback:command];
     }];
 }
@@ -111,37 +110,31 @@
 - (void) cancelAll:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* notifications = self.scheduledNotifications;
-
-        for (UILocalNotification* notification in notifications) {
-            [self cancelNotification:notification fireEvent:YES];
-        }
-
-        [[UIApplication sharedApplication]
-         cancelAllLocalNotifications];
-
-        [[UIApplication sharedApplication]
-         setApplicationIconBadgeNumber:0];
-
+        [self cancelAllLocalNotifications];
+        [self fireEvent:@"cancelall"];
         [self execCallback:command];
     }];
 }
 
 /**
- * Checks wether a notification with an ID is scheduled.
+ * If a notification by ID is scheduled.
  *
- * @param {NSString} id
+ * @param id
  *      The ID of the notification
- * @param callback
- *      The callback function to be called with the result
  */
 - (void) isScheduled:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* arguments = [command arguments];
-        NSString* id       = [arguments objectAtIndex:0];
-        bool isScheduled   = [self isNotificationScheduledWithId:id];
+        NSString* id = [[command arguments]
+                        objectAtIndex:0];
+
         CDVPluginResult* result;
+        UILocalNotification* notification;
+
+        notification = [[UIApplication sharedApplication]
+                        scheduledLocalNotificationWithId:id];
+
+        bool isScheduled = notification != NULL;
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                      messageAsBool:isScheduled];
@@ -152,25 +145,16 @@
 }
 
 /**
- * Retrieves a list of ids from all currently pending notifications.
- *
- * @param callback
- *      The callback function to be called with the result
+ * List of ids from all currently pending notifications.
  */
 - (void) getScheduledIds:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* notifications = self.scheduledNotifications;
-
-        NSMutableArray* scheduledIds = [[NSMutableArray alloc] init];
         CDVPluginResult* result;
+        NSArray* scheduledIds;
 
-        for (UILocalNotification* notification in notifications)
-        {
-            NSString* id = [notification.userInfo objectForKey:@"id"];
-
-            [scheduledIds addObject:id];
-        }
+        scheduledIds = [[UIApplication sharedApplication]
+                        scheduledLocalNotificationIds];
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                     messageAsArray:scheduledIds];
@@ -183,18 +167,22 @@
 /**
  * Checks wether a notification with an ID was triggered.
  *
- * @param {NSString} id
+ * @param id
  *      The ID of the notification
- * @param callback
- *      The callback function to be called with the result
  */
 - (void) isTriggered:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* arguments = [command arguments];
-        NSString* id       = [arguments objectAtIndex:0];
-        bool isTriggered   = [self isNotificationTriggeredWithId:id];
+        NSString* id = [[command arguments]
+                        objectAtIndex:0];
+
         CDVPluginResult* result;
+        UILocalNotification* notification;
+
+        notification = [[UIApplication sharedApplication]
+                        triggeredLocalNotificationWithId:id];
+
+        bool isTriggered = notification != NULL;
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                      messageAsBool:isTriggered];
@@ -206,163 +194,155 @@
 
 /**
  * Retrieves a list of ids from all currently triggered notifications.
- *
- * @param callback
- *      The callback function to be called with the result
  */
 - (void) getTriggeredIds:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* notifications = self.scheduledNotifications;
-
-        NSMutableArray* scheduledIds = [[NSMutableArray alloc] init];
         CDVPluginResult* result;
+        NSArray* triggeredIds;
 
-        for (UILocalNotification* notification in notifications)
-        {
-            if (![self isNotificationTriggered:notification]) {
-                continue;
-            }
+        triggeredIds = [[UIApplication sharedApplication]
+                        triggeredLocalNotificationIds];
 
-            NSString* id = [notification.userInfo objectForKey:@"id"];
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
+                                    messageAsArray:triggeredIds];
 
-            [scheduledIds addObject:id];
-        }
+        [self.commandDelegate sendPluginResult:result
+                                    callbackId:command.callbackId];
+    }];
+}
+
+/**
+ * Inform if the app has the permission to show
+ * badges and local notifications.
+ */
+- (void) hasPermission:(CDVInvokedUrlCommand *)command
+{
+    [self.commandDelegate runInBackground:^{
+        CDVPluginResult* result;
+        BOOL hasPermission;
+
+        hasPermission = [[UIApplication sharedApplication]
+                         hasPermissionToScheduleLocalNotifications];
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                    messageAsArray:scheduledIds];
+                                     messageAsBool:hasPermission];
 
         [self.commandDelegate sendPluginResult:result
                                     callbackId:command.callbackId];
     }];
 }
 
+/**
+ * Ask for permission to show badges.
+ */
+- (void) registerPermission:(CDVInvokedUrlCommand *)command
+{
+    [self.commandDelegate runInBackground:^{
+        [[UIApplication sharedApplication]
+         registerPermissionToScheduleLocalNotifications];
+    }];
+}
+
 #pragma mark -
-#pragma mark Plugin core methods
+#pragma mark Core Logic
 
 /**
- * Schedules a new local notification and fies the coresponding event.
- *
- * @param {NSMutableDictionary} properties
- *      The properties of the notification
+ * Schedule the local notification.
  */
-- (void) scheduleNotificationWithProperties:(NSMutableDictionary*)properties
+- (void) scheduleLocalNotification:(UILocalNotification*)notification
 {
-    UILocalNotification* notification = [self notificationWithProperties:
-                                         properties];
+    [self cancelForerunnerLocalNotification:notification];
 
-    NSDictionary* userInfo = notification.userInfo;
-    NSString* id = [userInfo objectForKey:@"id"];
-    NSString* json = [userInfo objectForKey:@"json"];
+    NSString* state = self.applicationState;
 
-    [self fireEvent:@"add" id:id json:json];
+    if ([state isEqualToString:@"background"]) {
+        [[UIApplication sharedApplication]
+         presentLocalNotificationNow:notification];
+    }
 
     [[UIApplication sharedApplication]
      scheduleLocalNotification:notification];
 }
 
 /**
- * Cancels the given local notification
- * and fires the cancel event.
- *
- * @param {NSString} id
- *      The ID of the local notification
+ * Cancel the local notification.
  */
-- (void) cancelNotification:(UILocalNotification*)notification
-                  fireEvent:(BOOL)fireEvent
+- (void) cancelLocalNotification:(UILocalNotification*)notification
 {
-    NSDictionary* userInfo = notification.userInfo;
-    NSString* id           = [userInfo objectForKey:@"id"];
-    NSString* json         = [userInfo objectForKey:@"json"];
-
-    if (notification==nil) {
-        NSLog(@"cancelNotification: Notification equals nil");
-    }else{
-        [[UIApplication sharedApplication]
-         cancelLocalNotification:notification];
-    }
+    if (!notification)
+        return;
 
-    if (fireEvent) {
-        [self fireEvent:@"cancel" id:id json:json];
-    }
+    [[UIApplication sharedApplication]
+     cancelLocalNotification:notification];
 }
 
 /**
- * Cancels all local notification with are older then
- * a specific amount of seconds
- *
- * @param {float} seconds
- *      The time interval in seconds
+ * Cancel all currently scheduled notifications.
  */
-- (void) cancelAllNotificationsWhichAreOlderThen:(float)seconds
+- (void) cancelAllLocalNotifications
 {
-    NSDate* now = [NSDate date];
+    NSArray* notifications;
 
-    NSArray* notifications = self.scheduledNotifications;
+    notifications = [[UIApplication sharedApplication]
+                     scheduledLocalNotifications];
 
-    for (UILocalNotification* notification in notifications)
-    {
-        NSDate* fireDate = notification.fireDate;
-        NSTimeInterval fireDateDistance = [now timeIntervalSinceDate:
-                                           fireDate];
-
-        if (notification.repeatInterval == NSCalendarUnitEra
-            && fireDateDistance > seconds) {
-            [self cancelNotification:notification fireEvent:YES];
-        }
+    for (UILocalNotification* notification in notifications) {
+        [self cancelLocalNotification:notification];
     }
+
+    [[UIApplication sharedApplication]
+     cancelAllLocalNotifications];
+
+    [[UIApplication sharedApplication]
+     setApplicationIconBadgeNumber:0];
 }
 
 /**
- * Creates an notification object based on the given properties.
- *
- * @param {NSMutableDictionary} properties
- *      The properties for the local notification
- * @return {UILocalNotification}
+ * Cancel a maybe given forerunner with the same ID.
  */
-- (UILocalNotification*) notificationWithProperties:(NSMutableDictionary*)options
+- (void) cancelForerunnerLocalNotification:(UILocalNotification*)notification
 {
-    UILocalNotification* notification = [[UILocalNotification alloc] init];
+    NSString* id = notification.options.id;
+    UILocalNotification* forerunner;
 
-    double timestamp = [[options objectForKey:@"date"] doubleValue];
-    NSString* msg = [options objectForKey:@"message"];
-    NSString* title = [options objectForKey:@"title"];
-    NSString* sound = [options objectForKey:@"sound"];
-    NSString* repeat = [options objectForKey:@"repeat"];
-    NSInteger badge = [[options objectForKey:@"badge"] intValue];
+    forerunner = [[UIApplication sharedApplication]
+                  scheduledLocalNotificationWithId:id];
 
-    notification.fireDate = [NSDate dateWithTimeIntervalSince1970:timestamp];
-    notification.timeZone = [NSTimeZone defaultTimeZone];
-    notification.userInfo = [self userDict:options];
-    notification.applicationIconBadgeNumber = badge;
+    if (!forerunner)
+        return;
 
-    notification.repeatInterval = [[[self repeatDict] objectForKey:repeat]
-                                   intValue];
+    [self cancelLocalNotification:forerunner];
+}
 
-    if (![self stringIsNullOrEmpty:msg])
-    {
-        if (![self stringIsNullOrEmpty:title]) {
-            notification.alertBody = [NSString stringWithFormat:
-                                      @"%@\n%@", title, msg];
-        } else {
-            notification.alertBody = msg;
-        }
-    }
 
-    if (sound != (NSString*)[NSNull null])
+/**
+ * Cancels all local notification with are older then
+ * a specific amount of seconds
+ *
+ * @param {float} seconds
+ *      The time interval in seconds
+ */
+- (void) cancelAllNotificationsWhichAreOlderThen:(float)seconds
+{
+    NSArray* notifications;
+
+    notifications = [[UIApplication sharedApplication]
+                     scheduledLocalNotifications];
+
+    for (UILocalNotification* notification in notifications)
     {
-        if ([sound isEqualToString:@""]) {
-            notification.soundName = UILocalNotificationDefaultSoundName;
-        } else {
-            notification.soundName = sound;
+        if (notification && notification.repeatInterval == NSCalendarUnitEra
+            && notification.timeIntervalSinceFireDate > seconds)
+        {
+            [self cancelLocalNotification:notification];
+            [self fireEvent:@"cancel" localNotification:notification];
         }
     }
-
-    return notification;
 }
 
 #pragma mark -
-#pragma mark Plugin delegate and life cycle methods
+#pragma mark Delegates
 
 /**
  * Calls the cancel or trigger event after a local notification was received.
@@ -370,38 +350,36 @@
  */
 - (void) didReceiveLocalNotification:(NSNotification*)localNotification
 {
+    UIApplication* app = [UIApplication sharedApplication];
     UILocalNotification* notification = [localNotification object];
 
-    NSDictionary* userInfo = notification.userInfo;
-    NSString* id = [userInfo objectForKey:@"id"];
-    NSString* json = [userInfo objectForKey:@"json"];
-    BOOL autoCancel = [[userInfo objectForKey:@"autoCancel"] boolValue];
+    BOOL autoCancel = notification.options.autoCancel;
+    NSTimeInterval timeInterval = notification.timeIntervalSinceFireDate;
 
-    NSDate* now = [NSDate date];
-    NSDate* fireDate = notification.fireDate;
-    NSTimeInterval fireDateDistance = [now timeIntervalSinceDate:fireDate];
-    NSString* event = (fireDateDistance < 1) ? @"trigger" : @"click";
+    NSString* event = (timeInterval <= 1 && deviceready) ? @"trigger" : @"click";
 
-    if ([[self applicationState] isEqualToString:@"foreground"]) {
-        event = @"trigger";
-    }
+    app.applicationIconBadgeNumber -= 1;
+
+    [self fireEvent:event localNotification:notification];
 
     if (autoCancel && [event isEqualToString:@"click"]) {
-        [self cancelNotification:notification fireEvent:YES];
+        [self cancelLocalNotification:notification];
+        [self fireEvent:@"cancel" localNotification:notification];
     }
-
-    [self fireEvent:event id:id json:json];
 }
 
 /**
- * Calls the cancel or trigger event after a local notification was received.
+ * Called when app has started
+ * (by clicking on a local notification).
  */
 - (void) didFinishLaunchingWithOptions:(NSNotification*)notification
 {
     NSDictionary* launchOptions = [notification userInfo];
 
-    UILocalNotification* localNotification = [launchOptions objectForKey:
-                                              UIApplicationLaunchOptionsLocalNotificationKey];
+    UILocalNotification* localNotification;
+
+    localNotification = [launchOptions objectForKey:
+                         UIApplicationLaunchOptionsLocalNotificationKey];
 
     if (localNotification) {
         [self didReceiveLocalNotification:
@@ -410,6 +388,9 @@
     }
 }
 
+#pragma mark -
+#pragma mark Life Cycle
+
 /**
  * Registers obervers for the following events after plugin was initialized.
  *      didReceiveLocalNotification:
@@ -417,8 +398,10 @@
  */
 - (void) pluginInitialize
 {
-    NSNotificationCenter* notificationCenter = [NSNotificationCenter
-                                                defaultCenter];
+    NSNotificationCenter* notificationCenter;
+
+    notificationCenter = [NSNotificationCenter
+                          defaultCenter];
 
     eventQueue = [[NSMutableArray alloc] init];
 
@@ -443,153 +426,12 @@
 }
 
 #pragma mark -
-#pragma mark Plugin helper methods
-
-/**
- * Retrurns a key-value dictionary for repeat intervals.
- *
- * @return {NSMutableDictionary}
- */
-- (NSMutableDictionary*) repeatDict
-{
-    NSMutableDictionary* repeatDict = [[NSMutableDictionary alloc] init];
-
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitSecond] forKey:@"secondly"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitMinute] forKey:@"minutely"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitHour] forKey:@"hourly"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitDay] forKey:@"daily"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitWeekOfYear] forKey:@"weekly"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitMonth] forKey:@"monthly"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitYear] forKey:@"yearly"];
-    [repeatDict setObject:
-     [NSNumber numberWithInt:NSCalendarUnitEra] forKey:@""];
-
-    return repeatDict;
-}
-
-/**
- * Returns the userDict for a local notification.
- *
- * @param {NSMutableDictionary} options
- *      The properties for the local notification
- * @return {NSDictionary}
- */
-- (NSDictionary*) userDict:(NSMutableDictionary*)options
-{
-    NSString* id = [options objectForKey:@"id"];
-    NSString* ac = [options objectForKey:@"autoCancel"];
-    NSString* js = [options objectForKey:@"json"];
-
-    return [NSDictionary dictionaryWithObjectsAndKeys:
-            id, @"id", ac, @"autoCancel", js, @"json", nil];
-}
-
-/**
- * Checks weather the given string is empty or not.
- *
- * @param {NSString} str The string to be check
- * @return {BOOL}
- */
-- (BOOL) stringIsNullOrEmpty:(NSString*)str
-{
-    if (str == (NSString*)[NSNull null]) {
-        return YES;
-    }
-
-    if ([str isEqualToString:@""]) {
-        return YES;
-    }
-
-    return NO;
-}
-
-/**
- * Checks wether a notification with an ID is scheduled or not.
- *
- * @param id
- *      The ID of the notification
- * @return BOOL
- */
-- (BOOL) isNotificationScheduledWithId:(NSString*)id
-{
-    UILocalNotification* notification = [self notificationWithId:id];
-
-    return notification != NULL;
-}
-
-/**
- * Checks wether a notification with an ID was triggered or not.
- *
- * @param id
- *      The ID of the notification
- * @return BOOL
- */
-- (BOOL) isNotificationTriggeredWithId:(NSString*)id
-{
-    UILocalNotification* notification = [self notificationWithId:id];
-
-    if (notification == NULL) {
-        return NO;
-    }
-
-    return [self isNotificationTriggered:notification];
-}
-
-/**
- * Checks wether a notification was triggered or not.
- *
- * @param notification
- *      The notification
- * @return BOOL
- */
-- (BOOL) isNotificationTriggered:(UILocalNotification*)notification
-{
-    NSDate* now      = [NSDate date];
-    NSDate* fireDate = notification.fireDate;
-
-    bool isLaterThanOrEqualTo = !([now compare:fireDate] == NSOrderedAscending);
-
-    return isLaterThanOrEqualTo;
-}
-
-/**
- * Retrieves the local notification by its ID.
- *
- * @param {NSString} id
- *      The ID of the notification
- * @return UILocalNotification*
- */
-- (UILocalNotification*) notificationWithId:(NSString*)id
-{
-    NSArray* notifications = self.scheduledNotifications;
-
-    for (UILocalNotification* notification in notifications)
-    {
-        NSString* notId = NULL;
-        if ([[notification.userInfo objectForKey:@"id"] isKindOfClass:[NSString class]] ) {
-            notId = [notification.userInfo objectForKey:@"id"];
-        } else {
-            notId = [[notification.userInfo objectForKey:@"id"] stringValue];
-        }
-        if ([notId isEqualToString:id]) {
-            return notification;
-        }
-    }
-
-    return NULL;
-}
+#pragma mark Helper
 
 /**
  * Retrieves the application state
  *
- * @return {NSString}
+ * @return
  *      Either "background" or "foreground"
  */
 - (NSString*) applicationState
@@ -603,35 +445,8 @@
 }
 
 /**
- * Retrieves all scheduled notifications.
- *
- * @return {NSArray}
- *      A list of all scheduled local notifications
- */
-- (NSArray*) scheduledNotifications
-{
-    NSMutableArray* notificationsWithoutNIL = [[NSMutableArray alloc]
-                                               init];
-
-    NSArray* notifications = [[UIApplication sharedApplication]
-                              scheduledLocalNotifications];
-
-    for (UILocalNotification* notification in notifications)
-    {
-        if (notification) {
-            [notificationsWithoutNIL addObject:notification];
-        }
-    }
-
-    return notificationsWithoutNIL;
-}
-
-#pragma mark -
-#pragma mark Plugin callback methods
-
-/**
- * Simply invokes the callback without any parameter.
- */
+  * Simply invokes the callback without any parameter.
+  */
 - (void) execCallback:(CDVInvokedUrlCommand*)command
 {
     CDVPluginResult *result = [CDVPluginResult
@@ -642,26 +457,35 @@
 }
 
 /**
- * Fires the given event.
- *
- * @param {NSString} event
- *      The Name of the event
- * @param {NSString} id
- *      The ID of the notification
- * @param {NSString} json
- *      A custom (JSON) string
+ * Fire general event.
  */
-- (void) fireEvent:(NSString*)event id:(NSString*)id json:(NSString*)json
+- (void) fireEvent:(NSString*)event
 {
-    NSString* appState = self.applicationState;
+    [self fireEvent:event localNotification:NULL];
+}
 
+/**
+ * Fire event for local notification.
+ */
+- (void) fireEvent:(NSString*)event localNotification:(UILocalNotification*)notification
+{
+    NSString* js;
     NSString* params = [NSString stringWithFormat:
-                        @"\"%@\",\"%@\",\\'%@\\'",
-                        id, appState, json];
+                        @"\"%@\"", self.applicationState];
+
+    if (notification) {
+        NSString* id = notification.options.id;
+        NSString* json = notification.options.json;
+        NSString* args = [notification.options encodeToJSON];
+
+        params = [NSString stringWithFormat:
+                  @"\"%@\",\"%@\",\\'%@\\',JSON.parse(\\'%@\\')",
+                  id, self.applicationState, json, args];
+    }
 
-    NSString* js = [NSString stringWithFormat:
-                    @"setTimeout('plugin.notification.local.on%@(%@)',0)",
-                    event, params];
+    js = [NSString stringWithFormat:
+          @"setTimeout('plugin.notification.local.on%@(%@)',0)",
+          event, params];
 
     if (deviceready) {
         [self.commandDelegate evalJs:js];

+ 39 - 0
src/ios/APPLocalNotificationOptions.h

@@ -0,0 +1,39 @@
+/*
+ Copyright 2013-2014 appPlant UG
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+@interface APPLocalNotificationOptions : NSObject
+
+- (id) initWithDict:(NSDictionary*)dict;
+
+@property (readonly, getter=id) NSString* id;
+@property (readonly, getter=autoCancel) BOOL autoCancel;
+@property (readonly, getter=json) NSString* json;
+@property (readonly, getter=badgeNumber) NSInteger badgeNumber;
+@property (readonly, getter=alertBody) NSString* alertBody;
+@property (readonly, getter=soundName) NSString* soundName;
+@property (readonly, getter=fireDate) NSDate* fireDate;
+@property (readonly, getter=repeatInterval) NSCalendarUnit repeatInterval;
+@property (readonly, getter=userInfo) NSDictionary* userInfo;
+
+// Encode the user info dict to JSON
+- (NSString*) encodeToJSON;
+
+@end

+ 262 - 0
src/ios/APPLocalNotificationOptions.m

@@ -0,0 +1,262 @@
+/*
+ Copyright 2013-2014 appPlant UG
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "APPLocalNotificationOptions.h"
+#import <Cordova/CDVAvailability.h>
+
+@interface APPLocalNotificationOptions ()
+
+// The dictionary which contains all notification properties
+@property(readwrite, assign) NSDictionary* dict;
+
+@end
+
+@implementation APPLocalNotificationOptions
+
+@synthesize dict;
+
+#pragma mark -
+#pragma mark Initialization
+
+/**
+ * Initialize the object with the given options when calling on JS side:
+ * notification.local.add(options)
+ */
+- (id) initWithDict:(NSDictionary*)dictionary
+{
+    self = [super init];
+
+    self.dict = dictionary;
+
+    return self;
+}
+
+#pragma mark -
+#pragma mark Attributes
+
+/**
+ * The notification's ID.
+ */
+- (NSString*) id
+{
+    return [dict objectForKey:@"id"];
+}
+
+/**
+ * The notification's title.
+ */
+- (NSString*) title
+{
+    return [dict objectForKey:@"title"];
+}
+
+/**
+ * The notification's message.
+ */
+- (NSString*) message
+{
+    return [dict objectForKey:@"message"];
+}
+
+/**
+ * The notification's auto cancel flag.
+ */
+- (BOOL) autoCancel
+{
+    if (IsAtLeastiOSVersion(@"8.0")){
+        return YES;
+    } else {
+        return [[dict objectForKey:@"autoCancel"] boolValue];
+    }
+}
+
+/**
+ * The notification's JSON data.
+ */
+- (NSString*) json
+{
+    return [dict objectForKey:@"json"];
+}
+
+/**
+ * The notification's badge number.
+ */
+- (NSInteger) badgeNumber
+{
+    return [[dict objectForKey:@"badge"] intValue];
+}
+
+#pragma mark -
+#pragma mark Complex Attributes
+
+/**
+ * The notification's alert body.
+ */
+- (NSString*) alertBody
+{
+    NSString* title = [self title];
+    NSString* msg = [self message];
+
+    NSString* alertBody = msg;
+
+    if (![self stringIsNullOrEmpty:title])
+    {
+        alertBody = [NSString stringWithFormat:@"%@\n%@",
+                     title, msg];
+    }
+
+    return alertBody;
+}
+
+/**
+ * The notification's sound path.
+ */
+- (NSString*) soundName
+{
+    NSString* path = [dict objectForKey:@"sound"];
+
+    if ([path hasPrefix:@"file:/"])
+    {
+        return [self soundNameForAsset:path];
+    }
+    else if ([path hasPrefix:@"res:"])
+    {
+        return [self soundNameForResource:path];
+    }
+
+    return UILocalNotificationDefaultSoundName;
+}
+
+/**
+ * The notification's fire date.
+ */
+- (NSDate*) fireDate
+{
+    double timestamp = [[dict objectForKey:@"date"]
+                        doubleValue];
+
+    return [NSDate dateWithTimeIntervalSince1970:timestamp];
+}
+
+/**
+ * The notification's repeat interval.
+ */
+- (NSCalendarUnit) repeatInterval
+{
+    NSString* interval = [dict objectForKey:@"repeat"];
+
+    if ([interval isEqualToString:@"secondly"])
+    {
+        return NSCalendarUnitSecond;
+    }
+    else if ([interval isEqualToString:@"minutely"])
+    {
+        return NSCalendarUnitMinute;
+    }
+    else if ([interval isEqualToString:@"hourly"])
+    {
+        return NSCalendarUnitHour;
+    }
+    else if ([interval isEqualToString:@"daily"])
+    {
+        return NSCalendarUnitDay;
+    }
+    else if ([interval isEqualToString:@"weekly"])
+    {
+        return NSCalendarUnitWeekOfYear;
+    }
+    else if ([interval isEqualToString:@"monthly"])
+    {
+        return NSCalendarUnitMonth;
+    }
+    else if ([interval isEqualToString:@"yearly"])
+    {
+        return NSCalendarUnitYear;
+    }
+
+    return NSCalendarUnitEra;
+}
+
+/**
+ * The notification's user info dict.
+ */
+- (NSDictionary*) userInfo
+{
+    return dict;
+}
+
+/**
+ * Encode the user info dict to JSON.
+ */
+- (NSString*) encodeToJSON
+{
+    NSString* json;
+    NSData* data;
+    NSMutableDictionary* obj = [dict mutableCopy];
+
+    [obj removeObjectForKey:@"json"];
+
+    data = [NSJSONSerialization dataWithJSONObject:obj
+                                           options:NSJSONWritingPrettyPrinted
+                                             error:Nil];
+
+    json = [[NSString alloc] initWithData:data
+                                 encoding:NSUTF8StringEncoding];
+
+    return [json stringByReplacingOccurrencesOfString:@"\n"
+                                           withString:@""];
+}
+
+#pragma mark -
+#pragma mark Helpers
+
+/**
+ * Convert relative path to valid sound name attribute.
+ */
+- (NSString*) soundNameForAsset:(NSString*)path
+{
+    return [path stringByReplacingOccurrencesOfString:@"file:/"
+                                           withString:@"www"];
+}
+
+/**
+ * Convert resource path to valid sound name attribute.
+ */
+- (NSString*) soundNameForResource:(NSString*)path
+{
+    return [path pathComponents].lastObject;
+}
+
+/**
+ * If the string is empty.
+ */
+- (BOOL) stringIsNullOrEmpty:(NSString*)str
+{
+    if (str == (NSString*)[NSNull null])
+        return YES;
+
+    if ([str isEqualToString:@""])
+        return YES;
+
+    return NO;
+}
+
+@end

+ 37 - 0
src/ios/UIApplication+APPLocalNotification.h

@@ -0,0 +1,37 @@
+/*
+ Copyright 2013-2014 appPlant UG
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+@interface UIApplication (APPLocalNotification)
+
+@property (readonly, getter=triggeredLocalNotifications) NSArray* triggeredLocalNotifications;
+@property (readonly, getter=triggeredLocalNotificationIds) NSArray* triggeredLocalNotificationIds;
+@property (readonly, getter=scheduledLocalNotificationIds) NSArray* scheduledLocalNotificationIds;
+
+// If the app has the permission to schedule local notifications
+- (BOOL) hasPermissionToScheduleLocalNotifications;
+// Ask for permission to schedule local notifications
+- (void) registerPermissionToScheduleLocalNotifications;
+// Get the scheduled local notification by ID
+- (UILocalNotification*) scheduledLocalNotificationWithId:(NSString*)id;
+// Get the triggered local notification by ID
+- (UILocalNotification*) triggeredLocalNotificationWithId:(NSString*)id;
+
+@end

+ 162 - 0
src/ios/UIApplication+APPLocalNotification.m

@@ -0,0 +1,162 @@
+/*
+ Copyright 2013-2014 appPlant UG
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "UIApplication+APPLocalNotification.h"
+#import "UILocalNotification+APPLocalNotification.h"
+
+#import <Availability.h>
+
+@implementation UIApplication (APPLocalNotification)
+
+#pragma mark -
+#pragma mark Permissions
+
+/**
+ * If the app has the permission to schedule local notifications.
+ */
+- (BOOL) hasPermissionToScheduleLocalNotifications
+{
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
+    UIUserNotificationType types;
+    UIUserNotificationSettings *settings;
+
+    settings = [[UIApplication sharedApplication]
+                currentUserNotificationSettings];
+
+    types = UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound;
+
+    return (settings.types & types);
+#else
+    return YES;
+#endif
+}
+
+/**
+ * Ask for permission to schedule local notifications.
+ */
+- (void) registerPermissionToScheduleLocalNotifications
+{
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
+    UIUserNotificationType types;
+    UIUserNotificationSettings *settings;
+
+    types = UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound;
+
+    settings = [UIUserNotificationSettings settingsForTypes:types
+                                                 categories:nil];
+
+    [[UIApplication sharedApplication]
+     registerUserNotificationSettings:settings];
+#endif
+}
+
+#pragma mark -
+#pragma mark LocalNotifications
+
+/**
+ * List of all triggered local notifications which have been scheduled
+ * and not yet removed the notification center.
+ */
+- (NSArray*) triggeredLocalNotifications
+{
+    NSArray* scheduledNotifications = self.scheduledLocalNotifications;
+    NSMutableArray* triggeredNotifications = [[NSMutableArray alloc] init];
+
+    for (UILocalNotification* notification in scheduledNotifications)
+    {
+        if (notification && [notification wasTriggered])
+        {
+            [triggeredNotifications addObject:notification];
+        }
+    }
+
+    return triggeredNotifications;
+}
+
+/**
+ * List of all triggered local notifications IDs which have been scheduled
+ * and not yet removed the notification center.
+ */
+- (NSArray*) triggeredLocalNotificationIds
+{
+    NSArray* triggeredNotifications = self.triggeredLocalNotifications;
+    NSMutableArray* triggeredNotificationIds = [[NSMutableArray alloc] init];
+
+    for (UILocalNotification* notification in triggeredNotifications)
+    {
+        [triggeredNotificationIds addObject:notification.options.id];
+    }
+
+    return triggeredNotificationIds;
+}
+
+/**
+ * List of all scheduled local notifications IDs.
+ */
+- (NSArray*) scheduledLocalNotificationIds
+{
+    NSArray* scheduledNotifications = self.scheduledLocalNotifications;
+    NSMutableArray* scheduledNotificationIds = [[NSMutableArray alloc] init];
+
+    for (UILocalNotification* notification in scheduledNotifications)
+    {
+        if (notification)
+        {
+            [scheduledNotificationIds addObject:notification.options.id];
+        }
+    }
+
+    return scheduledNotificationIds;
+}
+
+/**
+ * Get the scheduled local notification by ID.
+ */
+- (UILocalNotification*) scheduledLocalNotificationWithId:(NSString*)id
+{
+    NSArray* notifications = self.scheduledLocalNotifications;
+
+    for (UILocalNotification* notification in notifications)
+    {
+        if (notification && [notification.options.id isEqualToString:id])
+        {
+            return notification;
+        }
+    }
+
+    return NULL;
+}
+
+/**
+ * Get the triggered local notification by ID.
+ */
+- (UILocalNotification*) triggeredLocalNotificationWithId:(NSString*)id
+{
+    UILocalNotification* notification = [self scheduledLocalNotificationWithId:id];
+
+    if (notification && [notification wasTriggered]) {
+        return notification;
+    }
+
+    return NULL;
+}
+
+@end

+ 37 - 0
src/ios/UILocalNotification+APPLocalNotification.h

@@ -0,0 +1,37 @@
+/*
+ Copyright 2013-2014 appPlant UG
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "APPLocalNotificationOptions.h"
+
+@interface UILocalNotification (APPLocalNotification)
+
+// Initialize a new local notification
+- (id) initWithOptions:(NSDictionary*)dict;
+// The options provided by the plug-in
+- (APPLocalNotificationOptions*) options;
+// Timeinterval since fire date
+- (NSTimeInterval) timeIntervalSinceFireDate;
+// If the fire date was in the past
+- (BOOL) wasInThePast;
+// If the notification was already triggered
+- (BOOL) wasTriggered;
+
+@end

+ 130 - 0
src/ios/UILocalNotification+APPLocalNotification.m

@@ -0,0 +1,130 @@
+/*
+ Copyright 2013-2014 appPlant UG
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "UILocalNotification+APPLocalNotification.h"
+#import "APPLocalNotificationOptions.h"
+#import <objc/runtime.h>
+
+static char optionsKey;
+
+@implementation UILocalNotification (APPLocalNotification)
+
+#pragma mark -
+#pragma mark Init methods
+
+/**
+ * Initialize a local notification with the given options when calling on JS side:
+ * notification.local.add(options)
+ */
+- (id) initWithOptions:(NSDictionary*)dict
+{
+    self = [super init];
+
+    [self setUserInfo:dict];
+    [self __init];
+
+    return self;
+}
+
+/**
+ * Applies the given options when calling on JS side:
+ * notification.local.add(options)
+
+ */
+- (void) __init
+{
+    APPLocalNotificationOptions* options = self.options;
+
+    self.fireDate = options.fireDate;
+    self.timeZone = [NSTimeZone defaultTimeZone];
+    self.applicationIconBadgeNumber = options.badgeNumber;
+    self.repeatInterval = options.repeatInterval;
+    self.alertBody = options.alertBody;
+    self.soundName = options.soundName;
+}
+
+/**
+ * The options provided by the plug-in.
+ */
+- (APPLocalNotificationOptions*) options
+{
+    APPLocalNotificationOptions* options = [self getOptions];
+
+    if (!options) {
+        options = [[APPLocalNotificationOptions alloc]
+                   initWithDict:[self userInfo]];
+
+        [self setOptions:options];
+    }
+
+    return options;
+}
+
+/**
+ * Get associated option object
+ */
+- (APPLocalNotificationOptions*) getOptions
+{
+    return objc_getAssociatedObject(self, &optionsKey);
+}
+
+/**
+ * Set associated option object
+ */
+- (void) setOptions:(APPLocalNotificationOptions*)options
+{
+    objc_setAssociatedObject(self, &optionsKey,
+                             options, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+/**
+ * Timeinterval since fire date.
+ */
+- (NSTimeInterval) timeIntervalSinceFireDate
+{
+    NSDate* now      = [NSDate date];
+    NSDate* fireDate = self.options.fireDate;
+
+    return [now timeIntervalSinceDate:fireDate];
+}
+
+/**
+ * If the fire date was in the past.
+ */
+- (BOOL) wasInThePast
+{
+    return [self timeIntervalSinceFireDate] < 0;
+}
+
+/**
+ * If the notification was already triggered.
+ */
+- (BOOL) wasTriggered
+{
+    NSDate* now      = [NSDate date];
+    NSDate* fireDate = self.fireDate;
+
+    bool isLaterThanOrEqualTo = !([now compare:fireDate] == NSOrderedAscending);
+
+    return isLaterThanOrEqualTo;
+}
+
+@end

+ 355 - 311
www/local-notification.js

@@ -19,338 +19,382 @@
     under the License.
 */
 
-var LocalNotification = function () {
-    this._defaults = {
-        message:    '',
-        title:      '',
-        autoCancel: false,
-        badge:      0,
-        id:         '0',
-        json:       '',
-        repeat:     ''
-    };
-};
-
-LocalNotification.prototype = {
-    /**
-     * Returns the default settings
-     *
-     * @return {Object}
-     */
-    getDefaults: function () {
-        return this._defaults;
-    },
-
-    /**
-     * Overwrite default settings
-     *
-     * @param {Object} defaults
-     */
-    setDefaults: function (newDefaults) {
-        var defaults = this.getDefaults();
-
-        for (var key in defaults) {
-            if (newDefaults[key] !== undefined) {
-                defaults[key] = newDefaults[key];
-            }
-        }
-    },
-
-    /**
-     * @private
-     *
-     * Merges custom properties with the default values.
-     *
-     * @param {Object} options
-     *      Set of custom values
-     *
-     * @retrun {Object}
-     *      The merged property list
-     */
-    mergeWithDefaults: function (options) {
-        var defaults = this.getDefaults();
-
-        for (var key in defaults) {
-            if (options[key] === undefined) {
-                options[key] = defaults[key];
-            }
-        }
-
-        return options;
-    },
-
-    /**
-     * @private
-     *
-     * Merges the platform specific properties into the default properties.
-     *
-     * @return {Object}
-     *      The default properties for the platform
-     */
-    applyPlatformSpecificOptions: function () {
-        var defaults = this._defaults;
-
-        switch (device.platform) {
-        case 'Android':
-            defaults.icon       = 'icon';
-            defaults.smallIcon  = null;
-            defaults.ongoing    = false;
-            defaults.led        = 'FFFFFF'; /*RRGGBB*/
-            defaults.sound      = 'TYPE_NOTIFICATION'; break;
-        case 'iOS':
-            defaults.sound      = ''; break;
-        case 'WinCE': case 'Win32NT':
-            defaults.smallImage = null;
-            defaults.image      = null;
-            defaults.wideImage  = null;
-        }
-
-        return defaults;
-    },
-
-    /**
-     * @private
-     *
-     * Creates a callback, which will be executed within a specific scope.
-     *
-     * @param {Function} callbackFn
-     *      The callback function
-     * @param {Object} scope
-     *      The scope for the function
-     *
-     * @return {Function}
-     *      The new callback function
-     */
-    createCallbackFn: function (callbackFn, scope) {
-        if (typeof callbackFn != 'function')
-            return;
-
-        return function () {
-            callbackFn.apply(scope || this, arguments);
-        };
-    },
-
-    /**
-     * Add a new entry to the registry
-     *
-     * @param {Object} options
-     *      The notification properties
-     * @param {Function} callback
-     *      A function to be called after the notification has been canceled
-     * @param {Object} scope
-     *      The scope for the callback function
-     *
-     * @return {Number}
-     *      The notification's ID
-     */
-    add: function (options, callback, scope) {
-        var options    = this.mergeWithDefaults(options),
-            callbackFn = this.createCallbackFn(callback, scope);
-
-        if (options.id) {
-            options.id = options.id.toString();
-        }
-
-        if (options.date === undefined) {
-            options.date = new Date();
-        }
-
-        if (options.title) {
-            options.title = options.title.toString();
-        }
-
-        if (options.message) {
-            options.message = options.message.toString();
-        }
-
-        if (typeof options.date == 'object') {
-            options.date = Math.round(options.date.getTime()/1000);
-        }
-
-        if (['WinCE', 'Win32NT'].indexOf(device.platform) > -1) {
-            callbackFn = function (cmd) {
-                eval(cmd);
-            };
-        }
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'add', [options]);
-
-        return options.id;
-    },
-
-    /**
-     * Cancels the specified notification.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {Function} callback
-     *      A function to be called after the notification has been canceled
-     * @param {Object} scope
-     *      The scope for the callback function
-     */
-    cancel: function (id, callback, scope) {
-        var id         = id.toString(),
-            callbackFn = this.createCallbackFn(callback, scope);
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'cancel', [id]);
-    },
-
-    /**
-     * Removes all previously registered notifications.
-     *
-     * @param {Function} callback
-     *      A function to be called after all notifications have been canceled
-     * @param {Object} scope
-     *      The scope for the callback function
-     */
-    cancelAll: function (callback, scope) {
-        var callbackFn = this.createCallbackFn(callback, scope);
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'cancelAll', []);
-    },
-
-    /**
-     * Retrieves a list with all currently pending notifications.
-     *
-     * @param {Function} callback
-     *      A callback function to be called with the list
-     * @param {Object} scope
-     *      The scope for the callback function
-     */
-    getScheduledIds: function (callback, scope) {
-        var callbackFn = this.createCallbackFn(callback, scope);
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'getScheduledIds', []);
-    },
-
-    /**
-     * Checks wether a notification with an ID is scheduled.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {Function} callback
-     *      A callback function to be called with the list
-     * @param {Object} scope
-     *      The scope for the callback function
-     */
-    isScheduled: function (id, callback, scope) {
-        var id         = id.toString(),
-            callbackFn = this.createCallbackFn(callback, scope);
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'isScheduled', [id]);
-    },
-
-    /**
-     * Retrieves a list with all triggered notifications.
-     *
-     * @param {Function} callback
-     *      A callback function to be called with the list
-     * @param {Object} scope
-     *      The scope for the callback function
-     */
-    getTriggeredIds: function (callback, scope) {
-        var callbackFn = this.createCallbackFn(callback, scope);
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'getTriggeredIds', []);
-    },
-
-    /**
-     * Checks wether a notification with an ID was triggered.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {Function} callback
-     *      A callback function to be called with the list
-     * @param {Object} scope
-     *      The scope for the callback function
-     */
-    isTriggered: function (id, callback, scope) {
-        var id         = id.toString(),
-            callbackFn = this.createCallbackFn(callback, scope);
-
-        cordova.exec(callbackFn, null, 'LocalNotification', 'isTriggered', [id]);
-    },
-
-    /**
-     * Occurs when a notification was added.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {String} state
-     *      Either "foreground" or "background"
-     * @param {String} json
-     *      A custom (JSON) string
-     */
-    onadd: function (id, state, json) {},
-
-    /**
-     * Occurs when the notification is triggered.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {String} state
-     *      Either "foreground" or "background"
-     * @param {String} json
-     *      A custom (JSON) string
-     */
-    ontrigger: function (id, state, json) {},
-
-    /**
-     * Fires after the notification was clicked.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {String} state
-     *      Either "foreground" or "background"
-     * @param {String} json
-     *      A custom (JSON) string
-     */
-    onclick: function (id, state, json) {},
-
-    /**
-     * Fires if the notification was canceled.
-     *
-     * @param {String} id
-     *      The ID of the notification
-     * @param {String} state
-     *      Either "foreground" or "background"
-     * @param {String} json
-     *      A custom (JSON) string
-     */
-    oncancel: function (id, state, json) {}
-};
-
-var plugin  = new LocalNotification(),
+var exec    = require('cordova/exec'),
     channel = require('cordova/channel');
 
-// Called after all 'deviceready' listener are called
+
+// Called after 'deviceready' event
 channel.deviceready.subscribe( function () {
-    // Device is ready now, the listeners are registered and all queued events
-    // can be executed now.
-    cordova.exec(null, null, 'LocalNotification', 'deviceready', []);
+    // Device is ready now, the listeners are registered
+    // and all queued events can be executed.
+    exec(null, null, 'LocalNotification', 'deviceready', []);
 });
 
+// Called before 'deviceready' event
 channel.onCordovaReady.subscribe( function () {
     // The cordova device plugin is ready now
     channel.onCordovaInfoReady.subscribe( function () {
         if (device.platform == 'Android') {
             channel.onPause.subscribe( function () {
                 // Necessary to set the state to `background`
-                cordova.exec(null, null, 'LocalNotification', 'pause', []);
+                exec(null, null, 'LocalNotification', 'pause', []);
             });
 
             channel.onResume.subscribe( function () {
                 // Necessary to set the state to `foreground`
-                cordova.exec(null, null, 'LocalNotification', 'resume', []);
+                exec(null, null, 'LocalNotification', 'resume', []);
             });
 
             // Necessary to set the state to `foreground`
-            cordova.exec(null, null, 'LocalNotification', 'resume', []);
+            exec(null, null, 'LocalNotification', 'resume', []);
         }
 
         // Merges the platform specific properties into the default properties
-        plugin.applyPlatformSpecificOptions();
+        exports.applyPlatformSpecificOptions();
     });
 });
 
-module.exports = plugin;
+
+/**
+ * @private
+ *
+ * Default values.
+ */
+exports._defaults = {
+    message:    '',
+    title:      '',
+    autoCancel: false,
+    badge:      0,
+    id:         '0',
+    json:       '',
+    repeat:     ''
+};
+
+
+/**
+ * Returns the default settings
+ *
+ * @return {Object}
+ */
+exports.getDefaults = function () {
+    return this._defaults;
+};
+
+/**
+ * Overwrite default settings
+ *
+ * @param {Object} defaults
+ */
+exports.setDefaults = function (newDefaults) {
+    var defaults = this.getDefaults();
+
+    for (var key in defaults) {
+        if (newDefaults[key] !== undefined) {
+            defaults[key] = newDefaults[key];
+        }
+    }
+};
+
+/**
+ * Add a new entry to the registry
+ *
+ * @param {Object} props
+ *      The notification properties
+ * @param {Function} callback
+ *      A function to be called after the notification has been canceled
+ * @param {Object} scope
+ *      The scope for the callback function
+ *
+ * @return {Number}
+ *      The notification's ID
+ */
+exports.add = function (props, callback, scope) {
+    var options = this.mergeWithDefaults(props),
+        fn      = this.createCallbackFn(callback, scope);
+
+    if (options.id) {
+        options.id = options.id.toString();
+    }
+
+    if (options.date === undefined) {
+        options.date = new Date();
+    }
+
+    if (options.title) {
+        options.title = options.title.toString();
+    }
+
+    if (options.message) {
+        options.message = options.message.toString();
+    }
+
+    if (typeof options.date == 'object') {
+        options.date = Math.round(options.date.getTime()/1000);
+    }
+
+    if (typeof options.json == 'object') {
+        options.json = JSON.stringify(options.json);
+    }
+
+    if (['WinCE', 'Win32NT'].indexOf(device.platform) > -1) {
+        fn = function (cmd) {
+            eval(cmd);
+        };
+    }
+
+    exec(fn, null, 'LocalNotification', 'add', [options]);
+
+    return options.id;
+};
+
+/**
+ * Cancels the specified notification.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {Function} callback
+ *      A function to be called after the notification has been canceled
+ * @param {Object} scope
+ *      The scope for the callback function
+ */
+exports.cancel = function (id, callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    exec(fn, null, 'LocalNotification', 'cancel', [(id || '0').toString()]);
+};
+
+/**
+ * Removes all previously registered notifications.
+ *
+ * @param {Function} callback
+ *      A function to be called after all notifications have been canceled
+ * @param {Object} scope
+ *      The scope for the callback function
+ */
+exports.cancelAll = function (callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    exec(fn, null, 'LocalNotification', 'cancelAll', []);
+};
+
+/**
+ * Retrieves a list with all currently pending notifications.
+ *
+ * @param {Function} callback
+ *      A callback function to be called with the list
+ * @param {Object} scope
+ *      The scope for the callback function
+ */
+exports.getScheduledIds = function (callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    exec(fn, null, 'LocalNotification', 'getScheduledIds', []);
+};
+
+/**
+ * Checks wether a notification with an ID is scheduled.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {Function} callback
+ *      A callback function to be called with the list
+ * @param {Object} scope
+ *      The scope for the callback function
+ */
+exports.isScheduled = function (id, callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    exec(fn, null, 'LocalNotification', 'isScheduled', [id.toString()]);
+};
+
+/**
+ * Retrieves a list with all triggered notifications.
+ *
+ * @param {Function} callback
+ *      A callback function to be called with the list
+ * @param {Object} scope
+ *      The scope for the callback function
+ */
+exports.getTriggeredIds = function (callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    exec(fn, null, 'LocalNotification', 'getTriggeredIds', []);
+};
+
+/**
+ * Checks wether a notification with an ID was triggered.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {Function} callback
+ *      A callback function to be called with the list
+ * @param {Object} scope
+ *      The scope for the callback function
+ */
+exports.isTriggered = function (id, callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    exec(fn, null, 'LocalNotification', 'isTriggered', [id.toString()]);
+};
+
+/**
+ * Informs if the app has the permission to show notifications.
+ *
+ * @param {Function} callback
+ *      The function to be exec as the callback
+ * @param {Object?} scope
+ *      The callback function's scope
+ */
+exports.hasPermission = function (callback, scope) {
+    var fn = this.createCallbackFn(callback, scope);
+
+    if (device.platform != 'iOS') {
+        fn(true);
+        return;
+    }
+
+    exec(fn, null, 'LocalNotification', 'hasPermission', []);
+};
+
+/**
+ * Register permission to show notifications if not already granted.
+ *
+ * @param {Function} callback
+ *      The function to be exec as the callback
+ * @param {Object?} scope
+ *      The callback function's scope
+ */
+exports.registerPermission = function (callback, scope) {
+    if (device.platform != 'iOS')
+        return;
+
+    exec(null, null, 'LocalNotification', 'registerPermission', []);
+};
+
+exports.promptForPermission = function (callback, scope) {
+    console.warn('Depreated: Please use `notification.local.registerPermission` instead.');
+    exports.registerPermission.apply(this, arguments);
+};
+
+/**
+ * Occurs when a notification was added.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {String} state
+ *      Either "foreground" or "background"
+ * @param {String} json
+ *      A custom (JSON) string
+ */
+exports.onadd = function (id, state, json) {};
+
+/**
+ * Occurs when the notification is triggered.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {String} state
+ *      Either "foreground" or "background"
+ * @param {String} json
+ *      A custom (JSON) string
+ */
+exports.ontrigger = function (id, state, json) {};
+
+/**
+ * Fires after the notification was clicked.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {String} state
+ *      Either "foreground" or "background"
+ * @param {String} json
+ *      A custom (JSON) string
+ */
+exports.onclick = function (id, state, json) {};
+
+/**
+ * Fires if the notification was canceled.
+ *
+ * @param {String} id
+ *      The ID of the notification
+ * @param {String} state
+ *      Either "foreground" or "background"
+ * @param {String} json
+ *      A custom (JSON) string
+ */
+exports.oncancel = function (id, state, json) {};
+
+
+/**
+ * @private
+ *
+ * Merges custom properties with the default values.
+ *
+ * @param {Object} options
+ *      Set of custom values
+ *
+ * @retrun {Object}
+ *      The merged property list
+ */
+exports.mergeWithDefaults = function (options) {
+    var defaults = this.getDefaults();
+
+    for (var key in defaults) {
+        if (options[key] === undefined) {
+            options[key] = defaults[key];
+        }
+    }
+
+    return options;
+};
+
+/**
+ * @private
+ *
+ * Merges the platform specific properties into the default properties.
+ *
+ * @return {Object}
+ *      The default properties for the platform
+ */
+exports.applyPlatformSpecificOptions = function () {
+    var defaults = this._defaults;
+
+    switch (device.platform) {
+    case 'Android':
+        defaults.icon       = 'icon';
+        defaults.smallIcon  = null;
+        defaults.ongoing    = false;
+        defaults.led        = 'FFFFFF'; /*RRGGBB*/
+        defaults.sound      = 'TYPE_NOTIFICATION'; break;
+    case 'iOS':
+        defaults.sound      = ''; break;
+    case 'WinCE': case 'Win32NT':
+        defaults.smallImage = null;
+        defaults.image      = null;
+        defaults.wideImage  = null;
+    }
+
+    return defaults;
+};
+
+/**
+ * @private
+ *
+ * Creates a callback, which will be executed within a specific scope.
+ *
+ * @param {Function} callbackFn
+ *      The callback function
+ * @param {Object} scope
+ *      The scope for the function
+ *
+ * @return {Function}
+ *      The new callback function
+ */
+exports.createCallbackFn = function (callbackFn, scope) {
+    if (typeof callbackFn != 'function')
+        return;
+
+    return function () {
+        callbackFn.apply(scope || this, arguments);
+    };
+};