diff --git a/LICENSE b/LICENSE
index 514acd345a7e9d202988c1315b5a141be19f5048..6505743db5636a8add183590577ec4ed33df06ac 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2013 Steve
+Copyright (c) 2016 Wix.com LTD
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/README.md b/README.md
index 3042c1672f81760d73aa01bad8c4f99a4cc8dc92..962278da9db0ef54cbd9c993376e14f1369cd026 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,11 @@
Handle all the aspects of push notifications for your app, including remote and local notifications, interactive notifications, silent notifications, and more.
-**All the native iOS notifications features are supported!** Android push support is in progress.
+**All the native iOS notifications features are supported!**
-### Supported Features (iOS)
+
+## Supported Features
+### iOS
- [Remote notifications](#handling-received-notifications).
- [Local notifications](#triggering-local-notifications).
@@ -15,6 +17,16 @@ Handle all the aspects of push notifications for your app, including remote and

+### Android
+
+>**Please advise that Android support is a work in progress and is subject to breaking changes in the near future**
+
+- Receiving notifications in any App state (foreground, background, "dead")
+- Built-in notification drawer management
+- High degree of code extensibility to allow for advanced custom layouts and any specific notifications behavior as available by [Android's API](https://developer.android.com/training/notify-user/build-notification.html)
+- Android equivalent of React-Native's implementation of [`PushNotificationsIOS.getInitialNotification()`](https://facebook.github.io/react-native/docs/pushnotificationios.html#getinitialnotification).
+
+_Upcoming: local notifications, background-state Rx queue (iOS equivalent)_
## Installation
@@ -59,12 +71,60 @@ And the following methods to support registration and receiving notifications:
```
### Android
-WIP.
+
+
+Add a reference to the library's native code in your global `settings.gradle`:
+
+```
+include ':reactnativenotifications'
+project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
+```
+
+Declare the library as a dependency in your **app-project's** `build.gradle`:
+
+```
+dependencies {
+ // ...
+ compile project(':reactnativenotifications')
+}
+```
+
+### Receiving push notifications
+
+> This section is only necessary in case you wish to **receive** push notifications in your React-Native app.
+
+Push notifications on Android are managed and dispatched using [Google's GCM service](https://developers.google.com/cloud-messaging/gcm) (now integrated into Firebase). The following installation steps are a TL;DR of [Google's GCM setup guide](https://developers.google.com/cloud-messaging/android/client). You can follow them to get GCM integrated quickly, but we recommend that you will in the very least have a peek at the guide's overview.
+
+##### Step #1: Subscribe to Google's GCM
+
+To set GCM in your app, you must first create a Google API-project and obtain a **Sender ID** and a **Server API Key**. If you have no existing API project yet, the easiest way to go about in creating one is using [this step-by-step installation process](https://developers.google.com/mobile/add); Use [this tutorial](https://code.tutsplus.com/tutorials/how-to-get-started-with-push-notifications-on-android--cms-25870) for insturctions.
+
+Alternatively, follow [Google's complete guide](https://developers.google.com/cloud-messaging/android/client#create-an-api-project).
+
+##### Step #2: Add Sender ID to Manifest File
+
+Once obtained, bundle the Sender ID onto your main `manifest.xml` file:
+
+```
+
+...
+
+ ...
+ // Replace '1234567890' with your sender ID.
+ // IMPORTANT: Leave the trailing \0 intact!!!
+
+
+
+
+```
+
---
## Register to Push Notifications
+### iOS
+
In order to handle notifications, you must register before- handle `remoteNotificationsRegistered` event.
In your React Native app:
@@ -79,7 +139,7 @@ class App extends Component {
}
onPushRegistered(deviceToken) {
- console.log("Device Token Received: " + deviceToken);
+ console.log("Device Token Received", deviceToken);
}
componentWillUnmount() {
@@ -92,10 +152,29 @@ class App extends Component {
When you have the device token, POST it to your server and register the device in your notifications provider (Amazon SNS, Azure, etc.).
+### Android
+
+The React-Native code equivalent on Android is:
+
+```javascript
+import {NotificationsAndroid} from 'react-native-notifications';
+
+// On Android, we allow for only one (global) listener per each event type.
+NotificationsAndroid.setRegistrationTokenUpdateListener((deviceToken) => {
+ console.log('Push-notifications regsitered!', deviceToken)
+});
+
+```
+
+`deviceToken` being the token used to identify the device on the GCM.
+
---
+
## Handling Received Notifications
+### iOS
+
When you receive a notification, the application can be in one of the following states:
1. **Forground**- When the app in running and is used by the user right now. in this case, `notificationReceivedForeground` event will be fired.
@@ -112,15 +191,15 @@ constructor() {
}
onNotificationReceivedForeground(notification) {
- console.log("Notification Received Foreground: " + JSON.stringify(notification));
+ console.log("Notification Received - Foreground", notification);
}
onNotificationReceivedBackground(notification) {
- console.log("Notification Received Background: " + JSON.stringify(notification));
+ console.log("Notification Received - Background", notification);
}
onNotificationOpened(notification) {
- console.log("Notification Opened: " + JSON.stringify(notification));
+ console.log("Notification opened by device user", notification);
}
componentWillUnmount() {
@@ -131,7 +210,7 @@ componentWillUnmount() {
}
```
-### Notification Object
+#### Notification Object
When you receive a push notification, you'll get an instance of `IOSNotification` object, contains the following methods:
- **`getMessage()`**- returns the notification's main message string.
@@ -141,17 +220,60 @@ When you receive a push notification, you'll get an instance of `IOSNotification
- **`getData()`**- returns the data payload (additional info) of the notification.
- **`getType()`**- returns `managed` for managed notifications, otherwise returns `regular`.
-
-### Background Queue (Important!)
+#### Background Queue (Important!)
When a push notification is opened but the app is not running, the application will be in a **cold launch** state, until the JS engine is up and ready to handle the notification.
The application will collect the events (notifications, actions, etc.) that happend during the cold launch for you.
When your app is ready (most of the time it's after the call to `requestPermissions()`), just call to `NotificationsIOS.consumeBackgroundQueue();` in order to consume the background queue. For more info see `index.ios.js` in the example app.
+### Android
+
+```javascript
+import {NotificationsAndroid} from 'react-native-notifications';
+
+// On Android, we allow for only one (global) listener per each event type.
+NotificationsAndroid.setNotificationReceivedListener((notification) => {
+ console.log("Notification received on device", notification.getData());
+});
+NotificationsAndroid.setNotificationOpenedListener((notification) => {
+ console.log("Notification opened by device user", notification.getData());
+});
+```
+
+#### Notification Object
+- **`getData()`**- content of the `data` section of the original message (sent to GCM).
+- **`getTitle()`**- Convenience for returning `data.title`.
+- **`getMessage()`**- Convenience for returning `data.body`.
+
---
+## Querying initial notification
+
+React-Native's [`PushNotificationsIOS.getInitialNotification()`](https://facebook.github.io/react-native/docs/pushnotificationios.html#getinitialnotification) allows for the async retrieval of the original notification used to open the App on iOS, but it has no equivalent implementation for Android.
+
+We provide a similar implementation on Android using `PendingNotifications.getInitialNotification()` which returns a promise:
+
+```javascript
+import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
+
+PendingNotifications.getInitialNotification()
+ .then((notification) => {
+ console.log("Initial notification was:", (notification ? notification.getData() : 'N/A');
+ })
+ .catch((err) => console.error("getInitialNotifiation() failed", err));
+
+```
+
+> Notifications are considered 'initial' under the following terms:
+
+> - User tapped on a notification, _AND_ -
+> - App was either not running at all ("dead" state), _OR_ it existed in the background with **no running activities** associated with it.
+
+
## Triggering Local Notifications
+> Currently, an iOS-only feture
+
You can schedule a local notification for future presentation.
Triggering local notifications is fully compatible with React Native `PushNotificationsIOS` library.
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..97f3bf2a52ba8e5bd84e43d9133013aca5a48675
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "24.0.1"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ // Google's GCM related framework components.
+ compile "com.google.android.gms:play-services-gcm:9+"
+ compile 'com.facebook.react:react-native:+'
+
+ testCompile 'junit:junit:4.12'
+}
diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..57dcf0f7c50e676d70b08177e13e257a85a20017
--- /dev/null
+++ b/android/proguard-rules.pro
@@ -0,0 +1,20 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/amitd/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
+-keep class * extends com.facebook.react.bridge.NativeModule { *; }
diff --git a/android/reactnativenotification.iml b/android/reactnativenotification.iml
new file mode 100644
index 0000000000000000000000000000000000000000..c77c19bda1ae6092c00391be4c9fdbc5313e414b
--- /dev/null
+++ b/android/reactnativenotification.iml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8061e77c1ded111400f43294b61d0dec7dbb3a95
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/src/main/java/com/wix/reactnativenotifications/Defs.java b/android/src/main/java/com/wix/reactnativenotifications/Defs.java
new file mode 100644
index 0000000000000000000000000000000000000000..fff51742cb3fb8ad9c888479bdead9b70d063558
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/Defs.java
@@ -0,0 +1,11 @@
+package com.wix.reactnativenotifications;
+
+public interface Defs {
+ String LOGTAG = "ReactNativeNotifs";
+ String GCM_SENDER_ID_ATTR_NAME = "com.wix.reactnativenotifications.gcmSenderId";
+
+ String TOKEN_RECEIVED_EVENT_NAME = "remoteNotificationsRegistered";
+
+ String NOTIFICATION_RECEIVED_EVENT_NAME = "notificationReceived";
+ String NOTIFICATION_OPENED_EVENT_NAME = "notificationOpened";
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java b/android/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8495c45278d29aa9170fad0355fe8eb7011a525
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/RNNotificationsPackage.java
@@ -0,0 +1,39 @@
+package com.wix.reactnativenotifications;
+
+import android.app.Application;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.uimanager.ViewManager;
+import com.wix.reactnativenotifications.core.RNNotificationsModule;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class RNNotificationsPackage implements ReactPackage {
+
+
+ final Application mApplication;
+
+ public RNNotificationsPackage(Application application) {
+ mApplication = application;
+ }
+
+ @Override
+ public List createNativeModules(ReactApplicationContext reactContext) {
+ return Arrays.asList(new RNNotificationsModule(mApplication, reactContext));
+ }
+
+ @Override
+ public List> createJSModules() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List createViewManagers(ReactApplicationContext reactContext) {
+ return Collections.emptyList();
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java b/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b098e7c5d26b7fa1f679da3a10e3b93dbbb888dc
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java
@@ -0,0 +1,45 @@
+package com.wix.reactnativenotifications.core;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class AppLaunchHelper {
+ private static final String TAG = AppLaunchHelper.class.getSimpleName();
+
+ private static final String LAUNCH_FLAG_KEY_NAME = "launchedFromNotification";
+
+ public static Intent getLaunchIntent(Context appContext) {
+ try {
+ // The desired behavior upon notification opening is as follows:
+ // - If app is in foreground (and possibly has several activities in stack), simply keep it as-is in foreground.
+ // - If app is in background, bring it to foreground as-is (context stack untampered).
+ // A distinction is made in this case such that if app went to back due to *back-button*, is should be recreated (this
+ // is Android's native behavior).
+ // - If app is dead, launch it through the main context (as Android launchers do).
+ // Overall, THIS IS EXACTLY THE SAME AS ANDROID LAUNCHERS WORK. So, we use the same configuration (action, categories and
+ // flags) as they do.
+ final Intent helperIntent = appContext.getPackageManager().getLaunchIntentForPackage(appContext.getPackageName());
+ final Intent intent = new Intent(appContext, Class.forName(helperIntent.getComponent().getClassName()));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ intent.putExtra(LAUNCH_FLAG_KEY_NAME, true);
+ return intent;
+ } catch (ClassNotFoundException e) {
+ // Note: this is an imaginary scenario cause we're asking for a class of our very own package.
+ Log.e(TAG, "Failed to launch/resume app", e);
+ return null;
+ }
+ }
+
+ public static boolean isLaunchIntentsActivity(Activity activity) {
+ final Intent helperIntent = activity.getPackageManager().getLaunchIntentForPackage(activity.getPackageName());
+ final String activityName = activity.getComponentName().getClassName();
+ final String launchIntentActivityName = helperIntent.getComponent().getClassName();
+ return activityName.equals(launchIntentActivityName);
+ }
+
+ public static boolean isLaunchIntent(Intent intent) {
+ return intent.getBooleanExtra(LAUNCH_FLAG_KEY_NAME, false);
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/AppLifecycleFacade.java b/android/src/main/java/com/wix/reactnativenotifications/core/AppLifecycleFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..04eae26a1b4687aa058364b0e39ec4e324f87873
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/AppLifecycleFacade.java
@@ -0,0 +1,14 @@
+package com.wix.reactnativenotifications.core;
+
+public interface AppLifecycleFacade {
+
+ interface AppVisibilityListener {
+ void onAppVisible();
+ void onAppNotVisible();
+ }
+
+ boolean isReactInitialized();
+ boolean isAppVisible();
+ void addVisibilityListener(AppVisibilityListener listener);
+ void removeVisibilityListener(AppVisibilityListener listener);
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/InitialNotification.java b/android/src/main/java/com/wix/reactnativenotifications/core/InitialNotification.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d3e5fe0d7030f576ebd008457f704b3f2c69389
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/InitialNotification.java
@@ -0,0 +1,22 @@
+package com.wix.reactnativenotifications.core;
+
+import android.support.annotation.Nullable;
+
+import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
+
+public class InitialNotification {
+ private static PushNotificationProps sNotification;
+
+ public static void set(PushNotificationProps pushNotificationProps) {
+ sNotification = pushNotificationProps;
+ }
+
+ public static void clear() {
+ sNotification = null;
+ }
+
+ @Nullable
+ public static PushNotificationProps get() {
+ return sNotification;
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java b/android/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..de89b848adc424c58bfd2d198eb1d29bf5be5d8c
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/NotificationIntentAdapter.java
@@ -0,0 +1,22 @@
+package com.wix.reactnativenotifications.core;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
+
+public class NotificationIntentAdapter {
+ private static final int PENDING_INTENT_CODE = 0;
+ private static final String PUSH_NOTIFICATION_EXTRA_NAME = "pushNotification";
+
+ public static PendingIntent createPendingNotificationIntent(Context appContext, Intent intent, PushNotificationProps notification) {
+ intent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, notification.asBundle());
+ return PendingIntent.getService(appContext, PENDING_INTENT_CODE, intent, 0);
+ }
+
+ public static Bundle extractPendingNotificationDataFromIntent(Intent intent) {
+ return intent.getBundleExtra(PUSH_NOTIFICATION_EXTRA_NAME);
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java b/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbc10c8b36bc7b101910d2c563959800a91f27b2
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java
@@ -0,0 +1,28 @@
+package com.wix.reactnativenotifications.core;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.wix.reactnativenotifications.core.notification.IPushNotification;
+import com.wix.reactnativenotifications.core.notification.PushNotification;
+
+public class ProxyService extends IntentService {
+
+ private static final String TAG = ProxyService.class.getSimpleName();
+
+ public ProxyService() {
+ super("notificationsProxyService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.d(TAG, "New intent: "+intent);
+ final Bundle notificationData = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
+ final IPushNotification pushNotification = PushNotification.get(this, notificationData, ReactAppLifecycleFacade.get());
+ if (pushNotification != null) {
+ pushNotification.onOpened();
+ }
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java b/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..5db1c3273b35712486f2f5a16031c9837d2e445b
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java
@@ -0,0 +1,115 @@
+package com.wix.reactnativenotifications.core;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
+import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
+import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
+import com.wix.reactnativenotifications.gcm.GcmInstanceIdRefreshHandlerService;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
+
+public class RNNotificationsModule extends ReactContextBaseJavaModule implements AppLifecycleFacade.AppVisibilityListener, Application.ActivityLifecycleCallbacks {
+
+ public RNNotificationsModule(Application application, ReactApplicationContext reactContext) {
+ super(reactContext);
+
+ ReactAppLifecycleFacade.get().init(reactContext);
+ ReactAppLifecycleFacade.get().addVisibilityListener(this);
+ application.registerActivityLifecycleCallbacks(this);
+ }
+
+ @Override
+ public String getName() {
+ return "WixRNNotifications";
+ }
+
+ @Override
+ public void initialize() {
+ Log.d(LOGTAG, "Native module init");
+ startGcmIntentService(GcmInstanceIdRefreshHandlerService.EXTRA_IS_APP_INIT);
+
+ final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
+ notificationsDrawer.onAppInit();
+ }
+
+ @ReactMethod
+ public void refreshToken() {
+ Log.d(LOGTAG, "Native method invocation: refreshToken()");
+ startGcmIntentService(GcmInstanceIdRefreshHandlerService.EXTRA_MANUAL_REFRESH);
+ }
+
+ @ReactMethod
+ public void getInitialNotification(final Promise promise) {
+ Log.d(LOGTAG, "Native method invocation: getInitialNotification");
+ Object result = null;
+
+ try {
+ final PushNotificationProps notification = InitialNotification.get();
+ if (notification == null) {
+ return;
+ }
+
+ result = Arguments.fromBundle(notification.asBundle());
+ } finally {
+ promise.resolve(result);
+ }
+ }
+
+ @Override
+ public void onAppVisible() {
+ final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
+ notificationsDrawer.onAppVisible();
+ }
+
+ @Override
+ public void onAppNotVisible() {
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
+ notificationsDrawer.onNewActivity(activity);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ }
+
+ protected void startGcmIntentService(String extraFlag) {
+ final Context appContext = getReactApplicationContext().getApplicationContext();
+ final Intent tokenFetchIntent = new Intent(appContext, GcmInstanceIdRefreshHandlerService.class);
+ tokenFetchIntent.putExtra(extraFlag, true);
+ appContext.startService(tokenFetchIntent);
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/ReactAppLifecycleFacade.java b/android/src/main/java/com/wix/reactnativenotifications/core/ReactAppLifecycleFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..26ef28f52d1a5b96e53b8fedeb45e79b2709c08e
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/ReactAppLifecycleFacade.java
@@ -0,0 +1,91 @@
+package com.wix.reactnativenotifications.core;
+
+import android.util.Log;
+
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.ReactContext;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
+
+public class ReactAppLifecycleFacade implements AppLifecycleFacade {
+
+ private static final ReactAppLifecycleFacade sInstance = new ReactAppLifecycleFacade();
+
+ private ReactContext mReactContext;
+ private boolean mIsVisible;
+ private Set mListeners = new CopyOnWriteArraySet<>();
+
+ public static ReactAppLifecycleFacade get() {
+ return sInstance;
+ }
+
+ public void init(ReactContext reactContext) {
+ mReactContext = reactContext;
+ reactContext.addLifecycleEventListener(new LifecycleEventListener() {
+ @Override
+ public void onHostResume() {
+ Log.d(LOGTAG, "onHostResume");
+ switchToVisible();
+ }
+
+ @Override
+ public void onHostPause() {
+ Log.d(LOGTAG, "onHostPause");
+ switchToInvisible();
+ }
+
+ @Override
+ public void onHostDestroy() {
+ Log.d(LOGTAG, "onHostDestroy");
+ switchToInvisible();
+ }
+ });
+ }
+
+ @Override
+ public synchronized boolean isReactInitialized() {
+ if (mReactContext == null) {
+ return false;
+ }
+
+ return mReactContext.hasActiveCatalystInstance();
+ }
+
+ @Override
+ public boolean isAppVisible() {
+ return mIsVisible;
+ }
+
+ @Override
+ public void addVisibilityListener(AppVisibilityListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeVisibilityListener(AppVisibilityListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private synchronized void switchToVisible() {
+ if (!mIsVisible) {
+ Log.d(LOGTAG, "App is now visible");
+ mIsVisible = true;
+ for (AppVisibilityListener listener : mListeners) {
+ listener.onAppVisible();
+ }
+ }
+ }
+
+ private synchronized void switchToInvisible() {
+ if (mIsVisible) {
+ Log.d(LOGTAG, "App is now not visible");
+ mIsVisible = false;
+ for (AppVisibilityListener listener : mListeners) {
+ listener.onAppNotVisible();
+ }
+ }
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b46988ed3d2c3db15f22a7fc121ad261d8b55ba
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java
@@ -0,0 +1,10 @@
+package com.wix.reactnativenotifications.core.notification;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.wix.reactnativenotifications.core.AppLifecycleFacade;
+
+public interface INotificationsApplication {
+ IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade facade);
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
new file mode 100644
index 0000000000000000000000000000000000000000..a142f4969b1bc24f98ba256841247f854010d30c
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
@@ -0,0 +1,22 @@
+package com.wix.reactnativenotifications.core.notification;
+
+public interface IPushNotification {
+ class InvalidNotificationException extends Exception {
+ public InvalidNotificationException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Handle an event where notification has just been received.
+ * @throws InvalidNotificationException
+ */
+ void onReceived() throws InvalidNotificationException;
+
+ /**
+ * Handle an event where notification has already been dispatched and is not being opened by the device user.
+ */
+ void onOpened();
+
+ PushNotificationProps asProps();
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
new file mode 100644
index 0000000000000000000000000000000000000000..5258fda098f8cec853f965fec60294657c80689b
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
@@ -0,0 +1,188 @@
+package com.wix.reactnativenotifications.core.notification;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.ReactNativeHost;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.wix.reactnativenotifications.core.AppLaunchHelper;
+import com.wix.reactnativenotifications.core.AppLifecycleFacade;
+import com.wix.reactnativenotifications.core.AppLifecycleFacade.AppVisibilityListener;
+import com.wix.reactnativenotifications.core.InitialNotification;
+import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
+import com.wix.reactnativenotifications.core.ProxyService;
+import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
+
+import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME;
+import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
+
+// TODO:
+// 1. Opening the from notif, then tapping 'home', the opening from launcher icon results in main activity over main activity
+// 2. Double check initial notification set up and clearing
+
+public class PushNotification implements IPushNotification {
+
+ final private Context mContext;
+ final private AppLifecycleFacade mAppLifecycleFacade;
+ final private PushNotificationProps mNotificationProps;
+ final private AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() {
+ @Override
+ public void onAppVisible() {
+ mAppLifecycleFacade.removeVisibilityListener(this);
+ dispatchImmediately();
+ }
+
+ @Override
+ public void onAppNotVisible() {}
+ };
+
+ protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade) {
+ mContext = context;
+ mAppLifecycleFacade = appLifecycleFacade;
+ mNotificationProps = new PushNotificationProps(bundle);
+ }
+
+ public static IPushNotification get(Context context, Bundle bundle, AppLifecycleFacade facade) {
+ Context appContext = context.getApplicationContext();
+ if (appContext instanceof INotificationsApplication) {
+ return ((INotificationsApplication) appContext).getPushNotification(context, bundle, facade);
+ }
+ return new PushNotification(context, bundle, facade);
+ }
+
+ @Override
+ public void onReceived() throws InvalidNotificationException {
+ postNotification();
+ notifyReceivedToJS();
+ }
+
+ @Override
+ public void onOpened() {
+ digestNotification();
+ PushNotificationsDrawer.get(mContext).onNotificationOpened();
+ }
+
+ @Override
+ public PushNotificationProps asProps() {
+ return mNotificationProps.copy();
+ }
+
+ protected void postNotification() {
+ final PendingIntent pendingIntent = getCTAPendingIntent();
+ final Notification notification = buildNotification(pendingIntent);
+ postNotification((int) System.currentTimeMillis(), notification);
+ }
+
+ protected void digestNotification() {
+ if (!mAppLifecycleFacade.isReactInitialized()) {
+ setAsInitialNotification();
+ launchOrResumeApp();
+ return;
+ }
+
+ final ReactContext reactContext = getRunningReactContext();
+ if (reactContext.getCurrentActivity() == null) {
+ setAsInitialNotification();
+ }
+
+ if (mAppLifecycleFacade.isAppVisible()) {
+ dispatchImmediately();
+ } else {
+ dispatchUponVisibility();
+ }
+ }
+
+ protected void setAsInitialNotification() {
+ InitialNotification.set(mNotificationProps);
+ }
+
+ protected void dispatchImmediately() {
+ notifyOpenedToJS();
+ }
+
+ protected void dispatchUponVisibility() {
+ mAppLifecycleFacade.addVisibilityListener(getIntermediateAppVisibilityListener());
+
+ // Make the app visible so that we'll dispatch the notification opening when visibility changes to 'true' (see
+ // above listener registration).
+ launchOrResumeApp();
+ }
+
+ protected AppVisibilityListener getIntermediateAppVisibilityListener() {
+ return mAppVisibilityListener;
+ }
+
+ protected PendingIntent getCTAPendingIntent() {
+ final Intent cta = new Intent(mContext, ProxyService.class);
+ return NotificationIntentAdapter.createPendingNotificationIntent(mContext, cta, mNotificationProps);
+ }
+
+ protected Notification buildNotification(PendingIntent intent) {
+ final Notification.Builder notificationBuilder = new Notification.Builder(mContext)
+ .setContentTitle(mNotificationProps.getTitle())
+ .setContentText(mNotificationProps.getBody())
+ .setSmallIcon(mContext.getApplicationInfo().icon)
+ .setContentIntent(intent)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setAutoCancel(true);
+
+ return notificationBuilder.build();
+ }
+
+ protected void postNotification(int id, Notification notification) {
+ final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(id, notification);
+ }
+
+ protected ReactContext getRunningReactContext() {
+ final ReactNativeHost rnHost = ((ReactApplication) mContext.getApplicationContext()).getReactNativeHost();
+ if (!rnHost.hasInstance()) {
+ return null;
+ }
+
+ final ReactInstanceManager instanceManager = rnHost.getReactInstanceManager();
+ final ReactContext reactContext = instanceManager.getCurrentReactContext();
+ if (reactContext == null || !reactContext.hasActiveCatalystInstance()) {
+ return null;
+ }
+
+ return reactContext;
+ }
+
+ private void notifyReceivedToJS() {
+ notifyJS(NOTIFICATION_RECEIVED_EVENT_NAME, null);
+ }
+
+ private void notifyOpenedToJS() {
+ notifyOpenedToJS(null);
+ }
+
+ private void notifyOpenedToJS(ReactContext reactContext) {
+ notifyJS(NOTIFICATION_OPENED_EVENT_NAME, reactContext);
+ }
+
+ private void notifyJS(String eventName, ReactContext reactContext) {
+ if (reactContext == null) {
+ reactContext = getRunningReactContext();
+ }
+
+ if (reactContext != null) {
+ final WritableMap notificationAsMap = Arguments.fromBundle(mNotificationProps.asBundle());
+ reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, notificationAsMap);
+ }
+ }
+
+ protected void launchOrResumeApp() {
+ final Intent intent = AppLaunchHelper.getLaunchIntent(mContext);
+ mContext.startActivity(intent);
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationProps.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationProps.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4512ced5ab6a874ddbcd0312aee5fb8209b5819
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationProps.java
@@ -0,0 +1,46 @@
+package com.wix.reactnativenotifications.core.notification;
+
+import android.os.Bundle;
+
+/**
+ * @author amitd
+ */
+public class PushNotificationProps {
+
+ private Bundle mBundle;
+
+ public PushNotificationProps(Bundle bundle) {
+ final String title = bundle.getString("title");
+ final String body = bundle.getString("body");
+ if (title == null || title.trim().isEmpty() || body == null || body.trim().isEmpty()) {
+ throw new IllegalArgumentException("Invalid notification");
+ }
+
+ mBundle = bundle;
+ }
+
+ public String getTitle() {
+ return mBundle.getString("title");
+ }
+
+ public String getBody() {
+ return mBundle.getString("body");
+ }
+
+ public Bundle asBundle() {
+ return (Bundle) mBundle.clone();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(1024);
+ for (String key : mBundle.keySet()) {
+ sb.append(key).append("=").append(mBundle.get(key)).append(", ");
+ }
+ return sb.toString();
+ }
+
+ protected PushNotificationProps copy() {
+ return new PushNotificationProps((Bundle) mBundle.clone());
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/INotificationsDrawerApplication.java b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/INotificationsDrawerApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe17c74c232aa729462c7b35b9a8a5ee4127a124
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/INotificationsDrawerApplication.java
@@ -0,0 +1,5 @@
+package com.wix.reactnativenotifications.core.notificationdrawer;
+
+public interface INotificationsDrawerApplication {
+ IPushNotificationsDrawer getPushNotificationsDrawer();
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
new file mode 100644
index 0000000000000000000000000000000000000000..6711baa30cfdf7f443cffa4752613def6007f5ab
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
@@ -0,0 +1,11 @@
+package com.wix.reactnativenotifications.core.notificationdrawer;
+
+import android.app.Activity;
+
+public interface IPushNotificationsDrawer {
+ void onAppInit();
+ void onAppVisible();
+ void onNewActivity(Activity activity);
+
+ void onNotificationOpened();
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0aa9235cd483ac295878d495cc0a6da580943a1
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
@@ -0,0 +1,54 @@
+package com.wix.reactnativenotifications.core.notificationdrawer;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Context;
+
+import com.wix.reactnativenotifications.core.AppLaunchHelper;
+import com.wix.reactnativenotifications.core.InitialNotification;
+
+public class PushNotificationsDrawer implements IPushNotificationsDrawer {
+
+ protected final Context mContext;
+
+ public PushNotificationsDrawer(Context context) {
+ mContext = context;
+ }
+
+ public static IPushNotificationsDrawer get(Context context) {
+ final Context appContext = context.getApplicationContext();
+ if (appContext instanceof INotificationsDrawerApplication) {
+ return ((INotificationsDrawerApplication) appContext).getPushNotificationsDrawer();
+ }
+
+ return new PushNotificationsDrawer(context);
+ }
+
+ @Override
+ public void onAppInit() {
+ clearAll();
+ }
+
+ @Override
+ public void onAppVisible() {
+ clearAll();
+ }
+
+ @Override
+ public void onNewActivity(Activity activity) {
+ if (AppLaunchHelper.isLaunchIntentsActivity(activity) &&
+ !AppLaunchHelper.isLaunchIntent(activity.getIntent())) {
+ InitialNotification.clear();
+ }
+ }
+
+ @Override
+ public void onNotificationOpened() {
+ clearAll();
+ }
+
+ protected void clearAll() {
+ final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmInstanceIdListenerService.java b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmInstanceIdListenerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..933415f5cdc3b96b055c2a5e1c76208e914dc9b6
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmInstanceIdListenerService.java
@@ -0,0 +1,21 @@
+package com.wix.reactnativenotifications.gcm;
+
+import android.content.Intent;
+
+import com.google.android.gms.iid.InstanceIDListenerService;
+
+/**
+ * Instance-ID + token refreshing handling service. Contacts the GCM to fetch the updated token.
+ *
+ * @author amitd
+ */
+public class GcmInstanceIdListenerService extends InstanceIDListenerService {
+
+ @Override
+ public void onTokenRefresh() {
+ // Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
+ // Google recommends running this from an intent service.
+ Intent intent = new Intent(this, GcmInstanceIdRefreshHandlerService.class);
+ startService(intent);
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmInstanceIdRefreshHandlerService.java b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmInstanceIdRefreshHandlerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3aa7aa9dc8e248d2fa10ef93f3b296ec158e9b93
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmInstanceIdRefreshHandlerService.java
@@ -0,0 +1,30 @@
+package com.wix.reactnativenotifications.gcm;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+public class GcmInstanceIdRefreshHandlerService extends IntentService {
+
+ public static String EXTRA_IS_APP_INIT = "isAppInit";
+ public static String EXTRA_MANUAL_REFRESH = "doManualRefresh";
+
+ public GcmInstanceIdRefreshHandlerService() {
+ super(GcmInstanceIdRefreshHandlerService.class.getSimpleName());
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ IGcmToken gcmToken = GcmToken.get(this);
+ if (gcmToken == null) {
+ return;
+ }
+
+ if (intent.getBooleanExtra(EXTRA_IS_APP_INIT, false)) {
+ gcmToken.onAppReady();
+ } else if (intent.getBooleanExtra(EXTRA_MANUAL_REFRESH, false)) {
+ gcmToken.onManualRefresh();
+ } else {
+ gcmToken.onNewTokenReady();
+ }
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmMessageHandlerService.java b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmMessageHandlerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a75a9a3acb4c3d78c80f86b4b294ef0c0c0a503
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmMessageHandlerService.java
@@ -0,0 +1,27 @@
+package com.wix.reactnativenotifications.gcm;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GcmListenerService;
+import com.wix.reactnativenotifications.core.notification.IPushNotification;
+import com.wix.reactnativenotifications.core.notification.PushNotification;
+import com.wix.reactnativenotifications.core.ReactAppLifecycleFacade;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
+
+public class GcmMessageHandlerService extends GcmListenerService {
+
+ @Override
+ public void onMessageReceived(String s, Bundle bundle) {
+ Log.d(LOGTAG, "New message from GCM: " + bundle);
+
+ try {
+ final IPushNotification notification = PushNotification.get(getApplicationContext(), bundle, ReactAppLifecycleFacade.get());
+ notification.onReceived();
+ } catch (IPushNotification.InvalidNotificationException e) {
+ // A GCM message, yes - but not the kind we know how to work with.
+ Log.v(LOGTAG, "GCM message handling aborted", e);
+ }
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmToken.java b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmToken.java
new file mode 100644
index 0000000000000000000000000000000000000000..b11a6b5f4a6a89342d5d684bd0ed2d7950ec5991
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/gcm/GcmToken.java
@@ -0,0 +1,132 @@
+package com.wix.reactnativenotifications.gcm;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.google.android.gms.iid.InstanceID;
+
+import static com.wix.reactnativenotifications.Defs.GCM_SENDER_ID_ATTR_NAME;
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
+import static com.wix.reactnativenotifications.Defs.TOKEN_RECEIVED_EVENT_NAME;
+
+public class GcmToken implements IGcmToken {
+
+ final protected Context mAppContext;
+
+ protected static String sToken;
+
+ protected GcmToken(Context appContext) {
+ if (!(appContext instanceof ReactApplication)) {
+ throw new IllegalStateException("Application instance isn't a react-application");
+ }
+ mAppContext = appContext;
+ }
+
+ public static IGcmToken get(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext instanceof INotificationsGcmApplication) {
+ return ((INotificationsGcmApplication) appContext).getGcmToken(context);
+ }
+ return new GcmToken(appContext);
+ }
+
+ @Override
+ public void onNewTokenReady() {
+ synchronized (mAppContext) {
+ refreshToken();
+ }
+ }
+
+ @Override
+ public void onManualRefresh() {
+ synchronized (mAppContext) {
+ if (sToken == null) {
+ Log.i(LOGTAG, "Manual token refresh => asking for new token");
+ refreshToken();
+ } else {
+ Log.i(LOGTAG, "Manual token refresh => publishing existing token ("+sToken+")");
+ sendTokenToJS();
+ }
+ }
+ }
+
+ @Override
+ public void onAppReady() {
+ synchronized (mAppContext) {
+ if (sToken == null) {
+ Log.i(LOGTAG, "App initialized => asking for new token");
+ refreshToken();
+ } else {
+ // Except for first run, this should be the case.
+ Log.i(LOGTAG, "App initialized => publishing existing token ("+sToken+")");
+ sendTokenToJS();
+ }
+ }
+ }
+
+ protected void refreshToken() {
+ try {
+ sToken = getNewToken();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to retrieve new token", e);
+ return;
+ }
+
+ sendTokenToJS();
+ }
+
+ @NonNull
+ protected String getNewToken() throws Exception {
+ final InstanceID instanceId = InstanceID.getInstance(mAppContext);
+ Log.d(LOGTAG, "GCM is refreshing token... instanceId=" + instanceId.getId());
+
+ // TODO why is this needed?
+ GoogleCloudMessaging.getInstance(mAppContext).close();
+
+ try {
+ final String registrationToken = instanceId.getToken(getSenderId(), GoogleCloudMessaging.INSTANCE_ID_SCOPE);
+ Log.i(LOGTAG, "GCM has a new token: instanceId=" + instanceId.getId() + ", token=" + registrationToken);
+ return registrationToken;
+ } catch (Exception e) {
+ throw new Exception("FATAL: Failed to fetch a fresh new token, instanceId=" + instanceId.getId(), e);
+ }
+ }
+
+ protected String getSenderId() {
+ final String senderId = getSenderIdFromManifest();
+ if (senderId == null) {
+ throw new IllegalStateException("Sender ID not found in manifest. Did you forget to add it as the value of a '"+GCM_SENDER_ID_ATTR_NAME+"' meta-data field?");
+ }
+ return senderId;
+ }
+
+ protected String getSenderIdFromManifest() {
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = mAppContext.getPackageManager().getApplicationInfo(mAppContext.getPackageName(), PackageManager.GET_META_DATA);
+ return appInfo.metaData.getString(GCM_SENDER_ID_ATTR_NAME);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Should REALLY never happen cause we're querying for our own package.
+ Log.e(LOGTAG, "Failed to resolve sender ID from manifest", e);
+ return null;
+ }
+ }
+
+ protected void sendTokenToJS() {
+ final ReactInstanceManager instanceManager = ((ReactApplication) mAppContext).getReactNativeHost().getReactInstanceManager();
+ final ReactContext reactContext = instanceManager.getCurrentReactContext();
+
+ // Note: Cannot assume react-context exists cause this is an async dispatched service.
+ if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
+ reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(TOKEN_RECEIVED_EVENT_NAME, sToken);
+ }
+ }
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/gcm/IGcmToken.java b/android/src/main/java/com/wix/reactnativenotifications/gcm/IGcmToken.java
new file mode 100644
index 0000000000000000000000000000000000000000..f324a591f64f0324be8132d41243c18b7b12f60f
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/gcm/IGcmToken.java
@@ -0,0 +1,21 @@
+package com.wix.reactnativenotifications.gcm;
+
+public interface IGcmToken {
+
+ /**
+ * Handle an event where we've been notified of a that a fresh token is now available from Google.
+ */
+ void onNewTokenReady();
+
+ /**
+ * Handle an event where application is ready; typically used for sending token to JS.
+ */
+ void onAppReady();
+
+ /**
+ * Handle a request to actively refresh the token on demand.
+ * This is in essence a workaround so as to allow apps to handle end-cases of token refreshing. It
+ * shouldn't be used by standard apps, as the token management is self sufficient.
+ */
+ void onManualRefresh();
+}
diff --git a/android/src/main/java/com/wix/reactnativenotifications/gcm/INotificationsGcmApplication.java b/android/src/main/java/com/wix/reactnativenotifications/gcm/INotificationsGcmApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..36f59f71cf2e4682d409c1fd21ac07a333d66a11
--- /dev/null
+++ b/android/src/main/java/com/wix/reactnativenotifications/gcm/INotificationsGcmApplication.java
@@ -0,0 +1,7 @@
+package com.wix.reactnativenotifications.gcm;
+
+import android.content.Context;
+
+public interface INotificationsGcmApplication {
+ IGcmToken getGcmToken(Context context);
+}
diff --git a/example/android/.gitignore b/example/android/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c6cbe562a42726ab875d51cb513723b234787300
--- /dev/null
+++ b/example/android/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/example/android/app/BUCK b/example/android/app/BUCK
deleted file mode 100644
index 282196f03d188f38095d5d5ce3fc106b92d13a57..0000000000000000000000000000000000000000
--- a/example/android/app/BUCK
+++ /dev/null
@@ -1,66 +0,0 @@
-import re
-
-# To learn about Buck see [Docs](https://buckbuild.com/).
-# To run your application with Buck:
-# - install Buck
-# - `npm start` - to start the packager
-# - `cd android`
-# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
-# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
-# - `buck install -r android/app` - compile, install and run application
-#
-
-lib_deps = []
-for jarfile in glob(['libs/*.jar']):
- name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile)
- lib_deps.append(':' + name)
- prebuilt_jar(
- name = name,
- binary_jar = jarfile,
- )
-
-for aarfile in glob(['libs/*.aar']):
- name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile)
- lib_deps.append(':' + name)
- android_prebuilt_aar(
- name = name,
- aar = aarfile,
- )
-
-android_library(
- name = 'all-libs',
- exported_deps = lib_deps
-)
-
-android_library(
- name = 'app-code',
- srcs = glob([
- 'src/main/java/**/*.java',
- ]),
- deps = [
- ':all-libs',
- ':build_config',
- ':res',
- ],
-)
-
-android_build_config(
- name = 'build_config',
- package = 'com.notificationsexampleapp',
-)
-
-android_resource(
- name = 'res',
- res = 'src/main/res',
- package = 'com.notificationsexampleapp',
-)
-
-android_binary(
- name = 'app',
- package_type = 'debug',
- manifest = 'src/main/AndroidManifest.xml',
- keystore = '//android/keystores:debug',
- deps = [
- ':app-code',
- ],
-)
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
deleted file mode 100644
index 761051481bd483eed266b300d4b23c0f1d7cf632..0000000000000000000000000000000000000000
--- a/example/android/app/build.gradle
+++ /dev/null
@@ -1,139 +0,0 @@
-apply plugin: "com.android.application"
-
-import com.android.build.OutputFile
-
-/**
- * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
- * and bundleReleaseJsAndAssets).
- * These basically call `react-native bundle` with the correct arguments during the Android build
- * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
- * bundle directly from the development server. Below you can see all the possible configurations
- * and their defaults. If you decide to add a configuration block, make sure to add it before the
- * `apply from: "../../node_modules/react-native/react.gradle"` line.
- *
- * project.ext.react = [
- * // the name of the generated asset file containing your JS bundle
- * bundleAssetName: "index.android.bundle",
- *
- * // the entry file for bundle generation
- * entryFile: "index.android.js",
- *
- * // whether to bundle JS and assets in debug mode
- * bundleInDebug: false,
- *
- * // whether to bundle JS and assets in release mode
- * bundleInRelease: true,
- *
- * // whether to bundle JS and assets in another build variant (if configured).
- * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
- * // The configuration property can be in the following formats
- * // 'bundleIn${productFlavor}${buildType}'
- * // 'bundleIn${buildType}'
- * // bundleInFreeDebug: true,
- * // bundleInPaidRelease: true,
- * // bundleInBeta: true,
- *
- * // the root of your project, i.e. where "package.json" lives
- * root: "../../",
- *
- * // where to put the JS bundle asset in debug mode
- * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
- *
- * // where to put the JS bundle asset in release mode
- * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
- *
- * // where to put drawable resources / React Native assets, e.g. the ones you use via
- * // require('./image.png')), in debug mode
- * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
- *
- * // where to put drawable resources / React Native assets, e.g. the ones you use via
- * // require('./image.png')), in release mode
- * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
- *
- * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
- * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
- * // date; if you have any other folders that you want to ignore for performance reasons (gradle
- * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
- * // for example, you might want to remove it from here.
- * inputExcludes: ["android/**", "ios/**"],
- *
- * // override which node gets called and with what additional arguments
- * nodeExecutableAndArgs: ["node"]
- *
- * // supply additional arguments to the packager
- * extraPackagerArgs: []
- * ]
- */
-
-apply from: "../../node_modules/react-native/react.gradle"
-
-/**
- * Set this to true to create two separate APKs instead of one:
- * - An APK that only works on ARM devices
- * - An APK that only works on x86 devices
- * The advantage is the size of the APK is reduced by about 4MB.
- * Upload all the APKs to the Play Store and people will download
- * the correct one based on the CPU architecture of their device.
- */
-def enableSeparateBuildPerCPUArchitecture = false
-
-/**
- * Run Proguard to shrink the Java bytecode in release builds.
- */
-def enableProguardInReleaseBuilds = false
-
-android {
- compileSdkVersion 23
- buildToolsVersion "23.0.1"
-
- defaultConfig {
- applicationId "com.notificationsexampleapp"
- minSdkVersion 16
- targetSdkVersion 22
- versionCode 1
- versionName "1.0"
- ndk {
- abiFilters "armeabi-v7a", "x86"
- }
- }
- splits {
- abi {
- reset()
- enable enableSeparateBuildPerCPUArchitecture
- universalApk false // If true, also generate a universal APK
- include "armeabi-v7a", "x86"
- }
- }
- buildTypes {
- release {
- minifyEnabled enableProguardInReleaseBuilds
- proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
- }
- }
- // applicationVariants are e.g. debug, release
- applicationVariants.all { variant ->
- variant.outputs.each { output ->
- // For each separate APK per architecture, set a unique version code as described here:
- // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
- def versionCodes = ["armeabi-v7a":1, "x86":2]
- def abi = output.getFilter(OutputFile.ABI)
- if (abi != null) { // null for the universal-debug, universal-release variants
- output.versionCodeOverride =
- versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
- }
- }
- }
-}
-
-dependencies {
- compile fileTree(dir: "libs", include: ["*.jar"])
- compile "com.android.support:appcompat-v7:23.0.1"
- compile "com.facebook.react:react-native:+" // From node_modules
-}
-
-// Run this once to be able to run the application with BUCK
-// puts all compile dependencies into folder libs for BUCK to use
-task copyDownloadableDepsToLibs(type: Copy) {
- from configurations.compile
- into 'libs'
-}
diff --git a/example/android/app/proguard-rules.pro b/example/android/app/proguard-rules.pro
deleted file mode 100644
index 48361a9015dc506ec9a8dd97336d5450246366e0..0000000000000000000000000000000000000000
--- a/example/android/app/proguard-rules.pro
+++ /dev/null
@@ -1,66 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Disabling obfuscation is useful if you collect stack traces from production crashes
-# (unless you are using a system that supports de-obfuscate the stack traces).
--dontobfuscate
-
-# React Native
-
-# Keep our interfaces so they can be used by other ProGuard rules.
-# See http://sourceforge.net/p/proguard/bugs/466/
--keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
--keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
--keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
-
-# Do not strip any method/class that is annotated with @DoNotStrip
--keep @com.facebook.proguard.annotations.DoNotStrip class *
--keep @com.facebook.common.internal.DoNotStrip class *
--keepclassmembers class * {
- @com.facebook.proguard.annotations.DoNotStrip *;
- @com.facebook.common.internal.DoNotStrip *;
-}
-
--keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
- void set*(***);
- *** get*();
-}
-
--keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
--keep class * extends com.facebook.react.bridge.NativeModule { *; }
--keepclassmembers,includedescriptorclasses class * { native ; }
--keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; }
--keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; }
--keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; }
-
--dontwarn com.facebook.react.**
-
-# okhttp
-
--keepattributes Signature
--keepattributes *Annotation*
--keep class okhttp3.** { *; }
--keep interface okhttp3.** { *; }
--dontwarn okhttp3.**
-
-# okio
-
--keep class sun.misc.Unsafe { *; }
--dontwarn java.nio.file.*
--dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
--dontwarn okio.**
diff --git a/example/android/app/react.gradle b/example/android/app/react.gradle
deleted file mode 100644
index 348fb127368b9e5d65319ecf03a6f86d0e800304..0000000000000000000000000000000000000000
--- a/example/android/app/react.gradle
+++ /dev/null
@@ -1,97 +0,0 @@
-import org.apache.tools.ant.taskdefs.condition.Os
-
-def config = project.hasProperty("react") ? project.react : [];
-
-def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
-def entryFile = config.entryFile ?: "index.android.js"
-
-// because elvis operator
-def elvisFile(thing) {
- return thing ? file(thing) : null;
-}
-
-def reactRoot = elvisFile(config.root) ?: file("../../")
-def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
-
-void runBefore(String dependentTaskName, Task task) {
- Task dependentTask = tasks.findByPath(dependentTaskName);
- if (dependentTask != null) {
- dependentTask.dependsOn task
- }
-}
-
-gradle.projectsEvaluated {
- // Grab all build types and product flavors
- def buildTypes = android.buildTypes.collect { type -> type.name }
- def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
-
- // When no product flavors defined, use empty
- if (!productFlavors) productFlavors.add('')
-
- productFlavors.each { productFlavorName ->
- buildTypes.each { buildTypeName ->
- // Create variant and target names
- def targetName = "${productFlavorName.capitalize()}${buildTypeName.capitalize()}"
- def targetPath = productFlavorName ?
- "${productFlavorName}/${buildTypeName}" :
- "${buildTypeName}"
-
- // React js bundle directories
- def jsBundleDirConfigName = "jsBundleDir${targetName}"
- def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
- file("$buildDir/intermediates/assets/${targetPath}")
-
- def resourcesDirConfigName = "resourcesDir${targetName}"
- def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
- file("$buildDir/intermediates/res/merged/${targetPath}")
- def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
-
- // Bundle task name for variant
- def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"
-
- def currentBundleTask = tasks.create(
- name: bundleJsAndAssetsTaskName,
- type: Exec) {
- group = "react"
- description = "bundle JS and assets for ${targetName}."
-
- // Create dirs if they are not there (e.g. the "clean" task just ran)
- doFirst {
- jsBundleDir.mkdirs()
- resourcesDir.mkdirs()
- }
-
- // Set up inputs and outputs so gradle can cache the result
- inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
- outputs.dir jsBundleDir
- outputs.dir resourcesDir
-
- // Set up the call to the react-native cli
- workingDir reactRoot
-
- // Set up dev mode
- def devEnabled = !targetName.toLowerCase().contains("release")
- if (Os.isFamily(Os.FAMILY_WINDOWS)) {
- commandLine "cmd", "/c", "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}",
- "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
- } else {
- commandLine "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}",
- "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
- }
-
- enabled config."bundleIn${targetName}" ||
- config."bundleIn${buildTypeName.capitalize()}" ?:
- targetName.toLowerCase().contains("release")
- }
-
- // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
- currentBundleTask.dependsOn("merge${targetName}Resources")
- currentBundleTask.dependsOn("merge${targetName}Assets")
-
- runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask)
- runBefore("processX86${targetName}Resources", currentBundleTask)
- runBefore("processUniversal${targetName}Resources", currentBundleTask)
- runBefore("process${targetName}Resources", currentBundleTask)
- }
- }
-}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 0f3a214ca7518b4202f35302293df0259a340b48..0000000000000000000000000000000000000000
--- a/example/android/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/example/android/app/src/main/java/com/notificationsexampleapp/MainActivity.java b/example/android/app/src/main/java/com/notificationsexampleapp/MainActivity.java
deleted file mode 100644
index c4329f349297228c49341171e691b88e1830438a..0000000000000000000000000000000000000000
--- a/example/android/app/src/main/java/com/notificationsexampleapp/MainActivity.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.notificationsexampleapp;
-
-import com.facebook.react.ReactActivity;
-
-public class MainActivity extends ReactActivity {
-
- /**
- * Returns the name of the main component registered from JavaScript.
- * This is used to schedule rendering of the component.
- */
- @Override
- protected String getMainComponentName() {
- return "NotificationsExampleApp";
- }
-}
diff --git a/example/android/app/src/main/java/com/notificationsexampleapp/MainApplication.java b/example/android/app/src/main/java/com/notificationsexampleapp/MainApplication.java
deleted file mode 100644
index 55dc8e4a847c7a8c91f0dc9f8260a7e81159fcc5..0000000000000000000000000000000000000000
--- a/example/android/app/src/main/java/com/notificationsexampleapp/MainApplication.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.notificationsexampleapp;
-
-import android.app.Application;
-import android.util.Log;
-
-import com.facebook.react.ReactApplication;
-import com.facebook.react.ReactInstanceManager;
-import com.facebook.react.ReactNativeHost;
-import com.facebook.react.ReactPackage;
-import com.facebook.react.shell.MainReactPackage;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class MainApplication extends Application implements ReactApplication {
-
- private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
- @Override
- protected boolean getUseDeveloperSupport() {
- return BuildConfig.DEBUG;
- }
-
- @Override
- protected List getPackages() {
- return Arrays.asList(
- new MainReactPackage()
- );
- }
- };
-
- @Override
- public ReactNativeHost getReactNativeHost() {
- return mReactNativeHost;
- }
-}
diff --git a/example/android/app/src/main/java/com/smartnotificationsapp/MainActivity.java b/example/android/app/src/main/java/com/smartnotificationsapp/MainActivity.java
deleted file mode 100644
index 72decbdf9160f4159a304c866f538eb2d4d4344f..0000000000000000000000000000000000000000
--- a/example/android/app/src/main/java/com/smartnotificationsapp/MainActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.smartnotificationsapp;
-
-import com.facebook.react.ReactActivity;
-import com.facebook.react.ReactPackage;
-import com.facebook.react.shell.MainReactPackage;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class MainActivity extends ReactActivity {
-
- /**
- * Returns the name of the main component registered from JavaScript.
- * This is used to schedule rendering of the component.
- */
- @Override
- protected String getMainComponentName() {
- return "NotificationsExampleApp";
- }
-
- /**
- * Returns whether dev mode should be enabled.
- * This enables e.g. the dev menu.
- */
- @Override
- protected boolean getUseDeveloperSupport() {
- return BuildConfig.DEBUG;
- }
-
- /**
- * A list of packages used by the app. If the app uses additional views
- * or modules besides the default ones, add more packages here.
- */
- @Override
- protected List getPackages() {
- return Arrays.asList(
- new MainReactPackage()
- );
- }
-}
diff --git a/example/android/app/src/main/res/values/strings.xml b/example/android/app/src/main/res/values/strings.xml
deleted file mode 100644
index 66110ffe5b890023941059c7f7f9cb53915cd3d7..0000000000000000000000000000000000000000
--- a/example/android/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- NotificationsExampleApp
-
diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
deleted file mode 100644
index 319eb0ca100b5aae62b669a5f375ec234aabe351..0000000000000000000000000000000000000000
--- a/example/android/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
diff --git a/example/android/build.gradle b/example/android/build.gradle
index fcba4c587f1ce3c04fec20487a0213057843b683..f2464c16c7efd4800b855f1afa6e1b52b164a8a4 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -5,20 +5,21 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.1'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
+ classpath 'com.android.tools.build:gradle:2.2.0'
}
}
allprojects {
repositories {
- mavenLocal()
jcenter()
+
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
}
}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 1fd964e90b1c5ec50e26364318e2c872a9dd6154..1d3591c8a4c9c29578c36c87f80c05a6aea3ee3f 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -15,6 +15,4 @@
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-android.useDeprecatedNdk=true
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar
index b5166dad4d90021f6a0b45268c0755719f1d5cd4..13372aef5e24af05341d49695ee84e5f9b594659 100644
Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and b/example/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index b9fbfaba0ead2162e30db62f51e95df8a670d4a6..1d4f8cb2697ed13ecbbf4b08e936cc6c4882b32b 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Sun Oct 09 14:30:04 IDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/example/android/gradlew b/example/android/gradlew
index 91a7e269e19dfc62e27137a0b57ef3e430cee4fd..9d82f78915133e1c35a6ea51252590fb38efac2f 100755
--- a/example/android/gradlew
+++ b/example/android/gradlew
@@ -42,11 +42,6 @@ case "`uname`" in
;;
esac
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
+cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
+cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -114,6 +109,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
diff --git a/example/android/myapplication/.gitignore b/example/android/myapplication/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9
--- /dev/null
+++ b/example/android/myapplication/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/example/android/myapplication/build.gradle b/example/android/myapplication/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..00e378ed0a1021b50bef8f44025ed7db8a190a4e
--- /dev/null
+++ b/example/android/myapplication/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "24.0.1"
+
+ defaultConfig {
+ applicationId "com.wix.reactnativenotifications.app"
+ minSdkVersion 16
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+
+ ndk {
+ abiFilters "armeabi-v7a", "x86"
+ }
+// packagingOptions {
+// exclude "lib/arm64-v8a/librealm-jni.so"
+// }
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+
+ compile 'com.android.support:appcompat-v7:23.4.0'
+ compile 'com.android.support:design:23.4.0'
+ compile 'com.facebook.react:react-native:+'
+ compile project(':reactnativenotifications')
+
+ testCompile 'junit:junit:4.12'
+}
diff --git a/example/android/myapplication/proguard-rules.pro b/example/android/myapplication/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..57dcf0f7c50e676d70b08177e13e257a85a20017
--- /dev/null
+++ b/example/android/myapplication/proguard-rules.pro
@@ -0,0 +1,20 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/amitd/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
+-keep class * extends com.facebook.react.bridge.NativeModule { *; }
diff --git a/example/android/myapplication/src/main/AndroidManifest.xml b/example/android/myapplication/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..214751cac600ef8a87168b9eaf0496b3bf2c52e6
--- /dev/null
+++ b/example/android/myapplication/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/android/myapplication/src/main/java/com/wix/reactnativenotifications/app/MainActivity.java b/example/android/myapplication/src/main/java/com/wix/reactnativenotifications/app/MainActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..35edad8bc2027c547f5b78c70b4b5b651c919c50
--- /dev/null
+++ b/example/android/myapplication/src/main/java/com/wix/reactnativenotifications/app/MainActivity.java
@@ -0,0 +1,60 @@
+package com.wix.reactnativenotifications.app;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+import com.facebook.react.ReactActivity;
+import com.facebook.react.ReactRootView;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+public class MainActivity extends ReactActivity {
+
+ private static final int OVERLAY_PERMISSION_REQ_CODE = 1234;
+
+ private ReactRootView mReactRootView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final ViewGroup layout = (ViewGroup) getLayoutInflater().inflate(R.layout.activity_main, null);
+ if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Toolbar toolbar = (Toolbar) layout.findViewById(R.id.toolbar);
+ setActionBar(toolbar);
+ }
+ mReactRootView = new ReactRootView(this);
+ layout.addView(mReactRootView);
+
+ setContentView(layout);
+
+ if (SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
+ startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
+ } else {
+ startReactApplication();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
+ if (Settings.canDrawOverlays(this)) {
+ startReactApplication();
+ } else {
+ finish();
+ }
+ }
+ }
+
+ private void startReactApplication() {
+ mReactRootView.startReactApplication(getReactInstanceManager(), "WixRNNotifications", null);
+ }
+}
diff --git a/example/android/myapplication/src/main/java/com/wix/reactnativenotifications/app/MainApplication.java b/example/android/myapplication/src/main/java/com/wix/reactnativenotifications/app/MainApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..350b838f89fca340854c8d557d884e745a30eff2
--- /dev/null
+++ b/example/android/myapplication/src/main/java/com/wix/reactnativenotifications/app/MainApplication.java
@@ -0,0 +1,35 @@
+package com.wix.reactnativenotifications.app;
+
+import android.app.Application;
+
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactNativeHost;
+import com.facebook.react.ReactPackage;
+import com.facebook.react.shell.MainReactPackage;
+import com.wix.reactnativenotifications.RNNotificationsPackage;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class MainApplication extends Application implements ReactApplication {
+
+ private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
+ @Override
+ protected boolean getUseDeveloperSupport() {
+ return BuildConfig.DEBUG;
+ }
+
+ @Override
+ protected List getPackages() {
+ return Arrays.asList(
+ new MainReactPackage(),
+ new RNNotificationsPackage(MainApplication.this)
+ );
+ }
+ };
+
+ @Override
+ public ReactNativeHost getReactNativeHost() {
+ return mReactNativeHost;
+ }
+}
diff --git a/example/android/myapplication/src/main/res/layout/activity_main.xml b/example/android/myapplication/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..349ccb5bbd86ccfcdcf96d4f058ef7191f2c3506
--- /dev/null
+++ b/example/android/myapplication/src/main/res/layout/activity_main.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/myapplication/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to example/android/myapplication/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/myapplication/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to example/android/myapplication/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/myapplication/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to example/android/myapplication/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to example/android/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/example/android/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..aee44e138434630332d88b1680f33c4b24c70ab3
Binary files /dev/null and b/example/android/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/example/android/myapplication/src/main/res/values-v21/styles.xml b/example/android/myapplication/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6b23c86e5e970e28b7c4d9ef827cbe17b24454a5
--- /dev/null
+++ b/example/android/myapplication/src/main/res/values-v21/styles.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/example/android/myapplication/src/main/res/values-w820dp/dimens.xml b/example/android/myapplication/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000000000000000000000000000000000000..63fc816444614bd64f68a372d1f93211628ee51d
--- /dev/null
+++ b/example/android/myapplication/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/example/android/myapplication/src/main/res/values/colors.xml b/example/android/myapplication/src/main/res/values/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3ab3e9cbce07f7cdc941fc8ba424c05e83ed80f0
--- /dev/null
+++ b/example/android/myapplication/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/example/android/myapplication/src/main/res/values/dimens.xml b/example/android/myapplication/src/main/res/values/dimens.xml
new file mode 100644
index 0000000000000000000000000000000000000000..47c82246738c4d056e8030d3a259206f42e8e15d
--- /dev/null
+++ b/example/android/myapplication/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/example/android/myapplication/src/main/res/values/strings.xml b/example/android/myapplication/src/main/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7fd259ad36551bda78f6d050440b24b80ec55231
--- /dev/null
+++ b/example/android/myapplication/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Wix RN Notifications
+
diff --git a/example/android/myapplication/src/main/res/values/styles.xml b/example/android/myapplication/src/main/res/values/styles.xml
new file mode 100644
index 0000000000000000000000000000000000000000..16dbab30f23fdd6b78a2bdff5c6bf2f9c76efe95
--- /dev/null
+++ b/example/android/myapplication/src/main/res/values/styles.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/example/android/send_notif.py b/example/android/send_notif.py
new file mode 100644
index 0000000000000000000000000000000000000000..969c79fbb2e9537f99f589370822022f460506c5
--- /dev/null
+++ b/example/android/send_notif.py
@@ -0,0 +1,30 @@
+from urllib2 import *
+import json
+import sys
+
+if len(sys.argv) < 2:
+ print 'Error: missing token argument'
+ sys.exit(1)
+
+API_KEY = 'AIzaSyBVtqdO_SgPVhhXnyNGC_VXSbIX-fxk1YY'
+TOKEN = sys.argv[1]
+data = {
+ "to": TOKEN,
+ "data" : {
+ "body": "SUCCESS! Sent from script :)",
+ "title": "Wix Example Project"
+ }
+}
+dataJson = json.dumps(data)
+
+request = Request(
+ 'https://gcm-http.googleapis.com/gcm/send',
+ dataJson,
+ {
+ "Authorization" : "key="+API_KEY,
+ "Content-type" : "application/json"
+ }
+)
+
+print "Sending notification..."
+print urlopen(request).read()
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index 9ec85f37fcc6c1b01d6205bd6fa10cb8629e7692..ac703f3bf6a6dee757b62a5747d3115d1b3eb2bf 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -1,3 +1,4 @@
-rootProject.name = 'NotificationsExampleApp'
+include ':myapplication'
-include ':app'
+include ':reactnativenotifications'
+project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
diff --git a/example/index.android.js b/example/index.android.js
index 893f30a359943fed705e980b2b27c5975ac9fb50..b0af4b48f3fc411cc4a36de8c1f478b3fe29cb83 100644
--- a/example/index.android.js
+++ b/example/index.android.js
@@ -1,51 +1,111 @@
-/**
- * Sample React Native App
- * https://github.com/facebook/react-native
- */
+'use strict';
+import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
-import React, {Component} from 'react';
-class NotificationsExampleApp extends Component {
- render() {
- return (
-
-
- Welcome to React Native Notifications Demo App!
-
-
- To get started, edit index.android.js
-
-
- Shake or press menu button for dev menu
-
-
- );
+import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
+
+let mainScreen;
+
+function onPushRegistered() {
+ if (mainScreen) {
+ mainScreen.onPushRegistered();
+ }
+}
+
+function onNotificationOpened(notification) {
+ if (mainScreen) {
+ mainScreen.onNotificationOpened(notification)
}
}
+function onNotificationReceived(notification) {
+ if (mainScreen) {
+ mainScreen.onNotificationReceived(notification)
+ }
+}
+
+// It's highly recommended to keep listeners registration at global scope rather than at screen-scope seeing that
+// component mount and unmount lifecycle tend to be asymmetric!
+NotificationsAndroid.setRegistrationTokenUpdateListener(onPushRegistered);
+NotificationsAndroid.setNotificationOpenedListener(onNotificationOpened);
+NotificationsAndroid.setNotificationReceivedListener(onNotificationReceived);
+
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#F5FCFF',
},
- welcome: {
- fontSize: 20,
+ titleText: {
+ fontSize: 22,
textAlign: 'center',
margin: 10,
},
- instructions: {
+ bodyText: {
+ fontSize: 18,
textAlign: 'center',
- color: '#333333',
- marginBottom: 5,
+ margin: 10,
},
});
-AppRegistry.registerComponent('NotificationsExampleApp', () => NotificationsExampleApp);
+class MainComponent extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ elapsed: 0,
+ lastNotification: undefined
+ };
+
+ console.log('ReactScreen', 'ReactScreen');
+ mainScreen = this;
+
+ setInterval(this.onTick.bind(this), 1000);
+ }
+
+ componentDidMount() {
+ console.log('ReactScreen', 'componentDidMount');
+ PendingNotifications.getInitialNotification()
+ .then((notification) => {console.log("getInitialNotification:", notification); this.setState({initialNotification: notification.getData()});})
+ .catch((err) => console.error("getInitialNotifiation failed", err));
+ }
+
+ componentWillUnmount() {
+ console.log('ReactScreen', 'componentWillUnmount');
+ }
+
+ onTick() {
+ this.setState({elapsed: this.state.elapsed + 1});
+ }
+
+ render() {
+ return (
+
+ Wix React Native Notifications
+ {this.state.initialNotification ? 'Opened from notification' : ''}
+ Last notification: {this.state.lastNotification ? '\n'+this.state.lastNotification.body + ` (opened at ''${this.state.notificationRxTime})` : "N/A"}
+ Time elapsed: {this.state.elapsed}
+
+ )
+ }
+
+ onPushRegistered() {
+ }
+
+ onNotificationOpened(notification) {
+ console.log("onNotificationOpened: ", notification);
+ this.setState({lastNotification: notification.getData(), notificationRxTime: this.state.elapsed});
+ }
+
+ onNotificationReceived(notification) {
+ console.log("onNotificationReceived: ", notification);
+ }
+}
+
+AppRegistry.registerComponent('WixRNNotifications', () => MainComponent);
diff --git a/example/package.json b/example/package.json
index 8712369c230f07dc0a50b5bb8f597166733ff188..29be05403937d7f42d260b61fee69c3993a590ac 100644
--- a/example/package.json
+++ b/example/package.json
@@ -7,7 +7,7 @@
},
"dependencies": {
"babel-preset-react-native-stage-0": "^1.0.1",
- "react": "^15.3.1",
+ "react": "15.3.1",
"react-native": "0.34.0",
"react-native-notifications": "../"
},
diff --git a/index.android.js b/index.android.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0517bc8b8a7d328a4423e2ff6cd3e3d94982ff3
--- /dev/null
+++ b/index.android.js
@@ -0,0 +1,50 @@
+import {NativeModules, DeviceEventEmitter} from 'react-native';
+import NotificationAndroid from './notification';
+
+const RNNotifications = NativeModules.WixRNNotifications;
+
+let notificationReceivedListener;
+let notificationOpenedListener;
+let registrationTokenUpdateListener;
+
+export class NotificationsAndroid {
+ static setRegistrationTokenUpdateListener(listener) {
+ registrationTokenUpdateListener = DeviceEventEmitter.addListener('remoteNotificationsRegistered', listener);
+ }
+
+ static clearRegistrationTokenUpdateListener() {
+ if (registrationTokenUpdateListener) {
+ registrationTokenUpdateListener.remove();
+ registrationTokenUpdateListener = null;
+ }
+ }
+
+ static setNotificationOpenedListener(listener) {
+ notificationOpenedListener = DeviceEventEmitter.addListener('notificationOpened', (notification) => listener(new NotificationAndroid(notification)));
+ }
+
+
+ static setNotificationReceivedListener(listener) {
+ notificationReceivedListener = DeviceEventEmitter.addListener('notificationReceived', (notification) => listener(new NotificationAndroid(notification)));
+ }
+
+ static clearNotificationOpenedListener() {
+ if (notificationOpenedListener) {
+ notificationOpenedListener.remove();
+ notificationOpenedListener = null;
+ }
+ }
+
+ static clearNotificationReceivedListener() {
+ if (notificationReceivedListener) {
+ notificationReceivedListener.remove();
+ notificationReceivedListener = null;
+ }
+ }
+}
+
+export class PendingNotifications {
+ static async getInitialNotification() {
+ return new NotificationAndroid(await RNNotifications.getInitialNotification());
+ }
+}
diff --git a/notification.android.js b/notification.android.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb2ace85e1dfa5656311edf703250dd2fdd352ec
--- /dev/null
+++ b/notification.android.js
@@ -0,0 +1,20 @@
+/** A wrapper to align Android with iOS in terms on notification structure. */
+export default class NotificationAndroid {
+
+ constructor(notification) {
+ this.data = notification;
+ }
+
+ getData() {
+ return this.data;
+ }
+
+ getTitle() {
+ return this.data.title;
+ }
+
+ getMessage() {
+ return this.data.body;
+ }
+}
+
diff --git a/package.json b/package.json
index 582e43284f12e869590bf630d648a1ee7ad68446..ec648ba9a7666c7b5e326550ce784db8335fef2c 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,8 @@
"uuid": "^2.0.3"
},
"peerDependencies": {
- "react-native": ">=0.25.1"
+ "react-native": ">=0.25.1",
+ "react": "^0.14.5"
},
"devDependencies": {
"babel-eslint": "^6.0.2",
@@ -55,7 +56,6 @@
"bugs": {
"url": "https://github.com/wix/react-native-notifications/issues"
},
- "main": "index.ios.js",
"babel": {
"presets": [
"react-native"