diff options
78 files changed, 11035 insertions, 13 deletions
| Binary files differ diff --git a/LICENSE.TXT b/LICENSE.TXT index 2ee406d4..7b70a659 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -10,7 +10,8 @@ Ninja icon; https://openclipart.org/detail/172393/ninja-logo License: https://openclipart.org/share - +Blop sound for notifications: +http://soundbible.com/2067-Blop.html -thanks diff --git a/plugins/android.json b/plugins/android.json index 869d6005..596baec2 100644 --- a/plugins/android.json +++ b/plugins/android.json @@ -79,6 +79,14 @@ { "xml": "<feature name=\"WebSocket\"><param name=\"android-package\" value=\"com.knowledgecode.cordova.websocket.WebSocket\" /></feature>", "count": 1 + }, + { + "xml": "<feature name=\"LocalNotification\"><param name=\"android-package\" value=\"de.appplant.cordova.plugin.localnotification.LocalNotification\" /></feature>", + "count": 1 + }, + { + "xml": "<feature name=\"Badge\"><param name=\"android-package\" value=\"de.appplant.cordova.plugin.badge.Badge\" /></feature>", + "count": 1 } ] } @@ -103,8 +111,50 @@ "count": 1 } ], - "/manifest": [], - "/manifest/application": [] + "/manifest": [ + { + "xml": "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />", + "count": 1 + }, + { + "xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />", + "count": 1 + } + ], + "/manifest/application": [ + { + "xml": "<receiver android:exported=\"false\" android:name=\"de.appplant.cordova.plugin.localnotification.TriggerReceiver\" />", + "count": 1 + }, + { + "xml": "<receiver android:exported=\"false\" android:name=\"de.appplant.cordova.plugin.localnotification.ClearReceiver\" />", + "count": 1 + }, + { + "xml": "<activity android:exported=\"false\" android:launchMode=\"singleInstance\" android:name=\"de.appplant.cordova.plugin.localnotification.ClickActivity\" android:theme=\"@android:style/Theme.NoDisplay\" />", + "count": 1 + }, + { + "xml": "<receiver android:exported=\"false\" android:name=\"de.appplant.cordova.plugin.notification.TriggerReceiver\" />", + "count": 1 + }, + { + "xml": "<receiver android:exported=\"false\" android:name=\"de.appplant.cordova.plugin.notification.ClearReceiver\" />", + "count": 1 + }, + { + "xml": "<receiver android:exported=\"false\" android:name=\"de.appplant.cordova.plugin.localnotification.RestoreReceiver\"><intent-filter><action android:name=\"android.intent.action.BOOT_COMPLETED\" /></intent-filter></receiver>", + "count": 1 + }, + { + "xml": "<activity android:exported=\"false\" android:launchMode=\"singleInstance\" android:name=\"de.appplant.cordova.plugin.notification.ClickActivity\" android:theme=\"@android:style/Theme.NoDisplay\" />", + "count": 1 + }, + { + "xml": "<activity android:exported=\"false\" android:launchMode=\"singleInstance\" android:name=\"de.appplant.cordova.plugin.badge.LaunchActivity\" android:theme=\"@android:style/Theme.Black.NoTitleBar\" />", + "count": 1 + } + ] } } } @@ -163,6 +213,12 @@ }, "cordova-plugin-websocket": { "PACKAGE_NAME": "com.pliablepixels.zmninja" + }, + "de.appplant.cordova.plugin.local-notification": { + "PACKAGE_NAME": "com.pliablepixels.zmninja" + }, + "de.appplant.cordova.plugin.badge": { + "PACKAGE_NAME": "com.pliablepixels.zmninja" } }, "dependent_plugins": {} diff --git a/plugins/de.appplant.cordova.common.registerusernotificationsettings/LICENSE b/plugins/de.appplant.cordova.common.registerusernotificationsettings/LICENSE new file mode 100644 index 00000000..28974b74 --- /dev/null +++ b/plugins/de.appplant.cordova.common.registerusernotificationsettings/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013-2015 appPlant UG, Inc. + + Licensed 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.
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.common.registerusernotificationsettings/README.md b/plugins/de.appplant.cordova.common.registerusernotificationsettings/README.md new file mode 100644 index 00000000..bebb014d --- /dev/null +++ b/plugins/de.appplant.cordova.common.registerusernotificationsettings/README.md @@ -0,0 +1,25 @@ + +Cordova RegisterUserNotificationSettings Plugin +=============================================== + +Implements didRegisterUserNotificationSettings and broadcasts the event for listening plugins. + +```obj-c +#import "AppDelegate+APPRegisterUserNotificationSettings.h" + +- (void) pluginInitialize +{ + NSNotificationCenter* center = [NSNotificationCenter + defaultCenter]; + + [center addObserver:self + selector:@selector(didRegisterUserNotificationSettings:) + name:UIApplicationRegisterUserNotificationSettings + object:nil]; +} + +- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings +{ + ... +} +```
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.common.registerusernotificationsettings/package.json b/plugins/de.appplant.cordova.common.registerusernotificationsettings/package.json new file mode 100644 index 00000000..9de11497 --- /dev/null +++ b/plugins/de.appplant.cordova.common.registerusernotificationsettings/package.json @@ -0,0 +1,21 @@ +{ + "version": "1.0.1", + "name": "de.appplant.cordova.common.registerusernotificationsettings", + "cordova_name": "RegisterUserNotificationSettings", + "description": "Implements didRegisterUserNotificationSettings and broadcasts the event.", + "license": "Apache 2.0", + "repo": "https://github.com/katzer/cordova-common-registerusernotificationsettings", + "keywords": [ + "appplant", + "didRegisterUserNotificationSettings" + ], + "platforms": [ + "ios" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.0.0" + } + ] +}
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.common.registerusernotificationsettings/plugin.xml b/plugins/de.appplant.cordova.common.registerusernotificationsettings/plugin.xml new file mode 100644 index 00000000..28f36358 --- /dev/null +++ b/plugins/de.appplant.cordova.common.registerusernotificationsettings/plugin.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:android="http://schemas.android.com/apk/res/android" + id="de.appplant.cordova.common.registerusernotificationsettings" + version="1.0.1"> + + <name>RegisterUserNotificationSettings</name> + + <description>Implements didRegisterUserNotificationSettings and broadcasts the event.</description> + + <repo>https://github.com/katzer/cordova-common-registerusernotificationsettings</repo> + + <keywords>appplant, didRegisterUserNotificationSettings</keywords> + + <license>Apache 2.0</license> + + <author>Sebastián Katzer</author> + + <!-- cordova --> + <engines> + <engine name="cordova" version=">=3.0.0" /> + </engines> + + <!-- ios --> + <platform name="ios"> + <header-file src="src/ios/AppDelegate+APPRegisterUserNotificationSettings.h" /> + <source-file src="src/ios/AppDelegate+APPRegisterUserNotificationSettings.m" /> + </platform> + +</plugin> diff --git a/plugins/de.appplant.cordova.common.registerusernotificationsettings/src/ios/AppDelegate+APPRegisterUserNotificationSettings.h b/plugins/de.appplant.cordova.common.registerusernotificationsettings/src/ios/AppDelegate+APPRegisterUserNotificationSettings.h new file mode 100644 index 00000000..78768260 --- /dev/null +++ b/plugins/de.appplant.cordova.common.registerusernotificationsettings/src/ios/AppDelegate+APPRegisterUserNotificationSettings.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "AppDelegate.h" +#import <Availability.h> + +extern NSString* const UIApplicationRegisterUserNotificationSettings; + +@interface AppDelegate (APPRegisterUserNotificationSettings) + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 +// Tells the delegate what types of notifications may be used +- (void) application:(UIApplication*)application + didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings; +#endif + +@end
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.common.registerusernotificationsettings/src/ios/AppDelegate+APPRegisterUserNotificationSettings.m b/plugins/de.appplant.cordova.common.registerusernotificationsettings/src/ios/AppDelegate+APPRegisterUserNotificationSettings.m new file mode 100644 index 00000000..e56a084b --- /dev/null +++ b/plugins/de.appplant.cordova.common.registerusernotificationsettings/src/ios/AppDelegate+APPRegisterUserNotificationSettings.m @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "AppDelegate+APPRegisterUserNotificationSettings.h" +#import <Availability.h> + +NSString* const UIApplicationRegisterUserNotificationSettings = @"UIApplicationRegisterUserNotificationSettings"; + +@implementation AppDelegate (APPRegisterUserNotificationSettings) + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 +/** + * Tells the delegate what types of notifications may be used + * to get the user’s attention. + */ +- (void) application:(UIApplication*)application + didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings +{ + NSNotificationCenter* center = [NSNotificationCenter + defaultCenter]; + + // re-post (broadcast) + [center postNotificationName:UIApplicationRegisterUserNotificationSettings + object:settings]; +} +#endif + +@end
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.plugin.badge/CHANGELOG.md b/plugins/de.appplant.cordova.plugin.badge/CHANGELOG.md new file mode 100644 index 00000000..b2aba530 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/CHANGELOG.md @@ -0,0 +1,83 @@ +## ChangeLog +#### Version 0.7.1 (30.07.2015) +- Support for app icon badges on selective Android platforms thanks to [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger) + - Sony + - Samsung + - LG + - HTC + - Xiaomi + - ASUS + - ADW, APEX, NOVA + +#### Version 0.7.0 (18.07.2015) +- New platform support: + - Amazon FireOS + - Browser + - Windows +- `get`, `set` and `clear` support callbacks. +- Support for [Glyphs](https://msdn.microsoft.com/de-de/library/windows/apps/hh779719#phone_badge) on _Windows_ platform. +- Added tests + +#### Version 0.6.4 (02.05.2015) +- Upgrade cordova dependency from 3.0 to 3.6 +- Fix incompatibility with local-notification plugin and PGB caused by the usage of hooks. + +#### Version 0.6.3 (22.03.2015) +- New interfaces to increase or decrease the badge number. +- Fix incompatibility with local-notification plugin. +- Add instead of replace permissions on iOS. +- Refreshed layout of the example app. + +#### Version 0.6.2 (01.03.2015) +- [change:] Renamed `promptForPermission` to `registerPermission`. Older one is still supported. +- [enhancement:] Support iOS8 and older SDK versions from a single binary. +- [enhancement:] `registerPermission` returns result of registration. +- [enhancement:] No need anymore to call `registerPermission` explicit before trying to set the badge number. + +#### Version 0.6.1 (03.10.2014) +- [bugfix:] `hasPermission` and `promptForPermission` let the app crash on iOS7 and older. + +#### Version 0.6.0 (29.09.2014) +- [enhancement:] iOS 8 support +- [enhancement:] All methods are asynchron now and do not block the main thread anymore. +- [feature:] New method `hasPermission` to ask if the user has granted to display badge notifications. +- [feature:] New method `promptForPermission` to promt the user to grant permission to display badge notifications. +- [feature:] New method `configure` to configure badge properties. +- [feature:] The small icon on Android can be changed through `configure`. +- [**change**:] The namespace `plugin.notification.badge` will be removed with v0.6.1 +- [**change**:] `setTitle` is deprecated, please use `configure({ title: 'title' })`. +- [**change**:] `clearOnTap` is deprecated, please use `configure({ autoClear: true })`. +- [bugfix:] `getBadge` still returned the number when autoClear: was set and the notification was already cleared by the system (Android). +- [bugfix:] `clean` was not working on Windows Phone. + +#### Version 0.5.3 (23.05.2014) +- Added new namespace `cordova.plugins.notification.badge`<br> + **Note:** The former `plugin.notification.badge` namespace is deprecated now and will be removed in the next major release. + +- [bugfix:] `get` returned the old value even after `clear` was called on Android. + +#### Version 0.5.2 (12.04.2014) +- [enhancement:] Badge can be cleared automatically through `setClearOnTap` +- [enhancement:] Badge can be retrieved through `get` + +#### Version 0.5.1 (25.01.2014) +- [enhancement:] Specify custom notification title on Android can be set through JS interface. +- [enhancement:] Setting launchMode to *singleInstance* isn't necessary anymore. App does not restart on click anymore. + +#### Version 0.5.0 (04.01.2014) +- Added Android support + +#### Version 0.4.1 (04.12.2013) +- Release under the Apache 2.0 license. + +#### Version 0.4.0 (07.10.2013) +- Added WP8 support +- **Note:** The former `plugin.badge` namespace is not longer available. + +#### Version 0.2.1 (15.08.2013) +- Added new namespace `plugin.notification.badge`<br> + **Note:** The former `plugin.badge` namespace is deprecated now and will be removed in the next major release. + +#### Version 0.2.0 (11.08.2013) +- Added iOS support<br> + *Based on the Badge iOS plugin made by* ***Joseph Stuhr*** diff --git a/plugins/de.appplant.cordova.plugin.badge/LICENSE b/plugins/de.appplant.cordova.plugin.badge/LICENSE new file mode 100644 index 00000000..7a4a3ea2 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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.
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.plugin.badge/README.md b/plugins/de.appplant.cordova.plugin.badge/README.md new file mode 100644 index 00000000..e7f44ba9 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/README.md @@ -0,0 +1,113 @@ + +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FF6GG425KEQ3E "Donate once-off to this project using Paypal") +[](http://badge.fury.io/js/de.appplant.cordova.plugin.badge) +[](https://codeclimate.com/github/katzer/cordova-plugin-badge) + +Cordova Badge Plugin +==================== + +The essential purpose of badge numbers is to enable an application to inform its users that it has something for them — for example, unread messages — when the application isn’t running in the foreground. + +<img height="150px" align="right" hspace="19" vspace="12" src="http://4.bp.blogspot.com/-GBwBSN92DvU/UB8Kut7Oz0I/AAAAAAAAJKs/mJgBmj1RKqU/s1600/whatsapp+wp8+10.png"></img> + +### How they appear to the user +Users see notifications in the following ways: +- Badging the app’s icon + + +## Supported Platforms +The current 0.7 branch does support the following platforms: +- __Amazon FireOS__ +- __Android__ +- __Browser__ +- __iOS__ +- __Windows__ +- __WP8__ and __WP8.1 Silverlight__ + +Find out more informations [here][wiki_platforms] in our wiki. + + +## Installation +The plugin is installable from source and available on Cordova Plugin Registry and PhoneGap Build. + +Find out more informations [here][wiki_installation] in our wiki. + + +## I want to get a quick overview +All wiki pages contain samples, but for a quick overview the sample section may be the fastest way. + +Find out more informations [here][wiki_samples] in our wiki. + + +## I want to get a deep overview +The plugin allows you to set, get, clear, increase and decrease the badge number. For Android the plugin offers additional configuration flags. + +Find out more about how to set, increase or decrease the badge [here][wiki_set]. + +To get a deep overview we recommend to read about all the topics in this wiki and try out the [Kitchen Sink App][wiki_kitchensink] + + +## I want to see the plugin in action +The plugin offers a kitchen sink sample app. Check out the cordova project and run the app directly from your command line or preferred IDE. + +Find out more informations [here][wiki_kitchensink] in our wiki. + + +## What's new +We are proud to announce our newest release version 0.7.x. Beside the hard work at the office and at the weekends it contains a lot of goodies, new features and easy to use APIs. + +Find out more informations [here][wiki_changelog] in our wiki. + + +## Sample +The sample demonstrates how to set a fix badge number and how to increase the current badge number. + +```javascript +// Set 10 on device ready +document.addEventListener('deviceready', function () { + cordova.plugins.notification.badge.set(10); +}, false); +``` +```javascript +// Increase the badge each time on pause +document.addEventListener('pause', function () { + cordova.plugins.notification.badge.increase(); +}, false); +``` + +Find out more informations [here][wiki_samples] in our wiki. + + +## Supporting +Your support is needed. If you use the plugin please support us in order to ensure further development and send us a drop through the donation button. + +Thank you! + +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FF6GG425KEQ3E "Donate once-off to this project using Paypal") + + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + + +## License + +This software is released under the [Apache 2.0 License][apache2_license]. + +© 2013-2015 appPlant UG, Inc. All rights reserved + + +[cordova]: https://cordova.apache.org +[wiki]: https://github.com/katzer/cordova-plugin-badge/wiki +[wiki_platforms]: https://github.com/katzer/cordova-plugin-badge/wiki/01.-Platforms +[wiki_installation]: https://github.com/katzer/cordova-plugin-badge/wiki/02.-Installation +[wiki_kitchensink]: https://github.com/katzer/cordova-plugin-badge/tree/example +[wiki_set]: https://github.com/katzer/cordova-plugin-badge/wiki/03.-Set-Badge +[wiki_samples]: https://github.com/katzer/cordova-plugin-badge/wiki/07.-Samples +[wiki_changelog]: https://github.com/katzer/cordova-plugin-badge/wiki/08.-Changelog +[apache2_license]: http://opensource.org/licenses/Apache-2.0 diff --git a/plugins/de.appplant.cordova.plugin.badge/package.json b/plugins/de.appplant.cordova.plugin.badge/package.json new file mode 100644 index 00000000..391794a7 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/package.json @@ -0,0 +1,40 @@ +{ + "version": "0.7.1", + "name": "de.appplant.cordova.plugin.badge", + "cordova_name": "Cordova Badge Plugin", + "description": "Cordova plugin to access and modify the badge number of the app icon", + "author": "Sebastián Katzer", + "license": "Apache 2.0", + "repo": "https://github.com/katzer/cordova-plugin-badge.git", + "issue": "https://github.com/katzer/cordova-plugin-badge/issues", + "keywords": [ + "appplant", + "badge", + "cordova", + "ecosystem:cordova", + "cordova-android", + "cordova-amazon-fireos", + "cordova-ios", + "cordova-wp8", + "cordova-windows", + "cordova-browser" + ], + "platforms": [ + "ios", + "wp8", + "android", + "amazon-fireos", + "browser", + "windows" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.0.0" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/katzer/cordova-plugin-badge.git" + } +} diff --git a/plugins/de.appplant.cordova.plugin.badge/plugin.xml b/plugins/de.appplant.cordova.plugin.badge/plugin.xml new file mode 100644 index 00000000..46bae990 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/plugin.xml @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ +--> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:android="http://schemas.android.com/apk/res/android" + id="de.appplant.cordova.plugin.badge" + version="0.7.1"> + + <name>Cordova Badge Plugin</name> + + <description> + Cordova plugin to access and modify the badge number of the app icon + </description> + + <repo>https://github.com/katzer/cordova-plugin-badge.git</repo> + + <issue>https://github.com/katzer/cordova-plugin-badge/issues</issue> + + <keywords>appplant, badge</keywords> + + <license>Apache 2.0</license> + + <author>Sebastián Katzer</author> + + <!-- cordova --> + <engines> + <engine name="cordova" version=">=3.0.0" /> + <engine name="cordova-android" version=">=4"/> + <engine name="cordova-plugman" version=">=4.2.0"/><!-- needed for gradleReference support --> + </engines> + + <!-- js --> + <js-module src="www/badge.js" name="Badge"> + <clobbers target="plugin.notification.badge" /> + <clobbers target="cordova.plugins.notification.badge" /> + </js-module> + + <!-- info --> + <info> + Your support is needed. If you use the badge plugin please support us in order to ensure further development. + https://github.com/katzer/cordova-plugin-badge#supporting + + Thank you! + </info> + + <!-- ios --> + <platform name="ios"> + + <dependency id="de.appplant.cordova.common.registerusernotificationsettings" /> + + <config-file target="config.xml" parent="/*"> + <feature name="Badge"> + <param name="ios-package" value="APPBadge"/> + </feature> + </config-file> + + <header-file src="src/ios/APPBadge.h" /> + <source-file src="src/ios/APPBadge.m" /> + + <header-file src="src/ios/UIApplication+APPBadge.h" /> + <source-file src="src/ios/UIApplication+APPBadge.m" /> + + </platform> + + <!-- wp8 --> + <platform name="wp8"> + + <config-file target="config.xml" parent="/*"> + <feature name="Badge"> + <param name="wp-package" value="Badge"/> + </feature> + </config-file> + + <source-file src="src/wp8/Badge.cs" /> + + </platform> + + <!-- android --> + <platform name="android"> + + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="Badge"> + <param name="android-package" value="de.appplant.cordova.plugin.badge.Badge"/> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/manifest/application"> + <!-- + * The launch activity is triggered when a notification is clicked by a user. + * The activity launches the main activity. + --> + <activity + android:name="de.appplant.cordova.plugin.badge.LaunchActivity" + android:theme="@android:style/Theme.Black.NoTitleBar" + android:launchMode="singleInstance" + android:exported="false" /> + </config-file> + + <framework src="src/android/badge.gradle" custom="true" type="gradleReference"/> + + <source-file + src="src/android/Badge.java" + target-dir="src/de/appplant/cordova/plugin/badge" /> + + <source-file + src="src/android/BadgeImpl.java" + target-dir="src/de/appplant/cordova/plugin/badge" /> + + <source-file + src="src/android/LaunchActivity.java" + target-dir="src/de/appplant/cordova/plugin/badge" /> + + </platform> + + <!-- amazon-fireos --> + <platform name="amazon-fireos"> + + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="Badge"> + <param name="android-package" value="de.appplant.cordova.plugin.badge.Badge"/> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/manifest/application"> + <!-- + * The launch activity is triggered when a notification is clicked by a user. + * The activity launches the main activity. + --> + <activity + android:name="de.appplant.cordova.plugin.badge.LaunchActivity" + android:theme="@android:style/Theme.Black.NoTitleBar" + android:launchMode="singleInstance" + android:exported="false" /> + </config-file> + + <framework src="src/android/badge.gradle" custom="true" type="gradleReference"/> + + <source-file + src="src/android/Badge.java" + target-dir="src/de/appplant/cordova/plugin/badge" /> + + <source-file + src="src/android/BadgeImpl.java" + target-dir="src/de/appplant/cordova/plugin/badge" /> + + <source-file + src="src/android/LaunchActivity.java" + target-dir="src/de/appplant/cordova/plugin/badge" /> + + </platform> + + <!-- browser --> + <platform name="browser"> + + <config-file target="config.xml" parent="/*"> + <feature name="Badge"> + <param name="browser-package" value="Badge"/> + </feature> + </config-file> + + <js-module src="src/browser/favico.min.js" name="Badge.Favico"> + <clobbers target="cordova.plugins.notification.badge.Favico" /> + </js-module> + + <js-module src="src/browser/BadgeProxy.js" name="Badge.Proxy"> + <runs /> + </js-module> + + </platform> + + <!-- windows --> + <platform name="windows"> + + <js-module src="src/windows/BadgeProxy.js" name="Badge.Proxy" > + <runs /> + </js-module> + + </platform> + +</plugin> diff --git a/plugins/de.appplant.cordova.plugin.badge/src/android/Badge.java b/plugins/de.appplant.cordova.plugin.badge/src/android/Badge.java new file mode 100644 index 00000000..97f710e6 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/android/Badge.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.badge; + +import android.content.Context; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.json.JSONArray; +import org.json.JSONException; + + +public class Badge extends CordovaPlugin { + + /** + * Implementation of the badge interface methods. + */ + private final BadgeImpl badgeImpl = new BadgeImpl(); + + /** + * Executes the request. + * + * @param action The action to execute. + * @param args The exec() arguments. + * @param callback The callback context used when + * calling back into JavaScript. + * + * @return + * Returning false results in a "MethodNotFound" error. + * + * @throws JSONException + */ + @Override + public boolean execute (String action, JSONArray args, CallbackContext callback) + throws JSONException { + + if (action.equalsIgnoreCase("clearBadge")) { + clearBadge(callback); + return true; + } + + if (action.equalsIgnoreCase("getBadge")) { + getBadge(callback); + return true; + } + + if (action.equalsIgnoreCase("hasPermission")) { + hasPermission(callback); + return true; + } + + if (action.equalsIgnoreCase("registerPermission")) { + hasPermission(callback); + return true; + } + + if (action.equalsIgnoreCase("setBadge")) { + setBadge(args, callback); + return true; + } + + return false; + } + + /** + * Clears the badge of the app icon. + * + * @param callback + * The function to be exec as the callback + */ + private void clearBadge (final CallbackContext callback) { + cordova.getThreadPool().execute(new Runnable() { + @Override + public void run() { + badgeImpl.clearBadge(getContext()); + badgeImpl.getBadge(callback, getContext()); + } + }); + } + + /** + * Retrieves the badge of the app icon. + * + * @param callback + * The function to be exec as the callback + */ + private void getBadge (final CallbackContext callback) { + cordova.getThreadPool().execute(new Runnable() { + @Override + public void run() { + badgeImpl.getBadge(callback, getContext()); + } + }); + } + + /** + * Informs if the app has the permission to show badges. + * + * @param callback + * The function to be exec as the callback + */ + private void hasPermission (final CallbackContext callback) { + cordova.getThreadPool().execute(new Runnable() { + @Override + public void run() { + badgeImpl.hasPermission(callback); + } + }); + } + + /** + * Sets the badge of the app icon. + * + * @param args + * The new badge number + * @param callback + * The function to be exec as the callback + */ + private void setBadge (final JSONArray args, + final CallbackContext callback) { + + cordova.getThreadPool().execute(new Runnable() { + @Override + public void run() { + badgeImpl.clearBadge(getContext()); + badgeImpl.setBadge(args, getContext()); + badgeImpl.getBadge(callback, getContext()); + } + }); + } + + /** + * Returns the context of the activity. + */ + private Context getContext () { + return cordova.getActivity(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.badge/src/android/BadgeImpl.java b/plugins/de.appplant.cordova.plugin.badge/src/android/BadgeImpl.java new file mode 100644 index 00000000..9f55c920 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/android/BadgeImpl.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2014-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.badge; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; + +import me.leolin.shortcutbadger.ShortcutBadger; +import me.leolin.shortcutbadger.impl.DefaultBadger; + +/** + * Implementation of the badge interface methods. + */ +class BadgeImpl { + + /** + * The ID for the notification + */ + private final int ID = -450793490; + + /** + * The name for the shared preferences key + */ + protected static final String KEY = "badge"; + + /** + * Bundle identifier for the autoCancel value + */ + protected static final String EXTRA_AUTO_CANCEL = "EXTRA_AUTO_CANCEL"; + + /** + * Finds out if badgeing the app icon is possible on that device. + * + * @param ctx + * The application context. + * @return + * true if its supported. + */ + private boolean canBadgeAppIcon (Context ctx) { + ShortcutBadger badger = ShortcutBadger.with(ctx); + + return !(badger instanceof DefaultBadger); + } + + /** + * Clears the badge of the app icon. + * + * @param ctx + * The application context. + */ + protected void clearBadge (Context ctx) { + saveBadge(0, ctx); + getNotificationManager(ctx).cancel(ID); + ShortcutBadger.with(ctx).remove(); + } + + /** + * Retrieves the badge of the app icon. + * + * @param ctx + * The application context. + * @param callback + * The function to be exec as the callback. + */ + protected void getBadge (CallbackContext callback, Context ctx) { + SharedPreferences settings = getSharedPreferences(ctx); + int badge = settings.getInt(KEY, 0); + PluginResult result; + + result = new PluginResult(PluginResult.Status.OK, badge); + + callback.sendPluginResult(result); + } + + /** + * Sets the badge of the app icon. + * + * @param args + * The new badge number + * @param ctx + * The application context + */ + protected void setBadge (JSONArray args, Context ctx) { + int badge = args.optInt(0); + + saveBadge(badge, ctx); + + if (canBadgeAppIcon(ctx)) { + ShortcutBadger.with(ctx).count(badge); + } else { + setBadgeNotification(badge, args, ctx); + } + } + + /** + * Sets the badge of the app icon. + * + * @param args + * The new badge number + * @param ctx + * The application context + */ + @SuppressWarnings("deprecation") + private void setBadgeNotification (int badge, JSONArray args, Context ctx) { + String title = args.optString(1, "%d new messages"); + String icon = args.optString(2); + boolean autoCancel = args.optBoolean(3, false); + + Resources res = ctx.getResources(); + Bitmap appIcon = BitmapFactory.decodeResource( + res, getDrawableIcon(ctx)); + + Intent intent = new Intent(ctx, LaunchActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + + intent.putExtra(EXTRA_AUTO_CANCEL, autoCancel); + + PendingIntent contentIntent = PendingIntent.getActivity( + ctx, ID, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + String title_ = String.format(title, badge); + + Notification.Builder notification = new Notification.Builder(ctx) + .setContentTitle(title_) + .setNumber(badge) + .setTicker(title_) + .setAutoCancel(autoCancel) + .setSmallIcon(getResIdForSmallIcon(icon, ctx)) + .setLargeIcon(appIcon) + .setContentIntent(contentIntent); + + if (Build.VERSION.SDK_INT<16) { + // Build notification for HoneyComb to ICS + getNotificationManager(ctx).notify(ID, notification.getNotification()); + } else if (Build.VERSION.SDK_INT>15) { + // Notification for Jellybean and above + getNotificationManager(ctx).notify(ID, notification.build()); + } + } + + /** + * Persist the badge of the app icon so that `getBadge` is able to return + * the badge number back to the client. + * + * @param badge + * The badge of the app icon. + * @param ctx + * The application context. + */ + protected void saveBadge (int badge, Context ctx) { + SharedPreferences.Editor editor = getSharedPreferences(ctx).edit(); + + editor.putInt(KEY, badge); + editor.apply(); + } + + /** + * Informs if the app has the permission to show badges. + * + * @param callback + * The function to be exec as the callback + */ + protected void hasPermission (final CallbackContext callback) { + PluginResult result = new PluginResult(PluginResult.Status.OK, true); + + callback.sendPluginResult(result); + } + + /** + * The Local storage for the application. + */ + private SharedPreferences getSharedPreferences (Context context) { + return context.getSharedPreferences(KEY, Context.MODE_PRIVATE); + } + + /** + * The NotificationManager for the app. + */ + private NotificationManager getNotificationManager (Context context) { + return (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + } + + /** + * Returns the ID for the given resource. + * + * @return + * The resource ID of the app icon + */ + private int getDrawableIcon (Context ctx) { + Resources res = ctx.getResources(); + String pkgName = ctx.getPackageName(); + + int resId; + resId = res.getIdentifier("icon", "drawable", pkgName); + + return resId; + } + + /** + * Returns the ID for the given resource. + * + * @return + * The resource ID for the small icon. + */ + private int getResIdForSmallIcon (String smallIcon, Context ctx) { + int resId; + + String pkgName = ctx.getPackageName(); + + resId = getResId(pkgName, smallIcon); + + if (resId == 0) { + resId = getResId("android", smallIcon); + } + + if (resId == 0) { + resId = getResId("android", "ic_dialog_email"); + } + + return resId; + } + + /** + * Returns numerical icon Value. + * + * @param className + * The class name prefix either from Android or the app. + * @param iconName + * The resource name. + */ + private int getResId (String className, String iconName) { + int icon = 0; + + try { + Class<?> klass = Class.forName(className + ".R$drawable"); + + icon = (Integer) klass.getDeclaredField(iconName).get(Integer.class); + } catch (Exception ignored) {} + + return icon; + } + +} diff --git a/plugins/de.appplant.cordova.plugin.badge/src/android/LaunchActivity.java b/plugins/de.appplant.cordova.plugin.badge/src/android/LaunchActivity.java new file mode 100644 index 00000000..8e2227fb --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/android/LaunchActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.badge; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; + +public class LaunchActivity extends Activity { + + /** + * Clears the badge and moves the launch intent + * (web view) back to front. + */ + @Override + public void onCreate (Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + boolean cancel = intent.getBooleanExtra( + BadgeImpl.EXTRA_AUTO_CANCEL, false); + + if (cancel) { + clearBagde(); + } + + launchMainIntent(); + } + + /** + * Launch main intent for package. + */ + private void launchMainIntent () { + Context context = getApplicationContext(); + String pkgName = context.getPackageName(); + Intent intent = context.getPackageManager() + .getLaunchIntentForPackage(pkgName); + + intent.addFlags( + Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); + + context.startActivity(intent); + } + + /** + * Removes the badge of the app icon so that `getBadge` + * will return 0 back to the client. + */ + private void clearBagde () { + SharedPreferences.Editor editor = getSharedPreferences().edit(); + + editor.putInt(BadgeImpl.KEY, 0); + editor.apply(); + } + + /** + * The Local storage for the application. + */ + private SharedPreferences getSharedPreferences () { + Context context = getApplicationContext(); + + return context.getSharedPreferences(BadgeImpl.KEY, Context.MODE_PRIVATE); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.badge/src/android/badge.gradle b/plugins/de.appplant.cordova.plugin.badge/src/android/badge.gradle new file mode 100644 index 00000000..b2a7b07d --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/android/badge.gradle @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +repositories { + mavenCentral() +} + +dependencies { + compile 'me.leolin:ShortcutBadger:1.1.2@aar' +} diff --git a/plugins/de.appplant.cordova.plugin.badge/src/browser/BadgeProxy.js b/plugins/de.appplant.cordova.plugin.badge/src/browser/BadgeProxy.js new file mode 100644 index 00000000..44edd023 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/browser/BadgeProxy.js @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + + +/** + * Instance of the Favico.js libary. + * @type {Favico} + */ +exports.favico = new cordova.plugins.notification.badge.Favico({ + animation: 'none' +}); + +/** + * Holds the current badge number value. + * @type {Number} + */ +exports.badgeNumber = 0; + + +/** + * Clears the badge of the app icon. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.clearBadge = function (success, error) { + exports.setBadge(success, error, [0]); +}; + +/** + * Sets the badge of the app icon. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {Number} badge + * The new badge number + */ +exports.setBadge = function (success, error, args) { + var badge = args[0]; + + exports.badgeNumber = badge; + + exports.favico.badge(badge); + success(badge); +}; + +/** + * Gets the badge of the app icon. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.getBadge = function (success, error) { + success(exports.badgeNumber); +}; + +/** + * Informs if the app has the permission to show badges. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.hasPermission = function (success, error) { + success(true); +}; + +/** + * Register permission to show badges if not already granted. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.registerPermission = function (success, error) { + success(true); +}; + + +cordova.commandProxy.add('Badge', exports); diff --git a/plugins/de.appplant.cordova.plugin.badge/src/browser/favico.min.js b/plugins/de.appplant.cordova.plugin.badge/src/browser/favico.min.js new file mode 100644 index 00000000..de0de6bc --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/browser/favico.min.js @@ -0,0 +1,7 @@ +/** + * @license MIT + * @fileOverview Favico animations + * @author Miroslav Magda, http://blog.ejci.net + * @version 0.3.9 + */ +!function(){var e=function(e){"use strict";function t(e){if(e.paused||e.ended||g)return!1;try{f.clearRect(0,0,s,l),f.drawImage(e,0,0,s,l)}catch(o){}p=setTimeout(t,S.duration,e),O.setIcon(h)}function o(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,o,n){return t+t+o+o+n+n});var o=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return o?{r:parseInt(o[1],16),g:parseInt(o[2],16),b:parseInt(o[3],16)}:!1}function n(e,t){var o,n={};for(o in e)n[o]=e[o];for(o in t)n[o]=t[o];return n}function r(){return b.hidden||b.msHidden||b.webkitHidden||b.mozHidden}e=e?e:{};var i,a,l,s,h,f,c,d,u,y,w,g,x,m,p,b,v={bgColor:"#d00",textColor:"#fff",fontFamily:"sans-serif",fontStyle:"bold",type:"circle",position:"down",animation:"slide",elementId:!1,dataUrl:!1,win:window};x={},x.ff="undefined"!=typeof InstallTrigger,x.chrome=!!window.chrome,x.opera=!!window.opera||navigator.userAgent.indexOf("Opera")>=0,x.ie=/*@cc_on!@*/!1,x.safari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,x.supported=x.chrome||x.ff||x.opera;var C=[];w=function(){},d=g=!1;var E=function(){i=n(v,e),i.bgColor=o(i.bgColor),i.textColor=o(i.textColor),i.position=i.position.toLowerCase(),i.animation=S.types[""+i.animation]?i.animation:v.animation,b=i.win.document;var t=i.position.indexOf("up")>-1,r=i.position.indexOf("left")>-1;if(t||r)for(var d=0;d<S.types[""+i.animation].length;d++){var u=S.types[""+i.animation][d];t&&(u.y=u.y<.6?u.y-.4:u.y-2*u.y+(1-u.w)),r&&(u.x=u.x<.6?u.x-.4:u.x-2*u.x+(1-u.h)),S.types[""+i.animation][d]=u}i.type=A[""+i.type]?i.type:v.type,a=O.getIcon(),h=document.createElement("canvas"),c=document.createElement("img"),a.hasAttribute("href")?(c.setAttribute("crossOrigin","anonymous"),c.setAttribute("src",a.getAttribute("href")),c.onload=function(){l=c.height>0?c.height:32,s=c.width>0?c.width:32,h.height=l,h.width=s,f=h.getContext("2d"),M.ready()}):(c.setAttribute("src",""),l=32,s=32,c.height=l,c.width=s,h.height=l,h.width=s,f=h.getContext("2d"),M.ready())},M={};M.ready=function(){d=!0,M.reset(),w()},M.reset=function(){d&&(C=[],u=!1,y=!1,f.clearRect(0,0,s,l),f.drawImage(c,0,0,s,l),O.setIcon(h),window.clearTimeout(m),window.clearTimeout(p))},M.start=function(){if(d&&!y){var e=function(){u=C[0],y=!1,C.length>0&&(C.shift(),M.start())};if(C.length>0){y=!0;var t=function(){["type","animation","bgColor","textColor","fontFamily","fontStyle"].forEach(function(e){e in C[0].options&&(i[e]=C[0].options[e])}),S.run(C[0].options,function(){e()},!1)};u?S.run(u.options,function(){t()},!0):t()}}};var A={},I=function(e){return e.n="number"==typeof e.n?Math.abs(0|e.n):e.n,e.x=s*e.x,e.y=l*e.y,e.w=s*e.w,e.h=l*e.h,e.len=(""+e.n).length,e};A.circle=function(e){e=I(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),f.clearRect(0,0,s,l),f.drawImage(c,0,0,s,l),f.beginPath(),f.font=i.fontStyle+" "+Math.floor(e.h*(e.n>99?.85:1))+"px "+i.fontFamily,f.textAlign="center",t?(f.moveTo(e.x+e.w/2,e.y),f.lineTo(e.x+e.w-e.h/2,e.y),f.quadraticCurveTo(e.x+e.w,e.y,e.x+e.w,e.y+e.h/2),f.lineTo(e.x+e.w,e.y+e.h-e.h/2),f.quadraticCurveTo(e.x+e.w,e.y+e.h,e.x+e.w-e.h/2,e.y+e.h),f.lineTo(e.x+e.h/2,e.y+e.h),f.quadraticCurveTo(e.x,e.y+e.h,e.x,e.y+e.h-e.h/2),f.lineTo(e.x,e.y+e.h/2),f.quadraticCurveTo(e.x,e.y,e.x+e.h/2,e.y)):f.arc(e.x+e.w/2,e.y+e.h/2,e.h/2,0,2*Math.PI),f.fillStyle="rgba("+i.bgColor.r+","+i.bgColor.g+","+i.bgColor.b+","+e.o+")",f.fill(),f.closePath(),f.beginPath(),f.stroke(),f.fillStyle="rgba("+i.textColor.r+","+i.textColor.g+","+i.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?f.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):f.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),f.closePath()},A.rectangle=function(e){e=I(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),f.clearRect(0,0,s,l),f.drawImage(c,0,0,s,l),f.beginPath(),f.font=i.fontStyle+" "+Math.floor(e.h*(e.n>99?.9:1))+"px "+i.fontFamily,f.textAlign="center",f.fillStyle="rgba("+i.bgColor.r+","+i.bgColor.g+","+i.bgColor.b+","+e.o+")",f.fillRect(e.x,e.y,e.w,e.h),f.fillStyle="rgba("+i.textColor.r+","+i.textColor.g+","+i.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?f.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):f.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),f.closePath()};var T=function(e,t){t=("string"==typeof t?{animation:t}:t)||{},w=function(){try{if("number"==typeof e?e>0:""!==e){var n={type:"badge",options:{n:e}};if("animation"in t&&S.types[""+t.animation]&&(n.options.animation=""+t.animation),"type"in t&&A[""+t.type]&&(n.options.type=""+t.type),["bgColor","textColor"].forEach(function(e){e in t&&(n.options[e]=o(t[e]))}),["fontStyle","fontFamily"].forEach(function(e){e in t&&(n.options[e]=t[e])}),C.push(n),C.length>100)throw new Error("Too many badges requests in queue.");M.start()}else M.reset()}catch(r){throw new Error("Error setting badge. Message: "+r.message)}},d&&w()},U=function(e){w=function(){try{var t=e.width,o=e.height,n=document.createElement("img"),r=o/l>t/s?t/s:o/l;n.setAttribute("crossOrigin","anonymous"),n.setAttribute("src",e.getAttribute("src")),n.height=o/r,n.width=t/r,f.clearRect(0,0,s,l),f.drawImage(n,0,0,s,l),O.setIcon(h)}catch(i){throw new Error("Error setting image. Message: "+i.message)}},d&&w()},R=function(e){w=function(){try{if("stop"===e)return g=!0,M.reset(),void(g=!1);e.addEventListener("play",function(){t(this)},!1)}catch(o){throw new Error("Error setting video. Message: "+o.message)}},d&&w()},L=function(e){if(window.URL&&window.URL.createObjectURL||(window.URL=window.URL||{},window.URL.createObjectURL=function(e){return e}),x.supported){var o=!1;navigator.getUserMedia=navigator.getUserMedia||navigator.oGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia||navigator.webkitGetUserMedia,w=function(){try{if("stop"===e)return g=!0,M.reset(),void(g=!1);o=document.createElement("video"),o.width=s,o.height=l,navigator.getUserMedia({video:!0,audio:!1},function(e){o.src=URL.createObjectURL(e),o.play(),t(o)},function(){})}catch(n){throw new Error("Error setting webcam. Message: "+n.message)}},d&&w()}},O={};O.getIcon=function(){var e=!1,t=function(){for(var e=b.getElementsByTagName("head")[0].getElementsByTagName("link"),t=e.length,o=t-1;o>=0;o--)if(/(^|\s)icon(\s|$)/i.test(e[o].getAttribute("rel")))return e[o];return!1};return i.element?e=i.element:i.elementId?(e=b.getElementById(i.elementId),e.setAttribute("href",e.getAttribute("src"))):(e=t(),e===!1&&(e=b.createElement("link"),e.setAttribute("rel","icon"),b.getElementsByTagName("head")[0].appendChild(e))),e.setAttribute("type","image/png"),e},O.setIcon=function(e){var t=e.toDataURL("image/png");if(i.dataUrl&&i.dataUrl(t),i.element)i.element.setAttribute("href",t),i.element.setAttribute("src",t);else if(i.elementId){var o=b.getElementById(i.elementId);o.setAttribute("href",t),o.setAttribute("src",t)}else if(x.ff||x.opera){var n=a;a=b.createElement("link"),x.opera&&a.setAttribute("rel","icon"),a.setAttribute("rel","icon"),a.setAttribute("type","image/png"),b.getElementsByTagName("head")[0].appendChild(a),a.setAttribute("href",t),n.parentNode&&n.parentNode.removeChild(n)}else a.setAttribute("href",t)};var S={};return S.duration=40,S.types={},S.types.fade=[{x:.4,y:.4,w:.6,h:.6,o:0},{x:.4,y:.4,w:.6,h:.6,o:.1},{x:.4,y:.4,w:.6,h:.6,o:.2},{x:.4,y:.4,w:.6,h:.6,o:.3},{x:.4,y:.4,w:.6,h:.6,o:.4},{x:.4,y:.4,w:.6,h:.6,o:.5},{x:.4,y:.4,w:.6,h:.6,o:.6},{x:.4,y:.4,w:.6,h:.6,o:.7},{x:.4,y:.4,w:.6,h:.6,o:.8},{x:.4,y:.4,w:.6,h:.6,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],S.types.none=[{x:.4,y:.4,w:.6,h:.6,o:1}],S.types.pop=[{x:1,y:1,w:0,h:0,o:1},{x:.9,y:.9,w:.1,h:.1,o:1},{x:.8,y:.8,w:.2,h:.2,o:1},{x:.7,y:.7,w:.3,h:.3,o:1},{x:.6,y:.6,w:.4,h:.4,o:1},{x:.5,y:.5,w:.5,h:.5,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],S.types.popFade=[{x:.75,y:.75,w:0,h:0,o:0},{x:.65,y:.65,w:.1,h:.1,o:.2},{x:.6,y:.6,w:.2,h:.2,o:.4},{x:.55,y:.55,w:.3,h:.3,o:.6},{x:.5,y:.5,w:.4,h:.4,o:.8},{x:.45,y:.45,w:.5,h:.5,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],S.types.slide=[{x:.4,y:1,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.8,w:.6,h:.6,o:1},{x:.4,y:.7,w:.6,h:.6,o:1},{x:.4,y:.6,w:.6,h:.6,o:1},{x:.4,y:.5,w:.6,h:.6,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],S.run=function(e,t,o,a){var l=S.types[r()?"none":i.animation];return a=o===!0?"undefined"!=typeof a?a:l.length-1:"undefined"!=typeof a?a:0,t=t?t:function(){},a<l.length&&a>=0?(A[i.type](n(e,l[a])),m=setTimeout(function(){o?a-=1:a+=1,S.run(e,t,o,a)},S.duration),O.setIcon(h),void 0):void t()},E(),{badge:T,video:R,image:U,webcam:L,reset:M.reset,browser:{supported:x.supported}}};"undefined"!=typeof define&&define.amd?define([],function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:this.Favico=e}(); diff --git a/plugins/de.appplant.cordova.plugin.badge/src/ios/APPBadge.h b/plugins/de.appplant.cordova.plugin.badge/src/ios/APPBadge.h new file mode 100644 index 00000000..bf9a1e58 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/ios/APPBadge.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import <Foundation/Foundation.h> +#import <Cordova/CDVPlugin.h> + +@interface APPBadge : CDVPlugin + +// Clears the badge of the app icon +- (void) clearBadge:(CDVInvokedUrlCommand *)command; +// Sets the badge of the app icon +- (void) setBadge:(CDVInvokedUrlCommand *)command; +// Gets the badge of the app icon +- (void) getBadge:(CDVInvokedUrlCommand *)command; +// Informs if the app has the permission to show badges +- (void) hasPermission:(CDVInvokedUrlCommand *)command; +// Register permission to show badges +- (void) registerPermission:(CDVInvokedUrlCommand *)command; + +@end diff --git a/plugins/de.appplant.cordova.plugin.badge/src/ios/APPBadge.m b/plugins/de.appplant.cordova.plugin.badge/src/ios/APPBadge.m new file mode 100644 index 00000000..e7db643f --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/ios/APPBadge.m @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "APPBadge.h" +#import "UIApplication+APPBadge.h" +#import "AppDelegate+APPRegisterUserNotificationSettings.h" + +@interface APPBadge () + +// Needed when calling `registerPermission` +@property (nonatomic, retain) CDVInvokedUrlCommand* command; + +@end + +@implementation APPBadge + +#pragma mark - +#pragma mark Interface + +/** + * Clears the badge of the app icon. + * + */ +- (void) clearBadge:(CDVInvokedUrlCommand *)command +{ + [self.commandDelegate runInBackground:^{ + [self.app setApplicationIconBadgeNumber:0]; + + [self sendPluginResult:CDVCommandStatus_OK + messageAsLong:0 + callbackId:command.callbackId]; + }]; +} + +/** + * Sets the badge of the app icon. + * + * @param badge + * The badge to be set + */ +- (void) setBadge:(CDVInvokedUrlCommand *)command +{ + NSArray* args = [command arguments]; + int number = [[args objectAtIndex:0] intValue]; + + [self.commandDelegate runInBackground:^{ + [self.app setApplicationIconBadgeNumber:number]; + + [self sendPluginResult:CDVCommandStatus_OK + messageAsLong:number + callbackId:command.callbackId]; + }]; +} + +/** + * Gets the badge of the app icon. + * + * @param callback + * The function to be exec as the callback + */ +- (void) getBadge:(CDVInvokedUrlCommand *)command +{ + [self.commandDelegate runInBackground:^{ + long badge = [self.app applicationIconBadgeNumber]; + + [self sendPluginResult:CDVCommandStatus_OK + messageAsLong:badge + callbackId:command.callbackId]; + }]; +} + +/** + * Informs if the app has the permission to show badges. + * + * @param callback + * The function to be exec as the callback + */ +- (void) hasPermission:(CDVInvokedUrlCommand *)command +{ + [self.commandDelegate runInBackground:^{ + BOOL hasPermission = [self.app hasPermissionToDisplayBadges]; + + [self sendPluginResult:CDVCommandStatus_OK + messageAsBool:hasPermission + callbackId:command.callbackId]; + }]; +} + +/** + * Register permission to show badges. + * + * @param callback + * The function to be exec as the callback + */ +- (void) registerPermission:(CDVInvokedUrlCommand *)command +{ + if (![[UIApplication sharedApplication] + respondsToSelector:@selector(registerUserNotificationSettings:)]) + { + return [self hasPermission:command]; + } + + _command = command; + + [self.commandDelegate runInBackground:^{ + [self.app registerPermissionToDisplayBadges]; + }]; +} + +#pragma mark - +#pragma mark Delegates + +/** + * Called on otification settings registration is completed. + */ +- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings +{ + if (_command) + { + [self hasPermission:_command]; + _command = NULL; + } +} + +#pragma mark - +#pragma mark Life Cycle + +/** + * Registers obervers after plugin was initialized. + */ +- (void) pluginInitialize +{ + NSNotificationCenter* center = [NSNotificationCenter + defaultCenter]; + + [center addObserver:self + selector:@selector(didRegisterUserNotificationSettings:) + name:UIApplicationRegisterUserNotificationSettings + object:nil]; +} + +#pragma mark - +#pragma mark Helper + +/** + * Short hand for shared application instance. + */ +- (UIApplication*) app +{ + return [UIApplication sharedApplication]; +} + +/** + * Sends a plugin result with the specified status and message. + */ +- (void) sendPluginResult:(CDVCommandStatus)status + messageAsBool:(BOOL)msg + callbackId:(NSString*)callbackId +{ + CDVPluginResult* result; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsBool:msg]; + + [self.commandDelegate sendPluginResult:result + callbackId:callbackId]; +} + +/** + * Sends a plugin result with the specified status and message. + */ +- (void) sendPluginResult:(CDVCommandStatus)status + messageAsLong:(long)msg + callbackId:(NSString*)callbackId +{ + CDVPluginResult* result; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDouble:msg]; + + [self.commandDelegate sendPluginResult:result + callbackId:callbackId]; +} + +@end diff --git a/plugins/de.appplant.cordova.plugin.badge/src/ios/UIApplication+APPBadge.h b/plugins/de.appplant.cordova.plugin.badge/src/ios/UIApplication+APPBadge.h new file mode 100644 index 00000000..9ce7f7e5 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/ios/UIApplication+APPBadge.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +@interface UIApplication (APPBadge) + +// If the app has the permission to display badges +- (BOOL) hasPermissionToDisplayBadges; +// Ask for permission to display badges +- (void) registerPermissionToDisplayBadges; + +@end diff --git a/plugins/de.appplant.cordova.plugin.badge/src/ios/UIApplication+APPBadge.m b/plugins/de.appplant.cordova.plugin.badge/src/ios/UIApplication+APPBadge.m new file mode 100644 index 00000000..3e4cd99b --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/ios/UIApplication+APPBadge.m @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "UIApplication+APPBadge.h" + +@implementation UIApplication (APPBadge) + +#pragma mark - +#pragma mark Permissions + +/** + * If the app has the permission to display badges. + */ +- (BOOL) hasPermissionToDisplayBadges +{ + if (![self respondsToRegisterUserNotificationSettings]) + return YES; + + UIUserNotificationSettings *settings; + + settings = [[UIApplication sharedApplication] + currentUserNotificationSettings]; + + return (settings.types & UIUserNotificationTypeBadge); +} + +/** + * Register permission to display badges. + */ +- (void) registerPermissionToDisplayBadges +{ + if (![self respondsToRegisterUserNotificationSettings]) + return; + + UIUserNotificationType types; + UIUserNotificationSettings *settings; + + settings = [[UIApplication sharedApplication] + currentUserNotificationSettings]; + + types = settings.types | UIUserNotificationTypeBadge; + + settings = [UIUserNotificationSettings settingsForTypes:types + categories:nil]; + + [[UIApplication sharedApplication] + registerUserNotificationSettings:settings]; +} + +#pragma mark - +#pragma mark Helper methods + +/** + * If UIApplication responds to seelctor registerUserNotificationSettings: + * + * @return + * true for iOS8 and above + */ +- (BOOL) respondsToRegisterUserNotificationSettings +{ + return [[UIApplication sharedApplication] + respondsToSelector:@selector(registerUserNotificationSettings:)]; +} + +@end diff --git a/plugins/de.appplant.cordova.plugin.badge/src/windows/BadgeProxy.js b/plugins/de.appplant.cordova.plugin.badge/src/windows/BadgeProxy.js new file mode 100644 index 00000000..33029f2c --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/windows/BadgeProxy.js @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + + +/** + * Clears the badge of the app icon. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.clearBadge = function (success, error) { + exports.setBadge(success, error, [0]); +}; + +/** + * Gets the badge of the app icon. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.getBadge = function (success, error) { + var app = WinJS.Application, + file = exports._cordova_badge_number; + + app.local.exists(file).then(function (exists) { + if (exists) { + app.local.readText(file).then(function (badge) { + success(isNaN(badge) ? badge : Number(badge)); + }); + } else { + success(0); + } + }); +}; + +/** + * Informs if the app has the permission to show badges. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.hasPermission = function (success, error) { + success(true); +}; + +/** + * Register permission to show badges if not already granted. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.registerPermission = function (success, error) { + exports.hasPermission(success, error); +}; + +/** + * Sets the badge of the app icon. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {Number} badge + * The new badge number + */ +exports.setBadge = function (success, error, args) { + var notifications = Windows.UI.Notifications, + badge = args[0], + type = notifications.BadgeTemplateType.badgeNumber, + xml = notifications.BadgeUpdateManager.getTemplateContent(type), + attrs = xml.getElementsByTagName('badge'), + notification = new notifications.BadgeNotification(xml); + + attrs[0].setAttribute('value', badge); + + notifications.BadgeUpdateManager + .createBadgeUpdaterForApplication() + .update(notification); + + exports._saveBadge(badge); + + success(badge); +}; + + +/******** + * UTIL * + ********/ + +/** + * Path to file that containes the badge number. + * @type {String} + */ +exports._cordova_badge_number = 'cordova_badge_number'; + +/** + * Persist the badge of the app icon so that `getBadge` is able to return the + * badge number back to the client. + * + * @param {Number|String} badge + * The badge number to save for. + * + * @return void + */ +exports._saveBadge = function (badge) { + WinJS.Application.local.writeText(exports._cordova_badge_number, badge); +}; + + +cordova.commandProxy.add('Badge', exports); diff --git a/plugins/de.appplant.cordova.plugin.badge/src/wp8/Badge.cs b/plugins/de.appplant.cordova.plugin.badge/src/wp8/Badge.cs new file mode 100644 index 00000000..eb384c38 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/src/wp8/Badge.cs @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +using System; +using System.Linq; + +using Microsoft.Phone.Shell; + +using WPCordovaClassLib.Cordova; +using WPCordovaClassLib.Cordova.Commands; +using WPCordovaClassLib.Cordova.JSON; +using System.IO.IsolatedStorage; + +namespace Cordova.Extension.Commands +{ + public class Badge : BaseCommand + { + + /// <summary> + /// Name for the shared preferences + /// <summary> + private const string KEY = "badge"; + + /// <summary> + /// Clears the count property of the live tile + /// </summary> + public void clearBadge (string args) + { + setBadge(args); + getBadge(args); + } + + /// <summary> + /// Sets the count property of the live tile + /// </summary> + public void setBadge (string args) + { + // Application Tile is always the first Tile, even if it is not pinned to Start. + ShellTile tile = ShellTile.ActiveTiles.First(); + + // Application should always be found + if (tile != null) + { + string[] ary = JsonHelper.Deserialize<string[]>(args); + int count = 0; + string title = ""; + + try { + count = int.Parse(ary[0]); + } + catch (FormatException) { }; + + if (ary.Length > 1) + { + title = ary[1].Replace("%d", "{0}"); + title = String.Format(title, count); + } + + StandardTileData TileData = new StandardTileData + { + Count = count, + BackTitle = title + }; + + SaveBadge(count); + + tile.Update(TileData); + } + + getBadge(args); + } + + /// <summary> + /// Gets the count property of the live tile + /// </summary> + public void getBadge (string args) + { + // Application Tile is always the first Tile, even if it is not pinned to Start. + ShellTile tile = ShellTile.ActiveTiles.First(); + + // Application should always be found + if (tile != null) + { + IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings; + int badge = 0; + PluginResult result; + + if (settings.Contains(KEY)) { + badge = (int)settings[KEY]; + } + + result = new PluginResult(PluginResult.Status.OK, badge); + + DispatchCommandResult(result); + } + } + + /// <summery> + /// Informs if the app has the permission to show badges. + /// </summery> + public void hasPermission (string args) + { + PluginResult result; + + result = new PluginResult(PluginResult.Status.OK, true); + + DispatchCommandResult(result); + } + + /// <summery> + /// Ask for permission to show badges. + /// </summery> + public void registerPermission (string args) + { + hasPermission(args); + } + + /// <summary> + /// Persist the badge of the app icon so that `getBadge` is able to return + /// the badge number back to the client. + /// </summary> + private void SaveBadge (int badge) + { + IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings; + + if (settings.Contains(KEY)) { + settings[KEY] = badge; + } + else { + settings.Add(KEY, badge); + } + } + + } +} diff --git a/plugins/de.appplant.cordova.plugin.badge/tests/plugin.xml b/plugins/de.appplant.cordova.plugin.badge/tests/plugin.xml new file mode 100644 index 00000000..f8d815bf --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/tests/plugin.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ +--> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:android="http://schemas.android.com/apk/res/android" + id="de.appplant.cordova.plugin.badge-tests" + version="0.7.0dev"> + + <name>Cordova Badge Plugin Tests</name> + <license>Apache 2.0</license> + + <js-module src="tests.js" name="tests" /> +</plugin> diff --git a/plugins/de.appplant.cordova.plugin.badge/tests/tests.js b/plugins/de.appplant.cordova.plugin.badge/tests/tests.js new file mode 100644 index 00000000..473cd02a --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/tests/tests.js @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + + +exports.defineAutoTests = function() { + + describe('Badge Plugin (cordova.plugins.notification.badge)', function () { + + describe('Plugin availability', function () { + + it("should exist", function() { + expect(cordova.plugins.notification.badge).toBeDefined(); + }); + + it("should define clear", function() { + expect(cordova.plugins.notification.badge.clear).toBeDefined(); + }); + + it("should define get", function() { + expect(cordova.plugins.notification.badge.get).toBeDefined(); + }); + + it("should define set", function() { + expect(cordova.plugins.notification.badge.set).toBeDefined(); + }); + + it("should define increase", function() { + expect(cordova.plugins.notification.badge.increase).toBeDefined(); + }); + + it("should define decrease", function() { + expect(cordova.plugins.notification.badge.decrease).toBeDefined(); + }); + + it("should define hasPermission", function() { + expect(cordova.plugins.notification.badge.hasPermission).toBeDefined(); + }); + + it("should define registerPermission", function() { + expect(cordova.plugins.notification.badge.registerPermission).toBeDefined(); + }); + + it("should define configure", function() { + expect(cordova.plugins.notification.badge.configure).toBeDefined(); + }); + + }); + + describe('API callbacks', function () { + + it("clear should invoke callback", function(done) { + cordova.plugins.notification.badge.clear(done); + }); + + it("get should invoke callback", function(done) { + cordova.plugins.notification.badge.get(done); + }); + + it("set should invoke callback", function(done) { + cordova.plugins.notification.badge.set(done); + }); + + it("increase should invoke callback", function(done) { + cordova.plugins.notification.badge.increase(done); + }); + + it("decrease should invoke callback", function(done) { + cordova.plugins.notification.badge.decrease(done); + }); + + it("hasPermission should invoke callback", function(done) { + cordova.plugins.notification.badge.hasPermission(done); + }); + + it("registerPermission should invoke callback", function(done) { + cordova.plugins.notification.badge.registerPermission(done); + }); + + }); + + describe('API functions', function () { + + it("clear should set badge to 0", function(done) { + cordova.plugins.notification.badge.clear(function (badge) { + expect(badge).toBe(0); + done(); + }); + }); + + it("should return badge", function(done) { + cordova.plugins.notification.badge.set(10, function (badge) { + expect(badge).toBe(10); + + cordova.plugins.notification.badge.get(function (badge2) { + expect(badge).toBe(badge2); + done(); + }); + }); + }); + + it("should increase badge", function(done) { + cordova.plugins.notification.badge.set(10, function () { + cordova.plugins.notification.badge.increase(1, function (badge) { + expect(badge).toBe(11); + done(); + }); + }); + }); + + it("should decrease badge", function(done) { + cordova.plugins.notification.badge.set(10, function () { + cordova.plugins.notification.badge.decrease(1, function (badge) { + expect(badge).toBe(9); + done(); + }); + }); + }); + + it("hasPermission should return boolean", function(done) { + cordova.plugins.notification.badge.hasPermission(function (has) { + expect(has === true || has === false).toBe(true); + done(); + }); + }); + + it("registerPermission should return boolean", function(done) { + cordova.plugins.notification.badge.registerPermission(function (has) { + expect(has === true || has === false).toBe(true); + done(); + }); + }); + + }); + + }); +}; diff --git a/plugins/de.appplant.cordova.plugin.badge/www/badge.js b/plugins/de.appplant.cordova.plugin.badge/www/badge.js new file mode 100644 index 00000000..ab6177cc --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.badge/www/badge.js @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +var exec = require('cordova/exec'), + channel = require('cordova/channel'); + + +/************* + * INTERFACE * + *************/ + +/** + * Clears the badge of the app icon. + * + * @param {Function} callback + * The function to be exec as the callback + * @param {Object?} scope + * The callback function's scope + */ +exports.clear = function (callback, scope) { + this.exec('clearBadge', null, callback, scope); +}; + +/** + * Sets the badge of the app icon. + * + * @param {Number} badge + * The new badge number + * @param {Function} callback + * The function to be exec as the callback + * @param {Object?} scope + * The callback function's scope + */ +exports.set = function (badge, callback, scope) { + var args = [ + parseInt(badge) || 0, + this._config.title, + this._config.smallIcon, + this._config.autoClear + ]; + + this.registerPermission(function (granted) { + if (granted) { + this.exec('setBadge', args, callback, scope); + } + }, this); +}; + +/** + * Gets the badge of the app icon. + * + * @param {Function} callback + * The function to be exec as the callback + * @param {Object?} scope + * The callback function's scope + */ +exports.get = function (callback, scope) { + this.exec('getBadge', null, callback, scope); +}; + +/** + * Increases the badge number. + * + * @param {Number} count + * Count to add to the current badge number + * @param {Function} callback + * The function to be exec as the callback + * @param {Object?} scope + * The callback function's scope + */ +exports.increase = function (count, callback, scope) { + this.get(function (badge) { + this.set(badge + (count || 1), callback, scope); + }, this); +}; + +/** + * Decreases the badge number. + * + * @param {Number} count + * Count to subtract from the current badge number + * @param {Function} callback + * The function to be exec as the callback + * @param {Object?} scope + * The callback function's scope + */ +exports.decrease = function (count, callback, scope) { + this.get(function (badge) { + this.set(Math.max(0, badge - (count || 1)), callback, scope); + }, this); +}; + +/** + * Informs if the app has the permission to show badges. + * + * @param {Function} callback + * The function to be exec as the callback + * @param {Object?} scope + * The callback function's scope + */ +exports.hasPermission = function (callback, scope) { + this.exec('hasPermission', null, callback, scope); +}; + +/** + * Register permission to show badges 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) { + this.exec('registerPermission', null, callback, scope); +}; + +/** + * Configures the plugin's platform options. + * + * @param {Hash?} object + * The new configuration settings + * + * @return {Hash} + * The current configuration settings + */ +exports.configure = function (config) { + for (var key in config) { + if (this._config.hasOwnProperty(key)) { + this._config[key] = config[key]; + } + } + + return this._config; +}; + + +/**************** + * DEPRECATIONS * + ****************/ + +/** + * Sets the custom notification title for Android. + * + * @param {String} title + * The title of the notification + */ +exports.setTitle = function (title) { + console.warn('badge.setTitle(title) is deprecated! Please use badge.configure({ title:title }) instead.'); + + this._config.title = title; +}; + +/** + * Tells the plugin if the badge needs to be cleared when the user taps + * the icon. + * + * @param {Boolean} clearOnTap + * Either true or false + */ +exports.setClearOnTap = function (clearOnTap) { + console.warn('badge.clearOnTap(bool) is deprecated! Please use badge.configure({ autoClear:bool }) instead.'); + + this._config.autoClear = clearOnTap; +}; + +/** + * Register permission to show notifications + * if not already granted. + */ +exports.promptForPermission = function () { + console.warn('Depreated: Please use `notification.badge.registerPermission` instead.'); + + this.registerPermission.apply(this, arguments); +}; + + +/*********** + * MEMBERS * + ***********/ + +exports._config = { + // Titel der Meldung für Android + title: '%d new messages', + // Ob die Badge Zahl automatisch beim Öffnen der App gelöscht werden soll + autoClear: false, + // Ob und welches Icon für Android verwendet werden soll + smallIcon: 'ic_dialog_email' +}; + + +/******** + * UTIL * + ********/ + +/** + * Create 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); + }; +}; + +/** + * 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, 'Badge', action, params); +}; + + +/********* + * HOOKS * + *********/ + +channel.onCordovaReady.subscribe(function () { + if (exports._config.autoClear) { exports.clear(); } +}); + +channel.onResume.subscribe(function () { + if (exports._config.autoClear) { exports.clear(); } +}); diff --git a/plugins/de.appplant.cordova.plugin.local-notification/CHANGELOG.md b/plugins/de.appplant.cordova.plugin.local-notification/CHANGELOG.md new file mode 100644 index 00000000..611e02f8 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/CHANGELOG.md @@ -0,0 +1,61 @@ +ChangeLog +--------- + +Please also read the [Upgrade Guide](https://github.com/katzer/cordova-plugin-local-notifications/wiki/Upgrade-Guide) for more information. + +#### Version 0.8.1 (08.03.2015) + +- Fix incompatibility with cordova version 3.5-3.0 +- Fire `clear` instead of `cancel` event when clicked on repeating notifications +- Do not fire `clear` or `cancel` event when clicked on persistent notifications + +### Version 0.8.0 (05.03.2015) + +- Support for iOS 8, Android 2 (SDK >= 7) and Android 5 + - Windows Phone 8.1 will be added soon +- New interfaces to ask for / register permissions required to schedule local notifications + - `hasPermission()` and `registerPermission()` + - _schedule()_ will register the permission automatically and schedule the notification if granted. +- New interface to update already scheduled|triggered local notifications + - `update()` +- New interfaces to clear the notification center + - `clear()` and `clearAll()` +- New interfaces to query for local notifications, their properties, their IDs and their existence depend on their state + - `isPresent()`, `isScheduled()`, `isTriggered()` + - `getIds()`, `getAllIds()`, `getScheduledIds()`, `getTriggeredIds()` + - `get()`, `getAll()`, `getScheduled()`, `getTriggered()` +- Schedule multiple local notifications at once + - `schedule( [{...},{...}] )` +- Update multiple local notifications at once + - `update( [{...},{...}] )` +- Clear multiple local notifications at once + - `clear( [1, 2] )` +- Cancel multiple local notifications at once + - `cancel( [1, 2] )` +- New URI format to specify sound and image resources + - `http(s):` for remote resources _(Android)_ + - `file:` for local resources relative to the _www_ folder + - `res:` for native resources +- New events + - `schedule`, `update`, `clear`, `clearall` and `cancelall` +- Enhanced event informations + - Listener will get called with the local notification object instead of only the ID +- Multiple listener for one event + - `on(event, callback, scope)` +- Unregister event listener + - `un(event, callback)` +- New Android specific properties + - `led` properties + - `sound` and `image` accepts remote resources +- Callback function and scope for all interface methods + - `schedule( notification, callback, scope )` +- Renamed `add()` to `schedule()` +- `autoCancel` property has been removed + - Use `ongoing: true` for persistent local notifications on Android +- Renamed repeat intervals + - `second`, `minute`, `hour`, `day`, `week`, `month` and `year` +- Renamed some local notification properties + - `date`, `json`, `message` and `repeat` + - Scheduling local notifications with the deprecated properties is still possible +- [Kitchen Sink sample app](https://github.com/katzer/cordova-plugin-local-notifications/tree/example) +- [Wiki](https://github.com/katzer/cordova-plugin-local-notifications/wiki) diff --git a/plugins/de.appplant.cordova.plugin.local-notification/LICENSE b/plugins/de.appplant.cordova.plugin.local-notification/LICENSE new file mode 100644 index 00000000..28974b74 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013-2015 appPlant UG, Inc. + + Licensed 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.
\ No newline at end of file diff --git a/plugins/de.appplant.cordova.plugin.local-notification/README.md b/plugins/de.appplant.cordova.plugin.local-notification/README.md new file mode 100644 index 00000000..878632ba --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/README.md @@ -0,0 +1,136 @@ + +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L3HKQCD9UA35A "Donate once-off to this project using Paypal") + +#### :bangbang: Please vote for these cordova-windows issues :bangbang: +1. https://issues.apache.org/jira/browse/CB-8674 _(Missing launch arguments)_ +2. https://issues.apache.org/jira/browse/CB-8946 _(Missing ToastCapable flag)_ + +Thanks a lot! + +Cordova Local-Notification Plugin +================================= + +The essential purpose of local notifications is to enable an application to inform its users that it has something for them — for example, a message or an upcoming appointment — when the application isn’t running in the foreground.<br> +They are scheduled by an application and delivered on the same device. + +<img width="35%" align="right" hspace="19" vspace="12" src="https://github.com/katzer/cordova-plugin-local-notifications/blob/example/images/android.png"></img> + +### How they appear to the user +Users see notifications in the following ways: +- Displaying an alert or banner +- Badging the app’s icon +- Playing a sound + + +### Examples of Notification Usage +Local notifications are ideally suited for applications with time-based behaviors, such as calendar and to-do list applications. Applications that run in the background for the limited period allowed by iOS might also find local notifications useful.<br> +For example, applications that depend on servers for messages or data can poll their servers for incoming items while running in the background; if a message is ready to view or an update is ready to download, they can then present a local notification immediately to inform their users. + + +## Supported Platforms +The current 0.8 branch supports the following platforms: +- __iOS__ _(including iOS8)_<br> +- __Android__ _(SDK >=7)_ +- __Windows 8.1__ _(added with v0.8.2)_ +- __Windows Phone 8.1__ _(added with v0.8.2)_ + +Find out more informations [here][wiki_platforms] in our wiki. + + +## Installation +The plugin is installable from source and available on Cordova Plugin Registry and PhoneGap Build. + +Find out more informations [here][wiki_installation] in our wiki. + + +## I want to get a quick overview +All wiki pages contain samples, but for a quick overview the sample section may be the fastest way. + +Find out more informations [here][wiki_samples] in our wiki. + + +## I want to get a deep overview +The plugin supports scheduling local notifications in various ways with a single interface. It also allows you to update, clear or cancel them. There are different interfaces to query for local notifications and a complete set of events to hook into the life cycle of local notifications. + +Find out more about how to schedule single, multiple, delayed or repeating local notifications [here][wiki_schedule].<br> +Informations about events like _click_ or _trigger_ can be found [here][wiki_events]. + +To get a deep overview we recommend to read about all the topics in our [wiki][wiki] and try out the [Kitchen Sink App][wiki_kitchensink] + + +## I want to see the plugin in action +The plugin offers a kitchen sink sample app. Check out the cordova project and run the app directly from your command line or preferred IDE. + +Find out more informations [here][wiki_kitchensink] in our wiki. + + +## What's new +We are proud to announce our newest release version 0.8.x. Beside the hard work at the office and at the weekends it contains a lot of goodies, new features and easy to use APIs. + +Find out more informations [here][wiki_changelog] in our wiki. + + +## Sample +The sample demonstrates how to schedule a local notification which repeats every week. The listener will be called when the user has clicked on the local notification. + +```javascript +cordova.plugins.notification.local.schedule({ + id: 1, + title: "Production Jour fixe", + text: "Duration 1h", + firstAt: monday_9_am, + every: "week", + sound: "file://sounds/reminder.mp3", + icon: "http://icons.com/?cal_id=1", + data: { meetingId:"123#fg8" } +}); + +cordova.plugins.notification.local.on("click", function (notification) { + joinMeeting(notification.data.meetingId); +}); +``` + +Find out more informations [here][wiki_samples] in our wiki. + + +## I would like to propose new features +We appricate any feature proposal and support for their development. Please describe them [here][feature_proposal_issue]. + +Find out more informations [here][wiki_next] in our wiki. + +## Supporting +Your support is needed. If you use the plugin please send us a drop through the donation button. + +Thank you! + +[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L3HKQCD9UA35A "Donate once-off to this project using Paypal") + + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + + +## License + +This software is released under the [Apache 2.0 License][apache2_license]. + +© 2013-2015 appPlant UG, Inc. All rights reserved + + +[cordova]: https://cordova.apache.org +[wiki]: https://github.com/katzer/cordova-plugin-local-notifications/wiki +[wiki_platforms]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/02.-Platforms +[wiki_installation]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/03.-Installation +[wiki_kitchensink]: https://github.com/katzer/cordova-plugin-local-notifications/tree/example +[wiki_schedule]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/04.-Scheduling +[wiki_events]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/09.-Events +[wiki_samples]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/11.-Samples +[wiki_changelog]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/Upgrade-Guide +[wiki_next]: https://github.com/katzer/cordova-plugin-local-notifications/wiki/Feature-Requests +[feature_proposal_issue]: https://github.com/katzer/cordova-plugin-local-notifications/issues/451 +[apache2_license]: http://opensource.org/licenses/Apache-2.0 diff --git a/plugins/de.appplant.cordova.plugin.local-notification/plugin.xml b/plugins/de.appplant.cordova.plugin.local-notification/plugin.xml new file mode 100644 index 00000000..7efd3434 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/plugin.xml @@ -0,0 +1,228 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:android="http://schemas.android.com/apk/res/android" + id="de.appplant.cordova.plugin.local-notification" + version="0.8.2-dev"> + + <name>LocalNotification</name> + + <description>The plugin supports scheduling local notifications in various ways with a single interface. It also allows you to update, clear or cancel them. There are different interfaces to query for local notifications and a complete set of events to hook into the life cycle of local notifications. To get a deep overview we recommend to read about all the topics in our wiki and try out the Kitchen Sink App</description> + + <repo>https://github.com/katzer/cordova-plugin-local-notifications.git</repo> + + <keywords>appplant, notification, local notification</keywords> + + <license>Apache 2.0</license> + + <author>Sebastián Katzer</author> + + <!-- cordova --> + <engines> + <engine name="cordova" version=">=3.6.0" /> + <engine name="cordova" version="<4.0.0" /> + </engines> + + <!-- dependencies --> + <dependency id="cordova-plugin-device" /> + + <!-- info --> + <info> + Your support is needed. If you use the local-notification plugin please support us in order to ensure further development. + https://github.com/katzer/cordova-plugin-local-notifications#supporting + + Thank you! + </info> + + <!-- js --> + <js-module src="www/local-notification.js" name="LocalNotification"> + <clobbers target="cordova.plugins.notification.local" /> + <clobbers target="plugin.notification.local" /> + </js-module> + + <js-module src="www/local-notification-core.js" name="LocalNotification.Core"> + <clobbers target="cordova.plugins.notification.local.core" /> + <clobbers target="plugin.notification.local.core" /> + </js-module> + + <js-module src="www/local-notification-util.js" name="LocalNotification.Util"> + <merges target="cordova.plugins.notification.local.core" /> + <merges target="plugin.notification.local.core" /> + </js-module> + + <!-- ios --> + <platform name="ios"> + + <dependency id="de.appplant.cordova.common.registerusernotificationsettings" /> + + <config-file target="config.xml" parent="/*"> + <feature name="LocalNotification"> + <param name="ios-package" value="APPLocalNotification" onload="true" /> + <param name="onload" value="true" /> + </feature> + </config-file> + + <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" /> + + <header-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 --> + <platform name="android"> + + <framework src="com.android.support:support-v4:+" /> + + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="LocalNotification"> + <param name="android-package" value="de.appplant.cordova.plugin.localnotification.LocalNotification"/> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/manifest/application"> + + <receiver + android:name="de.appplant.cordova.plugin.localnotification.TriggerReceiver" + android:exported="false" /> + + <receiver + android:name="de.appplant.cordova.plugin.localnotification.ClearReceiver" + android:exported="false" /> + + <activity + android:name="de.appplant.cordova.plugin.localnotification.ClickActivity" + android:launchMode="singleInstance" + android:theme="@android:style/Theme.NoDisplay" + android:exported="false" /> + + <receiver + android:name="de.appplant.cordova.plugin.notification.TriggerReceiver" + android:exported="false" /> + + <receiver + android:name="de.appplant.cordova.plugin.notification.ClearReceiver" + android:exported="false" /> + + <receiver android:name="de.appplant.cordova.plugin.localnotification.RestoreReceiver" android:exported="false" > + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + </receiver> + + <activity + android:name="de.appplant.cordova.plugin.notification.ClickActivity" + android:launchMode="singleInstance" + android:theme="@android:style/Theme.NoDisplay" + android:exported="false" /> + + </config-file> + + <config-file target="AndroidManifest.xml" parent="/manifest"> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + </config-file> + + <source-file + src="src/android/LocalNotification.java" + target-dir="src/de/appplant/cordova/plugin/localnotification" /> + + <source-file + src="src/android/TriggerReceiver.java" + target-dir="src/de/appplant/cordova/plugin/localnotification" /> + + <source-file + src="src/android/ClickActivity.java" + target-dir="src/de/appplant/cordova/plugin/localnotification" /> + + <source-file + src="src/android/ClearReceiver.java" + target-dir="src/de/appplant/cordova/plugin/localnotification" /> + + <source-file + src="src/android/RestoreReceiver.java" + target-dir="src/de/appplant/cordova/plugin/localnotification" /> + + <source-file + src="src/android/notification/AbstractClearReceiver.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/AbstractClickActivity.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/AbstractRestoreReceiver.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/AbstractTriggerReceiver.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/AssetUtil.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/Builder.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/ClearReceiver.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/ClickActivity.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/Manager.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/Notification.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/Options.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + <source-file + src="src/android/notification/TriggerReceiver.java" + target-dir="src/de/appplant/cordova/plugin/notification" /> + + </platform> + + <!-- windows --> + <platform name="windows"> + + <js-module src="src/windows/LocalNotificationProxy.js" name="LocalNotification.Proxy" > + <merges target="" /> + </js-module> + + <js-module src="src/windows/LocalNotificationCore.js" name="LocalNotification.Proxy.Core" > + <merges target="" /> + </js-module> + + <js-module src="src/windows/LocalNotificationUtil.js" name="LocalNotification.Proxy.Util" > + <merges target="" /> + </js-module> + + <!-- Platform Hooks --> + <hook type="after_platform_add" src="scripts/windows/setToastCapable.js" /> + <hook type="after_plugin_install" src="scripts/windows/setToastCapable.js" /> + + <hook type="after_platform_add" src="scripts/windows/broadcastActivateEvent.js" /> + <hook type="after_plugin_install" src="scripts/windows/broadcastActivateEvent.js" /> + <hook type="after_prepare" src="scripts/windows/broadcastActivateEvent.js" /> + + </platform> + +</plugin> diff --git a/plugins/de.appplant.cordova.plugin.local-notification/scripts/windows/broadcastActivateEvent.js b/plugins/de.appplant.cordova.plugin.local-notification/scripts/windows/broadcastActivateEvent.js new file mode 100755 index 00000000..40a90ca0 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/scripts/windows/broadcastActivateEvent.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + + +// Includes a snippet into the cordova-core js file +// to fire the activated event after device is ready + + +var fs = require('fs'), + rootdir = process.argv[2]; + +if (!rootdir) + return; + +/** + * Replaces a string with another one in a file. + * + * @param {String} path + * Absolute or relative file path from cordova root project. + * @param {String} to_replace + * The string to replace. + * @param {String} + * The string to replace with. + */ +function replace (filename, to_replace, replace_with) { + var data = fs.readFileSync(filename, 'utf8'), + result; + + if (data.indexOf(replace_with) > -1) + return; + + result = data.replace(to_replace, replace_with); + fs.writeFileSync(filename, result, 'utf8'); +} + +// Fires the activated event again after device is ready +var snippet = + "var activatedHandler = function (args) {" + + "channel.deviceready.subscribe(function () {" + + "app.queueEvent(args);" + + "});" + + "};" + + "app.addEventListener('activated', activatedHandler, false);" + + "document.addEventListener('deviceready', function () {" + + "app.removeEventListener('activated', activatedHandler);" + + "}, false);\n" + + " app.start();"; + +// Path to cordova-core js files where the snippet needs to be included +var files = [ + 'platforms/windows/www/cordova.js', + 'platforms/windows/platform_www/cordova.js' +]; + +// Includes the snippet before app.start() is called +for (var i = 0; i < files.length; i++) { + replace(files[i], 'app.start();', snippet); +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/scripts/windows/setToastCapable.js b/plugins/de.appplant.cordova.plugin.local-notification/scripts/windows/setToastCapable.js new file mode 100755 index 00000000..46b56102 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/scripts/windows/setToastCapable.js @@ -0,0 +1,63 @@ +#!/usr/bin/env node + +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + + +// Hook sets ToastCapable on true to enable local-notifications + + +var fs = require('fs'), + rootdir = process.argv[2]; + +if (!rootdir) + return; + +/** + * Replaces a string with another one in a file. + * + * @param {String} path + * Absolute or relative file path from cordova root project. + * @param {String} to_replace + * The string to replace. + * @param {String} + * The string to replace with. + */ +function replace (filename, to_replace, replace_with) { + var data = fs.readFileSync(filename, 'utf8'), + result; + + if (data.indexOf('ToastCapable') > -1) + return; + + result = data.replace(new RegExp(to_replace, 'g'), replace_with); + + fs.writeFileSync(filename, result, 'utf8'); +} + +// Set ToastCapable for Windows Phone +replace('platforms/windows/package.phone.appxmanifest', '<m3:VisualElements', '<m3:VisualElements ToastCapable="true"'); +// Set ToastCapable for Windows 8.1 +replace('platforms/windows/package.windows.appxmanifest', '<m2:VisualElements', '<m2:VisualElements ToastCapable="true"'); +// Set ToastCapable for Windows 8.0 +replace('platforms/windows/package.windows80.appxmanifest', '<VisualElements', '<VisualElements ToastCapable="true"'); diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/ClearReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/ClearReceiver.java new file mode 100644 index 00000000..e0892e37 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/ClearReceiver.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.localnotification; + +import de.appplant.cordova.plugin.notification.Notification; + + +/** + * The clear intent receiver is triggered when the user clears a + * notification manually. It un-persists the cleared notification from the + * shared preferences. + */ +public class ClearReceiver extends de.appplant.cordova.plugin.notification.ClearReceiver { + + /** + * Called when a local notification was cleared from outside of the app. + * + * @param notification + * Wrapper around the local notification + */ + @Override + public void onClear (Notification notification) { + super.onClear(notification); + LocalNotification.fireEvent("clear", notification); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/ClickActivity.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/ClickActivity.java new file mode 100644 index 00000000..d366d355 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/ClickActivity.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.localnotification; + +import de.appplant.cordova.plugin.notification.Builder; +import de.appplant.cordova.plugin.notification.Notification; +import de.appplant.cordova.plugin.notification.TriggerReceiver; + +/** + * The receiver activity is triggered when a notification is clicked by a user. + * The activity calls the background callback and brings the launch intent + * up to foreground. + */ +public class ClickActivity extends de.appplant.cordova.plugin.notification.ClickActivity { + + /** + * Called when local notification was clicked by the user. + * + * @param notification + * Wrapper around the local notification + */ + @Override + public void onClick(Notification notification) { + LocalNotification.fireEvent("click", notification); + + if (!notification.getOptions().isOngoing()) { + String event = notification.isRepeating() ? "clear" : "cancel"; + + LocalNotification.fireEvent(event, notification); + } + + super.onClick(notification); + } + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + @Override + public Notification buildNotification (Builder builder) { + return builder + .setTriggerReceiver(TriggerReceiver.class) + .build(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/LocalNotification.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/LocalNotification.java new file mode 100644 index 00000000..906c8d69 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/LocalNotification.java @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.localnotification; + +import android.app.Activity; +import android.os.Build; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import de.appplant.cordova.plugin.notification.Manager; +import de.appplant.cordova.plugin.notification.Notification; + +/** + * This plugin utilizes the Android AlarmManager in combination with local + * notifications. When a local notification is scheduled the alarm manager takes + * care of firing the event. When the event is processed, a notification is put + * in the Android notification center and status bar. + */ +public class LocalNotification extends CordovaPlugin { + + // Reference to the web view for static access + private static CordovaWebView webView = null; + + // Indicates if the device is ready (to receive events) + private static Boolean deviceready = false; + + // To inform the user about the state of the app in callbacks + protected static Boolean isInBackground = true; + + // Queues all events before deviceready + private static ArrayList<String> eventQueue = new ArrayList<String>(); + + /** + * Called after plugin construction and fields have been initialized. + * Prefer to use pluginInitialize instead since there is no value in + * having parameters on the initialize() function. + * + * pluginInitialize is not available for cordova 3.0-3.5 ! + */ + @Override + public void initialize (CordovaInterface cordova, CordovaWebView webView) { + LocalNotification.webView = super.webView; + } + + /** + * Called when the system is about to start resuming a previous activity. + * + * @param multitasking + * Flag indicating if multitasking is turned on for app + */ + @Override + public void onPause(boolean multitasking) { + super.onPause(multitasking); + isInBackground = true; + } + + /** + * Called when the activity will start interacting with the user. + * + * @param multitasking + * Flag indicating if multitasking is turned on for app + */ + @Override + public void onResume(boolean multitasking) { + super.onResume(multitasking); + isInBackground = false; + deviceready(); + } + + /** + * The final call you receive before your activity is destroyed. + */ + @Override + public void onDestroy() { + deviceready = false; + isInBackground = true; + } + + /** + * Executes the request. + * + * This method is called from the WebView thread. To do a non-trivial + * amount of work, use: + * cordova.getThreadPool().execute(runnable); + * + * To run on the UI thread, use: + * cordova.getActivity().runOnUiThread(runnable); + * + * @param action + * The action to execute. + * @param args + * The exec() arguments in JSON form. + * @param command + * The callback context used when calling back into JavaScript. + * @return + * Whether the action was valid. + */ + @Override + public boolean execute (final String action, final JSONArray args, + final CallbackContext command) throws JSONException { + + Notification.setDefaultTriggerReceiver(TriggerReceiver.class); + + cordova.getThreadPool().execute(new Runnable() { + public void run() { + if (action.equals("schedule")) { + schedule(args); + command.success(); + } + else if (action.equals("update")) { + update(args); + command.success(); + } + else if (action.equals("cancel")) { + cancel(args); + command.success(); + } + else if (action.equals("cancelAll")) { + cancelAll(); + command.success(); + } + else if (action.equals("clear")) { + clear(args); + command.success(); + } + else if (action.equals("clearAll")) { + clearAll(); + command.success(); + } + else if (action.equals("isPresent")) { + isPresent(args.optInt(0), command); + } + else if (action.equals("isScheduled")) { + isScheduled(args.optInt(0), command); + } + else if (action.equals("isTriggered")) { + isTriggered(args.optInt(0), command); + } + else if (action.equals("getAllIds")) { + getAllIds(command); + } + else if (action.equals("getScheduledIds")) { + getScheduledIds(command); + } + else if (action.equals("getTriggeredIds")) { + getTriggeredIds(command); + } + else if (action.equals("getSingle")) { + getSingle(args, command); + } + else if (action.equals("getSingleScheduled")) { + getSingleScheduled(args, command); + } + else if (action.equals("getSingleTriggered")) { + getSingleTriggered(args, command); + } + else if (action.equals("getAll")) { + getAll(args, command); + } + else if (action.equals("getScheduled")) { + getScheduled(args, command); + } + else if (action.equals("getTriggered")) { + getTriggered(args, command); + } + else if (action.equals("deviceready")) { + deviceready(); + } + } + }); + + return true; + } + + /** + * Schedule multiple local notifications. + * + * @param notifications + * Properties for each local notification + */ + private void schedule (JSONArray notifications) { + for (int i = 0; i < notifications.length(); i++) { + JSONObject options = notifications.optJSONObject(i); + + Notification notification = + getNotificationMgr().schedule(options, TriggerReceiver.class); + + fireEvent("schedule", notification); + } + } + + /** + * Update multiple local notifications. + * + * @param updates + * Notification properties including their IDs + */ + private void update (JSONArray updates) { + for (int i = 0; i < updates.length(); i++) { + JSONObject update = updates.optJSONObject(i); + int id = update.optInt("id", 0); + + Notification notification = + getNotificationMgr().update(id, update, TriggerReceiver.class); + + fireEvent("update", notification); + } + } + + /** + * Cancel multiple local notifications. + * + * @param ids + * Set of local notification IDs + */ + private void cancel (JSONArray ids) { + for (int i = 0; i < ids.length(); i++) { + int id = ids.optInt(i, 0); + + Notification notification = + getNotificationMgr().cancel(id); + + if (notification != null) { + fireEvent("cancel", notification); + } + } + } + + /** + * Cancel all scheduled notifications. + */ + private void cancelAll() { + getNotificationMgr().cancelAll(); + fireEvent("cancelall"); + } + + /** + * Clear multiple local notifications without canceling them. + * + * @param ids + * Set of local notification IDs + */ + private void clear(JSONArray ids){ + for (int i = 0; i < ids.length(); i++) { + int id = ids.optInt(i, 0); + + Notification notification = + getNotificationMgr().clear(id); + + if (notification != null) { + fireEvent("clear", notification); + } + } + } + + /** + * Clear all triggered notifications without canceling them. + */ + private void clearAll() { + getNotificationMgr().clearAll(); + fireEvent("clearall"); + } + + /** + * If a notification with an ID is present. + * + * @param id + * Notification ID + * @param command + * The callback context used when calling back into JavaScript. + */ + private void isPresent (int id, CallbackContext command) { + boolean exist = getNotificationMgr().exist(id); + + PluginResult result = new PluginResult( + PluginResult.Status.OK, exist); + + command.sendPluginResult(result); + } + + /** + * If a notification with an ID is scheduled. + * + * @param id + * Notification ID + * @param command + * The callback context used when calling back into JavaScript. + */ + private void isScheduled (int id, CallbackContext command) { + boolean exist = getNotificationMgr().exist( + id, Notification.Type.SCHEDULED); + + PluginResult result = new PluginResult( + PluginResult.Status.OK, exist); + + command.sendPluginResult(result); + } + + /** + * If a notification with an ID is triggered. + * + * @param id + * Notification ID + * @param command + * The callback context used when calling back into JavaScript. + */ + private void isTriggered (int id, CallbackContext command) { + boolean exist = getNotificationMgr().exist( + id, Notification.Type.TRIGGERED); + + PluginResult result = new PluginResult( + PluginResult.Status.OK, exist); + + command.sendPluginResult(result); + } + + /** + * Set of IDs from all existent notifications. + * + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getAllIds (CallbackContext command) { + List<Integer> ids = getNotificationMgr().getIds(); + + command.success(new JSONArray(ids)); + } + + /** + * Set of IDs from all scheduled notifications. + * + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getScheduledIds (CallbackContext command) { + List<Integer> ids = getNotificationMgr().getIdsByType( + Notification.Type.SCHEDULED); + + command.success(new JSONArray(ids)); + } + + /** + * Set of IDs from all triggered notifications. + * + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getTriggeredIds (CallbackContext command) { + List<Integer> ids = getNotificationMgr().getIdsByType( + Notification.Type.TRIGGERED); + + command.success(new JSONArray(ids)); + } + + /** + * Options from local notification. + * + * @param ids + * Set of local notification IDs + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getSingle (JSONArray ids, CallbackContext command) { + getOptions(ids.optString(0), Notification.Type.ALL, command); + } + + /** + * Options from scheduled notification. + * + * @param ids + * Set of local notification IDs + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getSingleScheduled (JSONArray ids, CallbackContext command) { + getOptions(ids.optString(0), Notification.Type.SCHEDULED, command); + } + + /** + * Options from triggered notification. + * + * @param ids + * Set of local notification IDs + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getSingleTriggered (JSONArray ids, CallbackContext command) { + getOptions(ids.optString(0), Notification.Type.TRIGGERED, command); + } + + /** + * Set of options from local notification. + * + * @param ids + * Set of local notification IDs + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getAll (JSONArray ids, CallbackContext command) { + getOptions(ids, Notification.Type.ALL, command); + } + + /** + * Set of options from scheduled notifications. + * + * @param ids + * Set of local notification IDs + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getScheduled (JSONArray ids, CallbackContext command) { + getOptions(ids, Notification.Type.SCHEDULED, command); + } + + /** + * Set of options from triggered notifications. + * + * @param ids + * Set of local notification IDs + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getTriggered (JSONArray ids, CallbackContext command) { + getOptions(ids, Notification.Type.TRIGGERED, command); + } + + /** + * Options from local notification. + * + * @param id + * Set of local notification IDs + * @param type + * The local notification life cycle type + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getOptions (String id, Notification.Type type, + CallbackContext command) { + + JSONArray ids = new JSONArray().put(id); + + JSONObject options = + getNotificationMgr().getOptionsBy(type, toList(ids)).get(0); + + command.success(options); + } + + /** + * Set of options from local notifications. + * + * @param ids + * Set of local notification IDs + * @param type + * The local notification life cycle type + * @param command + * The callback context used when calling back into JavaScript. + */ + private void getOptions (JSONArray ids, Notification.Type type, + CallbackContext command) { + + List<JSONObject> options; + + if (ids.length() == 0) { + options = getNotificationMgr().getOptionsByType(type); + } else { + options = getNotificationMgr().getOptionsBy(type, toList(ids)); + } + + command.success(new JSONArray(options)); + } + + /** + * Call all pending callbacks after the deviceready event has been fired. + */ + private static synchronized void deviceready () { + isInBackground = false; + deviceready = true; + + for (String js : eventQueue) { + sendJavascript(js); + } + + eventQueue.clear(); + } + + /** + * Fire given event on JS side. Does inform all event listeners. + * + * @param event + * The event name + */ + private void fireEvent (String event) { + fireEvent(event, null); + } + + /** + * Fire given event on JS side. Does inform all event listeners. + * + * @param event + * The event name + * @param notification + * Optional local notification to pass the id and properties. + */ + static void fireEvent (String event, Notification notification) { + String state = getApplicationState(); + String params = "\"" + state + "\""; + + if (notification != null) { + params = notification.toString() + "," + params; + } + + String js = "cordova.plugins.notification.local.core.fireEvent(" + + "\"" + event + "\"," + params + ")"; + + sendJavascript(js); + } + + /** + * Use this instead of deprecated sendJavascript + * + * @param js + * JS code snippet as string + */ + private static synchronized void sendJavascript(final String js) { + + if (!deviceready) { + eventQueue.add(js); + return; + } + Runnable jsLoader = new Runnable() { + public void run() { + webView.loadUrl("javascript:" + js); + } + }; + try { + Method post = webView.getClass().getMethod("post",Runnable.class); + post.invoke(webView,jsLoader); + } catch(Exception e) { + + ((Activity)(webView.getContext())).runOnUiThread(jsLoader); + } + } + + /** + * Convert JSON array of integers to List. + * + * @param ary + * Array of integers + */ + private List<Integer> toList (JSONArray ary) { + ArrayList<Integer> list = new ArrayList<Integer>(); + + for (int i = 0; i < ary.length(); i++) { + list.add(ary.optInt(i)); + } + + return list; + } + + /** + * Current application state. + * + * @return + * "background" or "foreground" + */ + static String getApplicationState () { + return isInBackground ? "background" : "foreground"; + } + + /** + * Notification manager instance. + */ + private Manager getNotificationMgr() { + return Manager.getInstance(cordova.getActivity()); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/RestoreReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/RestoreReceiver.java new file mode 100644 index 00000000..7de4e328 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/RestoreReceiver.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.localnotification; + +import de.appplant.cordova.plugin.notification.AbstractRestoreReceiver; +import de.appplant.cordova.plugin.notification.Builder; +import de.appplant.cordova.plugin.notification.Notification; + +/** + * This class is triggered upon reboot of the device. It needs to re-register + * the alarms with the AlarmManager since these alarms are lost in case of + * reboot. + */ +public class RestoreReceiver extends AbstractRestoreReceiver { + + /** + * Called when a local notification need to be restored. + * + * @param notification + * Wrapper around the local notification + */ + @Override + public void onRestore (Notification notification) { + if (notification.isScheduled()) { + notification.schedule(); + } + } + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + @Override + public Notification buildNotification (Builder builder) { + return builder + .setTriggerReceiver(TriggerReceiver.class) + .setClearReceiver(ClearReceiver.class) + .setClickActivity(ClickActivity.class) + .build(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/TriggerReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/TriggerReceiver.java new file mode 100644 index 00000000..3c423c01 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/TriggerReceiver.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.localnotification; + +import de.appplant.cordova.plugin.notification.Builder; +import de.appplant.cordova.plugin.notification.Notification; + +/** + * The alarm receiver is triggered when a scheduled alarm is fired. This class + * reads the information in the intent and displays this information in the + * Android notification bar. The notification uses the default notification + * sound and it vibrates the phone. + */ +public class TriggerReceiver extends de.appplant.cordova.plugin.notification.TriggerReceiver { + + /** + * Called when a local notification was triggered. Does present the local + * notification, re-schedule the alarm if necessary and fire trigger event. + * + * @param notification + * Wrapper around the local notification + * @param updated + * If an update has triggered or the original + */ + @Override + public void onTrigger (Notification notification, boolean updated) { + super.onTrigger(notification, updated); + + if (!updated) { + LocalNotification.fireEvent("trigger", notification); + } + } + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + @Override + public Notification buildNotification (Builder builder) { + return builder + .setTriggerReceiver(TriggerReceiver.class) + .setClickActivity(ClickActivity.class) + .setClearReceiver(ClearReceiver.class) + .build(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClearReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClearReceiver.java new file mode 100644 index 00000000..94d2a19b --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClearReceiver.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Abstract delete receiver for local notifications. Creates the local + * notification and calls the event functions for further proceeding. + */ +abstract public class AbstractClearReceiver extends BroadcastReceiver { + + /** + * Called when the notification was cleared from the notification center. + * + * @param context + * Application context + * @param intent + * Received intent with content data + */ + @Override + public void onReceive(Context context, Intent intent) { + Bundle bundle = intent.getExtras(); + JSONObject options; + + try { + String data = bundle.getString(Options.EXTRA); + options = new JSONObject(data); + } catch (JSONException e) { + e.printStackTrace(); + return; + } + + Notification notification = + new Builder(context, options).build(); + + onClear(notification); + } + + /** + * Called when a local notification was cleared from outside of the app. + * + * @param notification + * Wrapper around the local notification + */ + abstract public void onClear (Notification notification); + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClickActivity.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClickActivity.java new file mode 100644 index 00000000..a02a9981 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClickActivity.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Abstract content receiver activity for local notifications. Creates the + * local notification and calls the event functions for further proceeding. + */ +abstract public class AbstractClickActivity extends Activity { + + /** + * Called when local notification was clicked to launch the main intent. + * + * @param state + * Saved instance state + */ + @Override + public void onCreate (Bundle state) { + super.onCreate(state); + + Intent intent = getIntent(); + Bundle bundle = intent.getExtras(); + Context context = getApplicationContext(); + + try { + String data = bundle.getString(Options.EXTRA); + JSONObject options = new JSONObject(data); + + Builder builder = + new Builder(context, options); + + Notification notification = + buildNotification(builder); + + onClick(notification); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + /** + * Called when local notification was clicked by the user. + * + * @param notification + * Wrapper around the local notification + */ + abstract public void onClick (Notification notification); + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + abstract public Notification buildNotification (Builder builder); + + /** + * Launch main intent from package. + */ + public void launchApp() { + Context context = getApplicationContext(); + String pkgName = context.getPackageName(); + + Intent intent = context + .getPackageManager() + .getLaunchIntentForPackage(pkgName); + + intent.addFlags( + Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); + + context.startActivity(intent); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractRestoreReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractRestoreReceiver.java new file mode 100644 index 00000000..8a1f3656 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractRestoreReceiver.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.json.JSONObject; + +import java.util.List; + +/** + * This class is triggered upon reboot of the device. It needs to re-register + * the alarms with the AlarmManager since these alarms are lost in case of + * reboot. + */ +abstract public class AbstractRestoreReceiver extends BroadcastReceiver { + + /** + * Called on device reboot. + * + * @param context + * Application context + * @param intent + * Received intent with content data + */ + @Override + public void onReceive (Context context, Intent intent) { + Manager notificationMgr = + Manager.getInstance(context); + + List<JSONObject> options = + notificationMgr.getOptions(); + + for (JSONObject data : options) { + Builder builder = new Builder(context, data); + + Notification notification = + buildNotification(builder); + + onRestore(notification); + } + } + + /** + * Called when a local notification need to be restored. + * + * @param notification + * Wrapper around the local notification + */ + abstract public void onRestore (Notification notification); + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + abstract public Notification buildNotification (Builder builder); + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractTriggerReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractTriggerReceiver.java new file mode 100644 index 00000000..fc6759c5 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractTriggerReceiver.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Calendar; + +/** + * Abstract broadcast receiver for local notifications. Creates the + * notification options and calls the event functions for further proceeding. + */ +abstract public class AbstractTriggerReceiver extends BroadcastReceiver { + + /** + * Called when an alarm was triggered. + * + * @param context + * Application context + * @param intent + * Received intent with content data + */ + @Override + public void onReceive(Context context, Intent intent) { + Bundle bundle = intent.getExtras(); + Options options; + + try { + String data = bundle.getString(Options.EXTRA); + JSONObject dict = new JSONObject(data); + + options = new Options(context).parse(dict); + } catch (JSONException e) { + e.printStackTrace(); + return; + } + + if (options == null) + return; + + if (isFirstAlarmInFuture(options)) + return; + + Builder builder = new Builder(options); + Notification notification = buildNotification(builder); + boolean updated = notification.isUpdate(); + + onTrigger(notification, updated); + } + + /** + * Called when a local notification was triggered. + * + * @param notification + * Wrapper around the local notification + * @param updated + * If an update has triggered or the original + */ + abstract public void onTrigger (Notification notification, boolean updated); + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + abstract public Notification buildNotification (Builder builder); + + /* + * If you set a repeating alarm at 11:00 in the morning and it + * should trigger every morning at 08:00 o'clock, it will + * immediately fire. E.g. Android tries to make up for the + * 'forgotten' reminder for that day. Therefore we ignore the event + * if Android tries to 'catch up'. + */ + private Boolean isFirstAlarmInFuture (Options options) { + Notification notification = new Builder(options).build(); + + if (!notification.isRepeating()) + return false; + + Calendar now = Calendar.getInstance(); + Calendar alarm = Calendar.getInstance(); + + alarm.setTime(notification.getOptions().getTriggerDate()); + + int alarmHour = alarm.get(Calendar.HOUR_OF_DAY); + int alarmMin = alarm.get(Calendar.MINUTE); + int currentHour = now.get(Calendar.HOUR_OF_DAY); + int currentMin = now.get(Calendar.MINUTE); + + return (currentHour != alarmHour && currentMin != alarmMin); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java new file mode 100644 index 00000000..2da8a2c3 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.StrictMode; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Util class to map unified asset URIs to native URIs. URIs like file:/// + * map to absolute paths while file:// point relatively to the www folder + * within the asset resources. And res:// means a resource from the native + * res folder. Remote assets are accessible via http:// for example. + */ +class AssetUtil { + + // Name of the storage folder + private static final String STORAGE_FOLDER = "/localnotification"; + + // Placeholder URI for default sound + private static final String DEFAULT_SOUND = "res://platform_default"; + + // Ref to the context passed through the constructor to access the + // resources and app directory. + private final Context context; + + /** + * Constructor + * + * @param context + * Application context + */ + private AssetUtil(Context context) { + this.context = context; + } + + /** + * Static method to retrieve class instance. + * + * @param context + * Application context + */ + static AssetUtil getInstance(Context context) { + return new AssetUtil(context); + } + + /** + * Parse path path to native URI. + * + * @param path + * Path to path file + */ + Uri parseSound (String path) { + + if (path == null || path.isEmpty()) + return Uri.EMPTY; + + if (path.equalsIgnoreCase(DEFAULT_SOUND)) { + return RingtoneManager.getDefaultUri(RingtoneManager + .TYPE_NOTIFICATION); + } + + return parse(path); + } + + /** + * The URI for a path. + * + * @param path + * The given path + */ + Uri parse (String path) { + + if (path.startsWith("res:")) { + return getUriForResourcePath(path); + } else if (path.startsWith("file:///")) { + return getUriFromPath(path); + } else if (path.startsWith("file://")) { + return getUriFromAsset(path); + } else if (path.startsWith("http")){ + return getUriFromRemote(path); + } + + return Uri.EMPTY; + } + + /** + * URI for a file. + * + * @param path + * Absolute path like file:///... + * + * @return + * URI pointing to the given path + */ + private Uri getUriFromPath(String path) { + String absPath = path.replaceFirst("file://", ""); + File file = new File(absPath); + + if (!file.exists()) { + Log.e("Asset", "File not found: " + file.getAbsolutePath()); + return Uri.EMPTY; + } + + return Uri.fromFile(file); + } + + /** + * URI for an asset. + * + * @param path + * Asset path like file://... + * + * @return + * URI pointing to the given path + */ + private Uri getUriFromAsset(String path) { + File dir = context.getExternalCacheDir(); + + if (dir == null) { + Log.e("Asset", "Missing external cache dir"); + return Uri.EMPTY; + } + + String resPath = path.replaceFirst("file:/", "www"); + String fileName = resPath.substring(resPath.lastIndexOf('/') + 1); + String storage = dir.toString() + STORAGE_FOLDER; + File file = new File(storage, fileName); + + //noinspection ResultOfMethodCallIgnored + new File(storage).mkdir(); + + try { + AssetManager assets = context.getAssets(); + FileOutputStream outStream = new FileOutputStream(file); + InputStream inputStream = assets.open(resPath); + + copyFile(inputStream, outStream); + + outStream.flush(); + outStream.close(); + + return Uri.fromFile(file); + + } catch (Exception e) { + Log.e("Asset", "File not found: assets/" + resPath); + e.printStackTrace(); + } + + return Uri.EMPTY; + } + + /** + * The URI for a resource. + * + * @param path + * The given relative path + * + * @return + * URI pointing to the given path + */ + private Uri getUriForResourcePath(String path) { + File dir = context.getExternalCacheDir(); + + if (dir == null) { + Log.e("Asset", "Missing external cache dir"); + return Uri.EMPTY; + } + + String resPath = path.replaceFirst("res://", ""); + + int resId = getResIdForDrawable(resPath); + + if (resId == 0) { + Log.e("Asset", "File not found: " + resPath); + return Uri.EMPTY; + } + + String resName = extractResourceName(resPath); + String extName = extractResourceExtension(resPath); + String storage = dir.toString() + STORAGE_FOLDER; + File file = new File(storage, resName + extName); + + //noinspection ResultOfMethodCallIgnored + new File(storage).mkdir(); + + try { + Resources res = context.getResources(); + FileOutputStream outStream = new FileOutputStream(file); + InputStream inputStream = res.openRawResource(resId); + copyFile(inputStream, outStream); + + outStream.flush(); + outStream.close(); + + return Uri.fromFile(file); + + } catch (Exception e) { + e.printStackTrace(); + } + + return Uri.EMPTY; + } + + /** + * Uri from remote located content. + * + * @param path + * Remote address + * + * @return + * Uri of the downloaded file + */ + private Uri getUriFromRemote(String path) { + File dir = context.getExternalCacheDir(); + + if (dir == null) { + Log.e("Asset", "Missing external cache dir"); + return Uri.EMPTY; + } + + String resName = extractResourceName(path); + String extName = extractResourceExtension(path); + String storage = dir.toString() + STORAGE_FOLDER; + File file = new File(storage, resName + extName); + + //noinspection ResultOfMethodCallIgnored + new File(storage).mkdir(); + + try { + URL url = new URL(path); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + StrictMode.ThreadPolicy policy = + new StrictMode.ThreadPolicy.Builder().permitAll().build(); + + StrictMode.setThreadPolicy(policy); + + connection.setRequestProperty("Connection", "close"); + connection.setConnectTimeout(5000); + connection.connect(); + + InputStream input = connection.getInputStream(); + FileOutputStream outStream = new FileOutputStream(file); + + copyFile(input, outStream); + + outStream.flush(); + outStream.close(); + + return Uri.fromFile(file); + + } catch (MalformedURLException e) { + Log.e("Asset", "Incorrect URL"); + e.printStackTrace(); + } catch (FileNotFoundException e) { + Log.e("Asset", "Failed to create new File from HTTP Content"); + e.printStackTrace(); + } catch (IOException e) { + Log.e("Asset", "No Input can be created from http Stream"); + e.printStackTrace(); + } + + return Uri.EMPTY; + } + + /** + * Copy content from input stream into output stream. + * + * @param in + * The input stream + * @param out + * The output stream + */ + private void copyFile(InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + int read; + + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } + + /** + * Resource ID for drawable. + * + * @param resPath + * Resource path as string + */ + int getResIdForDrawable(String resPath) { + int resId = getResIdForDrawable(getPkgName(), resPath); + + if (resId == 0) { + resId = getResIdForDrawable("android", resPath); + } + + return resId; + } + + /** + * Resource ID for drawable. + * + * @param clsName + * Relative package or global android name space + * @param resPath + * Resource path as string + */ + int getResIdForDrawable(String clsName, String resPath) { + String drawable = extractResourceName(resPath); + int resId = 0; + + try { + Class<?> cls = Class.forName(clsName + ".R$drawable"); + + resId = (Integer) cls.getDeclaredField(drawable).get(Integer.class); + } catch (Exception ignore) {} + + return resId; + } + + /** + * Convert drawable resource to bitmap. + * + * @param drawable + * Drawable resource name + */ + Bitmap getIconFromDrawable (String drawable) { + Resources res = context.getResources(); + int iconId; + + iconId = getResIdForDrawable(getPkgName(), drawable); + + if (iconId == 0) { + iconId = getResIdForDrawable("android", drawable); + } + + if (iconId == 0) { + iconId = android.R.drawable.ic_menu_info_details; + } + + return BitmapFactory.decodeResource(res, iconId); + } + + /** + * Convert URI to Bitmap. + * + * @param uri + * Internal image URI + */ + Bitmap getIconFromUri (Uri uri) throws IOException { + InputStream input = context.getContentResolver().openInputStream(uri); + + return BitmapFactory.decodeStream(input); + } + + /** + * Extract name of drawable resource from path. + * + * @param resPath + * Resource path as string + */ + private String extractResourceName (String resPath) { + String drawable = resPath; + + if (drawable.contains("/")) { + drawable = drawable.substring(drawable.lastIndexOf('/') + 1); + } + + if (resPath.contains(".")) { + drawable = drawable.substring(0, drawable.lastIndexOf('.')); + } + + return drawable; + } + + /** + * Extract extension of drawable resource from path. + * + * @param resPath + * Resource path as string + */ + private String extractResourceExtension (String resPath) { + String extName = "png"; + + if (resPath.contains(".")) { + extName = resPath.substring(resPath.lastIndexOf('.')); + } + + return extName; + } + + /** + * Package name specified by context. + */ + private String getPkgName () { + return context.getPackageName(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Builder.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Builder.java new file mode 100644 index 00000000..a0be8b93 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Builder.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.v4.app.NotificationCompat; + +import org.json.JSONObject; + +import java.util.Random; + +/** + * Builder class for local notifications. Build fully configured local + * notification specified by JSON object passed from JS side. + */ +public class Builder { + + // Application context passed by constructor + private final Context context; + + // Notification options passed by JS + private final Options options; + + // Receiver to handle the trigger event + private Class<?> triggerReceiver; + + // Receiver to handle the clear event + private Class<?> clearReceiver = ClearReceiver.class; + + // Activity to handle the click event + private Class<?> clickActivity = ClickActivity.class; + + /** + * Constructor + * + * @param context + * Application context + * @param options + * Notification options + */ + public Builder(Context context, JSONObject options) { + this.context = context; + this.options = new Options(context).parse(options); + } + + /** + * Constructor + * + * @param options + * Notification options + */ + public Builder(Options options) { + this.context = options.getContext(); + this.options = options; + } + + /** + * Set trigger receiver. + * + * @param receiver + * Broadcast receiver + */ + public Builder setTriggerReceiver(Class<?> receiver) { + this.triggerReceiver = receiver; + return this; + } + + /** + * Set clear receiver. + * + * @param receiver + * Broadcast receiver + */ + public Builder setClearReceiver(Class<?> receiver) { + this.clearReceiver = receiver; + return this; + } + + /** + * Set click activity. + * + * @param activity + * Activity + */ + public Builder setClickActivity(Class<?> activity) { + this.clickActivity = activity; + return this; + } + + /** + * Creates the notification with all its options passed through JS. + */ + public Notification build() { + Uri sound = options.getSoundUri(); + NotificationCompat.BigTextStyle style; + NotificationCompat.Builder builder; + + style = new NotificationCompat.BigTextStyle() + .bigText(options.getText()); + + builder = new NotificationCompat.Builder(context) + .setDefaults(0) + .setContentTitle(options.getTitle()) + .setContentText(options.getText()) + .setNumber(options.getBadgeNumber()) + .setTicker(options.getText()) + .setSmallIcon(options.getSmallIcon()) + .setLargeIcon(options.getIconBitmap()) + .setAutoCancel(options.isAutoClear()) + .setOngoing(options.isOngoing()) + .setStyle(style) + .setLights(options.getLedColor(), 500, 500); + + if (sound != null) { + builder.setSound(sound); + } + + applyDeleteReceiver(builder); + applyContentReceiver(builder); + + return new Notification(context, options, builder, triggerReceiver); + } + + /** + * Set intent to handle the delete event. Will clean up some persisted + * preferences. + * + * @param builder + * Local notification builder instance + */ + private void applyDeleteReceiver(NotificationCompat.Builder builder) { + + if (clearReceiver == null) + return; + + Intent deleteIntent = new Intent(context, clearReceiver) + .setAction(options.getIdStr()) + .putExtra(Options.EXTRA, options.toString()); + + PendingIntent dpi = PendingIntent.getBroadcast( + context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT); + + builder.setDeleteIntent(dpi); + } + + /** + * Set intent to handle the click event. Will bring the app to + * foreground. + * + * @param builder + * Local notification builder instance + */ + private void applyContentReceiver(NotificationCompat.Builder builder) { + + if (clickActivity == null) + return; + + Intent intent = new Intent(context, clickActivity) + .putExtra(Options.EXTRA, options.toString()) + .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + + int requestCode = new Random().nextInt(); + + PendingIntent contentIntent = PendingIntent.getActivity( + context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + builder.setContentIntent(contentIntent); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClearReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClearReceiver.java new file mode 100644 index 00000000..761b6c5c --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClearReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +/** + * The clear intent receiver is triggered when the user clears a + * notification manually. It un-persists the cleared notification from the + * shared preferences. + */ +public class ClearReceiver extends AbstractClearReceiver { + + /** + * Called when a local notification was cleared from outside of the app. + * + * @param notification + * Wrapper around the local notification + */ + @Override + public void onClear (Notification notification) { + notification.clear(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClickActivity.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClickActivity.java new file mode 100644 index 00000000..01af5c45 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClickActivity.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +/** + * The receiver activity is triggered when a notification is clicked by a user. + * The activity calls the background callback and brings the launch intent + * up to foreground. + */ +public class ClickActivity extends AbstractClickActivity { + + /** + * Called when local notification was clicked by the user. Will + * move the app to foreground. + * + * @param notification + * Wrapper around the local notification + */ + @Override + public void onClick(Notification notification) { + launchApp(); + } + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + public Notification buildNotification (Builder builder) { + return builder.build(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java new file mode 100644 index 00000000..03ea384f --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.SharedPreferences; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY; + +/** + * Central way to access all or single local notifications set by specific + * state like triggered or scheduled. Offers shortcut ways to schedule, + * cancel or clear local notifications. + */ +public class Manager { + + // Context passed through constructor and used for notification builder. + private Context context; + + /** + * Constructor + * + * @param context + * Application context + */ + private Manager(Context context){ + this.context = context; + } + + /** + * Static method to retrieve class instance. + * + * @param context + * Application context + */ + public static Manager getInstance(Context context) { + return new Manager(context); + } + + /** + * Schedule local notification specified by JSON object. + * + * @param options + * JSON object with set of options + * @param receiver + * Receiver to handle the trigger event + */ + public Notification schedule (JSONObject options, Class<?> receiver) { + return schedule(new Options(context).parse(options), receiver); + } + + /** + * Schedule local notification specified by options object. + * + * @param options + * Set of notification options + * @param receiver + * Receiver to handle the trigger event + */ + public Notification schedule (Options options, Class<?> receiver) { + Notification notification = new Builder(options) + .setTriggerReceiver(receiver) + .build(); + + notification.schedule(); + + return notification; + } + + /** + * Clear local notification specified by ID. + * + * @param id + * The notification ID + * @param updates + * JSON object with notification options + * @param receiver + * Receiver to handle the trigger event + */ + public Notification update (int id, JSONObject updates, Class<?> receiver) { + Notification notification = get(id); + + if (notification == null) + return null; + + notification.cancel(); + + JSONObject options = mergeJSONObjects( + notification.getOptions().getDict(), updates); + + try { + options.putOpt("updatedAt", new Date().getTime()); + } catch (JSONException ignore) {} + + return schedule(options, receiver); + } + + /** + * Clear local notification specified by ID. + * + * @param id + * The notification ID + */ + public Notification clear (int id) { + Notification notification = get(id); + + if (notification != null) { + notification.clear(); + } + + return notification; + } + + /** + * Clear local notification specified by ID. + * + * @param id + * The notification ID + */ + public Notification cancel (int id) { + Notification notification = get(id); + + if (notification != null) { + notification.cancel(); + } + + return notification; + } + + /** + * Clear all local notifications. + */ + public void clearAll () { + List<Notification> notifications = getAll(); + + for (Notification notification : notifications) { + notification.clear(); + } + + getNotMgr().cancelAll(); + } + + /** + * Cancel all local notifications. + */ + public void cancelAll () { + List<Notification> notifications = getAll(); + + for (Notification notification : notifications) { + notification.cancel(); + } + + getNotMgr().cancelAll(); + } + + /** + * All local notifications IDs. + */ + public List<Integer> getIds() { + Set<String> keys = getPrefs().getAll().keySet(); + ArrayList<Integer> ids = new ArrayList<Integer>(); + + for (String key : keys) { + ids.add(Integer.parseInt(key)); + } + + return ids; + } + + /** + * All local notification IDs for given type. + * + * @param type + * The notification life cycle type + */ + public List<Integer> getIdsByType(Notification.Type type) { + List<Notification> notifications = getAll(); + ArrayList<Integer> ids = new ArrayList<Integer>(); + + for (Notification notification : notifications) { + if (notification.getType() == type) { + ids.add(notification.getId()); + } + } + + return ids; + } + + /** + * List of local notifications with matching ID. + * + * @param ids + * Set of notification IDs + */ + public List<Notification> getByIds(List<Integer> ids) { + ArrayList<Notification> notifications = new ArrayList<Notification>(); + + for (int id : ids) { + Notification notification = get(id); + + if (notification != null) { + notifications.add(notification); + } + } + + return notifications; + } + + /** + * List of all local notification. + */ + public List<Notification> getAll() { + return getByIds(getIds()); + } + + /** + * List of local notifications from given type. + * + * @param type + * The notification life cycle type + */ + public List<Notification> getByType(Notification.Type type) { + List<Notification> notifications = getAll(); + ArrayList<Notification> list = new ArrayList<Notification>(); + + if (type == Notification.Type.ALL) + return notifications; + + for (Notification notification : notifications) { + if (notification.getType() == type) { + list.add(notification); + } + } + + return list; + } + + /** + * List of local notifications with matching ID from given type. + * + * @param type + * The notification life cycle type + * @param ids + * Set of notification IDs + */ + @SuppressWarnings("UnusedDeclaration") + public List<Notification> getBy(Notification.Type type, List<Integer> ids) { + ArrayList<Notification> notifications = new ArrayList<Notification>(); + + for (int id : ids) { + Notification notification = get(id); + + if (notification != null && notification.isScheduled()) { + notifications.add(notification); + } + } + + return notifications; + } + + /** + * If a notification with an ID exists. + * + * @param id + * Notification ID + */ + public boolean exist (int id) { + return get(id) != null; + } + + /** + * If a notification with an ID and type exists. + * + * @param id + * Notification ID + * @param type + * Notification type + */ + public boolean exist (int id, Notification.Type type) { + Notification notification = get(id); + + return notification != null && notification.getType() == type; + } + + /** + * List of properties from all local notifications. + */ + public List<JSONObject> getOptions() { + return getOptionsById(getIds()); + } + + /** + * List of properties from local notifications with matching ID. + * + * @param ids + * Set of notification IDs + */ + public List<JSONObject> getOptionsById(List<Integer> ids) { + ArrayList<JSONObject> options = new ArrayList<JSONObject>(); + + for (int id : ids) { + Notification notification = get(id); + + if (notification != null) { + options.add(notification.getOptions().getDict()); + } + } + + return options; + } + + /** + * List of properties from all local notifications from given type. + * + * @param type + * The notification life cycle type + */ + public List<JSONObject> getOptionsByType(Notification.Type type) { + ArrayList<JSONObject> options = new ArrayList<JSONObject>(); + List<Notification> notifications = getByType(type); + + for (Notification notification : notifications) { + options.add(notification.getOptions().getDict()); + } + + return options; + } + + /** + * List of properties from local notifications with matching ID from + * given type. + * + * @param type + * The notification life cycle type + * @param ids + * Set of notification IDs + */ + public List<JSONObject> getOptionsBy(Notification.Type type, + List<Integer> ids) { + + if (type == Notification.Type.ALL) + return getOptionsById(ids); + + ArrayList<JSONObject> options = new ArrayList<JSONObject>(); + List<Notification> notifications = getByIds(ids); + + for (Notification notification : notifications) { + if (notification.getType() == type) { + options.add(notification.getOptions().getDict()); + } + } + + return options; + } + + /** + * Get existent local notification. + * + * @param id + * Notification ID + */ + public Notification get(int id) { + Map<String, ?> alarms = getPrefs().getAll(); + String notId = Integer.toString(id); + JSONObject options; + + if (!alarms.containsKey(notId)) + return null; + + + try { + String json = alarms.get(notId).toString(); + options = new JSONObject(json); + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + + Builder builder = new Builder(context, options); + + return builder.build(); + } + + /** + * Merge two JSON objects. + * + * @param obj1 + * JSON object + * @param obj2 + * JSON object with new options + */ + private JSONObject mergeJSONObjects (JSONObject obj1, JSONObject obj2) { + Iterator it = obj2.keys(); + + while (it.hasNext()) { + try { + String key = (String)it.next(); + + obj1.put(key, obj2.opt(key)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + return obj1; + } + + /** + * Shared private preferences for the application. + */ + private SharedPreferences getPrefs () { + return context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE); + } + + /** + * Notification manager for the application. + */ + private NotificationManager getNotMgr () { + return (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java new file mode 100644 index 00000000..5dba9d54 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + + +import android.app.AlarmManager; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.support.v4.app.NotificationCompat; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Date; + +/** + * Wrapper class around OS notification class. Handles basic operations + * like show, delete, cancel for a single local notification instance. + */ +public class Notification { + + // Used to differ notifications by their life cycle state + public enum Type { + ALL, SCHEDULED, TRIGGERED + } + + // Default receiver to handle the trigger event + private static Class<?> defaultReceiver = TriggerReceiver.class; + + // Key for private preferences + static final String PREF_KEY = "LocalNotification"; + + // Application context passed by constructor + private final Context context; + + // Notification options passed by JS + private final Options options; + + // Builder with full configuration + private final NotificationCompat.Builder builder; + + // Receiver to handle the trigger event + private Class<?> receiver = defaultReceiver; + + /** + * Constructor + * + * @param context + * Application context + * @param options + * Parsed notification options + * @param builder + * Pre-configured notification builder + */ + protected Notification (Context context, Options options, + NotificationCompat.Builder builder, Class<?> receiver) { + + this.context = context; + this.options = options; + this.builder = builder; + + this.receiver = receiver != null ? receiver : defaultReceiver; + } + + /** + * Get application context. + */ + public Context getContext () { + return context; + } + + /** + * Get notification options. + */ + public Options getOptions () { + return options; + } + + /** + * Get notification ID. + */ + public int getId () { + return options.getId(); + } + + /** + * If it's a repeating notification. + */ + public boolean isRepeating () { + return getOptions().getRepeatInterval() > 0; + } + + /** + * If the notification was in the past. + */ + public boolean wasInThePast () { + return new Date().after(options.getTriggerDate()); + } + + /** + * If the notification is scheduled. + */ + public boolean isScheduled () { + return isRepeating() || !wasInThePast(); + } + + /** + * If the notification is triggered. + */ + public boolean isTriggered () { + return wasInThePast(); + } + + /** + * If the notification is an update. + */ + protected boolean isUpdate () { + + if (!options.getDict().has("updatedAt")) + return false; + + long now = new Date().getTime(); + + long updatedAt = options.getDict().optLong("updatedAt", now); + + return (now - updatedAt) < 1000; + } + + /** + * Notification type can be one of pending or scheduled. + */ + public Type getType () { + return isTriggered() ? Type.TRIGGERED : Type.SCHEDULED; + } + + /** + * Schedule the local notification. + */ + public void schedule() { + long triggerTime = options.getTriggerTime(); + + persist(); + + // Intent gets called when the Notification gets fired + Intent intent = new Intent(context, receiver) + .setAction(options.getIdStr()) + .putExtra(Options.EXTRA, options.toString()); + + PendingIntent pi = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + if (isRepeating()) { + getAlarmMgr().setRepeating(AlarmManager.RTC_WAKEUP, + triggerTime, options.getRepeatInterval(), pi); + } else { + getAlarmMgr().set(AlarmManager.RTC_WAKEUP, triggerTime, pi); + } + } + + /** + * Clear the local notification without canceling repeating alarms. + * + */ + public void clear () { + if (!isRepeating() && wasInThePast()) { + unpersist(); + } else { + getNotMgr().cancel(getId()); + } + } + + /** + * Cancel the local notification. + * + * Create an intent that looks similar, to the one that was registered + * using schedule. Making sure the notification id in the action is the + * same. Now we can search for such an intent using the 'getService' + * method and cancel it. + */ + public void cancel() { + Intent intent = new Intent(context, receiver) + .setAction(options.getIdStr()); + + PendingIntent pi = PendingIntent. + getBroadcast(context, 0, intent, 0); + + getAlarmMgr().cancel(pi); + getNotMgr().cancel(options.getId()); + + unpersist(); + } + + /** + * Present the local notification to user. + */ + public void show () { + // TODO Show dialog when in foreground + showNotification(); + } + + /** + * Show as local notification when in background. + */ + @SuppressWarnings("deprecation") + private void showNotification () { + int id = getOptions().getId(); + + if (Build.VERSION.SDK_INT <= 15) { + // Notification for HoneyComb to ICS + getNotMgr().notify(id, builder.getNotification()); + } else { + // Notification for Jellybean and above + getNotMgr().notify(id, builder.build()); + } + } + + /** + * Show as modal dialog when in foreground. + */ + private void showDialog () { + // TODO + } + + /** + * Count of triggers since schedule. + */ + public int getTriggerCountSinceSchedule() { + long now = System.currentTimeMillis(); + long triggerTime = options.getTriggerTime(); + + if (!wasInThePast()) + return 0; + + if (!isRepeating()) + return 1; + + return (int) ((now - triggerTime) / options.getRepeatInterval()); + } + + /** + * Encode options to JSON. + */ + public String toString() { + JSONObject dict = options.getDict(); + JSONObject json = new JSONObject(); + + try { + json = new JSONObject(dict.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + + json.remove("firstAt"); + json.remove("updatedAt"); + json.remove("soundUri"); + json.remove("iconUri"); + + return json.toString(); + } + + /** + * Persist the information of this notification to the Android Shared + * Preferences. This will allow the application to restore the notification + * upon device reboot, app restart, retrieve notifications, aso. + */ + private void persist () { + SharedPreferences.Editor editor = getPrefs().edit(); + + editor.putString(options.getIdStr(), options.toString()); + + if (Build.VERSION.SDK_INT < 9) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Remove the notification from the Android shared Preferences. + */ + private void unpersist () { + SharedPreferences.Editor editor = getPrefs().edit(); + + editor.remove(options.getIdStr()); + + if (Build.VERSION.SDK_INT < 9) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Shared private preferences for the application. + */ + private SharedPreferences getPrefs () { + return context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE); + } + + /** + * Notification manager for the application. + */ + private NotificationManager getNotMgr () { + return (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + } + + /** + * Alarm manager for the application. + */ + private AlarmManager getAlarmMgr () { + return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + } + + /** + * Set default receiver to handle the trigger event. + * + * @param receiver + * broadcast receiver + */ + public static void setDefaultTriggerReceiver (Class<?> receiver) { + defaultReceiver = receiver; + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java new file mode 100644 index 00000000..198a52f4 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +import android.app.AlarmManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Date; + +/** + * Wrapper around the JSON object passed through JS which contains all + * possible option values. Class provides simple readers and more advanced + * methods to convert independent values into platform specific values. + */ +public class Options { + + // Key name for bundled extras + static final String EXTRA = "NOTIFICATION_OPTIONS"; + + // The original JSON object + private JSONObject options = new JSONObject(); + + // Repeat interval + private long interval = 0; + + // Application context + private final Context context; + + // Asset util instance + private final AssetUtil assets; + + + /** + * Constructor + * + * @param context + * Application context + */ + public Options(Context context){ + this.context = context; + this.assets = AssetUtil.getInstance(context); + } + + /** + * Parse given JSON properties. + * + * @param options + * JSON properties + */ + public Options parse (JSONObject options) { + this.options = options; + + parseInterval(); + parseAssets(); + + return this; + } + + /** + * Parse repeat interval. + */ + private void parseInterval() { + String every = options.optString("every").toLowerCase(); + + if (every.isEmpty()) { + interval = 0; + } else + if (every.equals("second")) { + interval = 1000; + } else + if (every.equals("minute")) { + interval = AlarmManager.INTERVAL_FIFTEEN_MINUTES / 15; + } else + if (every.equals("hour")) { + interval = AlarmManager.INTERVAL_HOUR; + } else + if (every.equals("day")) { + interval = AlarmManager.INTERVAL_DAY; + } else + if (every.equals("week")) { + interval = AlarmManager.INTERVAL_DAY * 7; + } else + if (every.equals("month")) { + interval = AlarmManager.INTERVAL_DAY * 31; + } else + if (every.equals("year")) { + interval = AlarmManager.INTERVAL_DAY * 365; + } else { + try { + interval = Integer.parseInt(every) * 60000; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Parse asset URIs. + */ + private void parseAssets() { + + if (options.has("iconUri")) + return; + + Uri iconUri = assets.parse(options.optString("icon", "icon")); + Uri soundUri = assets.parseSound(options.optString("sound", null)); + + try { + options.put("iconUri", iconUri.toString()); + options.put("soundUri", soundUri.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + /** + * Application context. + */ + public Context getContext () { + return context; + } + + /** + * Wrapped JSON object. + */ + JSONObject getDict () { + return options; + } + + /** + * Text for the local notification. + */ + public String getText() { + return options.optString("text", ""); + } + + /** + * Repeat interval (day, week, month, year, aso.) + */ + public long getRepeatInterval() { + return interval; + } + + /** + * Badge number for the local notification. + */ + public int getBadgeNumber() { + return options.optInt("badge", 0); + } + + /** + * ongoing flag for local notifications. + */ + public Boolean isOngoing() { + return options.optBoolean("ongoing", false); + } + + /** + * autoClear flag for local notifications. + */ + public Boolean isAutoClear() { + return options.optBoolean("autoClear", false); + } + + /** + * ID for the local notification as a number. + */ + public Integer getId() { + return options.optInt("id", 0); + } + + /** + * ID for the local notification as a string. + */ + public String getIdStr() { + return getId().toString(); + } + + /** + * Trigger date. + */ + public Date getTriggerDate() { + return new Date(getTriggerTime()); + } + + /** + * Trigger date in milliseconds. + */ + public long getTriggerTime() { + return Math.max( + System.currentTimeMillis(), + options.optLong("at", 0) * 1000 + ); + } + + /** + * Title for the local notification. + */ + public String getTitle() { + String title = options.optString("title", ""); + + if (title.isEmpty()) { + title = context.getApplicationInfo().loadLabel( + context.getPackageManager()).toString(); + } + + return title; + } + + /** + * @return + * The notification color for LED + */ + public int getLedColor() { + String hex = options.optString("led", "000000"); + int aRGB = Integer.parseInt(hex,16); + + aRGB += 0xFF000000; + + return aRGB; + } + + /** + * Sound file path for the local notification. + */ + public Uri getSoundUri() { + Uri uri = null; + + try{ + uri = Uri.parse(options.optString("soundUri")); + } catch (Exception e){ + e.printStackTrace(); + } + + return uri; + } + + /** + * Icon bitmap for the local notification. + */ + public Bitmap getIconBitmap() { + String icon = options.optString("icon", "icon"); + Bitmap bmp; + + try{ + Uri uri = Uri.parse(options.optString("iconUri")); + bmp = assets.getIconFromUri(uri); + } catch (Exception e){ + bmp = assets.getIconFromDrawable(icon); + } + + return bmp; + } + + /** + * Small icon resource ID for the local notification. + */ + public int getSmallIcon () { + String icon = options.optString("smallIcon", ""); + + int resId = assets.getResIdForDrawable(icon); + + if (resId == 0) { + resId = android.R.drawable.screen_background_dark; + } + + return resId; + } + + /** + * JSON object as string. + */ + public String toString() { + return options.toString(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java new file mode 100644 index 00000000..9427e31e --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +package de.appplant.cordova.plugin.notification; + +/** + * The alarm receiver is triggered when a scheduled alarm is fired. This class + * reads the information in the intent and displays this information in the + * Android notification bar. The notification uses the default notification + * sound and it vibrates the phone. + */ +public class TriggerReceiver extends AbstractTriggerReceiver { + + /** + * Called when a local notification was triggered. Does present the local + * notification and re-schedule the alarm if necessary. + * + * @param notification + * Wrapper around the local notification + * @param updated + * If an update has triggered or the original + */ + @Override + public void onTrigger (Notification notification, boolean updated) { + notification.show(); + } + + /** + * Build notification specified by options. + * + * @param builder + * Notification builder + */ + @Override + public Notification buildNotification (Builder builder) { + return builder.build(); + } + +} diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotification.h b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotification.h new file mode 100644 index 00000000..f86bf498 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotification.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import <Foundation/Foundation.h> +#import <Cordova/CDVPlugin.h> + +@interface APPLocalNotification : CDVPlugin + +// Execute all queued events +- (void) deviceready:(CDVInvokedUrlCommand*)command; + +// Inform if the app has the permission to show notifications +- (void) hasPermission:(CDVInvokedUrlCommand*)command; +// Register permission to show notifications +- (void) registerPermission:(CDVInvokedUrlCommand*)command; + +// Schedule set of notifications +- (void) schedule:(CDVInvokedUrlCommand*)command; +// Update set of notifications +- (void) update:(CDVInvokedUrlCommand*)command; +// Cancel set of notifications +- (void) cancel:(CDVInvokedUrlCommand*)command; +// Cancel all notifications +- (void) cancelAll:(CDVInvokedUrlCommand*)command; +// Clear set of notifications +- (void) clear:(CDVInvokedUrlCommand*)command; +// Clear all notifications +- (void) clearAll:(CDVInvokedUrlCommand*)command; + +// If a notification with an ID is present +- (void) isPresent:(CDVInvokedUrlCommand*)command; +// If a notification with an ID is scheduled +- (void) isScheduled:(CDVInvokedUrlCommand*)command; +// If a notification with an ID is triggered +- (void) isTriggered:(CDVInvokedUrlCommand*)command; + +// List all ids from all local notifications +- (void) getAllIds:(CDVInvokedUrlCommand*)command; +// List all ids from all pending notifications +- (void) getScheduledIds:(CDVInvokedUrlCommand*)command; +// List all ids from all triggered notifications +- (void) getTriggeredIds:(CDVInvokedUrlCommand*)command; + +// Propertys for given local notification +- (void) getSingle:(CDVInvokedUrlCommand*)command; +// Propertya for given scheduled notification +- (void) getSingleScheduled:(CDVInvokedUrlCommand*)command; +// Propertys for given triggered notification +- (void) getSingleTriggered:(CDVInvokedUrlCommand*)command; + +// Property list for given local notifications +- (void) getAll:(CDVInvokedUrlCommand*)command; +// Property list for given scheduled notifications +- (void) getScheduled:(CDVInvokedUrlCommand*)command; +// Property list for given triggered notifications +- (void) getTriggered:(CDVInvokedUrlCommand*)command; + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotification.m b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotification.m new file mode 100644 index 00000000..43cf9f8c --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotification.m @@ -0,0 +1,738 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "APPLocalNotification.h" +#import "APPLocalNotificationOptions.h" +#import "UIApplication+APPLocalNotification.h" +#import "UILocalNotification+APPLocalNotification.h" +#import "AppDelegate+APPRegisterUserNotificationSettings.h" + +@interface APPLocalNotification () + +// Retrieves the application state +@property (readonly, getter=applicationState) NSString* applicationState; +// All events will be queued until deviceready has been fired +@property (readwrite, assign) BOOL deviceready; +// Event queue +@property (readonly, nonatomic, retain) NSMutableArray* eventQueue; +// Needed when calling `registerPermission` +@property (nonatomic, retain) CDVInvokedUrlCommand* command; + +@end + +@implementation APPLocalNotification + +@synthesize deviceready, eventQueue; + +#pragma mark - +#pragma mark Interface + +/** + * Execute all queued events. + */ +- (void) deviceready:(CDVInvokedUrlCommand*)command +{ + deviceready = YES; + + for (NSString* js in eventQueue) { + [self.commandDelegate evalJs:js]; + } + + [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:^{ + for (NSDictionary* options in notifications) { + UILocalNotification* notification; + + notification = [[UILocalNotification alloc] + initWithOptions:options]; + + [self scheduleLocalNotification:[notification copy]]; + [self fireEvent:@"schedule" notification:notification]; + + if (notifications.count > 1) { + [NSThread sleepForTimeInterval:0.01]; + } + } + + [self execCallback:command]; + }]; +} + +/** + * Update a set of notifications. + * + * @param properties + * A dict of properties for each notification + */ +- (void) update:(CDVInvokedUrlCommand*)command +{ + NSArray* notifications = command.arguments; + + [self.commandDelegate runInBackground:^{ + for (NSDictionary* options in notifications) { + NSNumber* id = [options objectForKey:@"id"]; + UILocalNotification* notification; + + notification = [self.app localNotificationWithId:id]; + + if (!notification) + continue; + + [self updateLocalNotification:[notification copy] + withOptions:options]; + + [self fireEvent:@"update" notification:notification]; + + if (notifications.count > 1) { + [NSThread sleepForTimeInterval:0.01]; + } + } + + [self execCallback:command]; + }]; +} + +/** + * Cancel a set of notifications. + * + * @param ids + * The IDs of the notifications + */ +- (void) cancel:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + for (NSNumber* id in command.arguments) { + UILocalNotification* notification; + + notification = [self.app localNotificationWithId:id]; + + if (!notification) + continue; + + [self.app cancelLocalNotification:notification]; + [self fireEvent:@"cancel" notification:notification]; + } + + [self execCallback:command]; + }]; +} + +/** + * Cancel all local notifications. + */ +- (void) cancelAll:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + [self cancelAllLocalNotifications]; + [self fireEvent:@"cancelall"]; + [self execCallback:command]; + }]; +} + +/** + * Clear a set of notifications. + * + * @param ids + * The IDs of the notifications + */ +- (void) clear:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + for (NSNumber* id in command.arguments) { + UILocalNotification* notification; + + notification = [self.app localNotificationWithId:id]; + + if (!notification) + continue; + + [self.app clearLocalNotification:notification]; + [self fireEvent:@"clear" notification:notification]; + } + + [self execCallback:command]; + }]; +} + +/** + * Clear all local notifications. + */ +- (void) clearAll:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + [self clearAllLocalNotifications]; + [self fireEvent:@"clearall"]; + [self execCallback:command]; + }]; +} + +/** + * If a notification by ID is present. + * + * @param id + * The ID of the notification + */ +- (void) isPresent:(CDVInvokedUrlCommand *)command +{ + [self isPresent:command type:NotifcationTypeAll]; +} + +/** + * If a notification by ID is scheduled. + * + * @param id + * The ID of the notification + */ +- (void) isScheduled:(CDVInvokedUrlCommand*)command +{ + [self isPresent:command type:NotifcationTypeScheduled]; +} + +/** + * Check if a notification with an ID is triggered. + * + * @param id + * The ID of the notification + */ +- (void) isTriggered:(CDVInvokedUrlCommand*)command +{ + [self isPresent:command type:NotifcationTypeTriggered]; +} + +/** + * Check if a notification with an ID exists. + * + * @param type + * The notification life cycle type + */ +- (void) isPresent:(CDVInvokedUrlCommand*)command + type:(APPLocalNotificationType)type; +{ + [self.commandDelegate runInBackground:^{ + NSNumber* id = [command argumentAtIndex:0]; + BOOL exist; + + CDVPluginResult* result; + + if (type == NotifcationTypeAll) { + exist = [self.app localNotificationExist:id]; + } else { + exist = [self.app localNotificationExist:id type:type]; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsBool:exist]; + + [self.commandDelegate sendPluginResult:result + callbackId:command.callbackId]; + }]; +} + +/** + * List all ids from all local notifications. + */ +- (void) getAllIds:(CDVInvokedUrlCommand*)command +{ + [self getIds:command byType:NotifcationTypeAll]; +} + +/** + * List all ids from all pending notifications. + */ +- (void) getScheduledIds:(CDVInvokedUrlCommand*)command +{ + [self getIds:command byType:NotifcationTypeScheduled]; +} + +/** + * List all ids from all triggered notifications. + */ +- (void) getTriggeredIds:(CDVInvokedUrlCommand*)command +{ + [self getIds:command byType:NotifcationTypeTriggered]; +} + +/** + * List of ids for given local notifications. + * + * @param type + * Notification life cycle type + * @param ids + * The IDs of the notifications + */ +- (void) getIds:(CDVInvokedUrlCommand*)command + byType:(APPLocalNotificationType)type; +{ + [self.commandDelegate runInBackground:^{ + CDVPluginResult* result; + NSArray* ids; + + if (type == NotifcationTypeAll) { + ids = [self.app localNotificationIds]; + } else { + ids = [self.app localNotificationIdsByType:type]; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsArray:ids]; + + [self.commandDelegate sendPluginResult:result + callbackId:command.callbackId]; + }]; +} + +/** + * Propertys for given local notification. + */ +- (void) getSingle:(CDVInvokedUrlCommand*)command +{ + [self getOption:command byType:NotifcationTypeAll]; +} + +/** + * Propertya for given scheduled notification. + */ +- (void) getSingleScheduled:(CDVInvokedUrlCommand*)command +{ + [self getOption:command byType:NotifcationTypeScheduled]; +} + +// Propertys for given triggered notification +- (void) getSingleTriggered:(CDVInvokedUrlCommand*)command +{ + [self getOption:command byType:NotifcationTypeTriggered]; +} + +/** + * Property list for given local notifications. + * + * @param ids + * The IDs of the notifications + */ +- (void) getAll:(CDVInvokedUrlCommand*)command +{ + [self getOptions:command byType:NotifcationTypeAll]; +} + +/** + * Property list for given scheduled notifications. + * + * @param ids + * The IDs of the notifications + */ +- (void) getScheduled:(CDVInvokedUrlCommand*)command +{ + [self getOptions:command byType:NotifcationTypeScheduled]; +} + +/** + * Property list for given triggered notifications. + * + * @param ids + * The IDs of the notifications + */ +- (void) getTriggered:(CDVInvokedUrlCommand *)command +{ + [self getOptions:command byType:NotifcationTypeTriggered]; +} + +/** + * Propertys for given triggered notification. + * + * @param type + * Notification life cycle type + * @param ids + * The ID of the notification + */ +- (void) getOption:(CDVInvokedUrlCommand*)command + byType:(APPLocalNotificationType)type; +{ + [self.commandDelegate runInBackground:^{ + NSArray* ids = command.arguments; + NSArray* notifications; + CDVPluginResult* result; + + if (type == NotifcationTypeAll) { + notifications = [self.app localNotificationOptionsById:ids]; + } + else { + notifications = [self.app localNotificationOptionsByType:type + andId:ids]; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:notifications[0]]; + + [self.commandDelegate sendPluginResult:result + callbackId:command.callbackId]; + }]; +} + +/** + * Property list for given triggered notifications. + * + * @param type + * Notification life cycle type + * @param ids + * The IDs of the notifications + */ +- (void) getOptions:(CDVInvokedUrlCommand*)command + byType:(APPLocalNotificationType)type; +{ + [self.commandDelegate runInBackground:^{ + NSArray* ids = command.arguments; + NSArray* notifications; + CDVPluginResult* result; + + if (type == NotifcationTypeAll && ids.count == 0) { + notifications = [self.app localNotificationOptions]; + } + else if (type == NotifcationTypeAll) { + notifications = [self.app localNotificationOptionsById:ids]; + } + else if (ids.count == 0) { + notifications = [self.app localNotificationOptionsByType:type]; + } + else { + notifications = [self.app localNotificationOptionsByType:type + andId:ids]; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsArray:notifications]; + + [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 = [self.app hasPermissionToScheduleLocalNotifications]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsBool:hasPermission]; + + [self.commandDelegate sendPluginResult:result + callbackId:command.callbackId]; + }]; +} + +/** + * Ask for permission to show badges. + */ +- (void) registerPermission:(CDVInvokedUrlCommand*)command +{ + if ([[UIApplication sharedApplication] + respondsToSelector:@selector(registerUserNotificationSettings:)]) + { + _command = command; + + [self.commandDelegate runInBackground:^{ + [self.app registerPermissionToScheduleLocalNotifications]; + }]; + } else { + [self hasPermission:command]; + } +} + +#pragma mark - +#pragma mark Core Logic + +/** + * 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 all local notifications. + */ +- (void) cancelAllLocalNotifications +{ + [self.app cancelAllLocalNotifications]; + [self.app setApplicationIconBadgeNumber:0]; +} + +/** + * Clear all local notifications. + */ +- (void) clearAllLocalNotifications +{ + [self.app clearAllLocalNotifications]; + [self.app setApplicationIconBadgeNumber:0]; +} + +/** + * 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" notification:notification]; + } + } +} + +#pragma mark - +#pragma mark Delegates + +/** + * 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 wasUpdated]) + return; + + NSTimeInterval timeInterval = [notification timeIntervalSinceLastTrigger]; + + NSString* event = (timeInterval <= 1 && deviceready) ? @"trigger" : @"click"; + + [self fireEvent:event notification:notification]; + + if (![event isEqualToString:@"click"]) + return; + + if ([notification isRepeating]) { + [self fireEvent:@"clear" notification:notification]; + } else { + [self.app cancelLocalNotification:notification]; + [self fireEvent:@"cancel" notification: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 otification settings registration is completed. + */ +- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings +{ + if (_command) + { + [self hasPermission:_command]; + _command = NULL; + } +} + +#pragma mark - +#pragma mark Life Cycle + +/** + * Registers obervers after plugin was initialized. + */ +- (void) pluginInitialize +{ + NSNotificationCenter* center = [NSNotificationCenter + defaultCenter]; + + eventQueue = [[NSMutableArray alloc] init]; + + [center addObserver:self + selector:@selector(didReceiveLocalNotification:) + name:CDVLocalNotification + object:nil]; + + [center addObserver:self + selector:@selector(didFinishLaunchingWithOptions:) + name:UIApplicationDidFinishLaunchingNotification + object:nil]; + + [center addObserver:self + selector:@selector(didRegisterUserNotificationSettings:) + name:UIApplicationRegisterUserNotificationSettings + object:nil]; +} + +/** + * Clears all single repeating notifications which are older then 5 days + * before the app terminates. + */ +- (void) onAppTerminate +{ + [self cancelAllNotificationsWhichAreOlderThen:432000]; +} + +#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]; +} + +/** + * Fire general event. + */ +- (void) fireEvent:(NSString*)event +{ + [self fireEvent:event notification:NULL]; +} + +/** + * Fire event for local notification. + */ +- (void) fireEvent:(NSString*)event notification:(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]; + } +} + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotificationOptions.h b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotificationOptions.h new file mode 100644 index 00000000..73c3ef75 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotificationOptions.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +@interface APPLocalNotificationOptions : NSObject + +- (id) initWithDict:(NSDictionary*)dict; + +@property (readonly, getter=id) NSNumber* id; +@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; + +// If it's a repeating notification +- (BOOL) isRepeating; + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotificationOptions.m b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotificationOptions.m new file mode 100644 index 00000000..ac90f993 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/APPLocalNotificationOptions.m @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "APPLocalNotificationOptions.h" + +// Default sound ressource path +NSString* const DEFAULT_SOUND = @"res://platform_default"; + +@interface APPLocalNotificationOptions () + +// The dictionary which contains all notification properties +@property(nonatomic, retain) 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 = [self init]; + + self.dict = dictionary; + + return self; +} + +#pragma mark - +#pragma mark Attributes + +/** + * The notification's ID. + */ +- (NSNumber*) id +{ + NSInteger id = [[dict objectForKey:@"id"] integerValue]; + + return [NSNumber numberWithInteger:id]; +} + +/** + * The notification's title. + */ +- (NSString*) title +{ + return [dict objectForKey:@"title"]; +} + +/** + * The notification's message. + */ +- (NSString*) text +{ + return [dict objectForKey:@"text"]; +} + +/** + * 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 text]; + + 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 ([self stringIsNullOrEmpty:path]) + return NULL; + + if ([path isEqualToString:DEFAULT_SOUND]) + return UILocalNotificationDefaultSoundName; + + if ([path hasPrefix:@"file:/"]) + return [self soundNameForAsset:path]; + + if ([path hasPrefix:@"res:"]) + return [self soundNameForResource:path]; + + return NULL; +} + +/** + * The notification's fire date. + */ +- (NSDate*) fireDate +{ + double timestamp = [[dict objectForKey:@"at"] + doubleValue]; + + return [NSDate dateWithTimeIntervalSince1970:timestamp]; +} + +/** + * The notification's repeat interval. + */ +- (NSCalendarUnit) repeatInterval +{ + NSString* interval = [dict objectForKey:@"every"]; + + if ([self stringIsNullOrEmpty:interval]) { + return NSCalendarUnitEra; + } + else if ([interval isEqualToString:@"second"]) { + return NSCalendarUnitSecond; + } + else if ([interval isEqualToString:@"minute"]) { + return NSCalendarUnitMinute; + } + else if ([interval isEqualToString:@"hour"]) { + return NSCalendarUnitHour; + } + else if ([interval isEqualToString:@"day"]) { + return NSCalendarUnitDay; + } + else if ([interval isEqualToString:@"week"]) { + return NSCalendarUnitWeekOfYear; + } + else if ([interval isEqualToString:@"month"]) { + return NSCalendarUnitMonth; + } + else if ([interval isEqualToString:@"year"]) { + return NSCalendarUnitYear; + } + + return NSCalendarUnitEra; +} + +#pragma mark - +#pragma mark Methods + +/** + * The notification's user info dict. + */ +- (NSDictionary*) userInfo +{ + if ([dict objectForKey:@"updatedAt"]) { + NSMutableDictionary* data = [dict mutableCopy]; + + [data removeObjectForKey:@"updatedAt"]; + + return data; + } + + return dict; +} + +/** + * If it's a repeating notification. + */ +- (BOOL) isRepeating +{ + NSCalendarUnit interval = self.repeatInterval; + + return !(interval == NSCalendarUnitEra || interval == 0); +} + +#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 diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UIApplication+APPLocalNotification.h b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UIApplication+APPLocalNotification.h new file mode 100644 index 00000000..a18568e5 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UIApplication+APPLocalNotification.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "UILocalNotification+APPLocalNotification.h" + +@interface UIApplication (APPLocalNotification) + +@property (readonly, getter=localNotifications) NSArray* localNotifications; +@property (readonly, getter=localNotificationIds) NSArray* localNotificationIds; + +// If the app has the permission to schedule local notifications +- (BOOL) hasPermissionToScheduleLocalNotifications; +// Ask for permission to schedule local notifications +- (void) registerPermissionToScheduleLocalNotifications; + +// List of all local notification IDs from given type +- (NSArray*) localNotificationIdsByType:(APPLocalNotificationType)type; + +// If local notification with ID exists +- (BOOL) localNotificationExist:(NSNumber*)id; +// If local notification with ID and type exists +- (BOOL) localNotificationExist:(NSNumber*)id type:(APPLocalNotificationType)type; + +// Local notification by ID +- (UILocalNotification*) localNotificationWithId:(NSNumber*)id; +// Local notification by ID and type +- (UILocalNotification*) localNotificationWithId:(NSNumber*)id andType:(APPLocalNotificationType)type; + +// Property list from all local notifications +- (NSArray*) localNotificationOptions; +// Property list from given local notifications +- (NSArray*) localNotificationOptionsById:(NSArray*)ids; +// Property list from all local notifications with type constraint +- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type; +// Property list from given local notifications with type constraint +- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type andId:(NSArray*)ids; + +// Clear single local notfications +- (void) clearLocalNotification:(UILocalNotification*)notification; +// Clear all local notfications +- (void) clearAllLocalNotifications; + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UIApplication+APPLocalNotification.m b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UIApplication+APPLocalNotification.m new file mode 100644 index 00000000..60b6daac --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UIApplication+APPLocalNotification.m @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "UIApplication+APPLocalNotification.h" +#import "UILocalNotification+APPLocalNotification.h" + +@implementation UIApplication (APPLocalNotification) + +#pragma mark - +#pragma mark Permissions + +/** + * If the app has the permission to schedule local notifications. + */ +- (BOOL) hasPermissionToScheduleLocalNotifications +{ + if ([[UIApplication sharedApplication] + respondsToSelector:@selector(registerUserNotificationSettings:)]) + { + UIUserNotificationType types; + UIUserNotificationSettings *settings; + + settings = [[UIApplication sharedApplication] + currentUserNotificationSettings]; + + types = UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound; + + return (settings.types & types); + } else { + return YES; + } +} + +/** + * Ask for permission to schedule local notifications. + */ +- (void) registerPermissionToScheduleLocalNotifications +{ + if ([[UIApplication sharedApplication] + respondsToSelector:@selector(registerUserNotificationSettings:)]) + { + UIUserNotificationType types; + UIUserNotificationSettings *settings; + + settings = [[UIApplication sharedApplication] + currentUserNotificationSettings]; + + types = settings.types|UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound; + + settings = [UIUserNotificationSettings settingsForTypes:types + categories:nil]; + + [[UIApplication sharedApplication] + registerUserNotificationSettings:settings]; + } +} + +#pragma mark - +#pragma mark LocalNotifications + +/** + * List of all local notifications which have been added + * but not yet removed from the notification center. + */ +- (NSArray*) localNotifications +{ + NSArray* scheduledNotifications = self.scheduledLocalNotifications; + NSMutableArray* notifications = [[NSMutableArray alloc] init]; + + for (UILocalNotification* notification in scheduledNotifications) + { + if (notification) { + [notifications addObject:notification]; + } + } + + return notifications; +} + +/** + * List of all triggered local notifications which have been scheduled + * and not yet removed the notification center. + */ +- (NSArray*) triggeredLocalNotifications +{ + NSArray* notifications = self.localNotifications; + NSMutableArray* triggeredNotifications = [[NSMutableArray alloc] init]; + + for (UILocalNotification* notification in notifications) + { + if ([notification isTriggered]) { + [triggeredNotifications addObject:notification]; + } + } + + return triggeredNotifications; +} + +/** + * List of all local notifications IDs. + */ +- (NSArray*) localNotificationIds +{ + NSArray* notifications = self.localNotifications; + NSMutableArray* ids = [[NSMutableArray alloc] init]; + + for (UILocalNotification* notification in notifications) + { + [ids addObject:notification.options.id]; + } + + return ids; +} + +/** + * List of all local notifications IDs from given type. + * + * @param type + * Notification life cycle type + */ +- (NSArray*) localNotificationIdsByType:(APPLocalNotificationType)type +{ + NSArray* notifications = self.localNotifications; + NSMutableArray* ids = [[NSMutableArray alloc] init]; + + for (UILocalNotification* notification in notifications) + { + if (notification.type == type) { + [ids addObject:notification.options.id]; + } + } + + return ids; +} + +/* + * If local notification with ID exists. + * + * @param id + * Notification ID + */ +- (BOOL) localNotificationExist:(NSNumber*)id +{ + return [self localNotificationWithId:id] != NULL; +} + +/* If local notification with ID and type exists + * + * @param id + * Notification ID + * @param type + * Notification life cycle type + */ +- (BOOL) localNotificationExist:(NSNumber*)id type:(APPLocalNotificationType)type +{ + return [self localNotificationWithId:id andType:type] != NULL; +} + +/** + * Get local notification with ID. + * + * @param id + * Notification ID + */ +- (UILocalNotification*) localNotificationWithId:(NSNumber*)id +{ + NSArray* notifications = self.localNotifications; + + for (UILocalNotification* notification in notifications) + { + if ([notification.options.id isEqualToNumber:id]) { + return notification; + } + } + + return NULL; +} + +/* + * Get local notification with ID and type. + * + * @param id + * Notification ID + * @param type + * Notification life cycle type + */ +- (UILocalNotification*) localNotificationWithId:(NSNumber*)id andType:(APPLocalNotificationType)type +{ + UILocalNotification* notification = [self localNotificationWithId:id]; + + if (notification && notification.type == type) + return notification; + + return NULL; +} + +/** + * List of properties from all notifications. + */ +- (NSArray*) localNotificationOptions +{ + NSArray* notifications = self.localNotifications; + NSMutableArray* options = [[NSMutableArray alloc] init]; + + for (UILocalNotification* notification in notifications) + { + [options addObject:notification.options.userInfo]; + } + + return options; +} + +/** + * List of properties from all local notifications from given type. + * + * @param type + * Notification life cycle type + */ +- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type +{ + NSArray* notifications = self.localNotifications; + NSMutableArray* options = [[NSMutableArray alloc] init]; + + for (UILocalNotification* notification in notifications) + { + if (notification.type == type) { + [options addObject:notification.options.userInfo]; + } + } + + return options; +} + +/** + * List of properties from given local notifications. + * + * @param ids + * Notification IDs + */ +- (NSArray*) localNotificationOptionsById:(NSArray*)ids +{ + UILocalNotification* notification; + NSMutableArray* options = [[NSMutableArray alloc] init]; + + for (NSNumber* id in ids) + { + notification = [self localNotificationWithId:id]; + + if (notification) { + [options addObject:notification.options.userInfo]; + } + } + + return options; +} + +/** + * List of properties from given local notifications. + * + * @param type + * Notification life cycle type + * @param ids + * Notification IDs + */ +- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type andId:(NSArray*)ids +{ + UILocalNotification* notification; + NSMutableArray* options = [[NSMutableArray alloc] init]; + + for (NSNumber* id in ids) + { + notification = [self localNotificationWithId:id]; + + if (notification && notification.type == type) { + [options addObject:notification.options.userInfo]; + } + } + + return options; +} + +/* + * Clear all local notfications. + */ +- (void) clearAllLocalNotifications +{ + NSArray* notifications = self.triggeredLocalNotifications; + + for (UILocalNotification* notification in notifications) { + [self clearLocalNotification:notification]; + } +} + +/* + * Clear single local notfication. + * + * @param notification + * The local notification object + */ +- (void) clearLocalNotification:(UILocalNotification*)notification +{ + [self cancelLocalNotification:notification]; + + if ([notification isRepeating]) { + notification.fireDate = notification.options.fireDate; + + [self scheduleLocalNotification:notification]; + }; +} + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UILocalNotification+APPLocalNotification.h b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UILocalNotification+APPLocalNotification.h new file mode 100644 index 00000000..ac0fdc20 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UILocalNotification+APPLocalNotification.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "APPLocalNotificationOptions.h" + +typedef NS_ENUM(NSUInteger, APPLocalNotificationType) { + NotifcationTypeAll = 0, + NotifcationTypeScheduled = 1, + NotifcationTypeTriggered = 2 +}; + +@interface UILocalNotification (APPLocalNotification) + +// Initialize a new local notification +- (id) initWithOptions:(NSDictionary*)dict; +// The options provided by the plug-in +- (APPLocalNotificationOptions*) options; +// Timeinterval since last trigger date +- (double) timeIntervalSinceLastTrigger; +// Timeinterval since fire date +- (double) timeIntervalSinceFireDate; +// If the fire date was in the past +- (BOOL) wasInThePast; +// If the notification was already scheduled +- (BOOL) isScheduled; +// If the notification was already triggered +- (BOOL) isTriggered; +// If the notification was updated +- (BOOL) wasUpdated; +// If it's a repeating notification +- (BOOL) isRepeating; +// Notifciation type +- (APPLocalNotificationType) type; +// Encode the user info dict to JSON +- (NSString*) encodeToJSON; + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UILocalNotification+APPLocalNotification.m b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UILocalNotification+APPLocalNotification.m new file mode 100644 index 00000000..d225cf52 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/ios/UILocalNotification+APPLocalNotification.m @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +#import "UILocalNotification+APPLocalNotification.h" +#import "APPLocalNotificationOptions.h" +#import <objc/runtime.h> + +static char optionsKey; + +NSInteger const APPLocalNotificationTypeScheduled = 1; +NSInteger const APPLocalNotificationTypeTriggered = 2; + +@implementation UILocalNotification (APPLocalNotification) + +#pragma mark - +#pragma mark Init + +/** + * Initialize a local notification with the given options when calling on JS side: + * notification.local.add(options) + */ +- (id) initWithOptions:(NSDictionary*)dict +{ + self = [self 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; + + if ([self wasInThePast]) { + self.fireDate = [NSDate date]; + } +} + +#pragma mark - +#pragma mark Methods + +/** + * 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); +} + +/** + * The repeating interval in seconds. + */ +- (int) repeatIntervalInSeconds +{ + switch (self.repeatInterval) { + case NSCalendarUnitMinute: + return 60; + + case NSCalendarUnitHour: + return 60000; + + case NSCalendarUnitDay: + case NSCalendarUnitWeekOfYear: + case NSCalendarUnitMonth: + case NSCalendarUnitYear: + return 86400; + + default: + return 1; + } +} + +/** + * Timeinterval since fire date. + */ +- (double) timeIntervalSinceFireDate +{ + NSDate* now = [NSDate date]; + NSDate* fireDate = self.fireDate; + + int timespan = [now timeIntervalSinceDate:fireDate]; + + return timespan; +} + +/** + * Timeinterval since last trigger date. + */ +- (double) timeIntervalSinceLastTrigger +{ + int timespan = [self timeIntervalSinceFireDate]; + + if ([self isRepeating]) { + timespan = timespan % [self repeatIntervalInSeconds]; + } + + return timespan; +} + +/** + * Encode the user info dict to JSON. + */ +- (NSString*) encodeToJSON +{ + NSString* json; + NSData* data; + NSMutableDictionary* obj = [self.userInfo mutableCopy]; + + [obj removeObjectForKey:@"updatedAt"]; + + data = [NSJSONSerialization dataWithJSONObject:obj + options:NSJSONWritingPrettyPrinted + error:Nil]; + + json = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + + return [json stringByReplacingOccurrencesOfString:@"\n" + withString:@""]; +} + +#pragma mark - +#pragma mark State + +/** + * If the fire date was in the past. + */ +- (BOOL) wasInThePast +{ + return [self timeIntervalSinceLastTrigger] > 0; +} + +// If the notification was already scheduled +- (BOOL) isScheduled +{ + return [self isRepeating] || ![self wasInThePast]; +} + +/** + * If the notification was already triggered. + */ +- (BOOL) isTriggered +{ + NSDate* now = [NSDate date]; + NSDate* fireDate = self.fireDate; + + bool isLaterThanFireDate = !([now compare:fireDate] == NSOrderedAscending); + + return isLaterThanFireDate; +} + +/** + * If the notification was updated. + */ +- (BOOL) wasUpdated +{ + NSDate* now = [NSDate date]; + NSDate* updatedAt = [self.userInfo objectForKey:@"updatedAt"]; + + if (updatedAt == NULL) + return NO; + + int timespan = [now timeIntervalSinceDate:updatedAt]; + + return timespan < 1; +} + +/** + * If it's a repeating notification. + */ +- (BOOL) isRepeating +{ + return [self.options isRepeating]; +} + +/** + * Process state type of the local notification. + */ +- (APPLocalNotificationType) type +{ + return [self isTriggered] ? NotifcationTypeTriggered : NotifcationTypeScheduled; +} + +@end diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationCore.js b/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationCore.js new file mode 100644 index 00000000..f27193e1 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationCore.js @@ -0,0 +1,436 @@ +/*
+ Copyright 2013-2015 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.
+*/
+
+
+var proxy = require('de.appplant.cordova.plugin.local-notification.LocalNotification.Proxy');
+
+var Notifications = Windows.UI.Notifications;
+
+
+proxy.core = {
+
+ /**
+ * Executes all queued events.
+ */
+ deviceready: function () {
+ var plugin = cordova.plugins.notification.local,
+ events = this.eventQueue;
+
+ this.isReady = true;
+
+ for (var i = 0; i < events.length; i++) {
+ plugin.fireEvent.apply(plugin, events[i]);
+ }
+
+ this.eventQueue = [];
+ },
+
+ /**
+ * Schedules new local notifications.
+ *
+ * @param {Object[]} notifications
+ * Array of local notifications
+ * @param {String} event
+ * 'schedule' or 'update'
+ */
+ schedule: function (notifications) {
+ var triggerFn = function (notification) {
+ this.updateBadge(notification.badge);
+ this.fireEvent('trigger', notification);
+ };
+
+ for (var i = 0; i < notifications.length; i++) {
+ var options = notifications[i],
+ notification = this.build(options);
+
+ this.cancelLocalNotification(options.id);
+ this.scheduleLocalNotification(notification, options);
+ this.scheduleBackupNotification(notification, options);
+ this.fireEvent('schedule', options);
+ this.callOnTrigger(options, triggerFn);
+ }
+ },
+
+ /**
+ * Schedules a single local notification.
+ *
+ * @param {Windows.Data.Xml.Dom.XmlDocument} notification
+ * The local notification
+ * @param {Object} options
+ * Local notification properties
+ */
+ scheduleLocalNotification: function (notification, options) {
+ var interval = this.getRepeatInterval(options.every),
+ triggerTime = new Date((options.at * 1000)),
+ now = new Date().getTime(),
+ toast;
+
+ if (triggerTime <= now) {
+ triggerTime = new Date(now + 10);
+ }
+
+ try {
+ if (interval !== 0 && interval < 360001 && interval > 59999) {
+ toast = new Notifications.ScheduledToastNotification(
+ notification, triggerTime, interval, 5);
+ } else {
+ toast = new Notifications.ScheduledToastNotification(
+ notification, triggerTime);
+ }
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+
+ toast.id = options.id;
+ toast.tag = 'Toast' + toast.id;
+
+ Notifications.ToastNotificationManager
+ .createToastNotifier()
+ .addToSchedule(toast);
+ },
+
+ /**
+ * Schedules a backup local notification 10 years later.
+ *
+ * @param {Object} notification
+ * The local notification
+ */
+ scheduleBackupNotification: function (notification, options) {
+ var properties = Object.create(options);
+
+ properties.id = options.id + '-2';
+ properties.at = options.at + 315360000; // 10 years later
+
+ this.scheduleLocalNotification(notification, properties);
+ },
+
+ /**
+ * Updates the badge number of the active tile.
+ *
+ * @param {Number} badge
+ * The badge number. Zero will clean the badge.
+ */
+ updateBadge: function (badge) {
+ var notifications = Windows.UI.Notifications,
+ type = notifications.BadgeTemplateType.badgeNumber,
+ xml = notifications.BadgeUpdateManager.getTemplateContent(type),
+ attrs = xml.getElementsByTagName('badge'),
+ notification = new notifications.BadgeNotification(xml);
+
+ attrs[0].setAttribute('value', badge);
+
+ notifications.BadgeUpdateManager.createBadgeUpdaterForApplication()
+ .update(notification);
+ },
+
+ /**
+ * Updates existing notifications specified by IDs in options.
+ *
+ * @param {Object[]} notifications
+ * Array of local notifications
+ */
+ update: function (notifications) {
+ for (var i = 0; i < notifications.length; i++) {
+ var updates = notifications[i],
+ options = getAll(updates.id || '0')[0];
+
+ this.updateLocalNotification(options, updates);
+ this.fireEvent('update', options);
+ }
+ },
+
+ /**
+ * Updates a single local notification.
+ *
+ * @param {Object} notification
+ * The local notification
+ * @param {Object} updates
+ * Updated properties
+ */
+ updateLocalNotification: function (notification, updates) {
+ for (var key in updates) {
+ notification[key] = updates[key];
+ }
+
+ this.cancelLocalNotification(notification.id);
+ this.scheduleLocalNotification(notification);
+ },
+
+ /**
+ * Clears the specified notifications.
+ *
+ * @param {int[]} ids
+ * List of local notification IDs
+ */
+ clear: function (ids) {
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i],
+ notification = this.getAll([id])[0];
+
+ this.clearLocalNotification(id);
+ this.fireEvent('clear', notification);
+ }
+ },
+
+ /**
+ * Clears the local notification with the given ID.
+ *
+ * @param {String} id
+ * Local notification ID
+ */
+ clearLocalNotification: function (id) {
+ var notification = this.getAll([id])[0];
+
+ try {
+ this.getToastHistory().remove('Toast' + id);
+ } catch (e) {/*Only Phones support the NotificationHistory*/ }
+
+ if (this.isRepeating(notification))
+ return;
+
+ if (this.isTriggered(id) && !this.isScheduled(id)) {
+ this.cancelLocalNotification(id);
+ }
+ },
+
+ /**
+ * Clears all notifications.
+ */
+ clearAll: function () {
+ var ids = this.getTriggeredIds();
+
+ for (var i = 0; i < ids.length; i++) {
+ this.clearLocalNotification(ids[i]);
+ }
+
+ try {
+ this.getToastHistory().clear();
+ } catch (e) {/*Only Phones support the NotificationHistory*/ }
+ this.fireEvent('clearall');
+ },
+
+ /**
+ * Cancels all specified notifications.
+ *
+ * @param {int[]} ids
+ * List of local notification IDs
+ */
+ cancel: function (ids) {
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i],
+ notification = this.getAll([id])[0];
+
+ this.cancelLocalNotification(ids[i]);
+ this.fireEvent('cancel', notification);
+ }
+ },
+
+ /**
+ * Cancels the local notification with the given ID.
+ *
+ * @param {String} id
+ * Local notification ID
+ */
+ cancelLocalNotification: function (id) {
+ var notifier = this.getToastNotifier(),
+ history = this.getToastHistory(),
+ toasts = this.getScheduledToasts();
+
+ try {
+ history.remove('Toast' + id);
+ } catch (e) {/*Only Phones support the NotificationHistory*/ }
+
+ for (var i = 0; i < toasts.length; i++) {
+ var toast = toasts[i];
+
+ if (toast.id == id || toast.id == id + '-2') {
+ notifier.removeFromSchedule(toast);
+ }
+ }
+ },
+
+ /**
+ * Cancels all notifications.
+ */
+ cancelAll: function () {
+ var ids = this.getAllIds();
+
+ for (var i = 0; i < ids.length; i++) {
+ this.cancelLocalNotification(ids[i]);
+ }
+
+ try {
+ this.getToastHistory().clear();
+ } catch (e) {/*Only Phones support the NotificationHistory*/ }
+ this.fireEvent('cancelall');
+ },
+
+ /**
+ * Checks if a notification with an ID is present.
+ *
+ * @param {int} id
+ * Local notification ID
+ */
+ isPresent: function (id) {
+ return !!this.findToastById(id);
+ },
+
+ /**
+ * Checks if a notification with an ID was scheduled.
+ *
+ * @param {int} id
+ * Local notification ID
+ */
+ isScheduled: function (id) {
+ var toast = this.findToastById(id);
+
+ return toast && this.isToastScheduled(toast);
+ },
+
+ /**
+ * Checks if a notification with an ID was triggered.
+ *
+ * @param {int} id
+ * Local notification ID
+ */
+ isTriggered: function (id) {
+ var toast = this.findToastById(id);
+
+ return toast && this.isToastTriggered(toast);
+ },
+
+ /**
+ * Lists all local notification IDs.
+ */
+ getAllIds: function () {
+ var toasts = this.getScheduledToasts(),
+ ids = [];
+
+ for (var i = 0; i < toasts.length; i++) {
+ var toast = toasts[i];
+
+ ids.push(this.getToastId(toast));
+ }
+
+ return ids;
+ },
+
+ /**
+ * Lists all scheduled notification IDs.
+ */
+ getScheduledIds: function () {
+ var toasts = this.getScheduledToasts(),
+ ids = [];
+
+ for (var i = 0; i < toasts.length; i++) {
+ var toast = toasts[i];
+
+ if (!this.isToastScheduled(toast))
+ continue;
+
+ ids.push(this.getToastId(toast));
+ }
+
+ return ids;
+ },
+
+ /**
+ * Lists all scheduled notification IDs.
+ */
+ getTriggeredIds: function () {
+ var toasts = this.getScheduledToasts(),
+ ids = [];
+
+ for (var i = 0; i < toasts.length; i++) {
+ var toast = toasts[i];
+
+ if (!this.isToastTriggered(toast))
+ continue;
+
+ ids.push(this.getToastId(toast));
+ }
+
+ return ids;
+ },
+
+ /**
+ * Property list for given notifications.
+ * If called without IDs, all notification will be returned.
+ *
+ * @param {int[]} ids
+ * List of local notification IDs.
+ * @param {String?} type
+ * Local notification life cycle type
+ */
+ getAll: function (ids, type) {
+ var toasts = this.getScheduledToasts(),
+ notifications = [];
+
+ if (!ids || ids.length === 0) {
+ ids = this.getAllIds();
+ }
+
+ for (var index = 0; index < ids.length; index++) {
+ var id = ids[index],
+ toast = this.findToastById(id);
+
+ if (!toast || type && this.getToastType(toast) != type)
+ continue;
+
+ var json = toast.content.lastChild.lastChild.innerText;
+
+ notifications.push(JSON.parse(json));
+ }
+
+ return notifications;
+ },
+
+ /**
+ * Property list for given notifications.
+ * If called without IDs, all notification will be returned.
+ *
+ * @param {int[]} ids
+ * List of local notification IDs
+ */
+ getScheduled: function (ids) {
+ if (!ids || ids.length === 0) {
+ ids = this.getAllIds();
+ }
+
+ return this.getAll(ids, 'scheduled');
+ },
+
+ /**
+ * Property list for given notifications.
+ * If called without IDs, all notification will be returned.
+ *
+ * @param {int[]} ids
+ * List of local notification IDs
+ */
+ getTriggered: function (ids) {
+ if (!ids || ids.length === 0) {
+ ids = this.getAllIds();
+ }
+
+ return this.getAll(ids, 'triggered');
+ },
+};
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationProxy.js b/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationProxy.js new file mode 100644 index 00000000..2f7ed3d7 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationProxy.js @@ -0,0 +1,311 @@ +/* + Copyright 2013-2015 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. +*/ + +/** + * Executes all queued events. + */ +exports.deviceready = function () { + exports.core.deviceready(); +}; + +/** + * Schedule a new local notification. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {Object[]} notifications + * Array of local notifications + */ +exports.schedule = function (success, error, notifications) { + exports.core.schedule(notifications, 'schedule'); + + success(); +}; + +/** + * Update existing notifications specified by IDs in options. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {Object[]} notifications + * Array of local notifications + */ +exports.update = function (success, error, notifications) { + exports.core.update(notifications); + + success(); +}; + +/** + * Clear the specified notification. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.clear = function (success, error, ids) { + exports.core.clear(ids, true); + + success(); +}; + +/** + * Clear all previously sheduled notifications. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.clearAll = function (success, error) { + exports.core.clearAll(); + + success(); +}; + +/** + * Cancel the specified notifications. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.cancel = function (success, error, ids) { + exports.core.cancel(ids, true); + + success(); +}; + +/** + * Remove all previously registered notifications. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.cancelAll = function (success, error) { + exports.core.cancelAll(); + + success(); +}; + +/** + * Check if a notification with an ID is present. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int} id + * Local notification ID + */ +exports.isPresent = function (success, error, args) { + var found = exports.core.isPresent(args[0]); + + success(found); +}; + +/** + * Check if a notification with an ID is scheduled. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int} id + * Local notification ID + */ +exports.isScheduled = function (success, error, args) { + var found = exports.core.isScheduled(args[0]); + + success(found); +}; + +/** + * Check if a notification with an ID was triggered. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int} id + * Local notification ID + */ +exports.isTriggered = function (success, error, args) { + var found = exports.core.isTriggered(args[0]); + + success(found); +}; + +/** + * List all local notification IDs. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.getAllIds = function (success, error) { + var ids = exports.core.getAllIds(); + + success(ids); +}; + +/** + * List all scheduled notification IDs. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.getScheduledIds = function (success, error) { + var ids = exports.core.getScheduledIds(); + + success(ids); +}; + +/** + * List all triggered notification IDs. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + */ +exports.getTriggeredIds = function (success, error) { + var ids = exports.core.getTriggeredIds(); + + success(ids); +}; + +/** + * Propertys for given notification. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.getSingle = function (success, error, ids) { + var notification = exports.core.getAll(ids)[0]; + + success(notification); +}; + +/** + * Propertys for given scheduled notification. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.getSingleScheduled = function (success, error, ids) { + var notification = exports.core.getScheduled(ids)[0]; + + success(notification); +}; + +/** + * Propertys for given triggered notification. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.getSingleTriggered = function (success, error, ids) { + var notification = exports.core.getTriggered(ids)[0]; + + success(notification); +}; + +/** + * Property list for given notifications. + * If called without IDs, all notification will be returned. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.getAll = function (success, error, ids) { + var notifications = exports.core.getAll(ids); + + success(notifications); +}; + +/** + * Property list for given triggered notifications. + * If called without IDs, all notification will be returned. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.getScheduled = function (success, error, ids) { + var notifications = exports.core.getScheduled(ids); + + success(notifications); +}; + +/** + * Property list for given triggered notifications. + * If called without IDs, all notification will be returned. + * + * @param {Function} success + * Success callback + * @param {Function} error + * Error callback + * @param {int[]} ids + * List of local notification IDs + */ +exports.getTriggered = function (success, error, ids) { + var notifications = exports.core.getTriggered(ids); + + success(notifications); +}; + + +cordova.commandProxy.add('LocalNotification', exports); diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationUtil.js b/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationUtil.js new file mode 100644 index 00000000..4081a0b8 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/src/windows/LocalNotificationUtil.js @@ -0,0 +1,443 @@ +/* + Copyright 2013-2015 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. +*/ + + +exports = require('de.appplant.cordova.plugin.local-notification.LocalNotification.Proxy').core; + +var channel = require('cordova/channel'); + + +/*********** + * MEMBERS * + ***********/ + +// True if App is running, false if suspended +exports.isInBackground = true; + +// Indicates if the device is ready (to receive events) +exports.isReady = false; + +// Queues all events before deviceready +exports.eventQueue = []; + +/******** + * UTIL * + ********/ + +/** + * The repeating interval in milliseconds. + * + * @param {String} interval + * A number or a placeholder like `minute`. + * + * @return {Number} + * Interval in milliseconds + */ +exports.getRepeatInterval = function (every) { + + if (!every) + return 0; + + if (every == 'minute') + return 60000; + + if (every == 'hour') + return 360000; + + if (!NaN(every)) + return parseInt(every) * 60000; + + return 0; +}; + +/** + * If the notification is repeating. + * + * @param {Object} notification + * Local notification object + * + * @return Boolean + */ +exports.isRepeating = function (notification) { + return this.getRepeatInterval(notification.every) !== 0; +}; + +/** + * Parses sound file path. + * + * @param {String} path + * Relative path to sound resource + * + * @return {String} XML Tag for Sound-File + */ +exports.parseSound = function (path) { + if (!path.match(/^file/)) + return ''; + + var uri = this.parseUri(path), + audio = "<audio src=" + uri + " loop='false'/>"; + + return audio; +}; + +/** + * Parses image file path. + * + * @param {String} path + * Relative path to image resource + * + * @return {String} XML-Tag for Image-File + */ +exports.parseImage = function (path) { + if (!path.match(/^file/)) + return ''; + + var uri = this.parseUri(path), + image = "<image id='1' src=" + uri + " />"; + + return image; +}; + +/** + * Parses file path to URI. + * + * @param {String} path + * Relative path to a resource + * + * @return {String} URI to File + */ +exports.parseUri = function (path) { + var pkg = Windows.ApplicationModel.Package.current, + pkgId = pkg.id, + pkgName = pkgId.name; + + var uri = "'ms-appx://" + pkgName + "/www" + path.slice(6, path.length) + "'"; + + return uri; +}; + +/** + * Builds the xml payload for a local notification based on its options. + * + * @param {Object} options + * Local notification properties + * + * @return Windows.Data.Xml.Dom.XmlDocument + */ +exports.build = function (options) { + var template = this.buildToastTemplate(options), + notification = new Windows.Data.Xml.Dom.XmlDocument(); + + try { + notification.loadXml(template); + } catch (e) { + console.error( + 'LocalNotification#schedule', + 'Error loading the xml, check for invalid characters.'); + } + + // Launch Attribute to enable onClick event + var launchAttr = notification.createAttribute('launch'), + toastNode = notification.selectSingleNode('/toast'); + + launchAttr.value = options.id.toString(); + toastNode.attributes.setNamedItem(launchAttr); + + return notification; +}; + +/** + * Builds the toast template with the right style depend on the options. + * + * @param {Object} options + * Local notification properties + * + * @return String + */ +exports.buildToastTemplate = function (options) { + var title = options.title, + message = options.text || '', + json = JSON.stringify(options), + sound = ''; + + if (options.sound && options.sound !== '') { + sound = this.parseSound(options.sound); + } + + var templateName = "ToastText", + imageNode; + if (options.icon && options.icon !== '') { + imageNode = this.parseImage(options.icon); + // template with Image + if (imageNode !== '') { + templateName = "ToastImageAndText"; + } + } else { + imageNode = ""; + } + + var bindingNode; + if (title && title !== '') { + bindingNode = "<binding template='" + templateName + "02'>" + + imageNode + + "<text id='1'>" + title + "</text>" + + "<text id='2'>" + message + "</text>" + + "</binding>"; + } else { + bindingNode = "<binding template='" + templateName + "01'>" + + imageNode + + "<text id='1'>" + message + "</text>" + + "</binding>"; + } + return "<toast>" + + "<visual>" + + bindingNode + + "</visual>" + + sound + + "<json>" + json + "</json>" + + "</toast>"; +}; + +/** + * Short-hand method for the toast notification history. + */ +exports.getToastHistory = function () { + return Windows.UI.Notifications.ToastNotificationManager.history; +}; + +/** + * Gets a toast notifier instance. + * + * @return Object + */ +exports.getToastNotifier = function () { + return Windows.UI.Notifications.ToastNotificationManager + .createToastNotifier(); +}; + +/** + * List of all scheduled toast notifiers. + * + * @return Array + */ +exports.getScheduledToasts = function () { + return this.getToastNotifier().getScheduledToastNotifications(); +}; + +/** + * Gets the Id from the toast notifier. + * + * @param {Object} toast + * A toast notifier object + * + * @return String + */ +exports.getToastId = function (toast) { + var id = toast.id; + + if (id.match(/-2$/)) + return id.match(/^[^-]+/)[0]; + + return id; +}; + +/** + * Gets the notification life cycle type + * (scheduled or triggered) + * + * @param {Object} toast + * A toast notifier object + * + * @return String + */ +exports.getToastType = function (toast) { + return this.isToastTriggered(toast) ? 'triggered' : 'scheduled'; +}; + +/** + * If the toast is already scheduled. + * + * @param {Object} toast + * A toast notifier object + * + * @return Boolean + */ +exports.isToastScheduled = function (toast) { + return !this.isToastTriggered(toast); +}; + +/** + * If the toast is already triggered. + * + * @param {Object} toast + * A toast notifier object + * + * @return Boolean + */ +exports.isToastTriggered = function (toast) { + var id = this.getToastId(toast), + notification = this.getAll([id])[0], + fireDate = new Date((notification.at) * 1000); + + if (this.isRepeating(notification)) + return false; + + return fireDate <= new Date(); +}; + +/** + * Finds the toast by it's ID. + * + * @param {String} id + * Local notification ID + * + * @param Object + */ +exports.findToastById = function (id) { + var toasts = this.getScheduledToasts(); + + for (var i = 0; i < toasts.length; i++) { + var toast = toasts[i]; + + if (this.getToastId(toast) == id) + return toast; + } + + return null; +}; + +/** + * Sets trigger event for local notification. + * + * @param {Object} notification + * Local notification object + * @param {Function} callback + * Callback function + */ +exports.callOnTrigger = function (notification, callback) { + var triggerTime = new Date((notification.at * 1000)), + interval = triggerTime - new Date(); + + if (interval <= 0) { + callback.call(this, notification); + return; + } + + WinJS.Promise.timeout(interval).then(function () { + if (exports.isPresent(notification.id)) { + callback.call(exports, notification); + } + }); +}; + +/** + * Sets trigger event for all scheduled local notification. + * + * @param {Function} callback + * Callback function + */ +exports.callOnTriggerForScheduled = function (callback) { + var notifications = this.getScheduled(); + + for (var i = 0; i < notifications.length; i++) { + this.callOnTrigger(notifications[i], callback); + } +}; + +/** + * The application state - background or foreground. + * + * @return String + */ +exports.getApplicationState = function () { + return this.isInBackground ? 'background' : 'foreground'; +}; + +/** + * Fires the event about a local notification. + * + * @param {String} event + * The event + * @param {Object} notification + * The notification + */ +exports.fireEvent = function (event, notification) { + var plugin = cordova.plugins.notification.local.core, + state = this.getApplicationState(), + args; + + if (notification) { + args = [event, notification, state]; + } else { + args = [event, state]; + } + + if (this.isReady && plugin) { + plugin.fireEvent.apply(plugin, args); + } else { + this.eventQueue.push(args); + } +}; + + +/************** + * LIFE CYCLE * + **************/ + +// Called before 'deviceready' event +channel.onCordovaReady.subscribe(function () { + // Register trigger handler for each scheduled notification + exports.callOnTriggerForScheduled(function (notification) { + this.updateBadge(notification.badge); + this.fireEvent('trigger', notification); + }); +}); + +// Handle onclick event +WinJS.Application.addEventListener('activated', function (args) { + var id = args.detail.arguments, + notification = exports.getAll([id])[0]; + + if (!notification) + return; + + exports.clearLocalNotification(id); + + var repeating = exports.isRepeating(notification); + + exports.fireEvent('click', notification); + exports.fireEvent(repeating ? 'clear' : 'cancel', notification); +}, false); + +// App is running in background +document.addEventListener('pause', function () { + exports.isInBackground = true; +}, false); + +// App is running in foreground +document.addEventListener('resume', function () { + exports.isInBackground = false; +}, false); + +// App is running in foreground +document.addEventListener('deviceready', function () { + exports.isInBackground = false; +}, false); diff --git a/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification-core.js b/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification-core.js new file mode 100644 index 00000000..03faa834 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification-core.js @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +var exec = require('cordova/exec'); + + +/******** + * CORE * + ********/ + +/** + * 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} opts + * 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 + */ +exports.schedule = function (opts, callback, scope) { + this.registerPermission(function(granted) { + + if (!granted) + return; + + var notifications = Array.isArray(opts) ? opts : [opts]; + + for (var i = 0; i < notifications.length; i++) { + var properties = notifications[i]; + + this.mergeWithDefaults(properties); + this.convertProperties(properties); + } + + this.exec('schedule', notifications, callback, scope); + }, this); +}; + +/** + * Update existing notifications specified by IDs in options. + * + * @param {Object} options + * The notification properties to update + * @param {Function} callback + * A function to be called after the notification has been updated + * @param {Object?} scope + * The scope for the callback function + */ +exports.update = function (opts, callback, scope) { + var notifications = Array.isArray(opts) ? opts : [opts]; + + for (var i = 0; i < notifications.length; i++) { + var properties = notifications[i]; + + this.convertProperties(properties); + } + + this.exec('update', notifications, callback, scope); +}; + +/** + * Clear the specified notification. + * + * @param {String} id + * The ID of the notification + * @param {Function} callback + * A function to be called after the notification has been cleared + * @param {Object?} scope + * The scope for the callback function + */ +exports.clear = function (ids, callback, scope) { + ids = Array.isArray(ids) ? ids : [ids]; + ids = this.convertIds(ids); + + this.exec('clear', ids, callback, scope); +}; + +/** + * Clear all previously sheduled notifications. + * + * @param {Function} callback + * A function to be called after all notifications have been cleared + * @param {Object?} scope + * The scope for the callback function + */ +exports.clearAll = function (callback, scope) { + this.exec('clearAll', null, callback, scope); +}; + +/** + * Cancel the specified notifications. + * + * @param {String[]} ids + * The IDs of the notifications + * @param {Function} callback + * A function to be called after the notifications has been canceled + * @param {Object?} scope + * The scope for the callback function + */ +exports.cancel = function (ids, callback, scope) { + ids = Array.isArray(ids) ? ids : [ids]; + ids = this.convertIds(ids); + + this.exec('cancel', ids, callback, scope); +}; + +/** + * Remove 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) { + this.exec('cancelAll', null, callback, scope); +}; + +/** + * Check if a notification with an ID is present. + * + * @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.isPresent = function (id, callback, scope) { + this.exec('isPresent', id || 0, callback, scope); +}; + +/** + * Check if 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) { + this.exec('isScheduled', id || 0, callback, scope); +}; + +/** + * Check if 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) { + 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.exec('getAllIds', null, callback, scope); +}; + +/** + * Alias for `getAllIds`. + */ +exports.getIds = function () { + this.getAllIds.apply(this, arguments); +}; + +/** + * List all scheduled notification IDs. + * + * @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) { + this.exec('getScheduledIds', null, callback, scope); +}; + +/** + * List all triggered notification IDs. + * + * @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) { + this.exec('getTriggeredIds', null, callback, scope); +}; + +/** + * Property list for given local notifications. + * If called without IDs, all notification will be returned. + * + * @param {Number[]?} ids + * Set of notification IDs + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.get = function () { + 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); +}; + +/** + * Property list for all local notifications. + * + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getAll = function (callback, scope) { + this.exec('getAll', null, callback, scope); +}; + +/** + * Property list for given scheduled notifications. + * If called without IDs, all notification will be returned. + * + * @param {Number[]?} ids + * Set of notification IDs + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getScheduled = function () { + 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); +}; + +/** + * Property list for all scheduled notifications. + * + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getAllScheduled = function (callback, scope) { + this.exec('getScheduled', null, callback, scope); +}; + +/** + * Property list for given triggered notifications. + * If called without IDs, all notification will be returned. + * + * @param {Number[]?} ids + * Set of notification IDs + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getTriggered = function () { + 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); +}; + +/** + * Property list for 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.getAllTriggered = function (callback, scope) { + this.exec('getTriggered', null, callback, scope); +}; + +/** + * 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) { + var fn = this.createCallbackFn(callback, scope); + + if (device.platform != 'iOS') { + fn(true); + return; + } + + exec(fn, null, 'LocalNotification', 'registerPermission', []); +}; + + +/********** + * EVENTS * + **********/ + +/** + * 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) { + + 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 event's name + * @param {Function} callback + * The function to be exec as callback + */ +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; + } + } +}; diff --git a/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification-util.js b/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification-util.js new file mode 100644 index 00000000..7e96b64c --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification-util.js @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + +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://icon'; + defaults.smallIcon = 'res://ic_popup_reminder'; + defaults.ongoing = false; + defaults.autoClear = true; + defaults.led = 'FFFFFF'; + 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); + } + + return options; +}; + +/** + * Create 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); + }; +}; + +/** + * 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(); + }); +}); diff --git a/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification.js b/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification.js new file mode 100644 index 00000000..9e9bd284 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.local-notification/www/local-notification.js @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2013-2015 by appPlant UG. All rights reserved. + * + * @APPPLANT_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apache License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://opensource.org/licenses/Apache-2.0/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPPLANT_LICENSE_HEADER_END@ + */ + + +/************* + * INTERFACE * + *************/ + +/** + * Returns the default settings. + * + * @return {Object} + */ +exports.getDefaults = function () { + return this.core.getDefaults(); +}; + +/** + * Overwrite default settings. + * + * @param {Object} defaults + */ +exports.setDefaults = function (defaults) { + this.core.setDefaults(defaults); +}; + +/** + * Schedule a new local notification. + * + * @param {Object} opts + * 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 + */ +exports.schedule = function (opts, callback, scope) { + this.core.schedule(opts, callback, scope); +}; + +/** + * Update existing notifications specified by IDs in options. + * + * @param {Object} options + * The notification properties to update + * @param {Function} callback + * A function to be called after the notification has been updated + * @param {Object?} scope + * The scope for the callback function + */ +exports.update = function (opts, callback, scope) { + this.core.update(opts, callback, scope); +}; + +/** + * Clear the specified notification. + * + * @param {String} id + * The ID of the notification + * @param {Function} callback + * A function to be called after the notification has been cleared + * @param {Object?} scope + * The scope for the callback function + */ +exports.clear = function (ids, callback, scope) { + this.core.clear(ids, callback, scope); +}; + +/** + * Clear all previously sheduled notifications. + * + * @param {Function} callback + * A function to be called after all notifications have been cleared + * @param {Object?} scope + * The scope for the callback function + */ +exports.clearAll = function (callback, scope) { + this.core.clearAll(callback, scope); +}; + +/** + * Cancel the specified notifications. + * + * @param {String[]} ids + * The IDs of the notifications + * @param {Function} callback + * A function to be called after the notifications has been canceled + * @param {Object?} scope + * The scope for the callback function + */ +exports.cancel = function (ids, callback, scope) { + this.core.cancel(ids, callback, scope); +}; + +/** + * Remove 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) { + this.core.cancelAll(callback, scope); +}; + +/** + * Check if a notification with an ID is present. + * + * @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.isPresent = function (id, callback, scope) { + this.core.isPresent(id, callback, scope); +}; + +/** + * Check if 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) { + this.core.isScheduled(id, callback, scope); +}; + +/** + * Check if 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) { + this.core.isTriggered(id, 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); +}; + +/** + * Alias for `getAllIds`. + */ +exports.getIds = function () { + this.getAllIds.apply(this, arguments); +}; + +/** + * List all scheduled notification IDs. + * + * @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) { + this.core.getScheduledIds(callback, scope); +}; + +/** + * List all triggered notification IDs. + * + * @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) { + this.core.getTriggeredIds(callback, scope); +}; + +/** + * Property list for given local notifications. + * If called without IDs, all notification will be returned. + * + * @param {Number[]?} ids + * Set of notification IDs + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.get = function () { + this.core.get.apply(this.core, arguments); +}; + +/** + * Property list for all local notifications. + * + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getAll = function (callback, scope) { + this.core.getAll(callback, scope); +}; + +/** + * Property list for given scheduled notifications. + * If called without IDs, all notification will be returned. + * + * @param {Number[]?} ids + * Set of notification IDs + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getScheduled = function () { + this.core.getScheduled.apply(this.core, arguments); +}; + +/** + * Property list for all scheduled notifications. + * + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getAllScheduled = function (callback, scope) { + this.core.getAllScheduled(callback, scope); +}; + +/** + * Property list for given triggered notifications. + * If called without IDs, all notification will be returned. + * + * @param {Number[]?} ids + * Set of notification IDs + * @param {Function} callback + * A callback function to be called with the list + * @param {Object?} scope + * The scope for the callback function + */ +exports.getTriggered = function () { + this.core.getTriggered.apply(this.core, arguments); +}; + +/** + * Property list for 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.getAllTriggered = function (callback, scope) { + this.core.getAllTriggered(callback, scope); +}; + +/** + * 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) { + this.core.hasPermission(callback, scope); +}; + +/** + * 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) { + this.core.registerPermission(callback, scope); +}; + + +/**************** + * DEPRECATIONS * + ****************/ + +/** + * Schedule a new local notification. + */ +exports.add = function () { + console.warn('Depreated: Please use `notification.local.schedule` instead.'); + + this.schedule.apply(this, arguments); +}; + +/** + * Register permission to show notifications + * if not already granted. + */ +exports.promptForPermission = function () { + console.warn('Depreated: Please use `notification.local.registerPermission` instead.'); + + this.registerPermission.apply(this, arguments); +}; + + +/********** + * EVENTS * + **********/ + +/** + * 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); +}; + +/** + * 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, scope); +}; diff --git a/plugins/fetch.json b/plugins/fetch.json index 3b007112..9c0cfb80 100644 --- a/plugins/fetch.json +++ b/plugins/fetch.json @@ -154,5 +154,39 @@ }, "is_top_level": true, "variables": {} + }, + "de.appplant.cordova.plugin.local-notification": { + "source": { + "type": "git", + "url": "https://github.com/katzer/cordova-plugin-local-notifications.git", + "subdir": "." + }, + "is_top_level": true, + "variables": {} + }, + "cordova-plugin-device": { + "source": { + "type": "registry", + "id": "cordova-plugin-device" + }, + "is_top_level": false, + "variables": {} + }, + "de.appplant.cordova.common.registerusernotificationsettings": { + "source": { + "type": "registry", + "id": "de.appplant.cordova.common.registerusernotificationsettings" + }, + "is_top_level": false, + "variables": {} + }, + "de.appplant.cordova.plugin.badge": { + "source": { + "type": "git", + "url": "https://github.com/katzer/cordova-plugin-badge.git", + "subdir": "." + }, + "is_top_level": true, + "variables": {} } }
\ No newline at end of file diff --git a/plugins/ios.json b/plugins/ios.json index b9fb83d0..633d68fb 100644 --- a/plugins/ios.json +++ b/plugins/ios.json @@ -71,6 +71,14 @@ { "xml": "<feature name=\"LongPressFix\"><param name=\"ios-package\" value=\"LongPressFix\" /><param name=\"onload\" value=\"true\" /></feature>", "count": 1 + }, + { + "xml": "<feature name=\"LocalNotification\"><param name=\"ios-package\" onload=\"true\" value=\"APPLocalNotification\" /><param name=\"onload\" value=\"true\" /></feature>", + "count": 1 + }, + { + "xml": "<feature name=\"Badge\"><param name=\"ios-package\" value=\"APPBadge\" /></feature>", + "count": 1 } ] } @@ -171,7 +179,17 @@ }, "cordova-plugin-websocket": { "PACKAGE_NAME": "com.pliablepixels.zmninja" + }, + "de.appplant.cordova.plugin.local-notification": { + "PACKAGE_NAME": "com.pliablepixels.zmninja" + }, + "de.appplant.cordova.plugin.badge": { + "PACKAGE_NAME": "com.pliablepixels.zmninja" } }, - "dependent_plugins": {} + "dependent_plugins": { + "de.appplant.cordova.common.registerusernotificationsettings": { + "PACKAGE_NAME": "com.pliablepixels.zmninja" + } + } }
\ No newline at end of file diff --git a/www/.DS_Store b/www/.DS_Store Binary files differindex 39cf68c3..1d01a7a1 100644 --- a/www/.DS_Store +++ b/www/.DS_Store diff --git a/www/css/style.css b/www/css/style.css index 47ac0738..94b518fc 100644 --- a/www/css/style.css +++ b/www/css/style.css @@ -461,6 +461,7 @@ input[type=range]::-webkit-slider-thumb { { background: url('../img/background.png') no-repeat center center fixed; background-size: cover; + background-color:#555555; } diff --git a/www/js/DataModel.js b/www/js/DataModel.js index 8c8e84fb..51b2c9a3 100644 --- a/www/js/DataModel.js +++ b/www/js/DataModel.js @@ -16,6 +16,7 @@ angular.module('zmApp.controllers').service('ZMDataModel', $ionicPopup) { var zmAppVersion="unknown"; + var isBackground = false; var monitorsLoaded = 0; var montageSize = 3; var monitors = []; @@ -286,6 +287,14 @@ angular.module('zmApp.controllers').service('ZMDataModel', getAppVersion:function() { return(zmAppVersion); }, + + setBackground:function(val) { + isBackground = val; + }, + + isBackground: function() { + return isBackground; + }, diff --git a/www/js/EventCtrl.js b/www/js/EventCtrl.js index 6ef6c265..e039ad22 100644 --- a/www/js/EventCtrl.js +++ b/www/js/EventCtrl.js @@ -7,7 +7,7 @@ // and whether the new API has a better mechanism angular.module('zmApp.controllers') - .controller('zmApp.EventCtrl', ['$scope', '$rootScope', 'zm', 'ZMDataModel', 'message', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$ionicPlatform', '$ionicSlideBoxDelegate', '$ionicPosition', '$ionicPopover', '$ionicPopup', 'EventServer', function ($scope, $rootScope, zm, ZMDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $ionicPlatform, $ionicSlideBoxDelegate, $ionicPosition, $ionicPopover, $ionicPopup, EventServer) { + .controller('zmApp.EventCtrl', ['$scope', '$rootScope', 'zm', 'ZMDataModel', 'message', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$ionicPlatform', '$ionicSlideBoxDelegate', '$ionicPosition', '$ionicPopover', '$ionicPopup', 'EventServer', '$cordovaBadge', '$cordovaLocalNotification', function ($scope, $rootScope, zm, ZMDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, $ionicPlatform, $ionicSlideBoxDelegate, $ionicPosition, $ionicPopover, $ionicPopup, EventServer, $cordovaBadge, $cordovaLocalNotification) { // events in last 5 minutes // TODO https://server/zm/api/events/consoleEvents/5%20minute.json @@ -734,6 +734,16 @@ angular.module('zmApp.controllers') $scope.$on('$ionicView.enter', function () { console.log("**VIEW ** Events Ctrl Entered"); ZMDataModel.setAwake(false); + //reset badge count + $cordovaBadge.set(0).then(function() { + // You have permission, badge set. + }, function(err) { + ZMDataModel.zmDebug("zmNinja does not have badge permissions. Please check your phone notification settings"); + // You do not have permission. + }); + + $cordovaLocalNotification.clearAll(); + }); $scope.$on('$ionicView.leave', function () { diff --git a/www/js/EventServer.js b/www/js/EventServer.js index ea156234..e5e30f1a 100644 --- a/www/js/EventServer.js +++ b/www/js/EventServer.js @@ -9,11 +9,12 @@ angular.module('zmApp.controllers') .factory('EventServer', -[ 'ZMDataModel', '$rootScope','$websocket', '$ionicPopup', function - ( ZMDataModel, $rootScope, $websocket, $ionicPopup) { +[ 'ZMDataModel', '$rootScope','$websocket', '$ionicPopup', '$cordovaLocalNotification', '$cordovaBadge', function + ( ZMDataModel, $rootScope, $websocket, $ionicPopup,$cordovaLocalNotification, $cordovaBadge) { var ws; + var localNotificationId=5; function init() { @@ -62,21 +63,75 @@ angular.module('zmApp.controllers') ZMDataModel.displayBanner('error',['Event server rejected credentials', 'Please re-check credentials'],2000,6000); } + + var localNotText = "New Alarms:"; if (str.status == 'Success' && str.events) // new events { var eventsToDisplay=[]; for (var iter=0; iter<str.events.length; iter++) { eventsToDisplay.push(str.events[iter].Name+": new event ("+str.events[iter].EventId+")"); + localNotText = localNotText + str.events[iter].Name+","; } + localNotText = localNotText.substring(0, localNotText.length - 1); // lets stack the display so they don't overwrite - if (eventsToDisplay.length > 0) + + if (!ZMDataModel.isBackground()) { - ZMDataModel.displayBanner('alarm', eventsToDisplay, 5000, 5000*eventsToDisplay.length); - + ZMDataModel.zmDebug("App is in foreground, displaying banner"); + if (eventsToDisplay.length > 0) + { + + ZMDataModel.displayBanner('alarm', eventsToDisplay, 5000, 5000*eventsToDisplay.length); + + } } + else + { + ZMDataModel.zmDebug("App is in background, displaying localNotification"); + localNotificationId--; + + if ( localNotificationId == 0) // only slow last 5 + { + localNotificationId = 5; + + } + + if ($cordovaLocalNotification.isPresent(localNotificationId)) + { + $cordovaLocalNotification.clear(localNotificationId); + } + + $cordovaLocalNotification.schedule({ + id: localNotificationId, + title: 'ZoneMinder Alarms', + text: localNotText, + sound:"file://sounds/blop.mp3" + + }).then(function (result) { + // do nothing for now + }); + + + } + // lets set badge of app irrespective of background or foreground + $cordovaBadge.hasPermission().then(function(yes) { + + $cordovaBadge.set($rootScope.alarmCount).then(function() { + // You have permission, badge set. + }, function(err) { + // You do not have permission. + }); + + + // You have permission + }, function(no) { + ZMDataModel.zmDebug("zmNinja does not have badge permissions. Please check your phone notification settings"); + }); + + $rootScope.isAlarm = 1; if ($rootScope.alarmCount == "99") diff --git a/www/js/ModalCtrl.js b/www/js/ModalCtrl.js index dd3e47de..c1ce47a9 100644 --- a/www/js/ModalCtrl.js +++ b/www/js/ModalCtrl.js @@ -203,7 +203,7 @@ angular.module('zmApp.controllers').controller('ModalCtrl', ['$scope', '$rootSco function loadModalNotifications() { - console.log ("Inside Modal timer..."); + //console.log ("Inside Modal timer..."); $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1); } diff --git a/www/js/MontageCtrl.js b/www/js/MontageCtrl.js index 1f95f76a..ac47beaf 100644 --- a/www/js/MontageCtrl.js +++ b/www/js/MontageCtrl.js @@ -216,7 +216,7 @@ angular.module('zmApp.controllers').controller('zmApp.MontageCtrl', ['$scope', ' function loadNotifications() { $rootScope.rand = Math.floor((Math.random() * 100000) + 1); - console.log ("Inside Montage timer..."); + //console.log ("Inside Montage timer..."); } diff --git a/www/js/app.js b/www/js/app.js index 25cca497..113ec21b 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -5,6 +5,7 @@ var appVersion = "0.0.0"; + // core app start stuff angular.module('zmApp', [ 'ionic', @@ -459,7 +460,7 @@ angular.module('zmApp', [ // First run in ionic //------------------------------------------------------------------ -.run(function ($ionicPlatform, $ionicPopup, $rootScope, zm, $state, $stateParams, ZMDataModel, $cordovaSplashscreen, $http, $interval, zmAutoLogin, $fileLogger, $timeout, $ionicHistory, $window, $ionicSideMenuDelegate, EventServer) { +.run(function ($ionicPlatform, $ionicPopup, $rootScope, zm, $state, $stateParams, ZMDataModel, $cordovaSplashscreen, $http, $interval, zmAutoLogin, $fileLogger, $timeout, $ionicHistory, $window, $ionicSideMenuDelegate, EventServer, $cordovaLocalNotification) { $rootScope.zmGlobalCookie = ""; $rootScope.isEventFilterOn = false; @@ -541,6 +542,13 @@ angular.module('zmApp', [ // generates and error in desktops but works fine ZMDataModel.zmLog("Device is ready"); console.log("**** DEVICE READY ***"); + + if (!$cordovaLocalNotification.hasPermission()) + { + ZMDataModel.zmDebug("Prompting for pushnotification permission"); + $cordovaLocalNotification.registerPermission(); + } + $fileLogger.checkFile().then(function (resp) { if (parseInt(resp.size) > zm.logFileMaxSize) { @@ -628,6 +636,7 @@ angular.module('zmApp', [ // from foreground to background and back document.addEventListener("resume", function () { ZMDataModel.zmLog("App is resuming from background"); + ZMDataModel.setBackground(false); // don't animate $ionicHistory.nextViewOptions({ disableAnimate: true, @@ -657,6 +666,7 @@ angular.module('zmApp', [ document.addEventListener("pause", function () { console.log("****The application is going into background"); ZMDataModel.zmLog("App is going into background"); + ZMDataModel.setBackground(true); zmAutoLogin.stop(); if ($rootScope.zmPopup) diff --git a/www/lib/.DS_Store b/www/lib/.DS_Store Binary files differindex 8835fd14..39550787 100644 --- a/www/lib/.DS_Store +++ b/www/lib/.DS_Store diff --git a/www/sounds/blop.mp3 b/www/sounds/blop.mp3 Binary files differnew file mode 100755 index 00000000..28a62449 --- /dev/null +++ b/www/sounds/blop.mp3 |
