├── .gitignore ├── LICENSE ├── README.md ├── android ├── .classpath ├── .project ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── staltz │ └── reactnativeandroidlocalnotification │ ├── Notification.java │ ├── NotificationAttributes.java │ ├── NotificationEventReceiver.java │ ├── NotificationModule.java │ ├── NotificationPackage.java │ ├── NotificationPublisher.java │ ├── RCTNotificationManager.java │ └── SystemBootEventReceiver.java ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea/ 5 | .DS_Store 6 | build/ 7 | /captures 8 | .externalNativeBuild 9 | /android/.settings/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Original Work Copyright (c) 2016 Layman 4 | Modified Work Copyright (c) 2018, Staltz.com 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Local Android Notifications 2 | 3 | Forked from [https://github.com/anysome/react-native-pure-notification](https://github.com/anysome/react-native-pure-notification) to update its dependencies. 4 | 5 | --- 6 | 7 | ```js 8 | import React, { DeviceEventEmitter } from 'react-native'; 9 | import Notification from 'react-native-android-local-notification'; 10 | 11 | // Send a simple notification 12 | Notification.create({ subject: 'Hey', message: 'Yo! Hello world.' }); 13 | 14 | // Listen to notification-clicking events 15 | Notification.addListener('press', function(e) { 16 | console.log(e); 17 | }); 18 | 19 | // Custom payload for notifications 20 | Notification.create({ 21 | subject: 'Notification With Payload', 22 | message: 'This is a notification that contains custom payload.', 23 | payload: { number: 1, what: true, someAnswer: '42' } 24 | }); 25 | 26 | // Receive the payload on notification events 27 | Notification.addListener('press', function(e) { 28 | console.log(e.payload); // => { number: 1, what: true, someAnswer: '42' } 29 | }); 30 | 31 | // Customize notification 32 | Notification.create({ 33 | subject: 'Notification With Custom Icon', 34 | message: 'This is a notification with a specified icon.', 35 | smallIcon: 'ic_alert' 36 | }); 37 | 38 | // Scheduled notifications 39 | Notification.create({ 40 | subject: 'Scheduled Notification', 41 | message: 'This notification will show on every Friday morning at 8:30 AM, starts at 2015/9/9 and end after 10 times.', 42 | sendAt: new Date(2015, 9, 9, 8, 30), 43 | repeatEvery: 'week', 44 | count: 10 45 | }); 46 | ``` 47 | 48 | ## Installation 49 | 50 | - For React Native 0.60 or higher, use this package's version 3.0.0 or higher 51 | - For React Native 0.59 or lower, use this package's version 2.0.0 or lower 52 | 53 | - Run `npm install react-native-android-local-notification --save` to install using npm. 54 | 55 | - Add the following two lines to `android/settings.gradle`: 56 | 57 | ```gradle 58 | include ':react-native-android-local-notification' 59 | project(':react-native-android-local-notification').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-android-local-notification/android') 60 | ``` 61 | 62 | - Edit `android/app/build.gradle` and add the annoated lines as below: 63 | 64 | ```gradle 65 | ... 66 | 67 | dependencies { 68 | compile fileTree(dir: "libs", include: ["*.jar"]) 69 | compile "com.android.support:appcompat-v7:23.0.1" 70 | compile "com.facebook.react:react-native:0.16.+" 71 | compile project(':react-native-android-local-notification') // <- Add this line 72 | } 73 | ``` 74 | 75 | - Edit `android/app/src/main/AndroidManifest.xml` and add the annoated lines as below: 76 | 77 | ```xml 78 | 80 | 81 | 82 | 83 | 84 | 85 | 90 | 91 | ... 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ``` 105 | 106 | > The `RECEIVE_BOOT_COMPLETED` permission is used to re-register all scheduled notifications after reboot. 107 | > Requesting `VIBRATE` permission is required if you want to make the device vibrate while sending notifications. 108 | 109 | - Edit `MainActivity.java` (usually at `android/app/src/main/java/com//MainActivity.java`) and add the annoated lines as below: 110 | 111 | ```java 112 | ... 113 | 114 | import android.content.Intent; 115 | import android.os.Bundle; 116 | 117 | import com.facebook.react.ReactActivity; 118 | import com.facebook.react.ReactPackage; 119 | import com.facebook.react.shell.MainReactPackage; 120 | 121 | import com.staltz.reactnativeandroidlocalnotification.NotificationPackage; // <- Add this line 122 | 123 | public class MainApplication extends Application implements ReactApplication { 124 | 125 | ... 126 | 127 | @Override 128 | protected List getPackages() { 129 | return Arrays.asList( 130 | ... 131 | new NotificationPackage() // <- Add this line 132 | ); 133 | } 134 | 135 | ... 136 | ``` 137 | 138 | ## Usage 139 | 140 | ### Creating Notifications 141 | 142 | Just do: 143 | 144 | ```js 145 | Notification.create({ 146 | id: 1337, 147 | subject: 'Notification With Payload', 148 | message: 'This is a notification that contains custom payload.', 149 | smallIcon: 'ic_launcher', 150 | autoClear: true, 151 | payload: { number: 1, what: true, someAnswer: '42' } 152 | }); 153 | ``` 154 | 155 | > All functions of this module will return [promise](https://www.promisejs.org/)s with the notification object handing in. So you can get the data of the notification and do anything that is needed, like this: 156 | > 157 | > ```js 158 | > Notification.create({ message: 'Testing.' }).then(function(notification) { 159 | > console.log(notification); 160 | > console.log(notification.id); 161 | > }); 162 | > ``` 163 | 164 | All available options on a notification are listed below: 165 | 166 | #### Basic 167 | 168 | **id (`number`)** 169 | The unique ID of this notification. It will be randomly chosen if not specified. 170 | 171 | **subject (`string`)** 172 | The notification subject. Defaults to the application name on Android. 173 | 174 | **message (`string`)** 175 | The message showen in the notification. 176 | 177 | **action (`string`)** 178 | An action name that can be used to determine what to do when this notification is clicked. Defaults to `DEFAULT`. 179 | 180 | **payload (`object`)** 181 | A custom payload object. It can be retrieved on events of this notification. Defaults to `{}`. 182 | 183 | 184 | #### Scheduling 185 | 186 | **delay (`number`)** 187 | Milliseconds to delay before showing this notification after it is created. Useful when creating countdown alarms, reminders, etc. Note that it cannot be used with `sendAt`. 188 | 189 | **sendAt (`Date`)** 190 | Schedule this notification to show on a specified time. Note that it cannot be used with `delay`. 191 | 192 | **repeatEvery (`string` or `number`)** 193 | Must use with `sendAt`. Schedule this notification to repeat. Can be `minute`, `hour`, `halfDay`, `day`, `week`, `month`, `year` or a number of time in milliseconds. 194 | 195 | **repeatCount (`number`)** 196 | Must use with `sendAt` and `repeatEvery`. End repeating this notification after n times. Note that it cannot be used with `endAt`. 197 | 198 | **endAt (`Date`)** 199 | Must use with `sendAt` and `repeatEvery`. End repeating this notification after a specified time. Note that it cannot be used with `repeatCount`. 200 | 201 | 202 | > Some Samples of Scheduled Notifications 203 | > 204 | > ```js 205 | > Notification.create({ 206 | > subject: 'Scheduled Notification', 207 | > message: 'This notification will show on every Friday morning at 8:30 AM, starts at 2015/9/9 and end after 10 times.', 208 | > sendAt: new Date(2015, 9, 9, 8, 30), 209 | > repeatEvery: 'week', 210 | > repeatCount: 10 211 | > }); 212 | > ``` 213 | > 214 | > ```js 215 | > Notification.create({ 216 | > subject: 'Scheduled Notification', 217 | > message: 'This notification will show on 2015/9/9 morning at 8:30 AM, and repeat for 10 times every minute.', 218 | > sendAt: new Date(2015, 9, 9, 8, 30), 219 | > repeatEvery: 60000, 220 | > repeatCount: 10 221 | > }); 222 | > ``` 223 | > 224 | > ```js 225 | > Notification.create({ 226 | > subject: 'Delayed Notification', 227 | > message: 'This notification will show after 10 seconds, even the app has been stoped.', 228 | > delay: 10000 229 | > }); 230 | > ``` 231 | 232 | #### Customization 233 | 234 | **priority (`number`)** 235 | Priority of this notification, can be `-2`, `-1`, `0`, `1`, `2`. When this is set to `1` or `2`, heads-up notification will be more likely to show on Android 5+. Defaults to `1`. 236 | 237 | **smallIcon (`string`)** 238 | The icon (file name) to show. This icon must be placed in the project's `android/app/src/main/res/mipmap-*` folder. Defaults to `ic_launcher`. 239 | 240 | **largeIcon (`string`)** 241 | Not yet implemented. 242 | 243 | **sound (`string`)** 244 | Set the sound to play. Defaults to `default` as using the default notification sound, or set this to `null` to disable the sound. Other options are not yet implemented. 245 | 246 | **vibrate (`string`)** 247 | Set the vibration pattern to use. Defaults to `default` as using the default notification vibrate, or set this to `null` to disable the vibrate. Other options are not yet implemented. 248 | 249 | **lights (`string`)** 250 | Set the desired color for the indicator LED on the device. Defaults to `default` as using the default notification lights, or set this to `null` to disable the lights. Other options are not yet implemented. 251 | 252 | **autoClear (`boolean`)** 253 | Clear this notification automatically after the user clicks on it. Defaults to `true`. 254 | 255 | **onlyAlertOnce (`boolean`)** 256 | Do not let the sound, vibrate and ticker to be played if the notification is already showing. 257 | 258 | **tickerText (`string`)** 259 | Set the text to show on ticker. Defaults to `: `. Set this to `null` to disable ticker. 260 | 261 | **when (`Date`)** 262 | Add a timestamp pertaining to the notification (usually the time the event occurred). 263 | 264 | **bigText (`string`)** 265 | Set the text to be shown when the user expand the notification. 266 | 267 | **bigStyleImageBase64 (`string`)** 268 | Set the image in base64 to be shown when the user expand the notification. if bigText is not null, it have priority over bigStyleImageBase64. 269 | 270 | **bigStyleUrlImage (`string`)** 271 | Set URL of a image. Geting it by open a stream connection and it be shown when the user expand the notification.. if bigText is not null, it have priority over bigStyleUrlImage 272 | 273 | **subText (`string`)** 274 | Set the third line of text in the platform notification template. Note that it cannot be used with `progress`. 275 | 276 | **progress (`number`)** 277 | Set the progress this notification represents, range: `0.0` ~ `1.0`. Set this to a number lower then zero to get an indeterminate progress. Note that it cannot be used with `subText`. 278 | 279 | **color (`string`)** 280 | Color to be applied by the standard Style templates when presenting this notification. 281 | 282 | **number (`number`)** 283 | Set a number on the notification. 284 | 285 | **private (`boolean`)** 286 | Not yet implemented. 287 | 288 | **ongoing (`boolean`)** 289 | Not yet implemented. 290 | 291 | **category (`string`)** 292 | Set the notification category, e.g.: `alarm`, `call`, `email`, `event`, `progress`, `reminder`, `social`. It may be used by the Android system for ranking and filtering. 293 | 294 | **localOnly (`boolean`)** 295 | Set whether or not this notification should not bridge to other devices. 296 | 297 | ### Handle Notification Click Event 298 | 299 | Register a listener on `sysNotificationClick` events to handle notification clicking: 300 | 301 | ```js 302 | Notification.addListener('press', function(e) { 303 | console.log(e); 304 | }); 305 | ``` 306 | 307 | The action and payload of the notification can be retrieved on these events: 308 | 309 | ```js 310 | Notification.send({ message: 'Message', action: 'ACTION_NAME', payload: { data: 'Anything' } }); 311 | ``` 312 | 313 | ```js 314 | Notification.addListener('press', function(e) { 315 | switch (e.action) { 316 | case 'ACTION_NAME': 317 | console.log(`Action Triggered! Data: ${e.payload.data}`); 318 | break; 319 | 320 | case 'ANOTHER_ACTION_NAME': 321 | console.log(`Another Action Triggered! Data: ${e.payload.data}`); 322 | break; 323 | } 324 | }); 325 | ``` 326 | 327 | Once you no longer need to listen to `sysNotificationClick` events de-register the listener functions with: 328 | 329 | ```js 330 | Notification.removeAllListeners('press'); 331 | ``` 332 | 333 | ### Manage Scheduled Notifications 334 | 335 | Sometimes you'll need to get the scheduled notifications (which has `delay` or `sendAt` set up) that you had created before. You can use `Notification.getIDs()` to retrieve an array of IDs of available (i.e. will be send in the future) scheduled notifications. 336 | 337 | ```js 338 | Notification.getIDs().then(function(ids) { 339 | console.log(ids); // Array of ids 340 | }); 341 | ``` 342 | 343 | and use `Notification.find(notificationID)` to get data of an notification. 344 | 345 | ```js 346 | Notification.find(notificationID).then(function(notification) { 347 | console.log(notification); 348 | }); 349 | ``` 350 | 351 | or just cancel it with `Notification.delete(notificationID)`: 352 | 353 | ```js 354 | Notification.delete(notificationID); 355 | ``` 356 | 357 | Want to cancel all scheduled notifications set by your app? Sure: 358 | 359 | ```js 360 | Notification.deleteAll(); 361 | ``` 362 | 363 | > To update a scheduled notification, just use `Notification.create()` with the same id. 364 | 365 | ### Clearing Notifications 366 | 367 | When you want to clear a notification from the system statusbar, just use: 368 | 369 | ```js 370 | Notification.clearAll(); 371 | ``` 372 | 373 | or: 374 | 375 | ```js 376 | Notification.clear(notificationID); 377 | ``` 378 | -------------------------------------------------------------------------------- /android/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonSlurper 2 | 3 | def computeVersionName() { 4 | // dynamically retrieve version from package.json 5 | def slurper = new JsonSlurper() 6 | def json = slurper.parse(file('../package.json'), "utf-8") 7 | return json.version 8 | } 9 | 10 | buildscript { 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | 16 | dependencies { 17 | classpath 'com.android.tools.build:gradle:3.4.0' 18 | } 19 | } 20 | 21 | apply plugin: 'com.android.library' 22 | 23 | def safeExtGet(prop, fallback) { 24 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 25 | } 26 | 27 | def DEFAULT_COMPILE_SDK_VERSION = 23 28 | def DEFAULT_MIN_SDK_VERSION = 16 29 | def DEFAULT_SUPPORT_LIB_VERSION = '26.1.0' 30 | def DEFAULT_TARGET_SDK_VERSION = 23 31 | 32 | android { 33 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 34 | 35 | defaultConfig { 36 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 37 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 38 | versionCode 1 39 | versionName computeVersionName() 40 | } 41 | lintOptions { 42 | abortOnError false 43 | } 44 | } 45 | 46 | repositories { 47 | mavenCentral() 48 | } 49 | 50 | dependencies { 51 | implementation 'androidx.appcompat:appcompat:1.0.0' 52 | implementation 'com.google.code.gson:gson:+' 53 | implementation 'com.facebook.react:react-native:+' 54 | } 55 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staltz/react-native-android-local-notification/02e6064012e57b1eb958889868f73094ee1fff6a/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/Notification.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.os.Build; 6 | import android.os.SystemClock; 7 | import android.app.PendingIntent; 8 | import android.app.AlarmManager; 9 | import android.app.NotificationManager; 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.content.SharedPreferences; 13 | import androidx.annotation.Nullable; 14 | import android.net.Uri; 15 | 16 | import java.lang.System; 17 | import java.net.URL; 18 | 19 | import com.google.gson.Gson; 20 | 21 | import android.util.Base64; 22 | import androidx.core.app.NotificationCompat; 23 | import android.text.Html; 24 | import android.util.Log; 25 | import android.graphics.Color; 26 | 27 | /** 28 | * An object-oriented Wrapper class around the system notification class. 29 | * 30 | * Each instance is an representation of a single, or a set of scheduled 31 | * notifications. It handles operations like showing, canceling and clearing. 32 | */ 33 | public class Notification { 34 | private Context context; 35 | private int id; 36 | private NotificationAttributes attributes; 37 | 38 | /** 39 | * Constructor. 40 | */ 41 | public Notification(Context context, int id, @Nullable NotificationAttributes attributes) { 42 | this.context = context; 43 | this.id = id; 44 | this.attributes = attributes; 45 | } 46 | 47 | /** 48 | * Public context getter. 49 | */ 50 | public Context getContext() { 51 | return context; 52 | } 53 | 54 | /** 55 | * Public attributes getter. 56 | */ 57 | public NotificationAttributes getAttributes() { 58 | return attributes; 59 | } 60 | 61 | /** 62 | * Create the notification, show it now or set the schedule. 63 | */ 64 | public Notification create() { 65 | setAlarmAndSaveOrShow(); 66 | 67 | Log.i("ReactSystemNotification", "Notification Created: " + id); 68 | 69 | return this; 70 | } 71 | 72 | /** 73 | * Update the notification, resets its schedule. 74 | */ 75 | public Notification update(NotificationAttributes notificationAttributes) { 76 | delete(); 77 | attributes = notificationAttributes; 78 | setAlarmAndSaveOrShow(); 79 | 80 | return this; 81 | } 82 | 83 | /** 84 | * Clear the notification from the status bar. 85 | */ 86 | public Notification clear() { 87 | getSysNotificationManager().cancel(id); 88 | 89 | Log.i("ReactSystemNotification", "Notification Cleared: " + id); 90 | 91 | return this; 92 | } 93 | 94 | /** 95 | * Cancel the notification. 96 | */ 97 | public Notification delete() { 98 | getSysNotificationManager().cancel(id); 99 | 100 | if (attributes.delayed || attributes.scheduled) { 101 | cancelAlarm(); 102 | } 103 | 104 | deleteFromPreferences(); 105 | 106 | Log.i("ReactSystemNotification", "Notification Deleted: " + id); 107 | 108 | return this; 109 | } 110 | 111 | /** 112 | * Build the notification. 113 | */ 114 | public android.app.Notification build() { 115 | androidx.core.app.NotificationCompat.Builder notificationBuilder = new androidx.core.app.NotificationCompat.Builder( 116 | context); 117 | 118 | notificationBuilder.setContentTitle(attributes.subject).setContentText(attributes.message) 119 | .setSmallIcon( 120 | context.getResources().getIdentifier(attributes.smallIcon, "mipmap", context.getPackageName())) 121 | .setAutoCancel(attributes.autoClear).setContentIntent(getContentIntent()); 122 | 123 | if (attributes.priority != null) { 124 | notificationBuilder.setPriority(attributes.priority); 125 | } 126 | 127 | if (attributes.largeIcon != null) { 128 | int largeIconId = context.getResources().getIdentifier(attributes.largeIcon, "drawable", 129 | context.getPackageName()); 130 | Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), largeIconId); 131 | notificationBuilder.setLargeIcon(largeIcon); 132 | } 133 | 134 | if (attributes.group != null) { 135 | notificationBuilder.setGroup(attributes.group); 136 | notificationBuilder.setGroupSummary(true); 137 | } 138 | 139 | if (attributes.inboxStyle) { 140 | 141 | androidx.core.app.NotificationCompat.InboxStyle inboxStyle = new androidx.core.app.NotificationCompat.InboxStyle(); 142 | 143 | if (attributes.inboxStyleBigContentTitle != null) { 144 | inboxStyle.setBigContentTitle(attributes.inboxStyleBigContentTitle); 145 | } 146 | if (attributes.inboxStyleSummaryText != null) { 147 | inboxStyle.setSummaryText(attributes.inboxStyleSummaryText); 148 | } 149 | if (attributes.inboxStyleLines != null) { 150 | for (int i = 0; i < attributes.inboxStyleLines.size(); i++) { 151 | inboxStyle.addLine(Html.fromHtml(attributes.inboxStyleLines.get(i))); 152 | } 153 | } 154 | notificationBuilder.setStyle(inboxStyle); 155 | 156 | Log.i("ReactSystemNotification", "set inbox style!!"); 157 | 158 | } else { 159 | 160 | int defaults = 0; 161 | if ("default".equals(attributes.sound)) { 162 | defaults = defaults | android.app.Notification.DEFAULT_SOUND; 163 | } 164 | if ("default".equals(attributes.vibrate)) { 165 | defaults = defaults | android.app.Notification.DEFAULT_VIBRATE; 166 | } 167 | if ("default".equals(attributes.lights)) { 168 | defaults = defaults | android.app.Notification.DEFAULT_LIGHTS; 169 | } 170 | notificationBuilder.setDefaults(defaults); 171 | 172 | } 173 | 174 | if (attributes.onlyAlertOnce != null) { 175 | notificationBuilder.setOnlyAlertOnce(attributes.onlyAlertOnce); 176 | } 177 | 178 | if (attributes.tickerText != null) { 179 | notificationBuilder.setTicker(attributes.tickerText); 180 | } 181 | 182 | if (attributes.when != null) { 183 | notificationBuilder.setWhen(attributes.when); 184 | notificationBuilder.setShowWhen(true); 185 | } 186 | 187 | // if bigText is not null, it have priority over bigStyleImageBase64 188 | if (attributes.bigText != null) { 189 | notificationBuilder 190 | .setStyle(new androidx.core.app.NotificationCompat.BigTextStyle().bigText(attributes.bigText)); 191 | } else if (attributes.bigStyleUrlImage != null && !attributes.bigStyleUrlImage.equals("")) { 192 | 193 | Bitmap bigPicture = null; 194 | 195 | try { 196 | 197 | Log.i("ReactSystemNotification", "start to get image from URL : " + attributes.bigStyleUrlImage); 198 | URL url = new URL(attributes.bigStyleUrlImage); 199 | bigPicture = BitmapFactory.decodeStream(url.openStream()); 200 | Log.i("ReactSystemNotification", "finishing to get image from URL"); 201 | 202 | } catch (Exception e) { 203 | Log.e("ReactSystemNotification", "Error when getting image from URL" + e.getStackTrace()); 204 | } 205 | 206 | if (bigPicture != null) { 207 | notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bigPicture)); 208 | } 209 | } else if (attributes.bigStyleImageBase64 != null) { 210 | 211 | Bitmap bigPicture = null; 212 | 213 | try { 214 | 215 | Log.i("ReactSystemNotification", "start to convert bigStyleImageBase64 to bitmap"); 216 | // Convert base64 image to Bitmap 217 | byte[] bitmapAsBytes = Base64.decode(attributes.bigStyleImageBase64.getBytes(), Base64.DEFAULT); 218 | bigPicture = BitmapFactory.decodeByteArray(bitmapAsBytes, 0, bitmapAsBytes.length); 219 | Log.i("ReactSystemNotification", "finished to convert bigStyleImageBase64 to bitmap"); 220 | 221 | } catch (Exception e) { 222 | Log.e("ReactSystemNotification", "Error when converting base 64 to Bitmap" + e.getStackTrace()); 223 | } 224 | 225 | if (bigPicture != null) { 226 | notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bigPicture)); 227 | } 228 | } 229 | 230 | if (attributes.color != null) { 231 | notificationBuilder.setColor(Color.parseColor(attributes.color)); 232 | } 233 | 234 | if (attributes.subText != null) { 235 | notificationBuilder.setSubText(attributes.subText); 236 | } 237 | 238 | if (attributes.progress != null) { 239 | if (attributes.progress < 0 || attributes.progress > 1000) { 240 | notificationBuilder.setProgress(1000, 100, true); 241 | } else { 242 | notificationBuilder.setProgress(1000, attributes.progress, false); 243 | } 244 | } 245 | 246 | if (attributes.number != null) { 247 | notificationBuilder.setNumber(attributes.number); 248 | } 249 | 250 | if (attributes.localOnly != null) { 251 | notificationBuilder.setLocalOnly(attributes.localOnly); 252 | } 253 | 254 | if (attributes.sound != null) { 255 | notificationBuilder.setSound(Uri.parse(attributes.sound)); 256 | } 257 | 258 | return notificationBuilder.build(); 259 | } 260 | 261 | /** 262 | * Show the notification now. 263 | */ 264 | public void show() { 265 | getSysNotificationManager().notify(id, build()); 266 | 267 | Log.i("ReactSystemNotification", "Notification Show: " + id); 268 | } 269 | 270 | /** 271 | * Setup alarm or show the notification. 272 | */ 273 | public void setAlarmAndSaveOrShow() { 274 | if (attributes.delayed) { 275 | setDelay(); 276 | saveAttributesToPreferences(); 277 | 278 | } else if (attributes.scheduled) { 279 | setSchedule(); 280 | saveAttributesToPreferences(); 281 | 282 | } else { 283 | show(); 284 | } 285 | } 286 | 287 | /** 288 | * Schedule the delayed notification. 289 | */ 290 | public void setDelay() { 291 | PendingIntent pendingIntent = getScheduleNotificationIntent(); 292 | 293 | long futureInMillis = SystemClock.elapsedRealtime() + attributes.delay; 294 | getAlarmManager().set(AlarmManager.ELAPSED_REALTIME_WAKEUP, futureInMillis, pendingIntent); 295 | 296 | Log.i("ReactSystemNotification", 297 | "Notification Delay Alarm Set: " + id + ", Repeat Type: " + attributes.repeatType + ", Current Time: " 298 | + System.currentTimeMillis() + ", Delay: " + attributes.delay); 299 | } 300 | 301 | /** 302 | * Schedule the notification. 303 | */ 304 | public void setSchedule() { 305 | PendingIntent pendingIntent = getScheduleNotificationIntent(); 306 | 307 | if (attributes.repeatType == null) { 308 | getAlarmManager().set(AlarmManager.RTC_WAKEUP, attributes.sendAt, pendingIntent); 309 | Log.i("ReactSystemNotification", "Set One-Time Alarm: " + id); 310 | 311 | } else { 312 | switch (attributes.repeatType) { 313 | case "time": 314 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, attributes.repeatTime, 315 | pendingIntent); 316 | Log.i("ReactSystemNotification", "Set " + attributes.repeatTime + "ms Alarm: " + id); 317 | break; 318 | 319 | case "minute": 320 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, 60000, pendingIntent); 321 | Log.i("ReactSystemNotification", "Set Minute Alarm: " + id); 322 | break; 323 | 324 | case "hour": 325 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, AlarmManager.INTERVAL_HOUR, 326 | pendingIntent); 327 | Log.i("ReactSystemNotification", "Set Hour Alarm: " + id); 328 | break; 329 | 330 | case "halfDay": 331 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, 332 | AlarmManager.INTERVAL_HALF_DAY, pendingIntent); 333 | Log.i("ReactSystemNotification", "Set Half-Day Alarm: " + id); 334 | break; 335 | 336 | case "day": 337 | case "week": 338 | case "month": 339 | case "year": 340 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, AlarmManager.INTERVAL_DAY, 341 | pendingIntent); 342 | Log.i("ReactSystemNotification", "Set Day Alarm: " + id + ", Type: " + attributes.repeatType); 343 | break; 344 | 345 | default: 346 | getAlarmManager().set(AlarmManager.RTC_WAKEUP, attributes.sendAt, pendingIntent); 347 | Log.i("ReactSystemNotification", "Set One-Time Alarm: " + id); 348 | break; 349 | } 350 | } 351 | 352 | Log.i("ReactSystemNotification", 353 | "Notification Schedule Alarm Set: " + id + ", Repeat Type: " + attributes.repeatType 354 | + ", Current Time: " + System.currentTimeMillis() + ", First Send At: " + attributes.sendAt); 355 | } 356 | 357 | /** 358 | * Cancel the delayed notification. 359 | */ 360 | public void cancelAlarm() { 361 | PendingIntent pendingIntent = getScheduleNotificationIntent(); 362 | getAlarmManager().cancel(pendingIntent); 363 | 364 | Log.i("ReactSystemNotification", "Notification Alarm Canceled: " + id); 365 | } 366 | 367 | public void saveAttributesToPreferences() { 368 | SharedPreferences.Editor editor = getSharedPreferences().edit(); 369 | 370 | String attributesJSONString = new Gson().toJson(attributes); 371 | 372 | editor.putString(Integer.toString(id), attributesJSONString); 373 | 374 | if (Build.VERSION.SDK_INT < 9) { 375 | editor.commit(); 376 | } else { 377 | editor.apply(); 378 | } 379 | 380 | Log.i("ReactSystemNotification", "Notification Saved To Pref: " + id + ": " + attributesJSONString); 381 | } 382 | 383 | public void loadAttributesFromPreferences() { 384 | String attributesJSONString = getSharedPreferences().getString(Integer.toString(id), null); 385 | this.attributes = (NotificationAttributes) new Gson().fromJson(attributesJSONString, 386 | NotificationAttributes.class); 387 | 388 | Log.i("ReactSystemNotification", "Notification Loaded From Pref: " + id + ": " + attributesJSONString); 389 | } 390 | 391 | public void deleteFromPreferences() { 392 | SharedPreferences.Editor editor = getSharedPreferences().edit(); 393 | 394 | editor.remove(Integer.toString(id)); 395 | 396 | if (Build.VERSION.SDK_INT < 9) { 397 | editor.commit(); 398 | } else { 399 | editor.apply(); 400 | } 401 | 402 | Log.i("ReactSystemNotification", "Notification Deleted From Pref: " + id); 403 | } 404 | 405 | private NotificationManager getSysNotificationManager() { 406 | return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 407 | } 408 | 409 | private AlarmManager getAlarmManager() { 410 | return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 411 | } 412 | 413 | private SharedPreferences getSharedPreferences() { 414 | return RCTNotificationManager.getSharedPreferences(context); 415 | } 416 | 417 | private PendingIntent getContentIntent() { 418 | Intent intent = new Intent(context, NotificationEventReceiver.class); 419 | 420 | intent.putExtra(NotificationEventReceiver.NOTIFICATION_ID, id); 421 | intent.putExtra(NotificationEventReceiver.ACTION, attributes.action); 422 | intent.putExtra(NotificationEventReceiver.PAYLOAD, attributes.payload); 423 | 424 | return PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT); 425 | } 426 | 427 | private PendingIntent getScheduleNotificationIntent() { 428 | Intent notificationIntent = new Intent(context, NotificationPublisher.class); 429 | notificationIntent.putExtra(NotificationPublisher.NOTIFICATION_ID, id); 430 | 431 | return PendingIntent.getBroadcast(context, id, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/NotificationAttributes.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import com.facebook.react.bridge.ReadableArray; 4 | import com.facebook.react.bridge.ReadableMap; 5 | import com.facebook.react.bridge.WritableArray; 6 | import com.facebook.react.bridge.WritableMap; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class NotificationAttributes { 11 | public Integer id; 12 | public String subject; 13 | public String message; 14 | public String action; 15 | public String payload; 16 | 17 | public Boolean delayed; 18 | public Integer delay; 19 | 20 | public Boolean scheduled; 21 | public Long sendAt; 22 | public Integer sendAtYear; 23 | public Integer sendAtMonth; 24 | public Integer sendAtDay; 25 | public Integer sendAtWeekDay; 26 | public Integer sendAtHour; 27 | public Integer sendAtMinute; 28 | 29 | public String repeatEvery; 30 | public String repeatType; 31 | public Integer repeatTime; 32 | public Integer repeatCount; 33 | public Long endAt; 34 | 35 | public Integer priority; 36 | public String smallIcon; 37 | public String largeIcon; 38 | public String sound; 39 | public String vibrate; 40 | public String lights; 41 | public Boolean autoClear; 42 | public Boolean onlyAlertOnce; 43 | public String tickerText; 44 | public Long when; 45 | public String bigText; 46 | public String bigStyleUrlImage; 47 | public String bigStyleImageBase64; 48 | public String subText; 49 | public Integer progress; 50 | public Integer lifetime; 51 | public Integer progressEnd; 52 | public String color; 53 | public Integer number; 54 | public String category; 55 | public Boolean localOnly; 56 | 57 | public Boolean inboxStyle; 58 | public String inboxStyleBigContentTitle; 59 | public String inboxStyleSummaryText; 60 | public ArrayList inboxStyleLines; 61 | 62 | public String group; 63 | 64 | public void loadFromReadableMap(ReadableMap readableMap) { 65 | if (readableMap.hasKey("id")) 66 | id = readableMap.getInt("id"); 67 | if (readableMap.hasKey("subject")) 68 | subject = readableMap.getString("subject"); 69 | if (readableMap.hasKey("message")) 70 | message = readableMap.getString("message"); 71 | if (readableMap.hasKey("action")) 72 | action = readableMap.getString("action"); 73 | if (readableMap.hasKey("payload")) 74 | payload = readableMap.getString("payload"); 75 | 76 | if (readableMap.hasKey("delayed")) 77 | delayed = readableMap.getBoolean("delayed"); 78 | if (readableMap.hasKey("delay")) 79 | delay = readableMap.getInt("delay"); 80 | 81 | if (readableMap.hasKey("scheduled")) 82 | scheduled = readableMap.getBoolean("scheduled"); 83 | if (readableMap.hasKey("sendAt")) 84 | sendAt = Long.parseLong(readableMap.getString("sendAt")); 85 | if (readableMap.hasKey("sendAtYear")) 86 | sendAtYear = readableMap.getInt("sendAtYear"); 87 | if (readableMap.hasKey("sendAtMonth")) 88 | sendAtMonth = readableMap.getInt("sendAtMonth"); 89 | if (readableMap.hasKey("sendAtDay")) 90 | sendAtDay = readableMap.getInt("sendAtDay"); 91 | if (readableMap.hasKey("sendAtWeekDay")) 92 | sendAtWeekDay = readableMap.getInt("sendAtWeekDay"); 93 | if (readableMap.hasKey("sendAtHour")) 94 | sendAtHour = readableMap.getInt("sendAtHour"); 95 | if (readableMap.hasKey("sendAtMinute")) 96 | sendAtMinute = readableMap.getInt("sendAtMinute"); 97 | 98 | if (readableMap.hasKey("repeatEvery")) 99 | repeatEvery = readableMap.getString("repeatEvery"); 100 | if (readableMap.hasKey("repeatType")) 101 | repeatType = readableMap.getString("repeatType"); 102 | if (readableMap.hasKey("repeatTime")) 103 | repeatTime = readableMap.getInt("repeatTime"); 104 | if (readableMap.hasKey("repeatCount")) 105 | repeatCount = readableMap.getInt("repeatCount"); 106 | if (readableMap.hasKey("endAt")) 107 | endAt = Long.parseLong(readableMap.getString("endAt")); 108 | 109 | if (readableMap.hasKey("priority")) 110 | priority = readableMap.getInt("priority"); 111 | if (readableMap.hasKey("smallIcon")) 112 | smallIcon = readableMap.getString("smallIcon"); 113 | if (readableMap.hasKey("largeIcon")) 114 | largeIcon = readableMap.getString("largeIcon"); 115 | if (readableMap.hasKey("sound")) 116 | sound = readableMap.getString("sound"); 117 | if (readableMap.hasKey("vibrate")) 118 | vibrate = readableMap.getString("vibrate"); 119 | if (readableMap.hasKey("lights")) 120 | lights = readableMap.getString("lights"); 121 | if (readableMap.hasKey("autoClear")) 122 | autoClear = readableMap.getBoolean("autoClear"); 123 | else 124 | autoClear = true; 125 | if (readableMap.hasKey("onlyAlertOnce")) 126 | onlyAlertOnce = readableMap.getBoolean("onlyAlertOnce"); 127 | if (readableMap.hasKey("tickerText")) 128 | tickerText = readableMap.getString("tickerText"); 129 | if (readableMap.hasKey("when")) 130 | when = Long.parseLong(readableMap.getString("when")); 131 | if (readableMap.hasKey("bigText")) 132 | bigText = readableMap.getString("bigText"); 133 | if (readableMap.hasKey("bigStyleUrlImage")) 134 | bigStyleUrlImage = readableMap.getString("bigStyleUrlImage"); 135 | if (readableMap.hasKey("bigStyleImageBase64")) 136 | bigStyleImageBase64 = readableMap.getString("bigStyleImageBase64"); 137 | if (readableMap.hasKey("subText")) 138 | subText = readableMap.getString("subText"); 139 | if (readableMap.hasKey("progress")) 140 | progress = readableMap.getInt("progress"); 141 | if (readableMap.hasKey("progressEnd")) 142 | progressEnd = readableMap.getInt("progressEnd"); 143 | if (readableMap.hasKey("lifetime")) 144 | lifetime = readableMap.getInt("lifetime"); 145 | 146 | if (readableMap.hasKey("color")) 147 | color = readableMap.getString("color"); 148 | if (readableMap.hasKey("number")) 149 | number = readableMap.getInt("number"); 150 | if (readableMap.hasKey("category")) 151 | category = readableMap.getString("category"); 152 | if (readableMap.hasKey("localOnly")) 153 | localOnly = readableMap.getBoolean("localOnly"); 154 | if (readableMap.hasKey("group")) 155 | group = readableMap.getString("group"); 156 | 157 | if (readableMap.hasKey("inboxStyle")) { 158 | inboxStyle = true; 159 | ReadableMap inboxStyleMap = readableMap.getMap("inboxStyle"); 160 | 161 | inboxStyleBigContentTitle = inboxStyleMap.getString("bigContentTitle"); 162 | inboxStyleSummaryText = inboxStyleMap.getString("summaryText"); 163 | 164 | ReadableArray inboxLines = inboxStyleMap.getArray("lines"); 165 | if (inboxLines != null) { 166 | inboxStyleLines = new ArrayList<>(); 167 | for (int i = 0; i < inboxLines.size(); i++) { 168 | inboxStyleLines.add(inboxLines.getString(i)); 169 | } 170 | } 171 | } else { 172 | inboxStyle = false; 173 | } 174 | 175 | } 176 | 177 | public ReadableMap asReadableMap() { 178 | WritableMap writableMap = new com.facebook.react.bridge.WritableNativeMap(); 179 | 180 | if (id != null) 181 | writableMap.putInt("id", id); 182 | if (subject != null) 183 | writableMap.putString("subject", subject); 184 | if (message != null) 185 | writableMap.putString("message", message); 186 | if (action != null) 187 | writableMap.putString("action", action); 188 | if (payload != null) 189 | writableMap.putString("payload", payload); 190 | 191 | if (delayed != null) 192 | writableMap.putBoolean("delayed", delayed); 193 | if (delay != null) 194 | writableMap.putInt("delay", delay); 195 | 196 | if (scheduled != null) 197 | writableMap.putBoolean("scheduled", scheduled); 198 | if (sendAt != null) 199 | writableMap.putString("sendAt", Long.toString(sendAt)); 200 | if (sendAtYear != null) 201 | writableMap.putInt("sendAtYear", sendAtYear); 202 | if (sendAtMonth != null) 203 | writableMap.putInt("sendAtMonth", sendAtMonth); 204 | if (sendAtDay != null) 205 | writableMap.putInt("sendAtDay", sendAtDay); 206 | if (sendAtWeekDay != null) 207 | writableMap.putInt("sendAtWeekDay", sendAtWeekDay); 208 | if (sendAtHour != null) 209 | writableMap.putInt("sendAtHour", sendAtHour); 210 | if (sendAtMinute != null) 211 | writableMap.putInt("sendAtMinute", sendAtMinute); 212 | 213 | if (repeatEvery != null) 214 | writableMap.putString("repeatEvery", repeatEvery); 215 | if (repeatType != null) 216 | writableMap.putString("repeatType", repeatType); 217 | if (repeatTime != null) 218 | writableMap.putInt("repeatTime", repeatTime); 219 | if (repeatCount != null) 220 | writableMap.putInt("repeatCount", repeatCount); 221 | if (endAt != null) 222 | writableMap.putString("endAt", Long.toString(endAt)); 223 | 224 | if (priority != null) 225 | writableMap.putInt("priority", priority); 226 | if (smallIcon != null) 227 | writableMap.putString("smallIcon", smallIcon); 228 | if (largeIcon != null) 229 | writableMap.putString("largeIcon", largeIcon); 230 | if (sound != null) 231 | writableMap.putString("sound", sound); 232 | if (vibrate != null) 233 | writableMap.putString("vibrate", vibrate); 234 | if (lights != null) 235 | writableMap.putString("lights", lights); 236 | if (autoClear != null) 237 | writableMap.putBoolean("autoClear", autoClear); 238 | if (onlyAlertOnce != null) 239 | writableMap.putBoolean("onlyAlertOnce", onlyAlertOnce); 240 | if (tickerText != null) 241 | writableMap.putString("tickerText", tickerText); 242 | if (when != null) 243 | writableMap.putString("when", Long.toString(when)); 244 | if (bigText != null) 245 | writableMap.putString("bigText", bigText); 246 | if (bigStyleImageBase64 != null) 247 | writableMap.putString("bigStyleImageBase64", bigStyleImageBase64); 248 | if (bigStyleUrlImage != null) 249 | writableMap.putString("bigStyleImageBase64", bigStyleUrlImage); 250 | if (subText != null) 251 | writableMap.putString("subText", subText); 252 | if (progress != null) 253 | writableMap.putInt("progress", progress); 254 | if (color != null) 255 | writableMap.putString("color", color); 256 | if (number != null) 257 | writableMap.putInt("number", number); 258 | if (category != null) 259 | writableMap.putString("category", category); 260 | if (localOnly != null) 261 | writableMap.putBoolean("localOnly", localOnly); 262 | if (group != null) 263 | writableMap.putString("group", group); 264 | 265 | if (progressEnd != null) 266 | writableMap.putInt("progressEnd", progressEnd); 267 | if (lifetime != null) 268 | writableMap.putInt("lifetime", lifetime); 269 | 270 | if (inboxStyle) { 271 | 272 | WritableMap inboxStyle = new com.facebook.react.bridge.WritableNativeMap(); 273 | if (inboxStyleBigContentTitle != null) 274 | inboxStyle.putString("bigContentTitle", inboxStyleBigContentTitle); 275 | if (inboxStyleSummaryText != null) 276 | inboxStyle.putString("summaryText", inboxStyleSummaryText); 277 | 278 | if (inboxStyleLines != null) { 279 | WritableArray inboxLines = new com.facebook.react.bridge.WritableNativeArray(); 280 | for (int i = 0; i < inboxStyleLines.size(); i++) { 281 | inboxLines.pushString(inboxStyleLines.get(i)); 282 | } 283 | inboxStyle.putArray("lines", inboxLines); 284 | } 285 | 286 | writableMap.putMap("inboxStyle", inboxStyle); 287 | } 288 | 289 | return writableMap; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/NotificationEventReceiver.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import android.content.ComponentName; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.app.ActivityManager; 7 | import android.app.ActivityManager.RunningAppProcessInfo; 8 | import android.content.Intent; 9 | import android.content.Context; 10 | import android.content.BroadcastReceiver; 11 | 12 | import java.util.List; 13 | 14 | import android.util.Log; 15 | 16 | /** 17 | * Handles user's interaction on notifications. 18 | * 19 | * Sends broadcast to the application, launches the app if needed. 20 | */ 21 | public class NotificationEventReceiver extends BroadcastReceiver { 22 | final static String NOTIFICATION_ID = "id"; 23 | final static String ACTION = "action"; 24 | final static String PAYLOAD = "payload"; 25 | 26 | public void onReceive(Context context, Intent intent) { 27 | Bundle extras = intent.getExtras(); 28 | 29 | Log.i("ReactSystemNotification", "NotificationEventReceiver: Recived: " + extras.getString(ACTION) 30 | + ", Notification ID: " + extras.getInt(NOTIFICATION_ID) + ", payload: " + extras.getString(PAYLOAD)); 31 | 32 | // If the application is not running or is not in foreground, start it with the 33 | // notification 34 | // passed in 35 | if (!applicationIsRunning(context)) { 36 | String packageName = context.getApplicationContext().getPackageName(); 37 | Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); 38 | 39 | launchIntent.putExtra("initialSysNotificationId", extras.getInt(NOTIFICATION_ID)); 40 | launchIntent.putExtra("initialSysNotificationAction", extras.getString(ACTION)); 41 | launchIntent.putExtra("initialSysNotificationPayload", extras.getString(PAYLOAD)); 42 | launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 43 | 44 | context.startActivity(launchIntent); 45 | Log.i("ReactSystemNotification", "NotificationEventReceiver: Launching: " + packageName); 46 | } else { 47 | sendBroadcast(context, extras); // If the application is already running in foreground, send a brodcast too 48 | } 49 | } 50 | 51 | private void sendBroadcast(Context context, Bundle extras) { 52 | Intent brodcastIntent = new Intent("NotificationEvent"); 53 | 54 | brodcastIntent.putExtra("id", extras.getInt(NOTIFICATION_ID)); 55 | brodcastIntent.putExtra("action", extras.getString(ACTION)); 56 | brodcastIntent.putExtra("payload", extras.getString(PAYLOAD)); 57 | 58 | context.sendBroadcast(brodcastIntent); 59 | Log.v("ReactSystemNotification", 60 | "NotificationEventReceiver: Broadcast Sent: NotificationEvent: " + extras.getString(ACTION) 61 | + ", Notification ID: " + extras.getInt(NOTIFICATION_ID) + ", payload: " 62 | + extras.getString(PAYLOAD)); 63 | } 64 | 65 | private boolean applicationIsRunning(Context context) { 66 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 67 | 68 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) { 69 | List processInfos = activityManager.getRunningAppProcesses(); 70 | for (RunningAppProcessInfo processInfo : processInfos) { 71 | if (processInfo.processName.equals(context.getApplicationContext().getPackageName())) { 72 | if (processInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { 73 | for (String d : processInfo.pkgList) { 74 | Log.v("ReactSystemNotification", "NotificationEventReceiver: ok: " + d); 75 | return true; 76 | } 77 | } 78 | } 79 | } 80 | } else { 81 | List taskInfo = activityManager.getRunningTasks(1); 82 | ComponentName componentInfo = taskInfo.get(0).topActivity; 83 | if (componentInfo.getPackageName().equals(context.getPackageName())) { 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/NotificationModule.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import android.os.Bundle; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.content.BroadcastReceiver; 8 | import android.app.Activity; 9 | 10 | import com.facebook.react.modules.core.DeviceEventManagerModule; 11 | import com.facebook.react.bridge.ReactApplicationContext; 12 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 13 | import com.facebook.react.bridge.ReactMethod; 14 | import com.facebook.react.bridge.Arguments; 15 | import com.facebook.react.bridge.Callback; 16 | import com.facebook.react.bridge.ReadableMap; 17 | import com.facebook.react.bridge.WritableMap; 18 | import com.facebook.react.bridge.ReadableArray; 19 | import com.facebook.react.bridge.WritableArray; 20 | import com.facebook.react.bridge.WritableNativeArray; 21 | 22 | import java.util.ArrayList; 23 | import android.util.Log; 24 | 25 | /** 26 | * The main React native module. 27 | * 28 | * Provides JS accessible API, bridge Java and JavaScript. 29 | */ 30 | public class NotificationModule extends ReactContextBaseJavaModule { 31 | 32 | public Context mContext = null; 33 | public RCTNotificationManager mNotificationManager = null; 34 | 35 | @Override 36 | public String getName() { 37 | return "NotificationModule"; 38 | } 39 | 40 | /** 41 | * Constructor. 42 | */ 43 | public NotificationModule(ReactApplicationContext reactContext) { 44 | super(reactContext); 45 | 46 | this.mContext = reactContext; 47 | this.mNotificationManager = new RCTNotificationManager(reactContext); 48 | 49 | listenNotificationEvent(); 50 | } 51 | 52 | /** 53 | * React method to create or update a notification. 54 | */ 55 | @ReactMethod 56 | public void rCreate(Integer notificationID, ReadableMap notificationAttributes, Callback errorCallback, 57 | Callback successCallback) { 58 | try { 59 | NotificationAttributes a = getNotificationAttributesFromReadableMap(notificationAttributes); 60 | Notification n = mNotificationManager.createOrUpdate(notificationID, a); 61 | 62 | successCallback.invoke(n.getAttributes().asReadableMap()); 63 | 64 | } catch (Exception e) { 65 | errorCallback.invoke(e.getMessage()); 66 | Log.e("ReactSystemNotification", "NotificationModule: rCreate Error: " + Log.getStackTraceString(e)); 67 | } 68 | } 69 | 70 | /** 71 | * React method to get all notification ids. 72 | */ 73 | @ReactMethod 74 | public void rGetIDs(Callback errorCallback, Callback successCallback) { 75 | try { 76 | ArrayList ids = mNotificationManager.getIDs(); 77 | WritableArray rids = new WritableNativeArray(); 78 | 79 | for (Integer id : ids) { 80 | rids.pushInt(id); 81 | } 82 | 83 | successCallback.invoke((ReadableArray) rids); 84 | 85 | } catch (Exception e) { 86 | errorCallback.invoke(e.getMessage()); 87 | Log.e("ReactSystemNotification", "NotificationModule: rGetIDs Error: " + Log.getStackTraceString(e)); 88 | } 89 | } 90 | 91 | /** 92 | * React method to get data of a notification. 93 | */ 94 | @ReactMethod 95 | public void rFind(Integer notificationID, Callback errorCallback, Callback successCallback) { 96 | try { 97 | Notification n = mNotificationManager.find(notificationID); 98 | successCallback.invoke(n.getAttributes().asReadableMap()); 99 | 100 | } catch (Exception e) { 101 | errorCallback.invoke(e.getMessage()); 102 | Log.e("ReactSystemNotification", "NotificationModule: rFind Error: " + Log.getStackTraceString(e)); 103 | } 104 | } 105 | 106 | /** 107 | * React method to delete (i.e. cancel a scheduled) notification. 108 | */ 109 | @ReactMethod 110 | public void rDelete(int notificationID, Callback errorCallback, Callback successCallback) { 111 | try { 112 | Notification n = mNotificationManager.delete(notificationID); 113 | 114 | successCallback.invoke(n.getAttributes().asReadableMap()); 115 | 116 | } catch (Exception e) { 117 | errorCallback.invoke(e.getMessage()); 118 | Log.e("ReactSystemNotification", "NotificationModule: rDelete Error: " + Log.getStackTraceString(e)); 119 | } 120 | } 121 | 122 | /** 123 | * React method to delete (i.e. cancel a scheduled) notification. 124 | */ 125 | @ReactMethod 126 | public void rDeleteAll(Callback errorCallback, Callback successCallback) { 127 | try { 128 | ArrayList ids = mNotificationManager.getIDs(); 129 | 130 | for (Integer id : ids) { 131 | try { 132 | mNotificationManager.delete(id); 133 | } catch (Exception e) { 134 | Log.e("ReactSystemNotification", 135 | "NotificationModule: rDeleteAll Error: " + Log.getStackTraceString(e)); 136 | } 137 | } 138 | 139 | successCallback.invoke(); 140 | 141 | } catch (Exception e) { 142 | errorCallback.invoke(e.getMessage()); 143 | Log.e("ReactSystemNotification", "NotificationModule: rDeleteAll Error: " + Log.getStackTraceString(e)); 144 | } 145 | } 146 | 147 | /** 148 | * React method to clear a notification. 149 | */ 150 | @ReactMethod 151 | public void rClear(int notificationID, Callback errorCallback, Callback successCallback) { 152 | try { 153 | Notification n = mNotificationManager.clear(notificationID); 154 | 155 | successCallback.invoke(n.getAttributes().asReadableMap()); 156 | 157 | } catch (Exception e) { 158 | errorCallback.invoke(e.getMessage()); 159 | Log.e("ReactSystemNotification", "NotificationModule: rClear Error: " + Log.getStackTraceString(e)); 160 | } 161 | } 162 | 163 | /** 164 | * React method to clear all notifications of this app. 165 | */ 166 | @ReactMethod 167 | public void rClearAll(Callback errorCallback, Callback successCallback) { 168 | try { 169 | mNotificationManager.clearAll(); 170 | successCallback.invoke(); 171 | 172 | } catch (Exception e) { 173 | errorCallback.invoke(e.getMessage()); 174 | Log.e("ReactSystemNotification", "NotificationModule: rClearAll Error: " + Log.getStackTraceString(e)); 175 | } 176 | } 177 | 178 | @ReactMethod 179 | public void rGetApplicationName(Callback errorCallback, Callback successCallback) { 180 | try { 181 | int stringId = getReactApplicationContext().getApplicationInfo().labelRes; 182 | successCallback.invoke(getReactApplicationContext().getString(stringId)); 183 | 184 | } catch (Exception e) { 185 | errorCallback.invoke(e.getMessage()); 186 | Log.e("ReactSystemNotification", 187 | "NotificationModule: rGetApplicationName Error: " + Log.getStackTraceString(e)); 188 | } 189 | } 190 | 191 | /** 192 | * Emit JavaScript events. 193 | */ 194 | private void sendEvent(String eventName, Object params) { 195 | getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, 196 | params); 197 | 198 | Log.i("ReactSystemNotification", "NotificationModule: sendEvent (to JS): " + eventName); 199 | } 200 | 201 | @ReactMethod 202 | public void getInitialSysNotification(Callback cb) { 203 | final Activity activity = getCurrentActivity(); 204 | 205 | if (activity == null) { 206 | return; 207 | } 208 | 209 | Intent intent = activity.getIntent(); 210 | Bundle extras = intent.getExtras(); 211 | 212 | if (extras != null) { 213 | Integer initialSysNotificationId = extras.getInt("initialSysNotificationId"); 214 | if (initialSysNotificationId != null) { 215 | cb.invoke(initialSysNotificationId, extras.getString("initialSysNotificationAction"), 216 | extras.getString("initialSysNotificationPayload")); 217 | return; 218 | } 219 | } 220 | } 221 | 222 | @ReactMethod 223 | public void removeInitialSysNotification() { 224 | final Activity activity = getCurrentActivity(); 225 | 226 | if (activity == null) { 227 | return; 228 | } 229 | 230 | activity.getIntent().removeExtra("initialSysNotificationId"); 231 | activity.getIntent().removeExtra("initialSysNotificationAction"); 232 | activity.getIntent().removeExtra("initialSysNotificationPayload"); 233 | } 234 | 235 | private NotificationAttributes getNotificationAttributesFromReadableMap(ReadableMap readableMap) { 236 | NotificationAttributes notificationAttributes = new NotificationAttributes(); 237 | 238 | notificationAttributes.loadFromReadableMap(readableMap); 239 | 240 | return notificationAttributes; 241 | } 242 | 243 | private void listenNotificationEvent() { 244 | IntentFilter intentFilter = new IntentFilter("NotificationEvent"); 245 | 246 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() { 247 | @Override 248 | public void onReceive(Context context, Intent intent) { 249 | 250 | Bundle extras = intent.getExtras(); 251 | 252 | WritableMap params = Arguments.createMap(); 253 | params.putInt("notificationID", extras.getInt(NotificationEventReceiver.NOTIFICATION_ID)); 254 | params.putString("action", extras.getString(NotificationEventReceiver.ACTION)); 255 | params.putString("payload", extras.getString(NotificationEventReceiver.PAYLOAD)); 256 | 257 | sendEvent("sysModuleNotificationClick", params); 258 | } 259 | }, intentFilter); 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/NotificationPackage.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * The React package. 14 | */ 15 | public class NotificationPackage implements ReactPackage { 16 | public NotificationPackage() { 17 | } 18 | 19 | @Override 20 | public List createNativeModules(ReactApplicationContext reactContext) { 21 | List modules = new ArrayList<>(); 22 | 23 | modules.add(new NotificationModule(reactContext)); 24 | return modules; 25 | } 26 | 27 | @Override 28 | public List createViewManagers(ReactApplicationContext reactContext) { 29 | return Collections.emptyList(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/NotificationPublisher.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import java.lang.System; 8 | import java.util.Calendar; 9 | import android.util.Log; 10 | 11 | /** 12 | * Publisher for scheduled notifications. 13 | */ 14 | public class NotificationPublisher extends BroadcastReceiver { 15 | 16 | final static String NOTIFICATION_ID = "notificationId"; 17 | 18 | @Override 19 | public void onReceive(Context context, Intent intent) { 20 | int id = intent.getIntExtra(NOTIFICATION_ID, 0); 21 | long currentTime = System.currentTimeMillis(); 22 | Log.i("ReactSystemNotification", 23 | "NotificationPublisher: Prepare To Publish: " + id + ", Now Time: " + currentTime); 24 | 25 | RCTNotificationManager notificationManager = new RCTNotificationManager(context); 26 | Notification notification = notificationManager.find(id); 27 | 28 | if (notification.getAttributes() != null) { 29 | 30 | // Delete notifications that are out-dated 31 | if (notification.getAttributes().endAt != null && notification.getAttributes().endAt < currentTime) { 32 | notification.cancelAlarm(); 33 | notification.deleteFromPreferences(); 34 | 35 | // Show and delete one-time notifications 36 | } else if (notification.getAttributes().repeatType == null) { 37 | notification.show(); 38 | notification.cancelAlarm(); 39 | notification.deleteFromPreferences(); 40 | 41 | // Special conditions for weekly based notifications 42 | } else if (notification.getAttributes().repeatType.equals("week")) { 43 | Calendar calendar = Calendar.getInstance(); 44 | int day = calendar.get(Calendar.DAY_OF_WEEK); 45 | day = day - 1; 46 | if (notification.getAttributes().sendAtWeekDay == day) 47 | notification.show(); 48 | 49 | // Special conditions for monthly based notifications 50 | } else if (notification.getAttributes().repeatType.equals("month")) { 51 | Calendar calendar = Calendar.getInstance(); 52 | int day = calendar.get(Calendar.DAY_OF_MONTH); 53 | if (notification.getAttributes().sendAtDay == day) 54 | notification.show(); 55 | 56 | // Special conditions for yearly based notifications 57 | } else if (notification.getAttributes().repeatType.equals("year")) { 58 | Calendar calendar = Calendar.getInstance(); 59 | int day = calendar.get(Calendar.DAY_OF_MONTH); 60 | int month = calendar.get(Calendar.MONTH); 61 | if (notification.getAttributes().sendAtDay == day && notification.getAttributes().sendAtMonth == month) 62 | notification.show(); 63 | 64 | // Other repeating notifications - just show them 65 | } else { 66 | notification.show(); 67 | } 68 | 69 | if (notification.getAttributes().delayed || !notification.getAttributes().scheduled) { 70 | notification.deleteFromPreferences(); 71 | } 72 | 73 | } else { 74 | notification.cancelAlarm(); 75 | notification.deleteFromPreferences(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/RCTNotificationManager.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.app.NotificationManager; 6 | import java.util.ArrayList; 7 | import java.util.Set; 8 | 9 | import android.util.Log; 10 | 11 | /** 12 | * A high level notification manager 13 | * 14 | * Warps the system notification API to make managing direct and scheduled 15 | * notification easy. 16 | */ 17 | public class RCTNotificationManager { 18 | final static String PREFERENCES_KEY = "ReactNativeSystemNotification"; 19 | public Context context = null; 20 | public SharedPreferences sharedPreferences = null; 21 | 22 | /** 23 | * Constructor. 24 | */ 25 | public RCTNotificationManager(Context context) { 26 | this.context = context; 27 | this.sharedPreferences = getSharedPreferences(context); 28 | } 29 | 30 | static SharedPreferences getSharedPreferences(Context context) { 31 | return context.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE); 32 | } 33 | 34 | /** 35 | * Create a notification. 36 | */ 37 | public Notification create(Integer notificationID, NotificationAttributes notificationAttributes) { 38 | Notification notification = new Notification(context, notificationID, notificationAttributes); 39 | 40 | notification.create(); 41 | 42 | return notification; 43 | } 44 | 45 | /** 46 | * Create or update (if exists) a notification. 47 | */ 48 | public Notification createOrUpdate(Integer notificationID, NotificationAttributes notificationAttributes) { 49 | if (getIDs().contains(notificationID)) { 50 | Notification notification = find(notificationID); 51 | 52 | notification.update(notificationAttributes); 53 | return notification; 54 | 55 | } else { 56 | return create(notificationID, notificationAttributes); 57 | } 58 | } 59 | 60 | /** 61 | * Get all notification ids. 62 | */ 63 | public ArrayList getIDs() { 64 | Set keys = sharedPreferences.getAll().keySet(); 65 | ArrayList ids = new ArrayList<>(); 66 | 67 | for (String key : keys) { 68 | try { 69 | ids.add(Integer.parseInt(key)); 70 | // TODO: Delete out-dated notifications BTW 71 | } catch (Exception e) { 72 | Log.e("ReactSystemNotification", "RCTNotificationManager: getIDs Error: " + Log.getStackTraceString(e)); 73 | } 74 | } 75 | 76 | return ids; 77 | } 78 | 79 | /** 80 | * Get a notification by its id. 81 | */ 82 | public Notification find(Integer notificationID) { 83 | Notification notification = new Notification(context, notificationID, null); 84 | 85 | if (notification.getAttributes() == null) 86 | notification.loadAttributesFromPreferences(); 87 | 88 | return notification; 89 | } 90 | 91 | /** 92 | * Delete a notification by its id. 93 | */ 94 | public Notification delete(Integer notificationID) { 95 | return find(notificationID).delete(); 96 | } 97 | 98 | /** 99 | * Clear a notification by its id. 100 | */ 101 | public Notification clear(Integer notificationID) { 102 | return find(notificationID).clear(); 103 | } 104 | 105 | /** 106 | * Clear all notifications. 107 | */ 108 | public void clearAll() { 109 | NotificationManager systemNotificationManager = (NotificationManager) context 110 | .getSystemService(Context.NOTIFICATION_SERVICE); 111 | systemNotificationManager.cancelAll(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /android/src/main/java/com/staltz/reactnativeandroidlocalnotification/SystemBootEventReceiver.java: -------------------------------------------------------------------------------- 1 | package com.staltz.reactnativeandroidlocalnotification; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import java.util.ArrayList; 8 | 9 | import android.util.Log; 10 | 11 | /** 12 | * Set alarms for scheduled notification after system reboot. 13 | */ 14 | public class SystemBootEventReceiver extends BroadcastReceiver { 15 | 16 | @Override 17 | public void onReceive(Context context, Intent intent) { 18 | Log.i("ReactSystemNotification", "SystemBootEventReceiver: Setting system alarms"); 19 | 20 | if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { 21 | RCTNotificationManager notificationManager = new RCTNotificationManager(context); 22 | 23 | ArrayList ids = notificationManager.getIDs(); 24 | 25 | for (Integer id : ids) { 26 | try { 27 | Notification notification = notificationManager.find(id); 28 | 29 | if (notification.getAttributes() != null) { 30 | notification.cancelAlarm(); 31 | notification.setAlarmAndSaveOrShow(); 32 | Log.i("ReactSystemNotification", 33 | "SystemBootEventReceiver: Alarm set for: " + notification.getAttributes().id); 34 | } 35 | } catch (Exception e) { 36 | Log.e("ReactSystemNotification", "SystemBootEventReceiver: onReceive Error: " + e.getMessage()); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | var { DeviceEventEmitter } = React; 5 | 6 | var NotificationModule = require('react-native').NativeModules.NotificationModule; 7 | 8 | // Warp the native module so we can do some pre/post processing to have a cleaner API. 9 | var Notification = { 10 | create: function(attributes = {}) { 11 | return new Promise(function(resolve, reject) { 12 | NotificationModule.rGetApplicationName(function(e) {}, function(applicationName) { 13 | 14 | // Set defaults 15 | if (!attributes.subject) attributes.subject = applicationName; 16 | attributes = encodeNativeNotification(attributes); 17 | 18 | NotificationModule.rCreate(attributes.id, attributes, reject, function(notification) { 19 | resolve(decodeNativeNotification(notification)); 20 | }); 21 | }); 22 | }); 23 | }, 24 | 25 | getIDs: function() { 26 | return new Promise(function(resolve, reject) { 27 | NotificationModule.rGetIDs(reject, resolve); 28 | }); 29 | }, 30 | 31 | find: function(id) { 32 | return new Promise(function(resolve, reject) { 33 | NotificationModule.rFind(id, reject, function(notification) { 34 | resolve(decodeNativeNotification(notification)); 35 | }); 36 | }); 37 | }, 38 | 39 | delete: function(id) { 40 | return new Promise(function(resolve, reject) { 41 | NotificationModule.rDelete(id, reject, function(notification) { 42 | resolve(decodeNativeNotification(notification)); 43 | }); 44 | }); 45 | }, 46 | 47 | deleteAll: function() { 48 | return new Promise(function(resolve, reject) { 49 | NotificationModule.rDeleteAll(reject, resolve); 50 | }); 51 | }, 52 | 53 | clear: function(id) { 54 | return new Promise(function(resolve, reject) { 55 | NotificationModule.rClear(id, reject, function(notification) { 56 | resolve(decodeNativeNotification(notification)); 57 | }); 58 | }); 59 | }, 60 | 61 | clearAll: function() { 62 | return new Promise(function(resolve, reject) { 63 | NotificationModule.rClearAll(reject, resolve); 64 | }); 65 | }, 66 | 67 | addListener: function(type, listener) { 68 | switch (type) { 69 | case 'press': 70 | case 'click': 71 | DeviceEventEmitter.addListener('sysNotificationClick', listener); 72 | 73 | NotificationModule.getInitialSysNotification(function(initialSysNotificationId, 74 | initialSysNotificationAction, 75 | initialSysNotificationPayload) { 76 | if (initialSysNotificationId) { 77 | var event = { 78 | action: initialSysNotificationAction, 79 | payload: JSON.parse(initialSysNotificationPayload) 80 | } 81 | 82 | listener(event); 83 | 84 | NotificationModule.removeInitialSysNotification(); 85 | } 86 | }); 87 | 88 | break; 89 | } 90 | }, 91 | 92 | removeAllListeners: function (type) { 93 | switch (type) { 94 | case 'press': 95 | case 'click': 96 | DeviceEventEmitter.removeAllListeners('sysNotificationClick'); 97 | break; 98 | } 99 | }, 100 | 101 | module: NotificationModule 102 | } 103 | 104 | module.exports = Notification; 105 | 106 | // Encode the JS notification to pass into the native model 107 | function encodeNativeNotification(attributes) { 108 | if (typeof attributes === 'string') attributes = JSON.parse(attributes); 109 | // Set defaults 110 | if (!attributes.smallIcon) attributes.smallIcon = 'ic_launcher'; 111 | if (!attributes.id) attributes.id = parseInt(Math.random() * 100000); 112 | if (!attributes.action) attributes.action = 'DEFAULT'; 113 | if (!attributes.payload) attributes.payload = {}; 114 | if (attributes.autoClear === undefined) attributes.autoClear = true; 115 | if (attributes.tickerText === undefined) { 116 | if (attributes.subject) { 117 | attributes.tickerText = attributes.subject + ': ' + attributes.message; 118 | } else { 119 | attributes.tickerText = attributes.message; 120 | } 121 | } 122 | 123 | if (attributes.priority === undefined) attributes.priority = 1; 124 | if (attributes.sound === undefined) attributes.sound = 'default'; 125 | if (attributes.vibrate === undefined) attributes.vibrate = 'default'; 126 | if (attributes.lights === undefined) attributes.lights = 'default'; 127 | 128 | attributes.delayed = (attributes.delay !== undefined); 129 | attributes.scheduled = (attributes.sendAt !== undefined); 130 | 131 | // Ensure date are Dates 132 | if (attributes.sendAt && typeof attributes.sendAt !== 'object') attributes.sendAt = new Date(attributes.sendAt); 133 | if (attributes.endAt && typeof attributes.endAt !== 'object') attributes.endAt = new Date(attributes.endAt); 134 | if (attributes.when && typeof attributes.when !== 'object') attributes.when = new Date(attributes.when); 135 | 136 | // Unfold sendAt 137 | if (attributes.sendAt !== undefined) { 138 | attributes.sendAtYear = attributes.sendAt.getFullYear(); 139 | attributes.sendAtMonth = attributes.sendAt.getMonth() + 1; 140 | attributes.sendAtDay = attributes.sendAt.getDate(); 141 | attributes.sendAtWeekDay = attributes.sendAt.getDay(); 142 | attributes.sendAtHour = attributes.sendAt.getHours(); 143 | attributes.sendAtMinute = attributes.sendAt.getMinutes(); 144 | } 145 | 146 | // Convert date objects into number 147 | if (attributes.sendAt) attributes.sendAt = attributes.sendAt.getTime(); 148 | if (attributes.endAt) attributes.endAt = attributes.endAt.getTime(); 149 | if (attributes.when) attributes.when = attributes.when.getTime(); 150 | 151 | // Prepare scheduled notifications 152 | if (attributes.sendAt !== undefined) { 153 | 154 | // Set repeatType for custom repeat time 155 | if (typeof attributes.repeatEvery === 'number') { 156 | attributes.repeatType = 'time'; 157 | attributes.repeatTime = attributes.repeatEvery; 158 | } else if (typeof attributes.repeatEvery === 'string') { 159 | attributes.repeatType = attributes.repeatEvery; 160 | } 161 | 162 | // Naitve module only recognizes the endAt attribute, so we need to 163 | // convert repeatCount to the endAt time base on repeatEvery 164 | if (attributes.repeatCount) { 165 | if (typeof attributes.repeatEvery === 'number') { 166 | attributes.endAt = parseInt(attributes.sendAt + attributes.repeatEvery * attributes.repeatCount + (attributes.repeatEvery / 2)); 167 | 168 | } else if (typeof attributes.repeatEvery === 'string') { 169 | switch (attributes.repeatEvery) { 170 | case 'minute': 171 | attributes.endAt = attributes.sendAt + 60000 * attributes.repeatCount + 1000 * 30; 172 | break; 173 | 174 | case 'hour': 175 | attributes.endAt = attributes.sendAt + 60000 * 60 * attributes.repeatCount + 60000 * 30; 176 | break; 177 | 178 | case 'halfDay': 179 | attributes.endAt = attributes.sendAt + 60000 * 60 * 12 * attributes.repeatCount + 60000 * 60 * 6; 180 | break; 181 | 182 | case 'day': 183 | attributes.endAt = attributes.sendAt + 60000 * 60 * 24 * attributes.repeatCount + 60000 * 60 * 12; 184 | break; 185 | 186 | case 'week': 187 | attributes.endAt = attributes.sendAt + 60000 * 60 * 24 * 7 * attributes.repeatCount + 60000 * 60 * 24 * 3; 188 | break; 189 | 190 | case 'month': 191 | attributes.endAt = attributes.sendAt + 60000 * 60 * 24 * 30 * attributes.repeatCount + 60000 * 60 * 24 * 15; 192 | break; 193 | 194 | case 'year': 195 | attributes.endAt = attributes.sendAt + 60000 * 60 * 24 * 365 * attributes.repeatCount + 60000 * 60 * 24 * 100; 196 | break; 197 | } 198 | } 199 | } 200 | } 201 | 202 | // Convert long numbers into string before passing them into native modle, 203 | // incase of integer overflow 204 | if (attributes.sendAt) attributes.sendAt = attributes.sendAt.toString(); 205 | if (attributes.endAt) attributes.endAt = attributes.endAt.toString(); 206 | if (attributes.when) attributes.when = attributes.when.toString(); 207 | if (attributes.repeatEvery) attributes.repeatEvery = attributes.repeatEvery.toString(); 208 | 209 | // Convert float into integer 210 | if (attributes.progress) attributes.progress = attributes.progress * 1000; 211 | 212 | // Stringify the payload 213 | attributes.payload = JSON.stringify(attributes.payload); 214 | 215 | return attributes; 216 | } 217 | 218 | // Decode the notification data from the native module to pass into JS 219 | function decodeNativeNotification(attributes) { 220 | // Convert dates back to date object 221 | if (attributes.sendAt) attributes.sendAt = new Date(parseInt(attributes.sendAt)); 222 | if (attributes.endAt) attributes.endAt = new Date(parseInt(attributes.endAt)); 223 | if (attributes.when) attributes.when = new Date(parseInt(attributes.when)); 224 | 225 | // Parse possible integer 226 | if (parseInt(attributes.repeatEvery).toString() === attributes.repeatEvery) attributes.repeatEvery = parseInt(attributes.repeatEvery); 227 | 228 | // Convert integer into float 229 | if (attributes.progress) attributes.progress = attributes.progress / 1000; 230 | 231 | // Parse the payload 232 | if (attributes.payload) attributes.payload = JSON.parse(attributes.payload); 233 | 234 | return attributes; 235 | } 236 | 237 | DeviceEventEmitter.addListener('sysModuleNotificationClick', function(e) { 238 | var event = { 239 | action: e.action, 240 | payload: JSON.parse(e.payload) 241 | } 242 | 243 | DeviceEventEmitter.emit('sysNotificationClick', event); 244 | }); 245 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-android-local-notification", 3 | "version": "3.0.0", 4 | "description": "Local-only notifications for React Native Android projects", 5 | "main": "index.js", 6 | "files": [ 7 | "android/src", 8 | "android/build.gradle", 9 | "index.js" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/staltz/react-native-android-local-notification.git" 14 | }, 15 | "nativePackage": true, 16 | "keywords": [ 17 | "notification", 18 | "android", 19 | "local", 20 | "simple" 21 | ], 22 | "author": "Layman ", 23 | "contributors": [ 24 | "Andre Staltz " 25 | ], 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/staltz/react-native-android-local-notification/issues" 29 | }, 30 | "homepage": "https://github.com/staltz/react-native-android-local-notification#readme" 31 | } --------------------------------------------------------------------------------