Kaynağa Gözat

Schedule notifications with support for custom intervals on iOS

Sebastián Katzer 8 yıl önce
ebeveyn
işleme
cac11bc16c

+ 2 - 2
plugin.xml

@@ -59,7 +59,7 @@
         <header-file src="src/ios/APPLocalNotification.h" />
         <source-file src="src/ios/APPLocalNotification.m" />
 
-        <!-- <header-file src="src/ios/APPLocalNotificationOptions.h" />
+        <header-file src="src/ios/APPLocalNotificationOptions.h" />
         <source-file src="src/ios/APPLocalNotificationOptions.m" />
 
         <header-file src="src/ios/UNUserNotificationCenter+APPLocalNotification.h" />
@@ -69,7 +69,7 @@
         <source-file src="src/ios/UNMutableNotificationContent+APPLocalNotification.m" />
 
         <header-file src="src/ios/UNNotificationRequest+APPLocalNotification.h" />
-        <source-file src="src/ios/UNNotificationRequest+APPLocalNotification.m" /> -->
+        <source-file src="src/ios/UNNotificationRequest+APPLocalNotification.m" />
 
     </platform>
 

+ 2 - 2
src/ios/APPLocalNotification.h

@@ -35,8 +35,8 @@
 // Request permission to show notifications
 - (void) request:(CDVInvokedUrlCommand*)command;
 
-//// Schedule set of notifications
-//- (void) schedule:(CDVInvokedUrlCommand*)command;
+// Schedule notifications
+- (void) schedule:(CDVInvokedUrlCommand*)command;
 //// Update set of notifications
 //- (void) update:(CDVInvokedUrlCommand*)command;
 //// Cancel set of notifications

+ 138 - 333
src/ios/APPLocalNotification.m

@@ -22,25 +22,18 @@
  */
 
 #import "APPLocalNotification.h"
-//#import "APPLocalNotificationOptions.h"
-//#import "UNUserNotificationCenter+APPLocalNotification.h"
-//#import "UNNotificationRequest+APPLocalNotification.h"
-//#import "UNMutableNotificationContent+APPLocalNotification.h"
+#import "APPLocalNotificationOptions.h"
+#import "UNUserNotificationCenter+APPLocalNotification.h"
+#import "UNNotificationRequest+APPLocalNotification.h"
+#import "UNMutableNotificationContent+APPLocalNotification.h"
 
 @interface APPLocalNotification ()
 
-// Property reader for [self app]
-@property (readonly, getter=app) UIApplication* app;
-// Property reader for [self center]
-@property (readonly, getter=center) UNUserNotificationCenter* center;
-// All events will be queued until deviceready has been fired
+@property (strong, nonatomic) UIApplication* app;
+@property (strong, nonatomic) UNUserNotificationCenter* center;
 @property (readwrite, assign) BOOL deviceready;
-// Event queue
 @property (readonly, nonatomic, retain) NSMutableArray* eventQueue;
 
-// IOS9: TODO remove later
-@property (nonatomic, retain) CDVInvokedUrlCommand* command;
-
 @end
 
 @implementation APPLocalNotification
@@ -52,6 +45,8 @@
 
 /**
  * Execute all queued events.
+ *
+ * @return [ Void ]
  */
 - (void) deviceready:(CDVInvokedUrlCommand*)command
 {
@@ -63,48 +58,32 @@
 
     [eventQueue removeAllObjects];
 }
-//
-///**
-// * Schedule a set of notifications.
-// *
-// * @param properties
-// *      A dict of properties for each notification
-// */
-//- (void) schedule:(CDVInvokedUrlCommand*)command
-//{
-//    NSArray* notifications = command.arguments;
-//
-//    [self.commandDelegate runInBackground:^{
-//        if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
-//            for (NSDictionary* options in notifications) {
-//                UNMutableNotificationContent* notification;
-//
-//                notification = [[UNMutableNotificationContent alloc]
-//                                initWithOptions:options];
-//
-//                [self scheduleNotification:notification];
-//                //[self fireEvent:@"add" notification:notification];
-//            }
-//        } else {
-//            for (NSDictionary* options in notifications) {
-//                UILocalNotification* notification;
-//
-//                notification = [[UILocalNotification alloc]
-//                                initWithOptions:options];
-//
-//                [self scheduleLocalNotification:[notification copy]];
-//                [self fireEvent:@"schedule" localnotification:notification];
-//
-//                if (notifications.count > 1) {
-//                    [NSThread sleepForTimeInterval:0.01];
-//                }
-//            }
-//        }
-//
-//        [self execCallback:command];
-//     }];
-//}
-//
+
+/**
+ * Schedule notifications.
+ *
+ * @param [Array<Hash>] properties A list of key-value properties.
+ *
+ * @return [ Void ]
+ */
+- (void) schedule:(CDVInvokedUrlCommand*)command
+{
+    NSArray* notifications = command.arguments;
+
+    [self.commandDelegate runInBackground:^{
+        for (NSDictionary* options in notifications) {
+            UNMutableNotificationContent* notification;
+
+            notification = [[UNMutableNotificationContent alloc]
+                            initWithOptions:options];
+
+            [self scheduleNotification:notification];
+        }
+
+        [self execCallback:command];
+     }];
+}
+
 ///**
 // * Update a set of notifications.
 // *
@@ -535,7 +514,7 @@
  */
 - (void) check:(CDVInvokedUrlCommand*)command
 {
-    [self.center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings* settings) {
+    [_center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings* settings) {
         BOOL authorized = settings.authorizationStatus == UNAuthorizationStatusAuthorized;
         BOOL enabled = settings.notificationCenterSetting == UNNotificationSettingEnabled;
         BOOL permitted = authorized && enabled;
@@ -563,19 +542,22 @@
 }
 
 #pragma mark -
-#pragma mark Core Logic
+#pragma mark Private
+
+/**
+ * Schedule the local notification.
+ */
+- (void) scheduleNotification:(UNMutableNotificationContent*)notification
+{
+    __weak APPLocalNotification* weakSelf = self;
+    UNNotificationRequest* request = notification.request;
+
+    [_center addNotificationRequest:request withCompletionHandler:^(NSError* e) {
+        __strong APPLocalNotification* strongSelf = weakSelf;
+        [strongSelf fireEvent:@"add" notification:request];
+    }];
+}
 
-///**
-// * Schedule the local notification.
-// */
-//- (void) scheduleNotification:(UNMutableNotificationContent*)notification
-//{
-//    [self.center addNotificationRequest:notification.request
-//                  withCompletionHandler:^(NSError* e) {
-//                      [self fireEvent:@"add" notification:notification.request];
-//                  }];
-//}
-//
 ///**
 // * Update the local notification.
 // */
@@ -620,44 +602,43 @@
 //
 //    [self.app setApplicationIconBadgeNumber:0];
 //}
-//
-//#pragma mark -
-//#pragma mark Delegates
-//
-///**
-// * Called when a notification is delivered to a foreground app.
-// */
-//- (void) userNotificationCenter:(UNUserNotificationCenter *)center
-//        willPresentNotification:(UNNotification *)notification
-//          withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
-//{
-//    [self fireEvent:@"trigger" notification:notification.request];
-//    completionHandler(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
-//}
-//
-//
-///**
-// * Called to let your app know which action was selected by the user for a given
-// * notification.
-// */
-//- (void) userNotificationCenter:(UNUserNotificationCenter *)center
-// didReceiveNotificationResponse:(UNNotificationResponse *)response
-//          withCompletionHandler:(void (^)())completionHandler
-//{
-//    UNNotificationRequest* notification = response.notification.request;
-//
-//    [self fireEvent:@"click" notification:notification];
-//
+
+#pragma mark -
+#pragma mark UNUserNotificationCenterDelegate
+
+/**
+ * Called when a notification is delivered to the app while being in foreground.
+ */
+- (void) userNotificationCenter:(UNUserNotificationCenter *)center
+        willPresentNotification:(UNNotification *)notification
+          withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
+{
+    [self fireEvent:@"trigger" notification:notification.request];
+    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
+}
+
+/**
+ * Called to let your app know which action was selected by the user for a given
+ * notification.
+ */
+- (void) userNotificationCenter:(UNUserNotificationCenter *)center
+ didReceiveNotificationResponse:(UNNotificationResponse *)response
+          withCompletionHandler:(void (^)())completionHandler
+{
+    UNNotificationRequest* notification = response.notification.request;
+
+    [self fireEvent:@"click" notification:notification];
+
 //    if ([notification.options isRepeating]) {
-//        [self.center clearNotification:notification];
+//        [_center clearNotification:notification];
 //        [self fireEvent:@"clear" notification:notification];
 //    } else {
-//        [self.center cancelNotification:notification];
+//        [_center cancelNotification:notification];
 //        [self fireEvent:@"cancel" notification:notification];
 //    }
-//
-//    completionHandler();
-//}
+
+    completionHandler();
+}
 
 #pragma mark -
 #pragma mark Life Cycle
@@ -668,249 +649,73 @@
 - (void) pluginInitialize
 {
     eventQueue = [[NSMutableArray alloc] init];
+    _app       = [UIApplication sharedApplication];
+    _center    = [UNUserNotificationCenter currentNotificationCenter];
 
-    self.center.delegate = self;
+    _center.delegate = self;
 }
 
 #pragma mark -
 #pragma mark Helper
 
-///**
-// * Retrieves the application state
-// *
-// * @return
-// *      Either "background" or "foreground"
-// */
-//- (NSString*) applicationState
-//{
-//    UIApplicationState state = [self.app applicationState];
-//
-//    bool isActive = state == UIApplicationStateActive;
-//
-//    return isActive ? @"foreground" : @"background";
-//}
-//
-///**
-// * Simply invokes the callback without any parameter.
-// */
-//- (void) execCallback:(CDVInvokedUrlCommand*)command
-//{
-//    CDVPluginResult *result = [CDVPluginResult
-//                               resultWithStatus:CDVCommandStatus_OK];
-//
-//    [self.commandDelegate sendPluginResult:result
-//                                callbackId:command.callbackId];
-//}
-//
-///**
-// * Short hand for shared application instance.
-// */
-//- (UIApplication*) app
-//{
-//    return [UIApplication sharedApplication];
-//}
+/**
+ * Retrieve the state of the application.
+ *
+ * @return "background" or "foreground"
+ */
+- (NSString*) applicationState
+{
+    UIApplicationState state = [_app applicationState];
+
+    bool isActive = state == UIApplicationStateActive;
+
+    return isActive ? @"foreground" : @"background";
+}
 
 /**
- * Short hand for current notification center.
+ * Simply invokes the callback without any parameter.
  */
-- (UNUserNotificationCenter*) center
+- (void) execCallback:(CDVInvokedUrlCommand*)command
 {
-    return [UNUserNotificationCenter currentNotificationCenter];
+    CDVPluginResult *result = [CDVPluginResult
+                               resultWithStatus:CDVCommandStatus_OK];
+
+    [self.commandDelegate sendPluginResult:result
+                                callbackId:command.callbackId];
 }
 
-///**
-// * Fire general event.
-// */
-//- (void) fireEvent:(NSString*)event
-//{
-//    [self fireEvent:event notification:NULL];
-//}
-//
-///**
-// * Fire event for local notification.
-// */
-//- (void) fireEvent:(NSString*)event
-//      notification:(UNNotificationRequest*)notification
-//{
-//    NSString* js;
-//    NSString* appState = [self applicationState];
-//    NSString* params   = [NSString stringWithFormat:@"\"%@\"", appState];
-//
-//    if (notification) {
-//        NSString* args = [notification encodeToJSON];
-//        params = [NSString stringWithFormat:@"%@,'%@'", args, appState];
-//    }
-//
-//    js = [NSString stringWithFormat:
-//          @"cordova.plugins.notification.local.core.fireEvent('%@', %@)",
-//          event, params];
-//
-//    if (deviceready) {
-//        [self.commandDelegate evalJs:js];
-//    } else {
-//        [self.eventQueue addObject:js];
-//    }
-//}
-//
-//#pragma mark -
-//#pragma mark ios 9
-//
-///**
-// * Schedule the local notification.
-// */
-//- (void) scheduleLocalNotification:(UILocalNotification*)notification
-//{
-//    [self cancelForerunnerLocalNotification:notification];
-//    [self.app scheduleLocalNotification:notification];
-//}
-//
-///**
-// * Update the local notification.
-// */
-//- (void) updateLocalNotification:(UILocalNotification*)notification
-//                     withOptions:(NSDictionary*)newOptions
-//{
-//    NSMutableDictionary* options = [notification.userInfo mutableCopy];
-//
-//    [options addEntriesFromDictionary:newOptions];
-//    [options setObject:[NSDate date] forKey:@"updatedAt"];
-//
-//    notification = [[UILocalNotification alloc]
-//                    initWithOptions:options];
-//
-//    [self scheduleLocalNotification:notification];
-//}
-//
-///**
-// * Cancel a maybe given forerunner with the same ID.
-// */
-//- (void) cancelForerunnerLocalNotification:(UILocalNotification*)notification
-//{
-//    NSNumber* id = notification.options.id;
-//    UILocalNotification* forerunner;
-//
-//    forerunner = [self.app localNotificationWithId:id];
-//
-//    if (!forerunner)
-//        return;
-//
-//    [self.app cancelLocalNotification:forerunner];
-//}
-//
-///**
-// * Cancels all non-repeating local notification older then
-// * a specific amount of seconds
-// */
-//- (void) cancelAllNotificationsWhichAreOlderThen:(float)seconds
-//{
-//    NSArray* notifications;
-//
-//    notifications = [self.app localNotifications];
-//
-//    for (UILocalNotification* notification in notifications)
-//    {
-//        if (![notification isRepeating]
-//            && notification.timeIntervalSinceFireDate > seconds)
-//        {
-//            [self.app cancelLocalNotification:notification];
-//            [self fireEvent:@"cancel" localnotification:notification];
-//        }
-//    }
-//}
-//
-///**
-// * Calls the cancel or trigger event after a local notification was received.
-// * Cancels the local notification if autoCancel was set to true.
-// */
-//- (void) didReceiveLocalNotification:(NSNotification*)localNotification
-//{
-//    UILocalNotification* notification = [localNotification object];
-//
-//    if ([notification userInfo] == NULL || [notification wasUpdated])
-//        return;
-//
-//    NSTimeInterval timeInterval = [notification timeIntervalSinceLastTrigger];
-//    NSString* event = timeInterval < 0.2 && deviceready ? @"trigger" : @"click";
-//
-//    [self fireEvent:event localnotification:notification];
-//
-//    if (![event isEqualToString:@"click"])
-//        return;
-//
-//    if ([notification isRepeating]) {
-//        [self fireEvent:@"clear" localnotification:notification];
-//    } else {
-//        [self.app cancelLocalNotification:notification];
-//        [self fireEvent:@"cancel" localnotification:notification];
-//    }
-//}
-//
-///**
-// * Called when app has started
-// * (by clicking on a local notification).
-// */
-//- (void) didFinishLaunchingWithOptions:(NSNotification*)notification
-//{
-//    NSDictionary* launchOptions = [notification userInfo];
-//
-//    UILocalNotification* localNotification;
-//
-//    localNotification = [launchOptions objectForKey:
-//                         UIApplicationLaunchOptionsLocalNotificationKey];
-//
-//    if (localNotification) {
-//        [self didReceiveLocalNotification:
-//         [NSNotification notificationWithName:CDVLocalNotification
-//                                       object:localNotification]];
-//    }
-//}
-//
-///**
-// * Called on notification settings registration is completed.
-// */
-//- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings
-//{
-//    if (_command) {
-//        [self hasPermission:_command];
-//        _command = NULL;
-//    }
-//}
-//
-///**
-// * Cancels all single non-repeating notifications which are older then 5 days
-// * before the app terminates.
-// */
-//- (void) onAppTerminate
-//{
-//    [self cancelAllNotificationsWhichAreOlderThen:432000];
-//}
-//
-///**
-// * Fire event for local notification.
-// */
-//- (void) fireEvent:(NSString*)event localnotification:(UILocalNotification*)notification
-//{
-//    NSString* js;
-//    NSString* params = [NSString stringWithFormat:
-//                        @"\"%@\"", self.applicationState];
-//
-//    if (notification) {
-//        NSString* args = [notification encodeToJSON];
-//
-//        params = [NSString stringWithFormat:
-//                  @"%@,'%@'",
-//                  args, self.applicationState];
-//    }
-//
-//    js = [NSString stringWithFormat:
-//          @"cordova.plugins.notification.local.core.fireEvent('%@', %@)",
-//          event, params];
-//
-//    if (deviceready) {
-//        [self.commandDelegate evalJs:js];
-//    } else {
-//        [self.eventQueue addObject:js];
-//    }
-//}
+/**
+ * Fire general event.
+ */
+- (void) fireEvent:(NSString*)event
+{
+    [self fireEvent:event notification:NULL];
+}
+
+/**
+ * Fire event for local notification.
+ */
+- (void) fireEvent:(NSString*)event
+      notification:(UNNotificationRequest*)notification
+{
+    NSString* js;
+    NSString* appState = [self applicationState];
+    NSString* params   = [NSString stringWithFormat:@"\"%@\"", appState];
+
+    if (notification) {
+        NSString* args = [notification encodeToJSON];
+        params = [NSString stringWithFormat:@"%@,'%@'", args, appState];
+    }
+
+    js = [NSString stringWithFormat:
+          @"cordova.plugins.notification.local.core.fireEvent('%@', %@)",
+          event, params];
+
+    if (deviceready) {
+        [self.commandDelegate evalJs:js];
+    } else {
+        [self.eventQueue addObject:js];
+    }
+}
 
 @end

+ 10 - 16
src/ios/APPLocalNotificationOptions.h

@@ -21,27 +21,21 @@
  * @APPPLANT_LICENSE_HEADER_END@
  */
 
-#import <UserNotifications/UNNotificationSound.h>
-#import <UserNotifications/UNNotificationRequest.h>
-#import <Foundation/Foundation.h>
-#import <UIKit/UIKit.h>
+@import UserNotifications;
 
 @interface APPLocalNotificationOptions : NSObject
 
-- (id) initWithDict:(NSDictionary*)dict;
-
-@property (readonly, getter=id) NSNumber* id;
-@property (readonly, getter=identifier) NSString* identifier;
-@property (readonly, getter=title) NSString* title;
-@property (readonly, getter=subtitle) NSString* subtitle;
-@property (readonly, getter=badge) NSNumber* badge;
-@property (readonly, getter=text) NSString* text;
-@property (readonly, getter=sound) UNNotificationSound* sound;
-@property (readonly, getter=userInfo) NSDictionary* userInfo;
+@property (readonly, getter=id)         NSNumber*            id;
+@property (readonly, getter=identifier) NSString*            identifier;
+@property (readonly, getter=title)      NSString*            title;
+@property (readonly, getter=subtitle)   NSString*            subtitle;
+@property (readonly, getter=badge)      NSNumber*            badge;
+@property (readonly, getter=text)       NSString*            text;
+@property (readonly, getter=sound)      UNNotificationSound* sound;
+@property (readonly, getter=userInfo)   NSDictionary*        userInfo;
 
-// If it's a repeating notification
+- (id) initWithDict:(NSDictionary*)dict;
 - (BOOL) isRepeating;
-// how and when to trigger the notification
 - (UNNotificationTrigger*) trigger;
 
 @end

+ 131 - 60
src/ios/APPLocalNotificationOptions.m

@@ -40,8 +40,11 @@
 #pragma mark Initialization
 
 /**
- * Initialize the object with the given options when calling on JS side:
- * notification.local.add(options)
+ * Initialize by using the given property values.
+ *
+ * @param [ NSDictionary* ] dict A key-value property map.
+ *
+ * @return [ APPLocalNotificationOptions ]
  */
 - (id) initWithDict:(NSDictionary*)dictionary
 {
@@ -53,10 +56,12 @@
 }
 
 #pragma mark -
-#pragma mark Attributes
+#pragma mark Properties
 
 /**
- * The notification's ID.
+ * The ID for the notification.
+ *
+ * @return [ NSNumber* ]
  */
 - (NSNumber*) id
 {
@@ -66,7 +71,9 @@
 }
 
 /**
- * The notification's ID as a string.
+ * The ID for the notification.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) identifier
 {
@@ -74,7 +81,9 @@
 }
 
 /**
- * The notification's title.
+ * The title for the notification.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) title
 {
@@ -82,7 +91,9 @@
 }
 
 /**
- * The notification's title.
+ * The subtitle for the notification.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) subtitle
 {
@@ -92,7 +103,9 @@
 }
 
 /**
- * The notification's message.
+ * The text for the notification.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) text
 {
@@ -100,7 +113,9 @@
 }
 
 /**
- * The notification's badge number.
+ * The badge number for the notification.
+ *
+ * @return [ NSNumber* ]
  */
 - (NSNumber*) badge
 {
@@ -108,14 +123,16 @@
 }
 
 /**
- * The notification's sound path.
+ * The sound file for the notification.
+ *
+ * @return [ UNNotificationSound* ]
  */
 - (UNNotificationSound*) sound
 {
     NSString* path = [dict objectForKey:@"sound"];
     NSString* file;
 
-    if ([self stringIsNullOrEmpty:path])
+    if (!path.length)
         return NULL;
 
     if ([path isEqualToString:@"res://platform_default"])
@@ -132,7 +149,9 @@
 }
 
 /**
- * The notification's fire date.
+ * The date when to fire the notification.
+ *
+ * @return [ NSDate* ]
  */
 - (NSDate*) fireDate
 {
@@ -142,29 +161,41 @@
     return [NSDate dateWithTimeIntervalSince1970:timestamp];
 }
 
+#pragma mark -
+#pragma mark Public
+
 /**
- * If it's a repeating notification.
+ * If the notification shall be repeating.
+ *
+ * @return [ BOOL ]
  */
 - (BOOL) isRepeating
 {
-    NSString* interval = [dict objectForKey:@"every"];
+    id every = [dict objectForKey:@"every"];
 
-    return ![self stringIsNullOrEmpty:interval];
-}
+    if ([every isKindOfClass:NSString.class])
+        return ((NSString*) every).length > 0;
 
-#pragma mark -
-#pragma mark Methods
+    return every > 0;
+}
 
 /**
  * Specify how and when to trigger the notification.
+ *
+ * @return [ UNNotificationTrigger* ]
  */
 - (UNNotificationTrigger*) trigger
 {
-    return [self isRepeating] ? [self triggerWithDateMatchingComponents] : [self triggerWithTimeInterval];
+    if ([self isRepeating])
+        return [self repeatingTrigger];
+
+    return [self nonRepeatingTrigger];
 }
 
 /**
  * The notification's user info dict.
+ *
+ * @return [ NSDictionary* ]
  */
 - (NSDictionary*) userInfo
 {
@@ -183,18 +214,55 @@
 #pragma mark Private
 
 /**
- * Returns a trigger based on a custom time interval in seconds.
+ * Non repeating trigger.
+ *
+ * @return [ UNTimeIntervalNotificationTrigger* ]
  */
-- (UNTimeIntervalNotificationTrigger*) triggerWithTimeInterval
+- (UNTimeIntervalNotificationTrigger*) nonRepeatingTrigger
 {
     return [UNTimeIntervalNotificationTrigger
             triggerWithTimeInterval:[self timeInterval] repeats:NO];
 }
 
 /**
- * Returns a trigger based on a calendar time.
+ * Repeating trigger.
+ *
+ * @return [ UNNotificationTrigger* ]
+ */
+- (UNNotificationTrigger*) repeatingTrigger
+{
+    id every = [dict objectForKey:@"every"];
+
+    if ([every isKindOfClass:NSString.class])
+        return [self repeatingTriggerWithDateMatchingComponents];
+
+    return [self repeatingTriggerWithTimeInterval];
+}
+
+/**
+ * A trigger based on a calendar time defined by the user.
+ *
+ * @return [ UNTimeIntervalNotificationTrigger* ]
+ */
+- (UNTimeIntervalNotificationTrigger*) repeatingTriggerWithTimeInterval
+{
+    long interval = [[dict objectForKey:@"every"] longValue];
+
+    if (interval < 60) {
+        NSLog(@"time interval must be at least 60 if repeating");
+        interval = 60;
+    }
+
+    return [UNTimeIntervalNotificationTrigger
+            triggerWithTimeInterval:interval repeats:YES];
+}
+
+/**
+ * A repeating trigger based on a calendar time defined by the user.
+ *
+ * @return [ UNCalendarNotificationTrigger* ]
  */
-- (UNCalendarNotificationTrigger*) triggerWithDateMatchingComponents
+- (UNCalendarNotificationTrigger*) repeatingTriggerWithDateMatchingComponents
 {
     NSCalendar* cal = [[NSCalendar alloc]
                        initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
@@ -209,7 +277,9 @@
 }
 
 /**
- * Timeinterval between future fire date and now.
+ * The time interval between the next fire date and now.
+ *
+ * @return [ double ]
  */
 - (double) timeInterval
 {
@@ -217,44 +287,49 @@
 }
 
 /**
- * The notification's repeat interval.
+ * The repeat interval for the notification.
+ *
+ * @return [ NSCalendarUnit ]
  */
 - (NSCalendarUnit) repeatInterval
 {
     NSString* interval = [dict objectForKey:@"every"];
-    NSCalendarUnit unitFlags = NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond;
+    NSCalendarUnit units = NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond;
 
 
-    if ([self stringIsNullOrEmpty:interval]) {
-        return unitFlags;
-    }
-    else if ([interval isEqualToString:@"second"]) {
-        return NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay;
-    }
-    else if ([interval isEqualToString:@"minute"]) {
-        return NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitSecond;
-    }
-    else if ([interval isEqualToString:@"hour"]) {
-        return NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitMinute;
-    }
-    else if ([interval isEqualToString:@"day"]) {
+    if (!interval.length)
+        return units;
+
+    if ([interval isEqualToString:@"second"])
+        return NSCalendarUnitNanosecond;
+
+    if ([interval isEqualToString:@"minute"])
+        return NSCalendarUnitSecond;
+
+    if ([interval isEqualToString:@"hour"])
+        return NSCalendarUnitMinute;
+
+    if ([interval isEqualToString:@"day"])
         return NSCalendarUnitHour|NSCalendarUnitMinute;
-    }
-    else if ([interval isEqualToString:@"week"]) {
-        return NSCalendarUnitWeekday|NSCalendarUnitHour|NSCalendarUnitMinute;
-    }
-    else if ([interval isEqualToString:@"month"]) {
-        return NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute;
-    }
-    else if ([interval isEqualToString:@"year"]) {
-        return NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute;
-    }
 
-    return unitFlags;
+    if ([interval isEqualToString:@"week"])
+        return NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitWeekday;
+
+    if ([interval isEqualToString:@"month"])
+        return NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitDay;
+
+    if ([interval isEqualToString:@"year"])
+        return NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitDay|NSCalendarUnitMonth;
+
+    return units;
 }
 
 /**
- * Convert relative path to valid sound name attribute.
+ * Convert an assets path to an valid sound name attribute.
+ *
+ * @param [ NSString* ] path A relative assets file path.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) soundNameForAsset:(NSString*)path
 {
@@ -263,19 +338,15 @@
 }
 
 /**
- * Convert resource path to valid sound name attribute.
+ * Convert a ressource path to an valid sound name attribute.
+ *
+ * @param [ NSString* ] path A relative ressource file path.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) soundNameForResource:(NSString*)path
 {
     return [path pathComponents].lastObject;
 }
 
-/**
- * If the string is empty.
- */
-- (BOOL) stringIsNullOrEmpty:(NSString*)str
-{
-    return (!str.length);
-}
-
 @end

+ 0 - 4
src/ios/UNMutableNotificationContent+APPLocalNotification.h

@@ -27,13 +27,9 @@
 
 @interface UNMutableNotificationContent (APPLocalNotification)
 
-// Initialize a new local notification
 - (id) initWithOptions:(NSDictionary*)dict;
-// The options provided by the plug-in
 - (APPLocalNotificationOptions*) options;
-// Fully configured request to add the notification to the notification center
 - (UNNotificationRequest*) request;
-// Encode the user info dict to JSON
 - (NSString*) encodeToJSON;
 
 @end

+ 48 - 30
src/ios/UNMutableNotificationContent+APPLocalNotification.m

@@ -35,8 +35,11 @@ static char optionsKey;
 #pragma mark Init
 
 /**
- * Initialize a local notification with the given options when calling on JS side:
- * notification.local.add(options)
+ * Initialize a notification with the given options.
+ *
+ * @param [ NSDictionary* ] dict A key-value property map.
+ *
+ * @return [ UNMutableNotificationContent ]
  */
 - (id) initWithOptions:(NSDictionary*)dict
 {
@@ -49,9 +52,9 @@ static char optionsKey;
 }
 
 /**
- * Applies the given options when calling on JS side:
- * notification.local.add(options)
-
+ * Initialize a notification by using the options found under userInfo.
+ *
+ * @return [ Void ]
  */
 - (void) __init
 {
@@ -65,10 +68,12 @@ static char optionsKey;
 }
 
 #pragma mark -
-#pragma mark Methods
+#pragma mark Public
 
 /**
- * The options provided by the plug-in.
+ * The options used to initialize the notification.
+ *
+ * @return [ APPLocalNotificationOptions* ] options
  */
 - (APPLocalNotificationOptions*) options
 {
@@ -84,31 +89,16 @@ static char optionsKey;
     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);
-}
-
 /**
  * The notifcations request ready to add to the notification center including
  * all informations about trigger behavior.
+ *
+ * @return [ UNNotificationRequest* ]
  */
 - (UNNotificationRequest*) request
 {
     APPLocalNotificationOptions* opts = [self getOptions];
-
+    
     return [UNNotificationRequest requestWithIdentifier:opts.identifier
                                                 content:self
                                                 trigger:opts.trigger];
@@ -116,27 +106,55 @@ static char optionsKey;
 
 /**
  * Encode the user info dict to JSON.
+ *
+ * @return [ NSString* ]
  */
 - (NSString*) encodeToJSON
 {
     NSString* json;
     NSData* data;
     NSMutableDictionary* obj = [self.userInfo mutableCopy];
-
+    
     [obj removeObjectForKey:@"updatedAt"];
-
+    
     if (obj == NULL || obj.count == 0)
         return json;
-
+    
     data = [NSJSONSerialization dataWithJSONObject:obj
                                            options:NSJSONWritingPrettyPrinted
                                              error:NULL];
-
+    
     json = [[NSString alloc] initWithData:data
                                  encoding:NSUTF8StringEncoding];
-
+    
     return [json stringByReplacingOccurrencesOfString:@"\n"
                                            withString:@""];
 }
 
+#pragma mark -
+#pragma mark Private
+
+/**
+ * The options used to initialize the notification.
+ *
+ * @return [ APPLocalNotificationOptions* ]
+ */
+- (APPLocalNotificationOptions*) getOptions
+{
+    return objc_getAssociatedObject(self, &optionsKey);
+}
+
+/**
+ * Set the options used to initialize the notification.
+ *
+ * @param [ NSDictionary* ] dict A key-value property map.
+ *
+ * @return [ Void ]
+ */
+- (void) setOptions:(APPLocalNotificationOptions*)options
+{
+    objc_setAssociatedObject(self, &optionsKey,
+                             options, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
 @end

+ 2 - 0
src/ios/UNNotificationRequest+APPLocalNotification.h

@@ -21,6 +21,8 @@
  * @APPPLANT_LICENSE_HEADER_END@
  */
 
+#import "APPLocalNotificationOptions.h"
+
 @import UserNotifications;
 
 @interface UNNotificationRequest (APPLocalNotification)

+ 1 - 1
src/ios/UNNotificationRequest+APPLocalNotification.m

@@ -21,9 +21,9 @@
  * @APPPLANT_LICENSE_HEADER_END@
  */
 
-#import "UNMutableNotificationContent+APPLocalNotification.h"
 #import "APPLocalNotificationOptions.h"
 #import "UNNotificationRequest+APPLocalNotification.h"
+#import "UNMutableNotificationContent+APPLocalNotification.h"
 #import <objc/runtime.h>
 
 @import UserNotifications;

+ 89 - 96
www/local-notification-core.js

@@ -55,66 +55,39 @@ exports.requestPermission = function (callback, scope) {
     exec(fn, null, 'LocalNotification', 'request', []);
 };
 
-// /**
-//  * 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.hasOwnProperty(key)) {
-//             defaults[key] = newDefaults[key];
-//         }
-//     }
-// };
-
-// /**
-//  * Schedule a new local notification.
-//  *
-//  * @param {Object} msgs
-//  *      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
-//  * @param {Object?} args
-//  *      skipPermission:true schedules the notifications immediatly without
-//  *                          registering or checking for permission
-//  */
-// exports.schedule = function (msgs, callback, scope, args) {
-//     var fn = function(granted) {
+/**
+ * Schedule notifications.
+ *
+ * @param [ Array ]    notifications The notifications to schedule.
+ * @param [ Function ] callback      The function to be exec as the callback.
+ * @param [ Object ]   scope         The callback function's scope.
+ * @param [ Object ]   args          Optional flags how to schedule.
+ *
+ * @return [ Void ]
+ */
+exports.schedule = function (msgs, callback, scope, args) {
+    var fn = function (granted) {
 
-//         if (!granted) return;
+        if (!granted) return;
 
-//         var notifications = Array.isArray(msgs) ? msgs : [msgs];
+        var notifications = Array.isArray(msgs) ? msgs : [msgs];
 
-//         for (var i = 0; i < notifications.length; i++) {
-//             var notification = notifications[i];
+        for (var i = 0; i < notifications.length; i++) {
+            var notification = notifications[i];
 
-//             this.mergeWithDefaults(notification);
-//             this.convertProperties(notification);
-//         }
+            this.mergeWithDefaults(notification);
+            this.convertProperties(notification);
+        }
 
-//         this.exec('schedule', notifications, callback, scope);
-//     };
+        this.exec('schedule', notifications, callback, scope);
+    };
 
-//     if (args && args.skipPermission) {
-//         fn.call(this, true);
-//     } else {
-//         this.registerPermission(fn, this);
-//     }
-// };
+    if (args && args.skipPermission) {
+        fn.call(this, true);
+    } else {
+        this.requestPermission(fn, this);
+    }
+};
 
 // /**
 //  * Update existing notifications specified by IDs in options.
@@ -435,55 +408,75 @@ exports.requestPermission = function (callback, scope) {
 //     this.exec('getTriggered', null, callback, scope);
 // };
 
+/**
+ * The (platform specific) default settings.
+ *
+ * @return [ Object ]
+ */
+exports.getDefaults = function () {
+    return this._defaults;
+};
+
+/**
+ * Overwrite default settings.
+ *
+ * @param [ Object ] newDefaults New default values.
+ *
+ * @return [ Void ]
+ */
+exports.setDefaults = function (newDefaults) {
+    var defaults = this.getDefaults();
 
-// /**********
-//  * EVENTS *
-//  **********/
+    for (var key in defaults) {
+        if (newDefaults.hasOwnProperty(key)) {
+            defaults[key] = newDefaults[key];
+        }
+    }
+};
 
-// /**
-//  * Register callback for given event.
-//  *
-//  * @param {String} event
-//  *      The event's name
-//  * @param {Function} callback
-//  *      The function to be exec as callback
-//  * @param {Object?} scope
-//  *      The callback function's scope
-//  */
-// exports.on = function (event, callback, scope) {
+/**
+ * Register callback for given event.
+ *
+ * @param [ String ]   event    The name of the event.
+ * @param [ Function ] callback The function to be exec as callback.
+ * @param [ Object ]   scope    The callback function's scope.
+ *
+ * @return [ Void ]
+ */
+exports.on = function (event, callback, scope) {
 
-//     if (typeof callback !== "function")
-//         return;
+    if (typeof callback !== "function")
+        return;
 
-//     if (!this._listener[event]) {
-//         this._listener[event] = [];
-//     }
+    if (!this._listener[event]) {
+        this._listener[event] = [];
+    }
 
-//     var item = [callback, scope || window];
+    var item = [callback, scope || window];
 
-//     this._listener[event].push(item);
-// };
+    this._listener[event].push(item);
+};
 
-// /**
-//  * Unregister callback for given event.
-//  *
-//  * @param {String} event
-//  *      The event's name
-//  * @param {Function} callback
-//  *      The function to be exec as callback
-//  */
-// exports.un = function (event, callback) {
-//     var listener = this._listener[event];
+/**
+ * Unregister callback for given event.
+ *
+ * @param [ String ]   event    The name of the event.
+ * @param [ Function ] callback The function to be exec as callback.
+ *
+ * @return [ Void ]
+ */
+exports.un = function (event, callback) {
+    var listener = this._listener[event];
 
-//     if (!listener)
-//         return;
+    if (!listener)
+        return;
 
-//     for (var i = 0; i < listener.length; i++) {
-//         var fn = listener[i][0];
+    for (var i = 0; i < listener.length; i++) {
+        var fn = listener[i][0];
 
-//         if (fn == callback) {
-//             listener.splice(i, 1);
-//             break;
-//         }
-//     }
-// };
+        if (fn == callback) {
+            listener.splice(i, 1);
+            break;
+        }
+    }
+};

+ 232 - 265
www/local-notification-util.js

@@ -15,166 +15,140 @@
  * limitations under the License.
  */
 
-// var exec    = require('cordova/exec'),
-//     channel = require('cordova/channel');
-
-
-// /***********
-//  * MEMBERS *
-//  ***********/
-
-// // Default values
-// exports._defaults = {
-//     text:  '',
-//     title: '',
-//     sound: 'res://platform_default',
-//     badge: 0,
-//     id:    0,
-//     data:  undefined,
-//     every: undefined,
-//     at:    undefined
-// };
-
-// // listener
-// exports._listener = {};
-
-
-// /********
-//  * UTIL *
-//  ********/
-
-// /**
-//  * Merge platform specific properties into the default ones.
-//  *
-//  * @return {Object}
-//  *      The default properties for the platform
-//  */
-// exports.applyPlatformSpecificOptions = function () {
-//     var defaults = this._defaults;
-
-//     switch (device.platform) {
-//     case 'Android':
-//         defaults.icon      = 'res://ic_popup_reminder';
-//         defaults.smallIcon = undefined;
-//         defaults.ongoing   = false;
-//         defaults.autoClear = true;
-//         defaults.led       = undefined;
-//         defaults.color     = undefined;
-//         break;
-//     }
-
-//     return defaults;
-// };
-
-// /**
-//  * Merge 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();
-
-//     options.at   = this.getValueFor(options, 'at', 'firstAt', 'date');
-//     options.text = this.getValueFor(options, 'text', 'message');
-//     options.data = this.getValueFor(options, 'data', 'json');
-
-//     if (defaults.hasOwnProperty('autoClear')) {
-//         options.autoClear = this.getValueFor(options, 'autoClear', 'autoCancel');
-//     }
-
-//     if (options.autoClear !== true && options.ongoing) {
-//         options.autoClear = false;
-//     }
-
-//     if (options.at === undefined || options.at === null) {
-//         options.at = new Date();
-//     }
-
-//     for (var key in defaults) {
-//         if (options[key] === null || options[key] === undefined) {
-//             if (options.hasOwnProperty(key) && ['data','sound'].indexOf(key) > -1) {
-//                 options[key] = undefined;
-//             } else {
-//                 options[key] = defaults[key];
-//             }
-//         }
-//     }
-
-//     for (key in options) {
-//         if (!defaults.hasOwnProperty(key)) {
-//             delete options[key];
-//             console.warn('Unknown property: ' + key);
-//         }
-//     }
-
-//     return options;
-// };
-
-// /**
-//  * Convert the passed values to their required type.
-//  *
-//  * @param {Object} options
-//  *      Set of custom values
-//  *
-//  * @retrun {Object}
-//  *      The converted property list
-//  */
-// exports.convertProperties = function (options) {
-
-//     if (options.id) {
-//         if (isNaN(options.id)) {
-//             options.id = this.getDefaults().id;
-//             console.warn('Id is not a number: ' + options.id);
-//         } else {
-//             options.id = Number(options.id);
-//         }
-//     }
-
-//     if (options.title) {
-//         options.title = options.title.toString();
-//     }
-
-//     if (options.text) {
-//         options.text  = options.text.toString();
-//     }
-
-//     if (options.badge) {
-//         if (isNaN(options.badge)) {
-//             options.badge = this.getDefaults().badge;
-//             console.warn('Badge number is not a number: ' + options.id);
-//         } else {
-//             options.badge = Number(options.badge);
-//         }
-//     }
-
-//     if (options.at) {
-//         if (typeof options.at == 'object') {
-//             options.at = options.at.getTime();
-//         }
-
-//         options.at = Math.round(options.at/1000);
-//     }
-
-//     if (typeof options.data == 'object') {
-//         options.data = JSON.stringify(options.data);
-//     }
-
-//     if (options.every) {
-//         if (device.platform == 'iOS' && typeof options.every != 'string') {
-//             options.every = this.getDefaults().every;
-//             var warning = 'Every option is not a string: ' + options.id;
-//             warning += '. Expects one of: second, minute, hour, day, week, ';
-//             warning += 'month, year on iOS.';
-//             console.warn(warning);
-//         }
-//     }
-
-//     return options;
-// };
+var exec    = require('cordova/exec'),
+    channel = require('cordova/channel');
+
+// Default values
+exports._defaults = {
+    text:  '',
+    title: '',
+    sound: 'res://platform_default',
+    badge: 0,
+    id:    0,
+    data:  undefined,
+    every: undefined,
+    at:    undefined
+};
+
+// Listener
+exports._listener = {};
+
+/**
+ * Merge platform specific properties into the default ones.
+ *
+ * @return [ Void ]
+ */
+exports.applyPlatformSpecificOptions = function () {
+    var defaults = this._defaults;
+
+    switch (device.platform) {
+    case 'Android':
+        defaults.icon      = 'res://ic_popup_reminder';
+        defaults.smallIcon = undefined;
+        defaults.ongoing   = false;
+        defaults.autoClear = true;
+        defaults.led       = undefined;
+        defaults.color     = undefined;
+
+        break;
+    }
+};
+
+/**
+ * Merge custom properties with the default values.
+ *
+ * @param [ Object ] options Set of custom values.
+ *
+ * @retrun [ Object ]
+ */
+exports.mergeWithDefaults = function (options) {
+    var defaults = this.getDefaults();
+
+    options.at   = this.getValueFor(options, 'at', 'firstAt', 'date');
+    options.text = this.getValueFor(options, 'text', 'message');
+    options.data = this.getValueFor(options, 'data', 'json');
+
+    if (defaults.hasOwnProperty('autoClear')) {
+        options.autoClear = this.getValueFor(options, 'autoClear', 'autoCancel');
+    }
+
+    if (options.autoClear !== true && options.ongoing) {
+        options.autoClear = false;
+    }
+
+    if (options.at === undefined || options.at === null) {
+        options.at = new Date();
+    }
+
+    for (var key in defaults) {
+        if (options[key] === null || options[key] === undefined) {
+            if (options.hasOwnProperty(key) && ['data','sound'].indexOf(key) > -1) {
+                options[key] = undefined;
+            } else {
+                options[key] = defaults[key];
+            }
+        }
+    }
+
+    for (key in options) {
+        if (!defaults.hasOwnProperty(key)) {
+            delete options[key];
+            console.warn('Unknown property: ' + key);
+        }
+    }
+
+    return options;
+};
+
+/**
+ * Convert the passed values to their required type.
+ *
+ * @param [ Object ] options Properties to convert for.
+ *
+ * @return [ Object ] The converted property list
+ */
+exports.convertProperties = function (options) {
+
+    if (options.id) {
+        if (isNaN(options.id)) {
+            options.id = this.getDefaults().id;
+            console.warn('Id is not a number: ' + options.id);
+        } else {
+            options.id = Number(options.id);
+        }
+    }
+
+    if (options.title) {
+        options.title = options.title.toString();
+    }
+
+    if (options.text) {
+        options.text  = options.text.toString();
+    }
+
+    if (options.badge) {
+        if (isNaN(options.badge)) {
+            options.badge = this.getDefaults().badge;
+            console.warn('Badge number is not a number: ' + options.id);
+        } else {
+            options.badge = Number(options.badge);
+        }
+    }
+
+    if (options.at) {
+        if (typeof options.at == 'object') {
+            options.at = options.at.getTime();
+        }
+
+        options.at = Math.round(options.at/1000);
+    }
+
+    if (typeof options.data == 'object') {
+        options.data = JSON.stringify(options.data);
+    }
+
+    return options;
+};
 
 /**
  * Create a callback function to get executed within a specific scope.
@@ -194,108 +168,101 @@ exports.createCallbackFn = function (fn, scope) {
     };
 };
 
-// /**
-//  * Convert the IDs to numbers.
-//  *
-//  * @param {String/Number[]} ids
-//  *
-//  * @return Array of Numbers
-//  */
-// exports.convertIds = function (ids) {
-//     var convertedIds = [];
-
-//     for (var i = 0; i < ids.length; i++) {
-//         convertedIds.push(Number(ids[i]));
-//     }
-
-//     return convertedIds;
-// };
-
-// /**
-//  * First found value for the given keys.
-//  *
-//  * @param {Object} options
-//  *      Object with key-value properties
-//  * @param {String[]} keys*
-//  *      Key list
-//  */
-// exports.getValueFor = function (options) {
-//     var keys = Array.apply(null, arguments).slice(1);
-
-//     for (var i = 0; i < keys.length; i++) {
-//         var key = keys[i];
-
-//         if (options.hasOwnProperty(key)) {
-//             return options[key];
-//         }
-//     }
-// };
-
-// /**
-//  * Fire event with given arguments.
-//  *
-//  * @param {String} event
-//  *      The event's name
-//  * @param {args*}
-//  *      The callback's arguments
-//  */
-// exports.fireEvent = function (event) {
-//     var args     = Array.apply(null, arguments).slice(1),
-//         listener = this._listener[event];
-
-//     if (!listener)
-//         return;
-
-//     for (var i = 0; i < listener.length; i++) {
-//         var fn    = listener[i][0],
-//             scope = listener[i][1];
-
-//         fn.apply(scope, args);
-//     }
-// };
-
-// /**
-//  * Execute the native counterpart.
-//  *
-//  * @param {String} action
-//  *      The name of the action
-//  * @param args[]
-//  *      Array of arguments
-//  * @param {Function} callback
-//  *      The callback function
-//  * @param {Object} scope
-//  *      The scope for the function
-//  */
-// exports.exec = function (action, args, callback, scope) {
-//     var fn = this.createCallbackFn(callback, scope),
-//         params = [];
-
-//     if (Array.isArray(args)) {
-//         params = args;
-//     } else if (args) {
-//         params.push(args);
-//     }
-
-//     exec(fn, null, 'LocalNotification', action, params);
-// };
-
-
-// /*********
-//  * HOOKS *
-//  *********/
-
-// // Called after 'deviceready' event
-// channel.deviceready.subscribe(function () {
-//     // 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 () {
-//     // Device plugin is ready now
-//     channel.onCordovaInfoReady.subscribe(function () {
-//         // Merge platform specifics into defaults
-//         exports.applyPlatformSpecificOptions();
-//     });
-// });
+/**
+ * Convert the IDs to numbers.
+ *
+ * @param [ Array ] ids
+ *
+ * @return [ Array<Number> ]
+ */
+exports.convertIds = function (ids) {
+    var convertedIds = [];
+
+    for (var i = 0; i < ids.length; i++) {
+        convertedIds.push(Number(ids[i]));
+    }
+
+    return convertedIds;
+};
+
+/**
+ * First found value for the given keys.
+ *
+ * @param [ Object ]         options Object with key-value properties.
+ * @param [ *Array<String> ] keys    List of keys.
+ *
+ * @return [ Object ]
+ */
+exports.getValueFor = function (options) {
+    var keys = Array.apply(null, arguments).slice(1);
+
+    for (var i = 0; i < keys.length; i++) {
+        var key = keys[i];
+
+        if (options.hasOwnProperty(key)) {
+            return options[key];
+        }
+    }
+};
+
+/**
+ * Fire the event with given arguments.
+ *
+ * @param [ String ] event The event's name.
+ * @param [ *Array]  args  The callback's arguments.
+ *
+ * @return [ Void]
+ */
+exports.fireEvent = function (event) {
+    var args     = Array.apply(null, arguments).slice(1),
+        listener = this._listener[event];
+
+    if (!listener)
+        return;
+
+    for (var i = 0; i < listener.length; i++) {
+        var fn    = listener[i][0],
+            scope = listener[i][1];
+
+        fn.apply(scope, args);
+    }
+};
+
+/**
+ * Execute the native counterpart.
+ *
+ * @param [ String ]  action   The name of the action.
+ * @param [ Array ]   args     Array of arguments.
+ * @param [ Function] callback The callback function.
+ * @param [ Object ] scope     The scope for the function.
+ *
+ * @return [ Void ]
+ */
+exports.exec = function (action, args, callback, scope) {
+    var fn = this.createCallbackFn(callback, scope),
+        params = [];
+
+    if (Array.isArray(args)) {
+        params = args;
+    } else if (args) {
+        params.push(args);
+    }
+
+    exec(fn, null, 'LocalNotification', action, params);
+};
+
+// Called after 'deviceready' event
+channel.deviceready.subscribe(function () {
+    // 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 () {
+    // Device plugin is ready now
+    channel.onCordovaInfoReady.subscribe(function () {
+        // Merge platform specifics into defaults
+        exports.applyPlatformSpecificOptions();
+    });
+});

+ 226 - 80
www/local-notification.js

@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+var exec = require('cordova/exec');
+
 /**
  * Request permission to show notifications.
  *
@@ -24,7 +26,14 @@
  * @return [ Void ]
  */
 exports.hasPermission = function (callback, scope) {
-    this.core.hasPermission(callback, scope);
+    var fn = this.createCallbackFn(callback, scope);
+
+    if (device.platform != 'iOS') {
+        fn(true);
+        return;
+    }
+
+    exec(fn, null, 'LocalNotification', 'check', []);
 };
 
 /**
@@ -36,43 +45,49 @@ exports.hasPermission = function (callback, scope) {
  * @return [ Void ]
  */
 exports.requestPermission = function (callback, scope) {
-    this.core.requestPermission(callback, scope);
+    var fn = this.createCallbackFn(callback, scope);
+
+    if (device.platform != 'iOS') {
+        fn(true);
+        return;
+    }
+
+    exec(fn, null, 'LocalNotification', 'request', []);
 };
 
-// /**
-//  * Returns the default settings.
-//  *
-//  * @return {Object}
-//  */
-// exports.getDefaults = function () {
-//     return this.core.getDefaults();
-// };
+/**
+ * Schedule notifications.
+ *
+ * @param [ Array ]    notifications The notifications to schedule.
+ * @param [ Function ] callback      The function to be exec as the callback.
+ * @param [ Object ]   scope         The callback function's scope.
+ * @param [ Object ]   args          Optional flags how to schedule.
+ *
+ * @return [ Void ]
+ */
+exports.schedule = function (msgs, callback, scope, args) {
+    var fn = function (granted) {
 
-// /**
-//  * Overwrite default settings.
-//  *
-//  * @param {Object} defaults
-//  */
-// exports.setDefaults = function (defaults) {
-//     this.core.setDefaults(defaults);
-// };
+        if (!granted) return;
 
-// /**
-//  * Schedule a new local notification.
-//  *
-//  * @param {Object} notifications
-//  *      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
-//  * @param {Object?} args
-//  *      skipPermission:true schedules the notifications immediatly without
-//  *                          registering or checking for permission
-//  */
-// exports.schedule = function (notifications, callback, scope, args) {
-//     this.core.schedule(notifications, callback, scope, args);
-// };
+        var notifications = Array.isArray(msgs) ? msgs : [msgs];
+
+        for (var i = 0; i < notifications.length; i++) {
+            var notification = notifications[i];
+
+            this.mergeWithDefaults(notification);
+            this.convertProperties(notification);
+        }
+
+        this.exec('schedule', notifications, callback, scope);
+    };
+
+    if (args && args.skipPermission) {
+        fn.call(this, true);
+    } else {
+        this.requestPermission(fn, this);
+    }
+};
 
 // /**
 //  * Update existing notifications specified by IDs in options.
@@ -87,8 +102,27 @@ exports.requestPermission = function (callback, scope) {
 //  *      skipPermission:true schedules the notifications immediatly without
 //  *                          registering or checking for permission
 //  */
-// exports.update = function (notifications, callback, scope, args) {
-//     this.core.update(notifications, callback, scope, args);
+// exports.update = function (msgs, callback, scope, args) {
+//     var fn = function(granted) {
+
+//         if (!granted) return;
+
+//         var notifications = Array.isArray(msgs) ? msgs : [msgs];
+
+//         for (var i = 0; i < notifications.length; i++) {
+//             var notification = notifications[i];
+
+//             this.convertProperties(notification);
+//         }
+
+//         this.exec('update', notifications, callback, scope);
+//     };
+
+//     if (args && args.skipPermission) {
+//         fn.call(this, true);
+//     } else {
+//         this.registerPermission(fn, this);
+//     }
 // };
 
 // /**
@@ -102,7 +136,10 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.clear = function (ids, callback, scope) {
-//     this.core.clear(ids, callback, scope);
+//     ids = Array.isArray(ids) ? ids : [ids];
+//     ids = this.convertIds(ids);
+
+//     this.exec('clear', ids, callback, scope);
 // };
 
 // /**
@@ -114,7 +151,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.clearAll = function (callback, scope) {
-//     this.core.clearAll(callback, scope);
+//     this.exec('clearAll', null, callback, scope);
 // };
 
 // /**
@@ -128,7 +165,10 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.cancel = function (ids, callback, scope) {
-//     this.core.cancel(ids, callback, scope);
+//     ids = Array.isArray(ids) ? ids : [ids];
+//     ids = this.convertIds(ids);
+
+//     this.exec('cancel', ids, callback, scope);
 // };
 
 // /**
@@ -140,7 +180,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.cancelAll = function (callback, scope) {
-//     this.core.cancelAll(callback, scope);
+//     this.exec('cancelAll', null, callback, scope);
 // };
 
 // /**
@@ -154,7 +194,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.isPresent = function (id, callback, scope) {
-//     this.core.isPresent(id, callback, scope);
+//     this.exec('isPresent', id || 0, callback, scope);
 // };
 
 // /**
@@ -168,10 +208,10 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.isScheduled = function (id, callback, scope) {
-//     this.core.isScheduled(id, callback, scope);
+//     this.exec('isScheduled', id || 0, callback, scope);
 // };
 
-// *
+// /**
 //  * Check if a notification with an ID was triggered.
 //  *
 //  * @param {String} id
@@ -180,21 +220,21 @@ exports.requestPermission = function (callback, scope) {
 //  *      A callback function to be called with the list
 //  * @param {Object?} scope
 //  *      The scope for the callback function
-
+//  */
 // exports.isTriggered = function (id, callback, scope) {
-//     this.core.isTriggered(id, callback, scope);
+//     this.exec('isTriggered', id || 0, callback, scope);
 // };
 
-// /**
+// *
 //  * List all local notification IDs.
 //  *
 //  * @param {Function} callback
 //  *      A callback function to be called with the list
 //  * @param {Object?} scope
 //  *      The scope for the callback function
-//  */
+
 // exports.getAllIds = function (callback, scope) {
-//     this.core.getAllIds(callback, scope);
+//     this.exec('getAllIds', null, callback, scope);
 // };
 
 // /**
@@ -213,7 +253,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getScheduledIds = function (callback, scope) {
-//     this.core.getScheduledIds(callback, scope);
+//     this.exec('getScheduledIds', null, callback, scope);
 // };
 
 // /**
@@ -225,7 +265,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getTriggeredIds = function (callback, scope) {
-//     this.core.getTriggeredIds(callback, scope);
+//     this.exec('getTriggeredIds', null, callback, scope);
 // };
 
 // /**
@@ -240,7 +280,24 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.get = function () {
-//     this.core.get.apply(this.core, arguments);
+//     var args = Array.apply(null, arguments);
+
+//     if (typeof args[0] == 'function') {
+//         args.unshift([]);
+//     }
+
+//     var ids      = args[0],
+//         callback = args[1],
+//         scope    = args[2];
+
+//     if (!Array.isArray(ids)) {
+//         this.exec('getSingle', Number(ids), callback, scope);
+//         return;
+//     }
+
+//     ids = this.convertIds(ids);
+
+//     this.exec('getAll', ids, callback, scope);
 // };
 
 // /**
@@ -252,7 +309,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getAll = function (callback, scope) {
-//     this.core.getAll(callback, scope);
+//     this.exec('getAll', null, callback, scope);
 // };
 
 // /**
@@ -267,7 +324,28 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getScheduled = function () {
-//     this.core.getScheduled.apply(this.core, arguments);
+//     var args = Array.apply(null, arguments);
+
+//     if (typeof args[0] == 'function') {
+//         args.unshift([]);
+//     }
+
+//     var ids      = args[0],
+//         callback = args[1],
+//         scope    = args[2];
+
+//     if (!Array.isArray(ids)) {
+//         ids = [ids];
+//     }
+
+//     if (!Array.isArray(ids)) {
+//         this.exec('getSingleScheduled', Number(ids), callback, scope);
+//         return;
+//     }
+
+//     ids = this.convertIds(ids);
+
+//     this.exec('getScheduled', ids, callback, scope);
 // };
 
 // /**
@@ -279,7 +357,7 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getAllScheduled = function (callback, scope) {
-//     this.core.getAllScheduled(callback, scope);
+//     this.exec('getScheduled', null, callback, scope);
 // };
 
 // /**
@@ -294,7 +372,28 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getTriggered = function () {
-//     this.core.getTriggered.apply(this.core, arguments);
+//     var args = Array.apply(null, arguments);
+
+//     if (typeof args[0] == 'function') {
+//         args.unshift([]);
+//     }
+
+//     var ids      = args[0],
+//         callback = args[1],
+//         scope    = args[2];
+
+//     if (!Array.isArray(ids)) {
+//         ids = [ids];
+//     }
+
+//     if (!Array.isArray(ids)) {
+//         this.exec('getSingleTriggered', Number(ids), callback, scope);
+//         return;
+//     }
+
+//     ids = this.convertIds(ids);
+
+//     this.exec('getTriggered', ids, callback, scope);
 // };
 
 // /**
@@ -306,31 +405,78 @@ exports.requestPermission = function (callback, scope) {
 //  *      The scope for the callback function
 //  */
 // exports.getAllTriggered = function (callback, scope) {
-//     this.core.getAllTriggered(callback, scope);
+//     this.exec('getTriggered', null, callback, scope);
 // };
 
-// /**
-//  * Register callback for given event.
-//  *
-//  * @param {String} event
-//  *      The event's name
-//  * @param {Function} callback
-//  *      The function to be exec as callback
-//  * @param {Object?} scope
-//  *      The callback function's scope
-//  */
-// exports.on = function (event, callback, scope) {
-//     this.core.on(event, callback, scope);
-// };
+/**
+ * The (platform specific) default settings.
+ *
+ * @return [ Object ]
+ */
+exports.getDefaults = function () {
+    return this._defaults;
+};
 
-// /**
-//  * Unregister callback for given event.
-//  *
-//  * @param {String} event
-//  *      The event's name
-//  * @param {Function} callback
-//  *      The function to be exec as callback
-//  */
-// exports.un = function (event, callback) {
-//     this.core.un(event, callback);
-// };
+/**
+ * Overwrite default settings.
+ *
+ * @param [ Object ] newDefaults New default values.
+ *
+ * @return [ Void ]
+ */
+exports.setDefaults = function (newDefaults) {
+    var defaults = this.getDefaults();
+
+    for (var key in defaults) {
+        if (newDefaults.hasOwnProperty(key)) {
+            defaults[key] = newDefaults[key];
+        }
+    }
+};
+
+/**
+ * Register callback for given event.
+ *
+ * @param [ String ]   event    The name of the event.
+ * @param [ Function ] callback The function to be exec as callback.
+ * @param [ Object ]   scope    The callback function's scope.
+ *
+ * @return [ Void ]
+ */
+exports.on = function (event, callback, scope) {
+
+    if (typeof callback !== "function")
+        return;
+
+    if (!this._listener[event]) {
+        this._listener[event] = [];
+    }
+
+    var item = [callback, scope || window];
+
+    this._listener[event].push(item);
+};
+
+/**
+ * Unregister callback for given event.
+ *
+ * @param [ String ]   event    The name of the event.
+ * @param [ Function ] callback The function to be exec as callback.
+ *
+ * @return [ Void ]
+ */
+exports.un = function (event, callback) {
+    var listener = this._listener[event];
+
+    if (!listener)
+        return;
+
+    for (var i = 0; i < listener.length; i++) {
+        var fn = listener[i][0];
+
+        if (fn == callback) {
+            listener.splice(i, 1);
+            break;
+        }
+    }
+};