Commit 5b4bd214 authored by Libin Lu's avatar Libin Lu

local notification

parent 4f9e63c5
......@@ -151,6 +151,40 @@ Edit `AppDelegate.m`:
In [firebase console](https://console.firebase.google.com/), you can get `google-services.json` file and place it in `android/app` directory and get `GoogleService-Info.plist` file and place it in `/ios/your-project-name` directory (next to your `Info.plist`)
### Setup Local Notifications
#### IOS
Edit Appdelegate.m
```diff
+ -(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
+ {
+ [[NSNotificationCenter defaultCenter] postNotificationName:FCMLocalNotificationReceived object:self userInfo:notification.userInfo];
+ }
```
#### Android
Edit AndroidManifest.xml
```diff
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.VIBRATE" />
<application
+ <receiver android:name="com.evollu.react.fcm.FIRLocalMessagingPublisher"/>
+ <receiver android:enabled="true" android:exported="true" android:name="com.evollu.react.fcm.FIRSystemBootEventReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
+ <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </receiver>
</application>
```
NOTE: `com.evollu.react.fcm.FIRLocalMessagingPublisher` is required for presenting local notifications. `com.evollu.react.fcm.FIRSystemBootEventReceiver` is required only if you need to schedule future or recurring local notifications
## Usage
```javascript
......@@ -166,19 +200,58 @@ class App extends Component {
this.notificationUnsubscribe = FCM.on('notification', (notif) => {
// there are two parts of notif. notif.notification contains the notification payload, notif.data contains data payload
});
this.localNotificationUnsubscribe = FCM.on('localNotification', (notif) => {
// notif.notification contains the data
});
this.refreshUnsubscribe = FCM.on('refreshToken', (token) => {
console.log(token)
// fcm token may not be available on first load, catch it here
});
FCM.subscribeToTopic('/topics/foo-bar');
FCM.unsubscribeFromTopic('/topics/foo-bar');
}
componentWillUnmount() {
// prevent leaking
this.refreshUnsubscribe();
this.notificationUnsubscribe();
this.localNotificationUnsubscribe();
}
otherMethods(){
FCM.subscribeToTopic('/topics/foo-bar');
FCM.unsubscribeFromTopic('/topics/foo-bar');
FCM.getInitialNotification().then(...);
FCM.presentLocalNotification({
id: "UNIQ_ID_STRING", // (optional for instant notification)
title: "My Notification Title", // as FCM payload
body: "My Notification Message", // as FCM payload (required)
sound: "default", // as FCM payload
priority: "high", // as FCM payload
badge: 10 // as FCM payload IOS only, set 0 to clear badges
number: 10 // Android only
ticker: "My Notification Ticker", // Android only
auto_cancel: true, // Android only (default true)
largeIcon: "ic_launcher", // Android only
icon: "ic_notification", // as FCM payload
big_text: "Show when notification is expanded", // Android only
sub_text: "This is a subText", // Android only
color: "red", // Android only
vibrate: 300, // Android only default: 300, no vibration is you pass null
tag: 'some_tag', // Android only
group: "group", // Android only
my_custom_data:'my_custom_field_value', // extra data you want to throw
});
FCM.scheduleLocalNotification({
fire_date: new Date(),
id: "UNIQ_ID_STRING" //REQUIRED! this is what you use to lookup and delete notification
body: "from future past"
})
FCM.getScheduledLocalNotifications().then(...);
FCM.cancelLocalNotification("UNIQ_ID_STRING");
FCM.cancelAllLocalNotifications();
FCM.setBadgeNumber();
FCM.getBadgeNumber();
}
}
```
......@@ -271,3 +344,6 @@ All available features are [here](https://firebase.google.com/docs/cloud-messagi
#### Some features are missing
Issues and pull requests are welcome. Let's make this thing better!
#### Thanks
Local notification implementation is inspired by react-native-push-notification by zo0r and Neson
......@@ -102,32 +102,34 @@
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-v4-23.2.1" level="project" />
<orderEntry type="library" exported="" name="play-services-basement-9.4.0" level="project" />
<orderEntry type="library" exported="" name="okhttp-ws-3.4.1" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-okhttp3-0.11.0" level="project" />
<orderEntry type="library" exported="" name="firebase-iid-9.2.1" level="project" />
<orderEntry type="library" exported="" name="okhttp-3.2.0" level="project" />
<orderEntry type="library" exported="" name="okio-1.8.0" level="project" />
<orderEntry type="library" exported="" name="play-services-tasks-9.4.0" level="project" />
<orderEntry type="library" exported="" name="fbcore-0.11.0" level="project" />
<orderEntry type="library" exported="" name="firebase-common-9.2.1" level="project" />
<orderEntry type="library" exported="" name="jackson-core-2.2.3" level="project" />
<orderEntry type="library" exported="" name="drawee-0.11.0" level="project" />
<orderEntry type="library" exported="" name="firebase-messaging-9.4.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.2.1" level="project" />
<orderEntry type="library" exported="" name="okhttp-urlconnection-3.2.0" level="project" />
<orderEntry type="library" exported="" name="firebase-core-9.2.1" level="project" />
<orderEntry type="library" exported="" name="soloader-0.1.0" level="project" />
<orderEntry type="library" exported="" name="react-native-0.33.0" level="project" />
<orderEntry type="library" exported="" name="library-2.4.0" level="project" />
<orderEntry type="library" exported="" name="firebase-analytics-9.4.0" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-base-0.11.0" level="project" />
<orderEntry type="library" exported="" name="fresco-0.11.0" level="project" />
<orderEntry type="library" exported="" name="firebase-iid-9.4.0" level="project" />
<orderEntry type="library" exported="" name="okio-1.9.0" level="project" />
<orderEntry type="library" exported="" name="jsr305-3.0.0" level="project" />
<orderEntry type="library" exported="" name="okhttp-ws-3.2.0" level="project" />
<orderEntry type="library" exported="" name="okhttp-3.4.1" level="project" />
<orderEntry type="library" exported="" name="bolts-tasks-1.4.0" level="project" />
<orderEntry type="library" exported="" name="firebase-analytics-9.2.1" level="project" />
<orderEntry type="library" exported="" name="firebase-analytics-impl-9.2.1" level="project" />
<orderEntry type="library" exported="" name="play-services-basement-9.2.1" level="project" />
<orderEntry type="library" exported="" name="firebase-common-9.4.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="firebase-core-9.4.0" level="project" />
<orderEntry type="library" exported="" name="firebase-analytics-impl-9.4.0" level="project" />
<orderEntry type="library" exported="" name="javax.inject-1" level="project" />
<orderEntry type="library" exported="" name="android-jsc-r174650" level="project" />
<orderEntry type="library" exported="" name="okhttp-urlconnection-3.4.1" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-0.11.0" level="project" />
<orderEntry type="library" exported="" name="react-native-0.30.0" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="firebase-messaging-9.2.1" level="project" />
<orderEntry type="library" exported="" name="play-services-tasks-9.2.1" level="project" />
</component>
</module>
\ No newline at end of file
//steal from https://github.com/facebook/facebook-android-sdk/blob/master/facebook/src/main/java/com/facebook/internal/BundleJSONConverter.java
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
* copy, modify, and distribute this software in source code or binary form for use
* in connection with the web services and APIs provided by Facebook.
*
* As with any software that integrates with the Facebook platform, your use of
* this software is subject to the Facebook Developer Principles and Policies
* [http://developers.facebook.com/policy/]. This copyright notice shall be
* included in all copies or substantial portions of the software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.evollu.react.fcm;
import android.os.Bundle;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.*;
/**
* com.facebook.internal is solely for the use of other packages within the Facebook SDK for
* Android. Use of any of the classes in this package is unsupported, and they may be modified or
* removed without warning at any time.
*
* A helper class that can round trip between JSON and Bundle objects that contains the types:
* Boolean, Integer, Long, Double, String
* If other types are found, an IllegalArgumentException is thrown.
*/
public class BundleJSONConverter {
private static final Map<Class<?>, Setter> SETTERS = new HashMap<Class<?>, Setter>();
static {
SETTERS.put(Boolean.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
bundle.putBoolean(key, (Boolean) value);
}
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
json.put(key, value);
}
});
SETTERS.put(Integer.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
bundle.putInt(key, (Integer) value);
}
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
json.put(key, value);
}
});
SETTERS.put(Long.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
bundle.putLong(key, (Long) value);
}
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
json.put(key, value);
}
});
SETTERS.put(Double.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
bundle.putDouble(key, (Double) value);
}
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
json.put(key, value);
}
});
SETTERS.put(String.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
bundle.putString(key, (String) value);
}
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
json.put(key, value);
}
});
SETTERS.put(String[].class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
throw new IllegalArgumentException("Unexpected type from JSON");
}
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
JSONArray jsonArray = new JSONArray();
for (String stringValue : (String[])value) {
jsonArray.put(stringValue);
}
json.put(key, jsonArray);
}
});
SETTERS.put(JSONArray.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
JSONArray jsonArray = (JSONArray)value;
ArrayList<String> stringArrayList = new ArrayList<String>();
// Empty list, can't even figure out the type, assume an ArrayList<String>
if (jsonArray.length() == 0) {
bundle.putStringArrayList(key, stringArrayList);
return;
}
// Only strings are supported for now
for (int i = 0; i < jsonArray.length(); i++) {
Object current = jsonArray.get(i);
if (current instanceof String) {
stringArrayList.add((String)current);
} else {
throw new IllegalArgumentException("Unexpected type in an array: " + current.getClass());
}
}
bundle.putStringArrayList(key, stringArrayList);
}
@Override
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
throw new IllegalArgumentException("JSONArray's are not supported in bundles.");
}
});
}
public interface Setter {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException;
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException;
}
public static JSONObject convertToJSON(Bundle bundle) throws JSONException {
JSONObject json = new JSONObject();
for(String key : bundle.keySet()) {
Object value = bundle.get(key);
if (value == null) {
// Null is not supported.
continue;
}
// Special case List<String> as getClass would not work, since List is an interface
if (value instanceof List<?>) {
JSONArray jsonArray = new JSONArray();
@SuppressWarnings("unchecked")
List<String> listValue = (List<String>)value;
for (String stringValue : listValue) {
jsonArray.put(stringValue);
}
json.put(key, jsonArray);
continue;
}
// Special case Bundle as it's one way, on the return it will be JSONObject
if (value instanceof Bundle) {
json.put(key, convertToJSON((Bundle)value));
continue;
}
Setter setter = SETTERS.get(value.getClass());
if (setter == null) {
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
}
setter.setOnJSON(json, key, value);
}
return json;
}
public static Bundle convertToBundle(JSONObject jsonObject) throws JSONException {
Bundle bundle = new Bundle();
@SuppressWarnings("unchecked")
Iterator<String> jsonIterator = jsonObject.keys();
while (jsonIterator.hasNext()) {
String key = jsonIterator.next();
Object value = jsonObject.get(key);
if (value == null || value == JSONObject.NULL) {
// Null is not supported.
continue;
}
// Special case JSONObject as it's one way, on the return it would be Bundle.
if (value instanceof JSONObject) {
bundle.putBundle(key, convertToBundle((JSONObject)value));
continue;
}
Setter setter = SETTERS.get(value.getClass());
if (setter == null) {
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
}
setter.setOnBundle(bundle, key, value);
}
return bundle;
}
}
\ No newline at end of file
//Credits to react-native-push-notification
package com.evollu.react.fcm;
import android.app.*;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.content.SharedPreferences;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class FIRLocalMessagingHelper {
private static final long DEFAULT_VIBRATION = 300L;
private static final String TAG = FIRLocalMessagingHelper.class.getSimpleName();
private final static String PREFERENCES_KEY = "ReactNativeSystemNotification";
private Context mContext;
private SharedPreferences sharedPreferences = null;
public FIRLocalMessagingHelper(Application context) {
mContext = context;
sharedPreferences = (SharedPreferences) mContext.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE);
}
public Class getMainActivityClass() {
String packageName = mContext.getPackageName();
Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
private AlarmManager getAlarmManager() {
return (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
}
public void sendNotification(Bundle bundle) {
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
return;
}
if (bundle.getString("body") == null) {
return;
}
Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
String title = bundle.getString("title");
if (title == null) {
ApplicationInfo appInfo = mContext.getApplicationInfo();
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext)
.setContentTitle(title)
.setContentText(bundle.getString("body"))
.setTicker(bundle.getString("ticker"))
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setAutoCancel(bundle.getBoolean("auto_cancel", true))
.setNumber(bundle.getInt("number"))
.setSubText(bundle.getString("sub_text"))
.setGroup(bundle.getString("group"))
.setVibrate(new long[]{0, DEFAULT_VIBRATION})
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setExtras(bundle.getBundle("data"));
//priority
String priority = bundle.getString("priority", "");
switch(priority) {
case "min":
notification.setPriority(NotificationCompat.PRIORITY_MIN);
break;
case "high":
notification.setPriority(NotificationCompat.PRIORITY_HIGH);
break;
case "max":
notification.setPriority(NotificationCompat.PRIORITY_MAX);
break;
default:
notification.setPriority(NotificationCompat.PRIORITY_DEFAULT);
}
//icon
String smallIcon = bundle.getString("icon", "ic_launcher");
int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
notification.setSmallIcon(smallIconResId);
//large icon
String largeIcon = bundle.getString("large-icon");
if(largeIcon != null){
int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0 && (largeIcon != null || android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP)) {
notification.setLargeIcon(largeIconBitmap);
}
}
//big text
String bigText = bundle.getString("big_text");
if(bigText != null){
notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
}
//sound
if (bundle.containsKey("sound")) {
int soundResourceId = res.getIdentifier(bundle.getString("sound"), "raw", packageName);
notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId));
}
//color
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setCategory(NotificationCompat.CATEGORY_CALL);
String color = bundle.getString("color");
if (color != null) {
notification.setColor(Color.parseColor(color));
}
}
//vibrate
if(bundle.containsKey("vibrate")){
long vibrate = bundle.getLong("vibrate", Math.round(bundle.getDouble("vibrate", bundle.getInt("vibrate"))));
if(vibrate > 0){
notification.setVibrate(new long[]{0, vibrate});
}else{
notification.setVibrate(null);
}
}
Intent intent = new Intent(mContext, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("notification", bundle);
intent.putExtra("localNotification", true);
int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis();
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notification.setContentIntent(pendingIntent);
Notification info = notification.build();
if (bundle.containsKey("tag")) {
String tag = bundle.getString("tag");
notificationManager.notify(tag, notificationID, info);
} else {
notificationManager.notify(notificationID, info);
}
//clear out one time scheduled notification once fired
if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(bundle.getString("id"));
editor.apply();
}
} catch (Exception e) {
Log.e(TAG, "failed to send local notification", e);
}
}
public void sendNotificationScheduled(Bundle bundle) {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
return;
}
String notificationId = bundle.getString("id");
if(notificationId == null){
Log.e(TAG, "failed to schedule notification because id is missing");
return;
}
Long fireDate = bundle.getLong("fire_date", Math.round(bundle.getDouble("fire_date")));
if (fireDate == 0) {
Log.e(TAG, "failed to schedule notification because fire date is missing");
return;
}
Intent notificationIntent = new Intent(mContext, FIRLocalMessagingPublisher.class);
notificationIntent.putExtras(bundle);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Long interval = null;
switch (bundle.getString("repeat_interval", "")) {
case "minute":
interval = (long) 60000;
break;
case "hour":
interval = AlarmManager.INTERVAL_HOUR;
break;
case "day":
interval = AlarmManager.INTERVAL_DAY;
break;
case "week":
interval = AlarmManager.INTERVAL_DAY * 7;
break;
}
if(interval != null){
getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent);
} else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
getAlarmManager().setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}else {
getAlarmManager().set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}
//store intent
SharedPreferences.Editor editor = sharedPreferences.edit();
JSONObject json = null;
try {
json = BundleJSONConverter.convertToJSON(bundle);
} catch (JSONException e) {
e.printStackTrace();
}
editor.putString(notificationId, json.toString());
editor.apply();
}
public void cancelLocalNotification(String notificationId) {
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId.hashCode());
cancelAlarm(notificationId);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(notificationId);
editor.apply();
}
public void cancelAllLocalNotifications() {
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
java.util.Map<String, ?> keyMap = sharedPreferences.getAll();
SharedPreferences.Editor editor = sharedPreferences.edit();
for(java.util.Map.Entry<String, ?> entry:keyMap.entrySet()){
cancelAlarm(entry.getKey());
}
editor.clear();
editor.apply();
}
public void cancelAlarm(String notificationId) {
Intent notificationIntent = new Intent(mContext, FIRLocalMessagingPublisher.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
getAlarmManager().cancel(pendingIntent);
}
public ArrayList<Bundle> getScheduledLocalNotifications(){
ArrayList<Bundle> array = new ArrayList<Bundle>();
java.util.Map<String, ?> keyMap = sharedPreferences.getAll();
for(java.util.Map.Entry<String, ?> entry:keyMap.entrySet()){
try {
JSONObject json = new JSONObject((String)entry.getValue());
Bundle bundle = BundleJSONConverter.convertToBundle(json);
array.add(bundle);
} catch (JSONException e) {
e.printStackTrace();
}
}
return array;
}
}
\ No newline at end of file
package com.evollu.react.fcm;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class FIRLocalMessagingPublisher extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
new FIRLocalMessagingHelper((Application) context.getApplicationContext()).sendNotification(intent.getExtras());
}
}
\ No newline at end of file
......@@ -12,27 +12,33 @@ 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.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.RemoteMessage;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;
import android.content.Context;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class FIRMessagingModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ActivityEventListener {
private final static String TAG = FIRMessagingModule.class.getCanonicalName();
private FIRLocalMessagingHelper mFIRLocalMessagingHelper;
Intent initIntent;
public FIRMessagingModule(ReactApplicationContext reactContext) {
super(reactContext);
mFIRLocalMessagingHelper = new FIRLocalMessagingHelper((Application) reactContext.getApplicationContext());
getReactApplicationContext().addLifecycleEventListener(this);
getReactApplicationContext().addActivityEventListener(this);
registerTokenRefreshHandler();
......@@ -50,6 +56,11 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
return "RNFIRMessaging";
}
@ReactMethod
public void getInitialNotification(Promise promise){
promise.resolve(getCurrentActivity().getIntent());
}
@ReactMethod
public void requestPermissions(){
}
......@@ -60,6 +71,27 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
promise.resolve(FirebaseInstanceId.getInstance().getToken());
}
@ReactMethod
public void presentLocalNotification(ReadableMap details) {
Bundle bundle = Arguments.toBundle(details);
mFIRLocalMessagingHelper.sendNotification(bundle);
}
@ReactMethod
public void scheduleLocalNotification(ReadableMap details) {
Bundle bundle = Arguments.toBundle(details);
mFIRLocalMessagingHelper.sendNotificationScheduled(bundle);
}
@ReactMethod
public void cancelLocalNotification(String notificationID) {
mFIRLocalMessagingHelper.cancelLocalNotification(notificationID);
}
@ReactMethod
public void cancelAllLocalNotifications() {
mFIRLocalMessagingHelper.cancelAllLocalNotifications();
}
@ReactMethod
public void subscribeToTopic(String topic){
FirebaseMessaging.getInstance().subscribeToTopic(topic);
......@@ -70,6 +102,16 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
}
@ReactMethod
public void getScheduledLocalNotifications(Promise promise){
ArrayList<Bundle> bundles = mFIRLocalMessagingHelper.getScheduledLocalNotifications();
WritableArray array = Arguments.createArray();
for(Bundle bundle:bundles){
array.pushMap(Arguments.fromBundle(bundle));
}
promise.resolve(array);
}
private void sendEvent(String eventName, Object params) {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
......@@ -161,6 +203,8 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
@Override
public void onNewIntent(Intent intent){
sendEvent("FCMNotificationReceived", parseIntent(intent));
Bundle bundle = intent.getExtras();
Boolean isLocalNotification = bundle.getBoolean("localNotification", false);
sendEvent(isLocalNotification ? "FCMLocalNotificationReceived" : "FCMNotificationReceived", parseIntent(intent));
}
}
package com.evollu.react.fcm;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.ArrayList;
import android.os.Bundle;
import android.util.Log;
/**
* Set alarms for scheduled notification after system reboot.
*/
public class FIRSystemBootEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("FCMSystemBootReceiver", "Received reboot event");
FIRLocalMessagingHelper helper = new FIRLocalMessagingHelper((Application) context.getApplicationContext());
ArrayList<Bundle> bundles = helper.getScheduledLocalNotifications();
for(Bundle bundle: bundles){
helper.sendNotificationScheduled(bundle);
}
}
}
\ No newline at end of file
import {NativeModules, DeviceEventEmitter} from 'react-native';
import {NativeModules, DeviceEventEmitter, Platform} from 'react-native';
const eventsMap = {
refreshToken: 'FCMTokenRefreshed',
notification: 'FCMNotificationReceived'
notification: 'FCMNotificationReceived',
localNotification: 'FCMLocalNotificationReceived'
};
const FIRMessaging = NativeModules.RNFIRMessaging;
const FCM = {};
FCM.getInitialNotification = () => {
return FIRMessaging.getInitialNotification();
}
FCM.getFCMToken = () => {
return FIRMessaging.getFCMToken();
};
......@@ -17,9 +22,36 @@ FCM.requestPermissions = () => {
return FIRMessaging.requestPermissions();
};
FCM.presentLocalNotification = (details) => {
FIRMessaging.presentLocalNotification(details);
};
FCM.scheduleLocalNotification = function(details) {
FIRMessaging.scheduleLocalNotification(details);
};
FCM.getScheduledLocalNotifications = function() {
return FIRMessaging.getScheduledLocalNotifications();
};
FCM.cancelLocalNotification = (notificationID) => {
FIRMessaging.cancelLocalNotification(notificationID);
};
FCM.cancelAllLocalNotifications = () => {
FIRMessaging.cancelAllLocalNotifications();
};
FCM.setBadgeNumber = () => {
FIRMessaging.setBadgeNumber();
}
FCM.getBadgeNumber = () => {
return FIRMessaging.getBadgeNumber();
}
FCM.on = (event, callback) => {
const nativeEvent = eventsMap[event];
const listener = DeviceEventEmitter.addListener(nativeEvent, callback);
return function remove() {
......@@ -36,7 +68,7 @@ FCM.unsubscribeFromTopic = (topic) => {
};
//once doesn't seem to work
DeviceEventEmitter.addListener('FCMInitData', (data)=>{
DeviceEventEmitter.addListener('FCMInitData', (data) => {
FCM.initialData = data;
});
......
......@@ -7,6 +7,7 @@
extern NSString *const FCMNotificationReceived;
extern NSString *const FCMLocalNotificationReceived;
@interface RNFIRMessaging : NSObject <RCTBridgeModule>
......
......@@ -16,7 +16,55 @@
#endif
NSString *const FCMNotificationReceived = @"FCMNotificationReceived";
NSString *const FCMLocalNotificationReceived = @"FCMLocalNotificationReceived";
@implementation RCTConvert (NSCalendarUnit)
+ (NSCalendarUnit *)NSCalendarUnit:(id)json
{
NSString* key = [self NSString:json];
if([key isEqualToString:@"minute"]){
return NSCalendarUnitMinute;
}
if([key isEqualToString:@"second"]){
return NSCalendarUnitSecond;
}
if([key isEqualToString:@"day"]){
return NSCalendarUnitDay;
}
if([key isEqualToString:@"month"]){
return NSCalendarUnitMonth;
}
if([key isEqualToString:@"week"]){
return NSCalendarUnitWeekOfYear;
}
if([key isEqualToString:@"year"]){
return NSCalendarUnitYear;
}
return 0;
}
@end
@implementation RCTConvert (UILocalNotification)
+ (UILocalNotification *)UILocalNotification:(id)json
{
NSDictionary<NSString *, id> *details = [self NSDictionary:json];
UILocalNotification *notification = [UILocalNotification new];
notification.fireDate = [RCTConvert NSDate:details[@"fire_date"]] ?: [NSDate date];
notification.alertTitle = [RCTConvert NSString:details[@"title"]];
notification.alertBody = [RCTConvert NSString:details[@"body"]];
notification.alertAction = [RCTConvert NSString:details[@"alert_action"]];
notification.soundName = [RCTConvert NSString:details[@"sound"]] ?: UILocalNotificationDefaultSoundName;
notification.userInfo = details;
notification.category = [RCTConvert NSString:details[@"click_action"]];
notification.repeatInterval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]];
notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"badge"]];
return notification;
}
@end
@implementation RNFIRMessaging
......@@ -52,6 +100,11 @@ RCT_EXPORT_MODULE()
selector:@selector(connectToFCM)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleFCMLocalNotificationReceived:)
name:FCMLocalNotificationReceived
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(onTokenRefresh)
name:kFIRInstanceIDTokenRefreshNotification object:nil];
......@@ -75,13 +128,61 @@ RCT_EXPORT_MODULE()
NSLog(@"Disconnected from FCM");
}
RCT_REMAP_METHOD(getFCMToken,
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
resolve([_bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]);
}
RCT_EXPORT_METHOD(getFCMToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
resolve([[FIRInstanceID instanceID] token]);
}
RCT_EXPORT_METHOD(getScheduledLocalNotifications:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSMutableArray* list = [[NSMutableArray alloc] init];
for(UILocalNotification * notif in [RCTSharedApplication() scheduledLocalNotifications]){
NSString* interval;
switch(notif.repeatInterval){
case NSCalendarUnitMinute:
interval = @"minute";
break;
case NSCalendarUnitSecond:
interval = @"second";
break;
case NSCalendarUnitDay:
interval = @"day";
break;
case NSCalendarUnitMonth:
interval = @"month";
break;
case NSCalendarUnitWeekOfYear:
interval = @"week";
break;
case NSCalendarUnitYear:
interval = @"year";
break;
}
NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary];
if (notif.fireDate) {
NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"];
NSString *fireDateString = [formatter stringFromDate:notif.fireDate];
formattedLocalNotification[@"fire_date"] = fireDateString;
}
formattedLocalNotification[@"alert_action"] = RCTNullIfNil(notif.alertAction);
formattedLocalNotification[@"body"] = RCTNullIfNil(notif.alertBody);
formattedLocalNotification[@"title"] = RCTNullIfNil(notif.alertTitle);
formattedLocalNotification[@"badge"] = @(notif.applicationIconBadgeNumber);
formattedLocalNotification[@"click_action"] = RCTNullIfNil(notif.category);
formattedLocalNotification[@"sound"] = RCTNullIfNil(notif.soundName);
formattedLocalNotification[@"repeat_interval"] = RCTNullIfNil(interval);
formattedLocalNotification[@"data"] = RCTNullIfNil(RCTJSONClean(notif.userInfo));
[list addObject:formattedLocalNotification];
}
resolve(list);
}
- (void) onTokenRefresh
{
[_bridge.eventDispatcher sendDeviceEventWithName:@"FCMTokenRefreshed" body:[[FIRInstanceID instanceID] token]];
......@@ -116,12 +217,53 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic)
[[FIRMessaging messaging] unsubscribeFromTopic:topic];
}
RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification)
{
[RCTSharedApplication() presentLocalNotificationNow:notification];
}
RCT_EXPORT_METHOD(scheduleLocalNotification:(UILocalNotification *)notification)
{
[RCTSharedApplication() scheduleLocalNotification:notification];
}
RCT_EXPORT_METHOD(cancelAllLocalNotifications)
{
[RCTSharedApplication() cancelAllLocalNotifications];
}
RCT_EXPORT_METHOD(cancelLocalNotification:(NSString*) notificationId)
{
for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) {
NSDictionary<NSString *, id> *notificationInfo = notification.userInfo;
if([notificationId isEqualToString:[notificationInfo valueForKey:@"id"]]){
[[UIApplication sharedApplication] cancelLocalNotification:notification];
}
}
}
RCT_EXPORT_METHOD(setBadgeNumber: (NSInteger*) number)
{
[RCTSharedApplication() setApplicationIconBadgeNumber:number];
}
RCT_EXPORT_METHOD(getBadgeNumber: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
resolve(@([RCTSharedApplication() applicationIconBadgeNumber]));
}
- (void)handleFCMLocalNotificationReceived:(UILocalNotification *)notification
{
NSMutableDictionary *data = [[NSMutableDictionary alloc]initWithDictionary: notification.userInfo];
[data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"];
[_bridge.eventDispatcher sendDeviceEventWithName:FCMLocalNotificationReceived body:data];
}
- (void)handleRemoteNotificationReceived:(NSNotification *)notification
{
NSMutableDictionary *data = [[NSMutableDictionary alloc]initWithDictionary: notification.userInfo];
[data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"];
[_bridge.eventDispatcher sendDeviceEventWithName:FCMNotificationReceived
body:data];
[_bridge.eventDispatcher sendDeviceEventWithName:FCMNotificationReceived body:data];
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment