├── .npmignore
├── android
├── libs
│ └── js.jar
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── io
│ │ └── neson
│ │ └── react
│ │ └── notification
│ │ ├── NotificationPackage.java
│ │ ├── SystemBootEventReceiver.java
│ │ ├── WritableNativeMap.java
│ │ ├── NotificationPublisher.java
│ │ ├── NotificationManager.java
│ │ ├── NotificationEventReceiver.java
│ │ ├── GCMNotificationListenerService.java
│ │ ├── FCMNotificationListenerService.java
│ │ ├── NotificationModule.java
│ │ ├── NotificationAttributes.java
│ │ └── Notification.java
└── build.gradle
├── .gitignore
├── index.ios.js
├── package.json
├── index.android.js
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | android/build
3 | android/build/*
4 |
--------------------------------------------------------------------------------
/android/libs/js.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neson/react-native-system-notification/HEAD/android/libs/js.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build/
3 | npm-debug.log
4 | system-notification.iml
5 | react-native-system-notification.iml
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/index.ios.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | class Notification {
4 |
5 | static addEventListener(type, handler) {
6 | warning(false, 'Cannot listen to Notification events on IOS.');
7 | }
8 |
9 | static removeEventListener(type, handler) {
10 | warning(false, 'Cannot remove Notification listener on IOS.');
11 | }
12 |
13 | }
14 |
15 | Notification.currentState = null;
16 |
17 | module.exports = Notification;
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-system-notification",
3 | "version": "0.2.1",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/Neson/react-native-system-notification"
7 | },
8 | "nativePackage": true,
9 | "description": "Notification for React Native",
10 | "author": "Neson ",
11 | "homepage": "https://github.com/Neson/react-native-system-notification",
12 | "license": "MIT",
13 | "keywords": [
14 | "react-native",
15 | "android",
16 | "notification"
17 | ],
18 | "peerDependencies": {
19 | "react-native": ">=0.29.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "1.0"
12 | ndk {
13 | abiFilters "armeabi-v7a", "x86"
14 | }
15 | }
16 | }
17 |
18 | dependencies {
19 | compile fileTree(include: ['*.jar'], dir: 'libs')
20 | compile 'com.facebook.react:react-native:0.19.+'
21 | compile 'com.google.code.gson:gson:2.3.+'
22 | compile 'com.android.support:appcompat-v7:23.0.1'
23 | compile 'com.android.support:support-annotations:+'
24 | compile 'com.google.android.gms:play-services-gcm:8.1.0+'
25 | compile files('libs/js.jar')
26 | }
27 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/NotificationPackage.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import android.app.Activity;
4 |
5 | import com.facebook.react.ReactPackage;
6 | import com.facebook.react.bridge.JavaScriptModule;
7 | import com.facebook.react.bridge.NativeModule;
8 | import com.facebook.react.bridge.ReactApplicationContext;
9 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
10 | import com.facebook.react.uimanager.ViewManager;
11 | import java.util.ArrayList;
12 | import java.util.Arrays;
13 | import java.util.Collections;
14 | import java.util.List;
15 |
16 | /**
17 | * The React package.
18 | */
19 | public class NotificationPackage implements ReactPackage {
20 | public NotificationPackage() {
21 | }
22 |
23 | @Override
24 | public List createNativeModules(
25 | ReactApplicationContext reactContext) {
26 | List modules = new ArrayList<>();
27 |
28 | modules.add(new NotificationModule(reactContext));
29 | return modules;
30 | }
31 |
32 | @Override
33 | public List> createJSModules() {
34 | return Collections.emptyList();
35 | }
36 |
37 | @Override
38 | public List createViewManagers(ReactApplicationContext reactContext) {
39 | return Arrays.asList();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/SystemBootEventReceiver.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 |
8 | import java.util.ArrayList;
9 |
10 | import io.neson.react.notification.NotificationManager;
11 | import io.neson.react.notification.Notification;
12 |
13 | import android.util.Log;
14 |
15 | /**
16 | * Set alarms for scheduled notification after system reboot.
17 | */
18 | public class SystemBootEventReceiver extends BroadcastReceiver {
19 |
20 | @Override
21 | public void onReceive(Context context, Intent intent) {
22 | Log.i("ReactSystemNotification", "SystemBootEventReceiver: Setting system alarms");
23 |
24 | if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
25 | NotificationManager notificationManager = new NotificationManager(context);
26 | SharedPreferences sharedPreferences = context.getSharedPreferences(NotificationManager.PREFERENCES_KEY, Context.MODE_PRIVATE);
27 |
28 | ArrayList ids = notificationManager.getIDs();
29 |
30 | for (Integer id: ids) {
31 | try {
32 | Notification notification = notificationManager.find(id);
33 |
34 | if (notification.getAttributes() != null) {
35 | notification.cancelAlarm();
36 | notification.setAlarmAndSaveOrShow();
37 | Log.i("ReactSystemNotification", "SystemBootEventReceiver: Alarm set for: " + notification.getAttributes().id);
38 | }
39 | } catch (Exception e) {
40 | Log.e("ReactSystemNotification", "SystemBootEventReceiver: onReceive Error: " + e.getMessage());
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/WritableNativeMap.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import java.util.HashMap;
4 | import com.facebook.react.bridge.ReadableArray;
5 | import com.facebook.react.bridge.WritableArray;
6 | import com.facebook.react.bridge.ReadableMap;
7 | import com.facebook.react.bridge.WritableMap;
8 | import com.facebook.react.bridge.ReadableType;
9 | import com.facebook.react.bridge.ReadableMapKeySetIterator;
10 |
11 | public class WritableNativeMap extends HashMap implements WritableMap {
12 |
13 | @Override
14 | public boolean hasKey(String name) {
15 | return (this.get(name) != null);
16 | }
17 |
18 | @Override
19 | public boolean isNull(String name) {
20 | return (this.get(name) == null);
21 | }
22 |
23 | @Override
24 | public boolean getBoolean(String name) {
25 | return (boolean) this.get(name);
26 | }
27 |
28 | @Override
29 | public double getDouble(String name) {
30 | return (double) this.get(name);
31 | }
32 |
33 | @Override
34 | public int getInt(String name) {
35 | Number v = (Number) this.get(name);
36 | return (int) v.intValue();
37 | }
38 |
39 | @Override
40 | public String getString(String name) {
41 | return (String) this.get(name);
42 | }
43 |
44 | @Override
45 | public ReadableArray getArray(String name) {
46 | return (ReadableArray) this.get(name);
47 | }
48 |
49 | @Override
50 | public ReadableMap getMap(String name) {
51 | return (ReadableMap) this.get(name);
52 | }
53 |
54 | @Override
55 | public ReadableType getType(String name) { return null; }
56 |
57 | @Override
58 | public ReadableMapKeySetIterator keySetIterator() { return null; }
59 |
60 | @Override
61 | public void putNull(String key) {}
62 |
63 | @Override
64 | public void putBoolean(String key, boolean value) {
65 | this.put(key, value);
66 | }
67 |
68 | @Override
69 | public void putDouble(String key, double value) {
70 | this.put(key, value);
71 | }
72 |
73 | @Override
74 | public void putInt(String key, int value) {
75 | this.put(key, value);
76 | }
77 |
78 | @Override
79 | public void putString(String key, String value) {
80 | this.put(key, value);
81 | }
82 |
83 | @Override
84 | public void putArray(String key, WritableArray value) {
85 | this.put(key, value);
86 | }
87 |
88 | @Override
89 | public void putMap(String key, WritableMap value) {
90 | this.put(key, value);
91 | }
92 |
93 | @Override
94 | public void merge(ReadableMap source) {}
95 | }
96 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/NotificationPublisher.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
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 |
10 | import io.neson.react.notification.Notification;
11 | import io.neson.react.notification.NotificationManager;
12 |
13 | import android.util.Log;
14 |
15 | /**
16 | * Publisher for scheduled notifications.
17 | */
18 | public class NotificationPublisher extends BroadcastReceiver {
19 | final static String NOTIFICATION_ID = "notificationId";
20 | final static String NOTIFICATION = "notification";
21 |
22 | @Override
23 | public void onReceive(Context context, Intent intent) {
24 | int id = intent.getIntExtra(NOTIFICATION_ID, 0);
25 | long currentTime = System.currentTimeMillis();
26 | Log.i("ReactSystemNotification", "NotificationPublisher: Prepare To Publish: " + id + ", Now Time: " + currentTime);
27 |
28 | NotificationManager notificationManager = new NotificationManager(context);
29 | Notification notification = notificationManager.find(id);
30 |
31 | if (notification.getAttributes() != null) {
32 |
33 | // Delete notifications that are out-dated
34 | if (notification.getAttributes().endAt != null &&
35 | notification.getAttributes().endAt < currentTime) {
36 | notification.cancelAlarm();
37 | notification.deleteFromPreferences();
38 |
39 | // Show and delete one-time notifications
40 | } else if (notification.getAttributes().repeatType == null) {
41 | notification.show();
42 | notification.cancelAlarm();
43 | notification.deleteFromPreferences();
44 |
45 | // Special conditions for weekly based notifications
46 | } else if (notification.getAttributes().repeatType.equals("week")) {
47 | Calendar calendar = Calendar.getInstance();
48 | int day = calendar.get(Calendar.DAY_OF_WEEK);
49 | day = day - 1;
50 | if (notification.getAttributes().sendAtWeekDay == day) notification.show();
51 |
52 | // Special conditions for monthly based notifications
53 | } else if (notification.getAttributes().repeatType.equals("month")) {
54 | Calendar calendar = Calendar.getInstance();
55 | int day = calendar.get(Calendar.DAY_OF_MONTH);
56 | if (notification.getAttributes().sendAtDay == day) notification.show();
57 |
58 | // Special conditions for yearly based notifications
59 | } else if (notification.getAttributes().repeatType.equals("year")) {
60 | Calendar calendar = Calendar.getInstance();
61 | int day = calendar.get(Calendar.DAY_OF_MONTH);
62 | int month = calendar.get(Calendar.MONTH);
63 | if (notification.getAttributes().sendAtDay == day && notification.getAttributes().sendAtMonth == month) notification.show();
64 |
65 | // Other repeating notifications - just show them
66 | } else {
67 | notification.show();
68 | }
69 |
70 | if (notification.getAttributes().delayed ||
71 | !notification.getAttributes().scheduled) {
72 | notification.deleteFromPreferences();
73 | }
74 |
75 | } else {
76 | notification.cancelAlarm();
77 | notification.deleteFromPreferences();
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/NotificationManager.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import io.neson.react.notification.Notification;
7 | import io.neson.react.notification.NotificationAttributes;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Set;
11 |
12 | import android.util.Log;
13 |
14 | /**
15 | * A high level notification manager
16 | *
17 | * Warps the system notification API to make managing direct and scheduled
18 | * notification easy.
19 | */
20 | public class NotificationManager {
21 | final static String PREFERENCES_KEY = "ReactNativeSystemNotification";
22 | public Context context = null;
23 | public SharedPreferences sharedPreferences = null;
24 |
25 | /**
26 | * Constructor.
27 | */
28 | public NotificationManager(Context context) {
29 | this.context = context;
30 | this.sharedPreferences = (SharedPreferences) context.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE);
31 | }
32 |
33 | /**
34 | * Create a notification.
35 | */
36 | public Notification create(
37 | Integer notificationID,
38 | NotificationAttributes notificationAttributes
39 | ) {
40 | Notification notification = new Notification(context, notificationID, notificationAttributes);
41 |
42 | notification.create();
43 |
44 | return notification;
45 | }
46 |
47 | /**
48 | * Create or update (if exists) a notification.
49 | */
50 | public Notification createOrUpdate(
51 | Integer notificationID,
52 | NotificationAttributes notificationAttributes
53 | ) {
54 | if (getIDs().contains(notificationID)) {
55 | Notification notification = find(notificationID);
56 |
57 | notification.update(notificationAttributes);
58 | return notification;
59 |
60 | } else {
61 | return create(notificationID, notificationAttributes);
62 | }
63 | }
64 |
65 | /**
66 | * Get all notification ids.
67 | */
68 | public ArrayList getIDs() {
69 | Set keys = sharedPreferences.getAll().keySet();
70 | ArrayList ids = new ArrayList();
71 |
72 | for (String key : keys) {
73 | try {
74 | ids.add(Integer.parseInt(key));
75 | // TODO: Delete out-dated notifications BTW
76 | } catch (Exception e) {
77 | Log.e("ReactSystemNotification", "NotificationManager: getIDs Error: " + Log.getStackTraceString(e));
78 | }
79 | }
80 |
81 | return ids;
82 | }
83 |
84 | /**
85 | * Get a notification by its id.
86 | */
87 | public Notification find(Integer notificationID) {
88 | Notification notification = new Notification(context, notificationID, null);
89 |
90 | if (notification.getAttributes() == null) notification.loadAttributesFromPreferences();
91 |
92 | return notification;
93 | }
94 |
95 | /**
96 | * Delete a notification by its id.
97 | */
98 | public Notification delete(Integer notificationID) {
99 | return find(notificationID).delete();
100 | }
101 |
102 | /**
103 | * Clear a notification by its id.
104 | */
105 | public Notification clear(Integer notificationID) {
106 | return find(notificationID).clear();
107 | }
108 |
109 | /**
110 | * Clear all notifications.
111 | */
112 | public void clearAll() {
113 | android.app.NotificationManager systemNotificationManager = (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
114 | systemNotificationManager.cancelAll();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/NotificationEventReceiver.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import android.content.ComponentName;
4 | import android.os.Build;
5 | import android.os.Bundle;
6 | import android.os.SystemClock;
7 | import android.app.ActivityManager;
8 | import android.app.ActivityManager.RunningAppProcessInfo;
9 | import android.content.Intent;
10 | import android.content.Context;
11 | import android.content.BroadcastReceiver;
12 |
13 | import java.util.List;
14 |
15 | import android.util.Log;
16 |
17 | /**
18 | * Handles user's interaction on notifications.
19 | *
20 | * Sends broadcast to the application, launches the app if needed.
21 | */
22 | public class NotificationEventReceiver extends BroadcastReceiver {
23 | final static String NOTIFICATION_ID = "id";
24 | final static String ACTION = "action";
25 | final static String PAYLOAD = "payload";
26 |
27 | @Override
28 | public void onReceive(Context context, Intent intent) {
29 | Bundle extras = intent.getExtras();
30 |
31 | Log.i("ReactSystemNotification", "NotificationEventReceiver: Recived: " + extras.getString(ACTION) + ", Notification ID: " + extras.getInt(NOTIFICATION_ID) + ", payload: " + extras.getString(PAYLOAD));
32 |
33 | // If the application is not running or is not in foreground, start it with the 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", "NotificationEventReceiver: Broadcast Sent: NotificationEvent: " + extras.getString(ACTION) + ", Notification ID: " + extras.getInt(NOTIFICATION_ID) + ", payload: " + extras.getString(PAYLOAD));
60 | }
61 |
62 | private boolean applicationIsRunning(Context context) {
63 | ActivityManager activityManager = (ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE);
64 |
65 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {
66 | List processInfos = activityManager.getRunningAppProcesses();
67 | for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
68 | if (processInfo.processName.equals(context.getApplicationContext().getPackageName())) {
69 | if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
70 | for (String d: processInfo.pkgList) {
71 | Log.v("ReactSystemNotification", "NotificationEventReceiver: ok: " + d);
72 | return true;
73 | }
74 | }
75 | }
76 | }
77 | } else {
78 | List taskInfo = activityManager.getRunningTasks(1);
79 | ComponentName componentInfo = taskInfo.get(0).topActivity;
80 | if (componentInfo.getPackageName().equals(context.getPackageName())) {
81 | return true;
82 | }
83 | }
84 |
85 | return false;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/GCMNotificationListenerService.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import com.google.gson.Gson;
7 | import org.mozilla.javascript.*;
8 | import java.util.Map;
9 |
10 | import com.google.android.gms.gcm.GcmListenerService;
11 |
12 | public class GCMNotificationListenerService extends GcmListenerService {
13 |
14 | private static final String TAG = "GCMNotificationListenerService";
15 |
16 | // This is compressed manually from the encodeNativeNotification function
17 | // in index.js
18 | // https://skalman.github.io/UglifyJS-online/
19 | private static final String NOTIFICATION_ATTRIBUTES_JS_PARSING_CODE = "function encodeNativeNotification(e){if(\"string\"==typeof e&&(e=JSON.parse(e)),e.smallIcon||(e.smallIcon=\"ic_launcher\"),e.id||(e.id=parseInt(1e5*Math.random())),e.action||(e.action=\"DEFAULT\"),e.payload||(e.payload={}),void 0===e.autoClear&&(e.autoClear=!0),void 0===e.tickerText&&(e.tickerText=e.subject?e.subject+\": \"+e.message:e.message),void 0===e.priority&&(e.priority=1),void 0===e.sound&&(e.sound=\"default\"),void 0===e.vibrate&&(e.vibrate=\"default\"),void 0===e.lights&&(e.lights=\"default\"),e.delayed=void 0!==e.delay,e.scheduled=void 0!==e.sendAt,e.sendAt&&\"object\"!=typeof e.sendAt&&(e.sendAt=new Date(e.sendAt)),e.endAt&&\"object\"!=typeof e.endAt&&(e.endAt=new Date(e.endAt)),e.when&&\"object\"!=typeof e.when&&(e.when=new Date(e.when)),void 0!==e.sendAt&&(e.sendAtYear=e.sendAt.getFullYear(),e.sendAtMonth=e.sendAt.getMonth()+1,e.sendAtDay=e.sendAt.getDate(),e.sendAtWeekDay=e.sendAt.getDay(),e.sendAtHour=e.sendAt.getHours(),e.sendAtMinute=e.sendAt.getMinutes()),e.sendAt&&(e.sendAt=e.sendAt.getTime()),e.endAt&&(e.endAt=e.endAt.getTime()),e.when&&(e.when=e.when.getTime()),void 0!==e.sendAt&&(\"number\"==typeof e.repeatEvery?(e.repeatType=\"time\",e.repeatTime=e.repeatEvery):\"string\"==typeof e.repeatEvery&&(e.repeatType=e.repeatEvery),e.repeatCount))if(\"number\"==typeof e.repeatEvery)e.endAt=parseInt(e.sendAt+e.repeatEvery*e.repeatCount+e.repeatEvery/2);else if(\"string\"==typeof e.repeatEvery)switch(e.repeatEvery){case\"minute\":e.endAt=e.sendAt+6e4*e.repeatCount+3e4;break;case\"hour\":e.endAt=e.sendAt+36e5*e.repeatCount+18e5;break;case\"halfDay\":e.endAt=e.sendAt+432e5*e.repeatCount+216e5;break;case\"day\":e.endAt=e.sendAt+864e5*e.repeatCount+432e5;break;case\"week\":e.endAt=e.sendAt+6048e5*e.repeatCount+2592e5;break;case\"month\":e.endAt=e.sendAt+2592e6*e.repeatCount+1296e6;break;case\"year\":e.endAt=e.sendAt+31536e6*e.repeatCount+864e7}return e.sendAt&&(e.sendAt=\"\"+e.sendAt),e.endAt&&(e.endAt=\"\"+e.endAt),e.when&&(e.when=\"\"+e.when),e.repeatEvery&&(e.repeatEvery=\"\"+e.repeatEvery),e.progress&&(e.progress=1e3*e.progress),e.payload=JSON.stringify(e.payload),e}";
20 |
21 | @Override
22 | public void onMessageReceived(String from, Bundle bundle) {
23 | sendNotification(bundle);
24 |
25 | String notificationString = bundle.getString("notification");
26 |
27 | if (notificationString != null) {
28 | sendSysNotification(notificationString);
29 | }
30 | }
31 |
32 | private void sendNotification(Bundle bundle) {
33 | Log.d(TAG, "sendNotification");
34 |
35 | Intent i = new Intent("com.oney.gcm.GCMReceiveNotification");
36 | i.putExtra("bundle", bundle);
37 | sendOrderedBroadcast(i, null);
38 | }
39 |
40 | private void sendSysNotification(String notificationString) {
41 | Object[] functionParams = new Object[] { notificationString };
42 |
43 | Context rhino = Context.enter();
44 | rhino.setOptimizationLevel(-1);
45 |
46 | Scriptable scope = rhino.initStandardObjects();
47 |
48 | rhino.evaluateString(scope, NOTIFICATION_ATTRIBUTES_JS_PARSING_CODE, "script", 1, null);
49 |
50 | Function function = (Function) scope.get("encodeNativeNotification", scope);
51 |
52 | Object parsedParams = function.call(rhino, scope, scope, functionParams);
53 |
54 | Gson gson = new Gson();
55 | String parsedParamsJson = gson.toJson(parsedParams);
56 |
57 | Log.d(TAG, "Notification parsedParams: " + parsedParamsJson);
58 |
59 | NotificationAttributes notificationAttributes = new NotificationAttributes();
60 | notificationAttributes.loadFromMap((Map) parsedParams);
61 |
62 | NotificationManager nm = new NotificationManager(this);
63 | nm.create(notificationAttributes.id, notificationAttributes);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/FCMNotificationListenerService.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import com.google.gson.Gson;
7 | import org.mozilla.javascript.*;
8 | import java.util.Map;
9 | import com.google.firebase.messaging.FirebaseMessagingService;
10 | import com.google.firebase.messaging.RemoteMessage;
11 | import io.neson.react.notification.NotificationAttributes;
12 |
13 | public class FCMNotificationListenerService extends FirebaseMessagingService {
14 |
15 | private static final String TAG = "FCMNotificationListenerService";
16 | // This is compressed manually from the encodeNativeNotification function
17 | // in index.js
18 | // https://skalman.github.io/UglifyJS-online/
19 | private static final String NOTIFICATION_ATTRIBUTES_JS_PARSING_CODE = "function encodeNativeNotification(e){if(\"string\"==typeof e&&(e=JSON.parse(e)),e.smallIcon||(e.smallIcon=\"ic_launcher\"),e.id||(e.id=parseInt(1e5*Math.random())),e.action||(e.action=\"DEFAULT\"),e.payload||(e.payload={}),void 0===e.autoClear&&(e.autoClear=!0),void 0===e.tickerText&&(e.tickerText=e.subject?e.subject+\": \"+e.message:e.message),void 0===e.priority&&(e.priority=1),void 0===e.sound&&(e.sound=\"default\"),void 0===e.vibrate&&(e.vibrate=\"default\"),void 0===e.lights&&(e.lights=\"default\"),e.delayed=void 0!==e.delay,e.scheduled=void 0!==e.sendAt,e.sendAt&&\"object\"!=typeof e.sendAt&&(e.sendAt=new Date(e.sendAt)),e.endAt&&\"object\"!=typeof e.endAt&&(e.endAt=new Date(e.endAt)),e.when&&\"object\"!=typeof e.when&&(e.when=new Date(e.when)),void 0!==e.sendAt&&(e.sendAtYear=e.sendAt.getFullYear(),e.sendAtMonth=e.sendAt.getMonth()+1,e.sendAtDay=e.sendAt.getDate(),e.sendAtWeekDay=e.sendAt.getDay(),e.sendAtHour=e.sendAt.getHours(),e.sendAtMinute=e.sendAt.getMinutes()),e.sendAt&&(e.sendAt=e.sendAt.getTime()),e.endAt&&(e.endAt=e.endAt.getTime()),e.when&&(e.when=e.when.getTime()),void 0!==e.sendAt&&(\"number\"==typeof e.repeatEvery?(e.repeatType=\"time\",e.repeatTime=e.repeatEvery):\"string\"==typeof e.repeatEvery&&(e.repeatType=e.repeatEvery),e.repeatCount))if(\"number\"==typeof e.repeatEvery)e.endAt=parseInt(e.sendAt+e.repeatEvery*e.repeatCount+e.repeatEvery/2);else if(\"string\"==typeof e.repeatEvery)switch(e.repeatEvery){case\"minute\":e.endAt=e.sendAt+6e4*e.repeatCount+3e4;break;case\"hour\":e.endAt=e.sendAt+36e5*e.repeatCount+18e5;break;case\"halfDay\":e.endAt=e.sendAt+432e5*e.repeatCount+216e5;break;case\"day\":e.endAt=e.sendAt+864e5*e.repeatCount+432e5;break;case\"week\":e.endAt=e.sendAt+6048e5*e.repeatCount+2592e5;break;case\"month\":e.endAt=e.sendAt+2592e6*e.repeatCount+1296e6;break;case\"year\":e.endAt=e.sendAt+31536e6*e.repeatCount+864e7}return e.sendAt&&(e.sendAt=\"\"+e.sendAt),e.endAt&&(e.endAt=\"\"+e.endAt),e.when&&(e.when=\"\"+e.when),e.repeatEvery&&(e.repeatEvery=\"\"+e.repeatEvery),e.progress&&(e.progress=1e3*e.progress),e.payload=JSON.stringify(e.payload),e}";
20 |
21 | @Override
22 | public void onMessageReceived(RemoteMessage remoteMessage) {
23 | sendNotification(remoteMessage);
24 |
25 | Map data = remoteMessage.getData();
26 | String notificationString = data.get("notification");
27 |
28 |
29 | if (notificationString != null) {
30 | sendSysNotification(notificationString);
31 | }
32 | }
33 |
34 | private void sendNotification(RemoteMessage remoteMessage) {
35 | Log.d(TAG, "sendNotification");
36 |
37 | Intent i = new Intent("com.evollu.react.fcm.ReceiveNotification");
38 | i.putExtra("data", remoteMessage);
39 | sendOrderedBroadcast(i, null);
40 | }
41 |
42 | private void sendSysNotification(String notificationString) {
43 | Object[] functionParams = new Object[] { notificationString };
44 |
45 | Context rhino = Context.enter();
46 | rhino.setOptimizationLevel(-1);
47 |
48 | Scriptable scope = rhino.initStandardObjects();
49 |
50 | rhino.evaluateString(scope, NOTIFICATION_ATTRIBUTES_JS_PARSING_CODE, "script", 1, null);
51 |
52 | Function function = (Function) scope.get("encodeNativeNotification", scope);
53 |
54 | Object parsedParams = function.call(rhino, scope, scope, functionParams);
55 |
56 | Gson gson = new Gson();
57 | String parsedParamsJson = gson.toJson(parsedParams);
58 |
59 | Log.d(TAG, "Notification parsedParams: " + parsedParamsJson);
60 |
61 | NotificationAttributes notificationAttributes = new NotificationAttributes();
62 | notificationAttributes.loadFromMap((Map) parsedParams);
63 |
64 | NotificationManager nm = new NotificationManager(this);
65 | nm.create(notificationAttributes.id, notificationAttributes);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/index.android.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 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/NotificationModule.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
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.ReactContext;
13 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
14 | import com.facebook.react.bridge.ReactMethod;
15 | import com.facebook.react.bridge.Arguments;
16 | import com.facebook.react.bridge.Callback;
17 | import com.facebook.react.bridge.ReadableMap;
18 | import com.facebook.react.bridge.WritableMap;
19 | import com.facebook.react.bridge.ReadableArray;
20 | import com.facebook.react.bridge.WritableArray;
21 | import com.facebook.react.bridge.WritableNativeArray;
22 | import com.facebook.react.bridge.LifecycleEventListener;
23 |
24 | import io.neson.react.notification.NotificationManager;
25 | import io.neson.react.notification.Notification;
26 | import io.neson.react.notification.NotificationAttributes;
27 | import io.neson.react.notification.NotificationEventReceiver;
28 |
29 | import java.util.ArrayList;
30 | import java.util.Set;
31 | import java.util.HashMap;
32 | import java.util.Map;
33 |
34 | import android.util.Log;
35 |
36 | /**
37 | * The main React native module.
38 | *
39 | * Provides JS accessible API, bridge Java and JavaScript.
40 | */
41 | public class NotificationModule extends ReactContextBaseJavaModule {
42 | final static String PREFERENCES_KEY = "ReactNativeSystemNotification";
43 | public Context mContext = null;
44 | public NotificationManager mNotificationManager = null;
45 |
46 | @Override
47 | public String getName() {
48 | return "NotificationModule";
49 | }
50 |
51 | /**
52 | * Constructor.
53 | */
54 | public NotificationModule(ReactApplicationContext reactContext) {
55 | super(reactContext);
56 |
57 | this.mContext = reactContext;
58 | this.mNotificationManager = (NotificationManager) new NotificationManager(reactContext);
59 |
60 | listenNotificationEvent();
61 | }
62 |
63 | /**
64 | * React method to create or update a notification.
65 | */
66 | @ReactMethod
67 | public void rCreate(
68 | Integer notificationID,
69 | ReadableMap notificationAttributes,
70 | Callback errorCallback,
71 | Callback successCallback
72 | ) {
73 | try {
74 | NotificationAttributes a = getNotificationAttributesFromReadableMap(notificationAttributes);
75 | Notification n = mNotificationManager.createOrUpdate(notificationID, a);
76 |
77 | successCallback.invoke(n.getAttributes().asReadableMap());
78 |
79 | } catch (Exception e) {
80 | errorCallback.invoke(e.getMessage());
81 | Log.e("ReactSystemNotification", "NotificationModule: rCreate Error: " + Log.getStackTraceString(e));
82 | }
83 | }
84 |
85 | /**
86 | * React method to get all notification ids.
87 | */
88 | @ReactMethod
89 | public void rGetIDs(
90 | Callback errorCallback,
91 | Callback successCallback
92 | ) {
93 | try {
94 | ArrayList ids = mNotificationManager.getIDs();
95 | WritableArray rids = new WritableNativeArray();
96 |
97 | for (Integer id: ids) {
98 | rids.pushInt(id);
99 | }
100 |
101 | successCallback.invoke((ReadableArray) rids);
102 |
103 | } catch (Exception e) {
104 | errorCallback.invoke(e.getMessage());
105 | Log.e("ReactSystemNotification", "NotificationModule: rGetIDs Error: " + Log.getStackTraceString(e));
106 | }
107 | }
108 |
109 | /**
110 | * React method to get data of a notification.
111 | */
112 | @ReactMethod
113 | public void rFind(
114 | Integer notificationID,
115 | Callback errorCallback,
116 | Callback successCallback
117 | ) {
118 | try {
119 | Notification n = mNotificationManager.find(notificationID);
120 | successCallback.invoke(n.getAttributes().asReadableMap());
121 |
122 | } catch (Exception e) {
123 | errorCallback.invoke(e.getMessage());
124 | Log.e("ReactSystemNotification", "NotificationModule: rFind Error: " + Log.getStackTraceString(e));
125 | }
126 | }
127 |
128 | /**
129 | * React method to delete (i.e. cancel a scheduled) notification.
130 | */
131 | @ReactMethod
132 | public void rDelete(
133 | int notificationID,
134 | Callback errorCallback,
135 | Callback successCallback
136 | ) {
137 | try {
138 | Notification n = mNotificationManager.delete(notificationID);
139 |
140 | successCallback.invoke(n.getAttributes().asReadableMap());
141 |
142 | } catch (Exception e) {
143 | errorCallback.invoke(e.getMessage());
144 | Log.e("ReactSystemNotification", "NotificationModule: rDelete Error: " + Log.getStackTraceString(e));
145 | }
146 | }
147 |
148 | /**
149 | * React method to delete (i.e. cancel a scheduled) notification.
150 | */
151 | @ReactMethod
152 | public void rDeleteAll(
153 | Callback errorCallback,
154 | Callback successCallback
155 | ) {
156 | try {
157 | ArrayList ids = mNotificationManager.getIDs();
158 |
159 | for (Integer id: ids) {
160 | try {
161 | mNotificationManager.delete(id);
162 | } catch (Exception e) {
163 | Log.e("ReactSystemNotification", "NotificationModule: rDeleteAll Error: " + Log.getStackTraceString(e));
164 | }
165 | }
166 |
167 | successCallback.invoke();
168 |
169 | } catch (Exception e) {
170 | errorCallback.invoke(e.getMessage());
171 | Log.e("ReactSystemNotification", "NotificationModule: rDeleteAll Error: " + Log.getStackTraceString(e));
172 | }
173 | }
174 |
175 | /**
176 | * React method to clear a notification.
177 | */
178 | @ReactMethod
179 | public void rClear(
180 | int notificationID,
181 | Callback errorCallback,
182 | Callback successCallback
183 | ) {
184 | try {
185 | Notification n = mNotificationManager.clear(notificationID);
186 |
187 | successCallback.invoke(n.getAttributes().asReadableMap());
188 |
189 | } catch (Exception e) {
190 | errorCallback.invoke(e.getMessage());
191 | Log.e("ReactSystemNotification", "NotificationModule: rClear Error: " + Log.getStackTraceString(e));
192 | }
193 | }
194 |
195 | /**
196 | * React method to clear all notifications of this app.
197 | */
198 | @ReactMethod
199 | public void rClearAll(
200 | Callback errorCallback,
201 | Callback successCallback
202 | ) {
203 | try {
204 | mNotificationManager.clearAll();
205 | successCallback.invoke();
206 |
207 | } catch (Exception e) {
208 | errorCallback.invoke(e.getMessage());
209 | Log.e("ReactSystemNotification", "NotificationModule: rClearAll Error: " + Log.getStackTraceString(e));
210 | }
211 | }
212 |
213 | @ReactMethod
214 | public void rGetApplicationName(
215 | Callback errorCallback,
216 | Callback successCallback
217 | ) {
218 | try {
219 | int stringId = getReactApplicationContext().getApplicationInfo().labelRes;
220 | successCallback.invoke(getReactApplicationContext().getString(stringId));
221 |
222 | } catch (Exception e) {
223 | errorCallback.invoke(e.getMessage());
224 | Log.e("ReactSystemNotification", "NotificationModule: rGetApplicationName Error: " + Log.getStackTraceString(e));
225 | }
226 | }
227 |
228 | /**
229 | * Emit JavaScript events.
230 | */
231 | private void sendEvent(
232 | String eventName,
233 | Object params
234 | ) {
235 | getReactApplicationContext()
236 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
237 | .emit(eventName, params);
238 |
239 | Log.i("ReactSystemNotification", "NotificationModule: sendEvent (to JS): " + eventName);
240 | }
241 |
242 | @ReactMethod
243 | public void getInitialSysNotification(Callback cb) {
244 | final Activity activity = getCurrentActivity();
245 |
246 | if (activity == null) {
247 | return;
248 | }
249 |
250 | Intent intent = activity.getIntent();
251 | Bundle extras = intent.getExtras();
252 |
253 | if (extras != null) {
254 | Integer initialSysNotificationId = extras.getInt("initialSysNotificationId");
255 | if (initialSysNotificationId != null) {
256 | cb.invoke(initialSysNotificationId, extras.getString("initialSysNotificationAction"), extras.getString("initialSysNotificationPayload"));
257 | return;
258 | }
259 | }
260 | }
261 |
262 | @ReactMethod
263 | public void removeInitialSysNotification() {
264 | final Activity activity = getCurrentActivity();
265 |
266 | if (activity == null) {
267 | return;
268 | }
269 |
270 | activity.getIntent().removeExtra("initialSysNotificationId");
271 | activity.getIntent().removeExtra("initialSysNotificationAction");
272 | activity.getIntent().removeExtra("initialSysNotificationPayload");
273 | }
274 |
275 | private NotificationAttributes getNotificationAttributesFromReadableMap(
276 | ReadableMap readableMap
277 | ) {
278 | NotificationAttributes notificationAttributes = new NotificationAttributes();
279 |
280 | notificationAttributes.loadFromReadableMap(readableMap);
281 |
282 | return notificationAttributes;
283 | }
284 |
285 | private void listenNotificationEvent() {
286 | IntentFilter intentFilter = new IntentFilter("NotificationEvent");
287 |
288 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
289 | @Override
290 | public void onReceive(Context context, Intent intent) {
291 |
292 | Bundle extras = intent.getExtras();
293 |
294 | WritableMap params = Arguments.createMap();
295 | params.putInt("notificationID", extras.getInt(NotificationEventReceiver.NOTIFICATION_ID));
296 | params.putString("action", extras.getString(NotificationEventReceiver.ACTION));
297 | params.putString("payload", extras.getString(NotificationEventReceiver.PAYLOAD));
298 |
299 | sendEvent("sysModuleNotificationClick", params);
300 | }
301 | }, intentFilter);
302 | }
303 |
304 | }
305 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/NotificationAttributes.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
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 | import com.facebook.react.bridge.ReadableNativeArray;
8 |
9 |
10 | import java.util.ArrayList;
11 | import java.util.Map;
12 | import java.util.Iterator;
13 |
14 | import com.google.gson.Gson;
15 |
16 | public class NotificationAttributes {
17 | public Integer id;
18 | public String subject;
19 | public String message;
20 | public String action;
21 | public String payload;
22 |
23 | public Boolean delayed;
24 | public Integer delay;
25 |
26 | public Boolean scheduled;
27 | public Long sendAt;
28 | public Integer sendAtYear;
29 | public Integer sendAtMonth;
30 | public Integer sendAtDay;
31 | public Integer sendAtWeekDay;
32 | public Integer sendAtHour;
33 | public Integer sendAtMinute;
34 |
35 | public String repeatEvery;
36 | public String repeatType;
37 | public Integer repeatTime;
38 | public Integer repeatCount;
39 | public Long endAt;
40 |
41 | public Integer priority;
42 | public String smallIcon;
43 | public String largeIcon;
44 | public String sound;
45 | public String vibrate;
46 | public String lights;
47 | public Boolean autoClear;
48 | public Boolean onlyAlertOnce;
49 | public String tickerText;
50 | public Long when;
51 | public String bigText;
52 | public String bigStyleUrlImgage;
53 | public String bigStyleImageBase64;
54 | public String subText;
55 | public Integer progress;
56 | public Integer lifetime;
57 | public Integer progressEnd;
58 | public String color;
59 | public Integer number;
60 | public String category;
61 | public Boolean localOnly;
62 |
63 | public Boolean inboxStyle;
64 | public String inboxStyleBigContentTitle;
65 | public String inboxStyleSummaryText;
66 | public ArrayList inboxStyleLines;
67 |
68 | public String group;
69 |
70 | public void loadFromMap(Map map) {
71 | WritableMap writableMap = (WritableMap) new WritableNativeMap();
72 |
73 | Iterator entries = map.entrySet().iterator();
74 |
75 | while (entries.hasNext()) {
76 | Map.Entry entry = (Map.Entry) entries.next();
77 | String key = (String) entry.getKey();
78 | Object value = entry.getValue();
79 |
80 | if ("id".equals(key) || value.getClass().equals(Integer.class) || value.getClass().equals(Long.class)) {
81 | Number v = (Number) value;
82 | writableMap.putInt(key, (Integer) v.intValue());
83 |
84 | } else if (value.getClass().equals(Float.class) || value.getClass().equals(Double.class)) {
85 | writableMap.putDouble(key, (Double) value);
86 |
87 | } else if (value.getClass().equals(String.class)) {
88 | writableMap.putString(key, (String) value);
89 |
90 | } else if (value.getClass().equals(Boolean.class)) {
91 | writableMap.putBoolean(key, (Boolean) value);
92 |
93 |
94 | } else if ("inboxStyle".equals(key)) {
95 | inboxStyle = true;
96 | WritableMap inboxStyleMap = new WritableNativeMap();
97 |
98 | Map inboxMap = (Map) value;
99 | if (inboxMap.containsKey("bigContentTitle")) {
100 | inboxStyleBigContentTitle = (String) inboxMap.get("bigContentTitle");
101 | inboxStyleMap.putString("bigContentTitle", inboxStyleBigContentTitle);
102 | }
103 |
104 | if (inboxMap.containsKey("summaryText")) {
105 | inboxStyleSummaryText = (String) inboxMap.get("summaryText");
106 | inboxStyleMap.putString("summaryText", inboxStyleSummaryText);
107 | }
108 |
109 | if (inboxMap.containsKey("lines")) {
110 | WritableArray inboxLines = new com.facebook.react.bridge.WritableNativeArray();
111 | org.mozilla.javascript.NativeArray inboxStyleLines = (org.mozilla.javascript.NativeArray) inboxMap.get("lines");
112 |
113 | for(int i=0; i < inboxStyleLines.size(); i++){
114 | inboxLines.pushString((String) inboxStyleLines.get(i));
115 | }
116 | inboxStyleMap.putArray("lines", inboxLines);
117 | }
118 |
119 | writableMap.putMap("inboxStyle", inboxStyleMap);
120 |
121 | } else {
122 | Gson gson = new Gson();
123 | String json = gson.toJson(value);
124 | writableMap.putString(key, json);
125 | }
126 | }
127 |
128 | loadFromReadableMap((ReadableMap) writableMap);
129 | }
130 |
131 | public void loadFromReadableMap(ReadableMap readableMap) {
132 | if (readableMap.hasKey("id")) id = readableMap.getInt("id");
133 | if (readableMap.hasKey("subject")) subject = readableMap.getString("subject");
134 | if (readableMap.hasKey("message")) message = readableMap.getString("message");
135 | if (readableMap.hasKey("action")) action = readableMap.getString("action");
136 | if (readableMap.hasKey("payload")) payload = readableMap.getString("payload");
137 |
138 | if (readableMap.hasKey("delayed")) delayed = readableMap.getBoolean("delayed");
139 | if (readableMap.hasKey("delay")) delay = readableMap.getInt("delay");
140 |
141 | if (readableMap.hasKey("scheduled")) scheduled = readableMap.getBoolean("scheduled");
142 | if (readableMap.hasKey("sendAt")) sendAt = Long.parseLong(readableMap.getString("sendAt"));
143 | if (readableMap.hasKey("sendAtYear")) sendAtYear = readableMap.getInt("sendAtYear");
144 | if (readableMap.hasKey("sendAtMonth")) sendAtMonth = readableMap.getInt("sendAtMonth");
145 | if (readableMap.hasKey("sendAtDay")) sendAtDay = readableMap.getInt("sendAtDay");
146 | if (readableMap.hasKey("sendAtWeekDay")) sendAtWeekDay = readableMap.getInt("sendAtWeekDay");
147 | if (readableMap.hasKey("sendAtHour")) sendAtHour = readableMap.getInt("sendAtHour");
148 | if (readableMap.hasKey("sendAtMinute")) sendAtMinute = readableMap.getInt("sendAtMinute");
149 |
150 | if (readableMap.hasKey("repeatEvery")) repeatEvery = readableMap.getString("repeatEvery");
151 | if (readableMap.hasKey("repeatType")) repeatType = readableMap.getString("repeatType");
152 | if (readableMap.hasKey("repeatTime")) repeatTime = readableMap.getInt("repeatTime");
153 | if (readableMap.hasKey("repeatCount")) repeatCount = readableMap.getInt("repeatCount");
154 | if (readableMap.hasKey("endAt")) endAt = Long.parseLong(readableMap.getString("endAt"));
155 |
156 | if (readableMap.hasKey("priority")) priority = readableMap.getInt("priority");
157 | if (readableMap.hasKey("smallIcon")) smallIcon = readableMap.getString("smallIcon");
158 | if (readableMap.hasKey("largeIcon")) largeIcon = readableMap.getString("largeIcon");
159 | if (readableMap.hasKey("sound")) sound = readableMap.getString("sound");
160 | if (readableMap.hasKey("vibrate")) vibrate = readableMap.getString("vibrate");
161 | if (readableMap.hasKey("lights")) lights = readableMap.getString("lights");
162 | if (readableMap.hasKey("autoClear")) autoClear = readableMap.getBoolean("autoClear");
163 | else autoClear = true;
164 | if (readableMap.hasKey("onlyAlertOnce")) onlyAlertOnce = readableMap.getBoolean("onlyAlertOnce");
165 | if (readableMap.hasKey("tickerText")) tickerText = readableMap.getString("tickerText");
166 | if (readableMap.hasKey("when")) when = Long.parseLong(readableMap.getString("when"));
167 | if (readableMap.hasKey("bigText")) bigText = readableMap.getString("bigText");
168 | if (readableMap.hasKey("bigStyleUrlImgage")) bigStyleUrlImgage = readableMap.getString("bigStyleUrlImgage");
169 | if (readableMap.hasKey("bigStyleImageBase64")) bigStyleImageBase64 = readableMap.getString("bigStyleImageBase64");
170 | if (readableMap.hasKey("subText")) subText = readableMap.getString("subText");
171 | if (readableMap.hasKey("progress")) progress = readableMap.getInt("progress");
172 | if (readableMap.hasKey("progressEnd")) progressEnd = readableMap.getInt("progressEnd");
173 | if (readableMap.hasKey("lifetime")) lifetime = readableMap.getInt("lifetime");
174 |
175 | if (readableMap.hasKey("color")) color = readableMap.getString("color");
176 | if (readableMap.hasKey("number")) number = readableMap.getInt("number");
177 | if (readableMap.hasKey("category")) category = readableMap.getString("category");
178 | if (readableMap.hasKey("localOnly")) localOnly = readableMap.getBoolean("localOnly");
179 | if (readableMap.hasKey("group")) group = readableMap.getString("group");
180 |
181 | if (readableMap.hasKey("inboxStyle")){
182 | inboxStyle = true;
183 | ReadableMap inboxStyleMap = readableMap.getMap("inboxStyle");
184 |
185 | inboxStyleBigContentTitle = inboxStyleMap.getString("bigContentTitle");
186 | inboxStyleSummaryText = inboxStyleMap.getString("summaryText");
187 |
188 | ReadableArray inboxLines = inboxStyleMap.getArray("lines");
189 | if (inboxLines != null) {
190 | inboxStyleLines = new ArrayList<>();
191 | for(int i=0; i < inboxLines.size(); i++){
192 | inboxStyleLines.add(inboxLines.getString(i));
193 | }
194 | }
195 | }else{
196 | inboxStyle = false;
197 | }
198 |
199 | }
200 |
201 | public ReadableMap asReadableMap() {
202 | WritableMap writableMap = new com.facebook.react.bridge.WritableNativeMap();
203 |
204 | if (id != null) writableMap.putInt("id", id);
205 | if (subject != null) writableMap.putString("subject", subject);
206 | if (message != null) writableMap.putString("message", message);
207 | if (action != null) writableMap.putString("action", action);
208 | if (payload != null) writableMap.putString("payload", payload);
209 |
210 | if (delayed != null) writableMap.putBoolean("delayed", delayed);
211 | if (delay != null) writableMap.putInt("delay", delay);
212 |
213 | if (scheduled != null) writableMap.putBoolean("scheduled", scheduled);
214 | if (sendAt != null) writableMap.putString("sendAt", Long.toString(sendAt));
215 | if (sendAtYear != null) writableMap.putInt("sendAtYear", sendAtYear);
216 | if (sendAtMonth != null) writableMap.putInt("sendAtMonth", sendAtMonth);
217 | if (sendAtDay != null) writableMap.putInt("sendAtDay", sendAtDay);
218 | if (sendAtWeekDay != null) writableMap.putInt("sendAtWeekDay", sendAtWeekDay);
219 | if (sendAtHour != null) writableMap.putInt("sendAtHour", sendAtHour);
220 | if (sendAtMinute != null) writableMap.putInt("sendAtMinute", sendAtMinute);
221 |
222 | if (repeatEvery != null) writableMap.putString("repeatEvery", repeatEvery);
223 | if (repeatType != null) writableMap.putString("repeatType", repeatType);
224 | if (repeatTime != null) writableMap.putInt("repeatTime", repeatTime);
225 | if (repeatCount != null) writableMap.putInt("repeatCount", repeatCount);
226 | if (endAt != null) writableMap.putString("endAt", Long.toString(endAt));
227 |
228 | if (priority != null) writableMap.putInt("priority", priority);
229 | if (smallIcon != null) writableMap.putString("smallIcon", smallIcon);
230 | if (largeIcon != null) writableMap.putString("largeIcon", largeIcon);
231 | if (sound != null) writableMap.putString("sound", sound);
232 | if (vibrate != null) writableMap.putString("vibrate", vibrate);
233 | if (lights != null) writableMap.putString("lights", lights);
234 | if (autoClear != null) writableMap.putBoolean("autoClear", autoClear);
235 | if (onlyAlertOnce != null) writableMap.putBoolean("onlyAlertOnce", onlyAlertOnce);
236 | if (tickerText != null) writableMap.putString("tickerText", tickerText);
237 | if (when != null) writableMap.putString("when", Long.toString(when));
238 | if (bigText != null) writableMap.putString("bigText", bigText);
239 | if (bigStyleImageBase64 != null) writableMap.putString("bigStyleImageBase64", bigStyleImageBase64);
240 | if (bigStyleUrlImgage != null) writableMap.putString("bigStyleImageBase64", bigStyleUrlImgage);
241 | if (subText != null) writableMap.putString("subText", subText);
242 | if (progress != null) writableMap.putInt("progress", progress);
243 | if (color != null) writableMap.putString("color", color);
244 | if (number != null) writableMap.putInt("number", number);
245 | if (category != null) writableMap.putString("category", category);
246 | if (localOnly != null) writableMap.putBoolean("localOnly", localOnly);
247 | if (group != null) writableMap.putString("group", group);
248 |
249 | if (progressEnd != null) writableMap.putInt("progressEnd", progressEnd);
250 | if (lifetime != null) writableMap.putInt("lifetime", lifetime);
251 |
252 | if (inboxStyle){
253 |
254 | WritableMap inboxStyle = new WritableNativeMap();
255 |
256 | if (inboxStyleBigContentTitle != null) inboxStyle.putString("bigContentTitle", inboxStyleBigContentTitle);
257 | if (inboxStyleSummaryText != null) inboxStyle.putString("summaryText", inboxStyleSummaryText);
258 |
259 | if(inboxStyleLines != null){
260 | WritableArray inboxLines = new com.facebook.react.bridge.WritableNativeArray();
261 | for(int i=0; i < inboxStyleLines.size(); i++){
262 | inboxLines.pushString(inboxStyleLines.get(i));
263 | }
264 | inboxStyle.putArray("lines", inboxLines);
265 | }
266 |
267 | writableMap.putMap("inboxStyle", inboxStyle);
268 | }
269 |
270 | return (ReadableMap) writableMap;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-system-notification [](https://www.npmjs.com/package/react-native-system-notification)
2 |
3 | Send or schedule Android system notifications for React Native.
4 |
5 |
6 |
7 | ## Table of Contents
8 |
9 | * [Installation](#installation)
10 | * [Usage](#usage)
11 | * [Creating Notifications](#creating-notifications)
12 | * [Basic](#basic)
13 | * [Scheduling](#scheduling)
14 | * [Customization](#customization)
15 | * [Handle Notification Click Event](#handle-notification-click-event)
16 | * [Manage Scheduled Notifications](#manage-scheduled-notifications)
17 | * [Clearing Notifications](#clearing-notifications)
18 | * [Push Notifications On Android](#push-notifications-on-android)
19 |
20 | ---
21 |
22 | ```js
23 | import React, { DeviceEventEmitter } from 'react-native';
24 | import Notification from 'react-native-system-notification';
25 |
26 | // Send a simple notification
27 | Notification.create({ subject: 'Hey', message: 'Yo! Hello world.' });
28 |
29 | // Listen to notification-clicking events
30 | Notification.addListener('press', function(e) {
31 | console.log(e);
32 | });
33 |
34 | // Custom payload for notifications
35 | Notification.create({
36 | subject: 'Notification With Payload',
37 | message: 'This is a notification that contains custom payload.',
38 | payload: { number: 1, what: true, someAnswer: '42' }
39 | });
40 |
41 | // Receive the payload on notification events
42 | Notification.addListener('press', function(e) {
43 | console.log(e.payload); // => { number: 1, what: true, someAnswer: '42' }
44 | });
45 |
46 | // Customize notification
47 | Notification.create({
48 | subject: 'Notification With Custom Icon',
49 | message: 'This is a notification with a specified icon.',
50 | smallIcon: 'ic_alert'
51 | });
52 |
53 | // Scheduled notifications
54 | Notification.create({
55 | subject: 'Scheduled Notification',
56 | message: 'This notification will show on every Friday morning at 8:30 AM, starts at 2015/9/9 and end after 10 times.',
57 | sendAt: new Date(2015, 9, 9, 8, 30),
58 | repeatEvery: 'week',
59 | count: 10
60 | });
61 | ```
62 |
63 | ## Installation
64 |
65 | - Run `npm install react-native-system-notification --save` to install using npm.
66 |
67 | - Add the following two lines to `android/settings.gradle`:
68 |
69 | ```gradle
70 | include ':react-native-system-notification'
71 | project(':react-native-system-notification').projectDir = new File(settingsDir, '../node_modules/react-native-system-notification/android')
72 | ```
73 |
74 | - Edit `android/app/build.gradle` and add the annoated lines as below:
75 |
76 | ```gradle
77 | ...
78 |
79 | dependencies {
80 | compile fileTree(dir: "libs", include: ["*.jar"])
81 | compile "com.android.support:appcompat-v7:23.0.1"
82 | compile "com.facebook.react:react-native:0.16.+"
83 | compile project(':react-native-system-notification') // <- Add this line
84 | }
85 | ```
86 |
87 | - Edit `android/app/src/main/AndroidManifest.xml` and add the annoated lines as below:
88 |
89 | ```xml
90 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
103 |
104 | ...
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | ```
118 |
119 | > The `RECEIVE_BOOT_COMPLETED` permission is used to re-register all scheduled notifications after reboot.
120 | > Requesting `VIBRATE` permission is required if you want to make the device vibrate while sending notifications.
121 |
122 | - Edit `MainActivity.java` (usually at `android/app/src/main/java/com//MainActivity.java`) and add the annoated lines as below:
123 |
124 | ```java
125 | ...
126 |
127 | import android.content.Intent;
128 | import android.os.Bundle;
129 |
130 | import com.facebook.react.ReactActivity;
131 | import com.facebook.react.ReactPackage;
132 | import com.facebook.react.shell.MainReactPackage;
133 |
134 | import io.neson.react.notification.NotificationPackage; // <- Add this line
135 |
136 | public class MainApplication extends Application implements ReactApplication {
137 |
138 | ...
139 |
140 | @Override
141 | protected List getPackages() {
142 | return Arrays.asList(
143 | ...
144 | new NotificationPackage() // <- Add this line
145 | );
146 | }
147 |
148 | ...
149 | ```
150 |
151 | ## Usage
152 |
153 | ### Creating Notifications
154 |
155 | Just do:
156 |
157 | ```js
158 | Notification.create({
159 | id: 1337,
160 | subject: 'Notification With Payload',
161 | message: 'This is a notification that contains custom payload.',
162 | smallIcon: 'ic_launcher',
163 | autoClear: true,
164 | payload: { number: 1, what: true, someAnswer: '42' }
165 | });
166 | ```
167 |
168 | > 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:
169 | >
170 | > ```js
171 | > Notification.create({ message: 'Testing.' }).then(function(notification) {
172 | > console.log(notification);
173 | > console.log(notification.id);
174 | > });
175 | > ```
176 |
177 | All available options on a notification are listed below:
178 |
179 | #### Basic
180 |
181 | **id (`number`)**
182 | The unique ID of this notification. It will be randomly chosen if not specified.
183 |
184 | **subject (`string`)**
185 | The notification subject. Defaults to the application name on Android.
186 |
187 | **message (`string`)**
188 | The message showen in the notification.
189 |
190 | **action (`string`)**
191 | An action name that can be used to determine what to do when this notification is clicked. Defaults to `DEFAULT`.
192 |
193 | **payload (`object`)**
194 | A custom payload object. It can be retrieved on events of this notification. Defaults to `{}`.
195 |
196 |
197 | #### Scheduling
198 |
199 | **delay (`number`)**
200 | 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`.
201 |
202 | **sendAt (`Date`)**
203 | Schedule this notification to show on a specified time. Note that it cannot be used with `delay`.
204 |
205 | **repeatEvery (`string` or `number`)**
206 | 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.
207 |
208 | **repeatCount (`number`)**
209 | Must use with `sendAt` and `repeatEvery`. End repeating this notification after n times. Note that it cannot be used with `endAt`.
210 |
211 | **endAt (`Date`)**
212 | Must use with `sendAt` and `repeatEvery`. End repeating this notification after a specified time. Note that it cannot be used with `repeatCount`.
213 |
214 |
215 | > Some Samples of Scheduled Notifications
216 | >
217 | > ```js
218 | > Notification.create({
219 | > subject: 'Scheduled Notification',
220 | > message: 'This notification will show on every Friday morning at 8:30 AM, starts at 2015/9/9 and end after 10 times.',
221 | > sendAt: new Date(2015, 9, 9, 8, 30),
222 | > repeatEvery: 'week',
223 | > repeatCount: 10
224 | > });
225 | > ```
226 | >
227 | > ```js
228 | > Notification.create({
229 | > subject: 'Scheduled Notification',
230 | > message: 'This notification will show on 2015/9/9 morning at 8:30 AM, and repeat for 10 times every minute.',
231 | > sendAt: new Date(2015, 9, 9, 8, 30),
232 | > repeatEvery: 60000,
233 | > repeatCount: 10
234 | > });
235 | > ```
236 | >
237 | > ```js
238 | > Notification.create({
239 | > subject: 'Delayed Notification',
240 | > message: 'This notification will show after 10 seconds, even the app has been stoped.',
241 | > delay: 10000
242 | > });
243 | > ```
244 |
245 | #### Customization
246 |
247 | **priority (`number`)**
248 | 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`.
249 |
250 | **smallIcon (`string`)**
251 | 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`.
252 |
253 | **largeIcon (`string`)**
254 | Not yet implemented.
255 |
256 | **sound (`string`)**
257 | 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.
258 |
259 | **vibrate (`string`)**
260 | 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.
261 |
262 | **lights (`string`)**
263 | 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.
264 |
265 | **autoClear (`boolean`)**
266 | Clear this notification automatically after the user clicks on it. Defaults to `true`.
267 |
268 | **onlyAlertOnce (`boolean`)**
269 | Do not let the sound, vibrate and ticker to be played if the notification is already showing.
270 |
271 | **tickerText (`string`)**
272 | Set the text to show on ticker. Defaults to `: `. Set this to `null` to disable ticker.
273 |
274 | **when (`Date`)**
275 | Add a timestamp pertaining to the notification (usually the time the event occurred).
276 |
277 | **bigText (`string`)**
278 | Set the text to be shown when the user expand the notification.
279 |
280 | **bigStyleImageBase64 (`string`)**
281 | Set the image in base64 to be shown when the user expand the notification. if bigText is not null, it have priority over bigStyleImageBase64.
282 |
283 | **bigStyleUrlImgage (`string`)**
284 | 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 bigStyleUrlImgage
285 |
286 | **subText (`string`)**
287 | Set the third line of text in the platform notification template. Note that it cannot be used with `progress`.
288 |
289 | **progress (`number`)**
290 | 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`.
291 |
292 | **color (`string`)**
293 | Color to be applied by the standard Style templates when presenting this notification.
294 |
295 | **number (`number`)**
296 | Set a number on the notification.
297 |
298 | **private (`boolean`)**
299 | Not yet implemented.
300 |
301 | **ongoing (`boolean`)**
302 | Not yet implemented.
303 |
304 | **category (`string`)**
305 | 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.
306 |
307 | **localOnly (`boolean`)**
308 | Set whether or not this notification should not bridge to other devices.
309 |
310 | ### Handle Notification Click Event
311 |
312 | Register a listener on `sysNotificationClick` events to handle notification clicking:
313 |
314 | ```js
315 | Notification.addListener('press', function(e) {
316 | console.log(e);
317 | });
318 | ```
319 |
320 | The action and payload of the notification can be retrieved on these events:
321 |
322 | ```js
323 | Notification.send({ message: 'Message', action: 'ACTION_NAME', payload: { data: 'Anything' } });
324 | ```
325 |
326 | ```js
327 | Notification.addListener('press', function(e) {
328 | switch (e.action) {
329 | case 'ACTION_NAME':
330 | console.log(`Action Triggered! Data: ${e.payload.data}`);
331 | break;
332 |
333 | case 'ANOTHER_ACTION_NAME':
334 | console.log(`Another Action Triggered! Data: ${e.payload.data}`);
335 | break;
336 | }
337 | });
338 | ```
339 |
340 | Once you no longer need to listen to `sysNotificationClick` events de-register the listener functions with:
341 |
342 | ```js
343 | Notification.removeAllListeners('press');
344 | ```
345 |
346 | ### Manage Scheduled Notifications
347 |
348 | 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.
349 |
350 | ```js
351 | Notification.getIDs().then(function(ids) {
352 | console.log(ids); // Array of ids
353 | });
354 | ```
355 |
356 | and use `Notification.find(notificationID)` to get data of an notification.
357 |
358 | ```js
359 | Notification.find(notificationID).then(function(notification) {
360 | console.log(notification);
361 | });
362 | ```
363 |
364 | or just cancel it with `Notification.delete(notificationID)`:
365 |
366 | ```js
367 | Notification.delete(notificationID);
368 | ```
369 |
370 | Want to cancel all scheduled notifications set by your app? Sure:
371 |
372 | ```js
373 | Notification.deleteAll();
374 | ```
375 |
376 | > To update a scheduled notification, just use `Notification.create()` with the same id.
377 |
378 | ### Clearing Notifications
379 |
380 | When you want to clear a notification from the system statusbar, just use:
381 |
382 | ```js
383 | Notification.clearAll();
384 | ```
385 |
386 | or:
387 |
388 | ```js
389 | Notification.clear(notificationID);
390 | ```
391 |
392 |
393 | ## Push Notifications On Android
394 |
395 | Sending push notification via web servers to Android is also easy! With [react-native-gcm-android](https://github.com/oney/react-native-gcm-android) intergrated, you can just pass notification arguments through GCM (with the same format as JavaScript), your app will show it directly or put it into schedule. To set this up, follow these directions:
396 |
397 | 1. Run `npm install react-native-gcm-android --save` to add react-native-gcm-android to your app
398 |
399 | 2. Setup GCM, follow [react-native-gcm-android](https://github.com/oney/react-native-gcm-android/tree/17b1e54)'s [README](https://github.com/oney/react-native-gcm-android/blob/17b1e54/README.md) to get GCM working.
400 |
401 | 3. Open `android/app/src/main/AndroidManifest.xml`, change `com.oney.gcm.RNGcmListenerService` to `io.neson.react.notification.GCMNotificationListenerService`.
402 |
403 | Then you can send push notifications like this, putting an `notification` object (just as the same thing that you use in JavaScript Notification.create()) into the GCM `data` (curl example):
404 |
405 | ```bash
406 | curl -X POST -H "Authorization: key=" -H "Content-Type: application/json" -d '
407 | {
408 | "data": {
409 | "notification": {
410 | "subject": "Hello GCM",
411 | "message": "Hello from the server side!"
412 | }
413 | },
414 | "to" : ""
415 | }' 'https://gcm-http.googleapis.com/gcm/send'
416 | ```
417 |
418 | The notification will be created natively (so this works even if the app is closed) when the device received the GCM message.
419 |
--------------------------------------------------------------------------------
/android/src/main/java/io/neson/react/notification/Notification.java:
--------------------------------------------------------------------------------
1 | package io.neson.react.notification;
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 android.support.annotation.Nullable;
14 | import android.net.Uri;
15 |
16 | import java.lang.System;
17 | import java.net.URL;
18 | import java.util.HashMap;
19 | import java.util.Map;
20 |
21 | import com.google.gson.Gson;
22 |
23 | import io.neson.react.notification.NotificationAttributes;
24 | import io.neson.react.notification.NotificationEventReceiver;
25 | import io.neson.react.notification.NotificationPublisher;
26 |
27 | import android.util.Base64;
28 | import android.support.v7.app.NotificationCompat;
29 | import android.text.Html;
30 | import android.util.Base64;
31 | import android.util.Log;
32 | import android.graphics.Color;
33 |
34 | /**
35 | * An object-oriented Wrapper class around the system notification class.
36 | *
37 | * Each instance is an representation of a single, or a set of scheduled
38 | * notifications. It handles operations like showing, canceling and clearing.
39 | */
40 | public class Notification {
41 | private Context context;
42 | private int id;
43 | private NotificationAttributes attributes;
44 |
45 | /**
46 | * Constructor.
47 | */
48 | public Notification(Context context, int id, @Nullable NotificationAttributes attributes) {
49 | this.context = context;
50 | this.id = id;
51 | this.attributes = attributes;
52 | }
53 |
54 | /**
55 | * Public context getter.
56 | */
57 | public Context getContext() {
58 | return context;
59 | }
60 |
61 | /**
62 | * Public attributes getter.
63 | */
64 | public NotificationAttributes getAttributes() {
65 | return attributes;
66 | }
67 |
68 | /**
69 | * Create the notification, show it now or set the schedule.
70 | */
71 | public Notification create() {
72 | setAlarmAndSaveOrShow();
73 |
74 | Log.i("ReactSystemNotification", "Notification Created: " + id);
75 |
76 | return this;
77 | }
78 |
79 | /**
80 | * Update the notification, resets its schedule.
81 | */
82 | public Notification update(NotificationAttributes notificationAttributes) {
83 | delete();
84 | attributes = notificationAttributes;
85 | setAlarmAndSaveOrShow();
86 |
87 | return this;
88 | }
89 |
90 | /**
91 | * Clear the notification from the status bar.
92 | */
93 | public Notification clear() {
94 | getSysNotificationManager().cancel(id);
95 |
96 | Log.i("ReactSystemNotification", "Notification Cleared: " + id);
97 |
98 | return this;
99 | }
100 |
101 | /**
102 | * Cancel the notification.
103 | */
104 | public Notification delete() {
105 | getSysNotificationManager().cancel(id);
106 |
107 | if (attributes.delayed || attributes.scheduled) {
108 | cancelAlarm();
109 | }
110 |
111 | deleteFromPreferences();
112 |
113 | Log.i("ReactSystemNotification", "Notification Deleted: " + id);
114 |
115 | return this;
116 | }
117 |
118 | /**
119 | * Build the notification.
120 | */
121 | public android.app.Notification build() {
122 | android.support.v7.app.NotificationCompat.Builder notificationBuilder = new android.support.v7.app.NotificationCompat.Builder(context);
123 |
124 | notificationBuilder
125 | .setContentTitle(attributes.subject)
126 | .setContentText(attributes.message)
127 | .setSmallIcon(context.getResources().getIdentifier(attributes.smallIcon, "mipmap", context.getPackageName()))
128 | .setAutoCancel(attributes.autoClear)
129 | .setContentIntent(getContentIntent());
130 |
131 |
132 | if (attributes.priority != null) {
133 | notificationBuilder.setPriority(attributes.priority);
134 | }
135 |
136 | if (attributes.largeIcon != null) {
137 | int largeIconId = context.getResources().getIdentifier(attributes.largeIcon, "drawable", context.getPackageName());
138 | Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), largeIconId);
139 | notificationBuilder.setLargeIcon(largeIcon);
140 | }
141 |
142 | if (attributes.group != null) {
143 | notificationBuilder.setGroup(attributes.group);
144 | notificationBuilder.setGroupSummary(true);
145 | }
146 |
147 | if(attributes.inboxStyle){
148 |
149 | android.support.v7.app.NotificationCompat.InboxStyle inboxStyle = new android.support.v7.app.NotificationCompat.InboxStyle();
150 |
151 | if(attributes.inboxStyleBigContentTitle != null){
152 | inboxStyle.setBigContentTitle(attributes.inboxStyleBigContentTitle);
153 | }
154 | if(attributes.inboxStyleSummaryText != null){
155 | inboxStyle.setSummaryText(attributes.inboxStyleSummaryText);
156 | }
157 | if(attributes.inboxStyleLines != null){
158 | for(int i=0; i< attributes.inboxStyleLines.size(); i++){
159 | inboxStyle.addLine(Html.fromHtml(attributes.inboxStyleLines.get(i)));
160 | }
161 | }
162 | notificationBuilder.setStyle(inboxStyle);
163 |
164 |
165 | Log.i("ReactSystemNotification", "set inbox style!!");
166 |
167 | }else{
168 |
169 | int defaults = 0;
170 | if ("default".equals(attributes.sound)) {
171 | defaults = defaults | android.app.Notification.DEFAULT_SOUND;
172 | }
173 | if ("default".equals(attributes.vibrate)) {
174 | defaults = defaults | android.app.Notification.DEFAULT_VIBRATE;
175 | }
176 | if ("default".equals(attributes.lights)) {
177 | defaults = defaults | android.app.Notification.DEFAULT_LIGHTS;
178 | }
179 | notificationBuilder.setDefaults(defaults);
180 |
181 | }
182 |
183 | if (attributes.onlyAlertOnce != null) {
184 | notificationBuilder.setOnlyAlertOnce(attributes.onlyAlertOnce);
185 | }
186 |
187 | if (attributes.tickerText != null) {
188 | notificationBuilder.setTicker(attributes.tickerText);
189 | }
190 |
191 | if (attributes.when != null) {
192 | notificationBuilder.setWhen(attributes.when);
193 | notificationBuilder.setShowWhen(true);
194 | }
195 |
196 | // if bigText is not null, it have priority over bigStyleImageBase64
197 | if (attributes.bigText != null) {
198 | notificationBuilder
199 | .setStyle(new android.support.v7.app.NotificationCompat.BigTextStyle()
200 | .bigText(attributes.bigText));
201 | }
202 | else if (attributes.bigStyleUrlImgage != null && attributes.bigStyleUrlImgage != "") {
203 |
204 | Bitmap bigPicture = null;
205 |
206 | try {
207 |
208 | Log.i("ReactSystemNotification", "start to get image from URL : " + attributes.bigStyleUrlImgage);
209 | URL url = new URL(attributes.bigStyleUrlImgage);
210 | bigPicture = BitmapFactory.decodeStream(url.openStream());
211 | Log.i("ReactSystemNotification", "finishing to get image from URL");
212 |
213 | } catch (Exception e) {
214 | Log.e("ReactSystemNotification", "Error when getting image from URL" + e.getStackTrace());
215 | }
216 |
217 | if (bigPicture != null) {
218 | notificationBuilder
219 | .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bigPicture));
220 | }
221 | }
222 | else if (attributes.bigStyleImageBase64 != null) {
223 |
224 | Bitmap bigPicture = null;
225 |
226 | try {
227 |
228 | Log.i("ReactSystemNotification", "start to convert bigStyleImageBase64 to bitmap");
229 | // Convert base64 image to Bitmap
230 | byte[] bitmapAsBytes = Base64.decode(attributes.bigStyleImageBase64.getBytes(), Base64.DEFAULT);
231 | bigPicture = BitmapFactory.decodeByteArray(bitmapAsBytes, 0, bitmapAsBytes.length);
232 | Log.i("ReactSystemNotification", "finished to convert bigStyleImageBase64 to bitmap");
233 |
234 | } catch (Exception e) {
235 | Log.e("ReactSystemNotification", "Error when converting base 64 to Bitmap" + e.getStackTrace());
236 | }
237 |
238 | if (bigPicture != null) {
239 | notificationBuilder
240 | .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bigPicture));
241 | }
242 | }
243 |
244 | if (attributes.color != null) {
245 | notificationBuilder.setColor(Color.parseColor(attributes.color));
246 | }
247 |
248 | if (attributes.subText != null) {
249 | notificationBuilder.setSubText(attributes.subText);
250 | }
251 |
252 | if (attributes.progress != null) {
253 | if (attributes.progress < 0 || attributes.progress > 1000) {
254 | notificationBuilder.setProgress(1000, 100, true);
255 | } else {
256 | notificationBuilder.setProgress(1000, attributes.progress, false);
257 | }
258 | }
259 |
260 | if (attributes.number != null) {
261 | notificationBuilder.setNumber(attributes.number);
262 | }
263 |
264 | if (attributes.localOnly != null) {
265 | notificationBuilder.setLocalOnly(attributes.localOnly);
266 | }
267 |
268 | if (attributes.sound != null) {
269 | notificationBuilder.setSound(Uri.parse(attributes.sound));
270 | }
271 |
272 |
273 |
274 | return notificationBuilder.build();
275 | }
276 |
277 | /**
278 | * Show the notification now.
279 | */
280 | public void show() {
281 | getSysNotificationManager().notify(id, build());
282 |
283 | Log.i("ReactSystemNotification", "Notification Show: " + id);
284 | }
285 |
286 | /**
287 | * Setup alarm or show the notification.
288 | */
289 | public void setAlarmAndSaveOrShow() {
290 | if (attributes.delayed) {
291 | setDelay();
292 | saveAttributesToPreferences();
293 |
294 | } else if (attributes.scheduled) {
295 | setSchedule();
296 | saveAttributesToPreferences();
297 |
298 | } else {
299 | show();
300 | }
301 | }
302 |
303 | /**
304 | * Schedule the delayed notification.
305 | */
306 | public void setDelay() {
307 | PendingIntent pendingIntent = getScheduleNotificationIntent();
308 |
309 | long futureInMillis = SystemClock.elapsedRealtime() + attributes.delay;
310 | getAlarmManager().set(AlarmManager.ELAPSED_REALTIME_WAKEUP, futureInMillis, pendingIntent);
311 |
312 | Log.i("ReactSystemNotification", "Notification Delay Alarm Set: " + id + ", Repeat Type: " + attributes.repeatType + ", Current Time: " + System.currentTimeMillis() + ", Delay: " + attributes.delay);
313 | }
314 |
315 | /**
316 | * Schedule the notification.
317 | */
318 | public void setSchedule() {
319 | PendingIntent pendingIntent = getScheduleNotificationIntent();
320 |
321 | if (attributes.repeatType == null) {
322 | getAlarmManager().set(AlarmManager.RTC_WAKEUP, attributes.sendAt, pendingIntent);
323 | Log.i("ReactSystemNotification", "Set One-Time Alarm: " + id);
324 |
325 | } else {
326 | switch (attributes.repeatType) {
327 | case "time":
328 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, attributes.repeatTime, pendingIntent);
329 | Log.i("ReactSystemNotification", "Set " + attributes.repeatTime + "ms Alarm: " + id);
330 | break;
331 |
332 | case "minute":
333 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, 60000, pendingIntent);
334 | Log.i("ReactSystemNotification", "Set Minute Alarm: " + id);
335 | break;
336 |
337 | case "hour":
338 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, AlarmManager.INTERVAL_HOUR, pendingIntent);
339 | Log.i("ReactSystemNotification", "Set Hour Alarm: " + id);
340 | break;
341 |
342 | case "halfDay":
343 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, AlarmManager.INTERVAL_HALF_DAY, pendingIntent);
344 | Log.i("ReactSystemNotification", "Set Half-Day Alarm: " + id);
345 | break;
346 |
347 | case "day":
348 | case "week":
349 | case "month":
350 | case "year":
351 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, attributes.sendAt, AlarmManager.INTERVAL_DAY, pendingIntent);
352 | Log.i("ReactSystemNotification", "Set Day Alarm: " + id + ", Type: " + attributes.repeatType);
353 | break;
354 |
355 | default:
356 | getAlarmManager().set(AlarmManager.RTC_WAKEUP, attributes.sendAt, pendingIntent);
357 | Log.i("ReactSystemNotification", "Set One-Time Alarm: " + id);
358 | break;
359 | }
360 | }
361 |
362 | Log.i("ReactSystemNotification", "Notification Schedule Alarm Set: " + id + ", Repeat Type: " + attributes.repeatType + ", Current Time: " + System.currentTimeMillis() + ", First Send At: " + attributes.sendAt);
363 | }
364 |
365 | /**
366 | * Cancel the delayed notification.
367 | */
368 | public void cancelAlarm() {
369 | PendingIntent pendingIntent = getScheduleNotificationIntent();
370 | getAlarmManager().cancel(pendingIntent);
371 |
372 | Log.i("ReactSystemNotification", "Notification Alarm Canceled: " + id);
373 | }
374 |
375 | public void saveAttributesToPreferences() {
376 | SharedPreferences.Editor editor = getSharedPreferences().edit();
377 |
378 | String attributesJSONString = new Gson().toJson(attributes);
379 |
380 | editor.putString(Integer.toString(id), attributesJSONString);
381 |
382 | if (Build.VERSION.SDK_INT < 9) {
383 | editor.commit();
384 | } else {
385 | editor.apply();
386 | }
387 |
388 | Log.i("ReactSystemNotification", "Notification Saved To Pref: " + id + ": " + attributesJSONString);
389 | }
390 |
391 | public void loadAttributesFromPreferences() {
392 | String attributesJSONString = getSharedPreferences().getString(Integer.toString(id), null);
393 | this.attributes = (NotificationAttributes) new Gson().fromJson(attributesJSONString, NotificationAttributes.class);
394 |
395 | Log.i("ReactSystemNotification", "Notification Loaded From Pref: " + id + ": " + attributesJSONString);
396 | }
397 |
398 | public void deleteFromPreferences() {
399 | SharedPreferences.Editor editor = getSharedPreferences().edit();
400 |
401 | editor.remove(Integer.toString(id));
402 |
403 | if (Build.VERSION.SDK_INT < 9) {
404 | editor.commit();
405 | } else {
406 | editor.apply();
407 | }
408 |
409 | Log.i("ReactSystemNotification", "Notification Deleted From Pref: " + id);
410 | }
411 |
412 | private NotificationManager getSysNotificationManager() {
413 | return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
414 | }
415 |
416 | private AlarmManager getAlarmManager() {
417 | return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
418 | }
419 |
420 | private SharedPreferences getSharedPreferences () {
421 | return (SharedPreferences) context.getSharedPreferences(io.neson.react.notification.NotificationManager.PREFERENCES_KEY, Context.MODE_PRIVATE);
422 | }
423 |
424 | private PendingIntent getContentIntent() {
425 | Intent intent = new Intent(context, NotificationEventReceiver.class);
426 |
427 | intent.putExtra(NotificationEventReceiver.NOTIFICATION_ID, id);
428 | intent.putExtra(NotificationEventReceiver.ACTION, attributes.action);
429 | intent.putExtra(NotificationEventReceiver.PAYLOAD, attributes.payload);
430 |
431 | return PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
432 | }
433 |
434 | private PendingIntent getScheduleNotificationIntent() {
435 | Intent notificationIntent = new Intent(context, NotificationPublisher.class);
436 | notificationIntent.putExtra(NotificationPublisher.NOTIFICATION_ID, id);
437 |
438 | PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
439 |
440 | return pendingIntent;
441 | }
442 | }
443 |
--------------------------------------------------------------------------------