[notifications] Continue android implementation

This commit is contained in:
Chris Bianca 2018-02-15 14:59:21 +00:00
parent 99b4b6550b
commit cd0ef4e3b7
15 changed files with 413 additions and 276 deletions

View File

@ -15,8 +15,8 @@ buildscript {
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion "25.0.3"
compileSdkVersion 27
buildToolsVersion "27.0.2"
defaultConfig {
minSdkVersion 16
targetSdkVersion 26
@ -82,6 +82,7 @@ rootProject.gradle.buildFinished { buildResult ->
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile "com.facebook.react:react-native:+" // From node_modules
compile "com.android.support:support-v4:27.0.2"
compile 'me.leolin:ShortcutBadger:1.1.21@aar'
compile "com.google.android.gms:play-services-base:$firebaseVersion"
compile "com.google.firebase:firebase-core:$firebaseVersion"

View File

@ -42,23 +42,8 @@ public class RNFirebaseLocalMessagingHelper {
sharedPreferences = mContext.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE);
}
public void sendNotification(Bundle bundle) {
}
public void setApplicationForeground(boolean foreground){
mIsForeground = foreground;
}
private 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;
}
}
}

View File

@ -155,6 +155,9 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
}
//////////////////////////////////////////////////////////////////////
// Start ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
// FCM functionality does not need this function
@ -168,6 +171,9 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements A
Utils.sendEvent(getReactApplicationContext(), "messaging_message_received", messageMap);
}
}
//////////////////////////////////////////////////////////////////////
// End ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
private WritableMap parseIntentForMessage(Intent intent) {
// Check if FCM data exists

View File

@ -2,15 +2,12 @@ package io.invertase.firebase.notifications;
import android.app.AlarmManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@ -19,6 +16,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
@ -29,7 +27,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
@ -39,9 +36,11 @@ import io.invertase.firebase.messaging.BundleJSONConverter;
public class RNFirebaseNotificationManager {
private static final String PREFERENCES_KEY = "RNFNotifications";
public static final String SCHEDULED_NOTIFICATION_EVENT = "notifications-scheduled-notification";
private static final String TAG = "RNFNotificationManager";
private AlarmManager alarmManager;
private Context context;
private boolean isForeground = false;
private NotificationManager notificationManager;
private SharedPreferences preferences;
@ -72,32 +71,100 @@ public class RNFirebaseNotificationManager {
preferences.edit().remove(notificationId).apply();
}
public void displayNotification(Bundle notification) {
displayNotification(notification, null);
}
public void displayNotification(ReadableMap notification, Promise promise) {
Bundle notificationBundle = Arguments.toBundle(notification);
displayNotification(notificationBundle, promise);
}
public void displayScheduledNotification(Bundle notification) {
// Broadcast the notification to the RN Application
Intent scheduledNotificationEvent = new Intent(SCHEDULED_NOTIFICATION_EVENT);
scheduledNotificationEvent.putExtra("notification", notification);
LocalBroadcastManager.getInstance(context).sendBroadcast(scheduledNotificationEvent);
// If this isn't a repeated notification, clear it from the scheduled notifications list
if (!notification.getBundle("schedule").containsKey("repeated")
|| !notification.getBundle("schedule").getBoolean("repeated")) {
String notificationId = notification.getString("notificationId");
preferences.edit().remove(notificationId).apply();;
}
// If the app isn't in the foreground, then we display it
// Otherwise, it is up to the JS to decide whether to send the notification
if (!isForeground) {
displayNotification(notification, null);
}
}
public ArrayList<Bundle> getScheduledNotifications(){
ArrayList<Bundle> array = new ArrayList<>();
Map<String, ?> notifications = preferences.getAll();
for(String notificationId : notifications.keySet()){
try {
JSONObject json = new JSONObject((String)notifications.get(notificationId));
Bundle bundle = BundleJSONConverter.convertToBundle(json);
array.add(bundle);
} catch (JSONException e) {
Log.e(TAG, e.getMessage());
}
}
return array;
}
public void removeAllDeliveredNotifications() {
notificationManager.cancelAll();
}
public void removeDeliveredNotification(String notificationId) {
notificationManager.cancel(notificationId.hashCode());
}
public void rescheduleNotifications() {
ArrayList<Bundle> bundles = getScheduledNotifications();
for(Bundle bundle: bundles){
scheduleNotification(bundle, null);
}
}
public void scheduleNotification(ReadableMap notification, Promise promise) {
Bundle notificationBundle = Arguments.toBundle(notification);
scheduleNotification(notificationBundle, promise);
}
public void setIsForeground(boolean isForeground) {
this.isForeground = isForeground;
}
private void cancelAlarm(String notificationId) {
Intent notificationIntent = new Intent(context, RNFirebaseNotificationManager.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent);
}
private void displayNotification(Bundle notification, Promise promise) {
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
if (promise != null) {
promise.reject("notification/display_notification_error", "Could not find main activity class");
}
return;
}
if (bundle.getString("body") == null) {
return;
}
Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
String channelId = notification.getString("channelId");
String notificationId = notification.getString("notificationId");
NotificationCompat.Builder nb = new NotificationCompat.Builder(context, channelId);
NotificationCompat.Builder nb;
// TODO: Change 27 to 'Build.VERSION_CODES.O_MR1' when using appsupport v27
if (Build.VERSION.SDK_INT >= 27) {
nb = new NotificationCompat.Builder(context, channelId);
} else {
nb = new NotificationCompat.Builder(context);
}
if (notification.containsKey("body")) {
nb = nb.setContentText(notification.getString("body"));
@ -106,8 +173,16 @@ public class RNFirebaseNotificationManager {
nb = nb.setExtras(notification.getBundle("data"));
}
if (notification.containsKey("sound")) {
// TODO: Sound URI;
nb = nb.setSound();
String sound = notification.getString("sound");
if (sound.equalsIgnoreCase("default")) {
nb = nb.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
} else {
int soundResourceId = getResourceId("raw", sound);
if (soundResourceId == 0) {
soundResourceId = getResourceId("raw", sound.substring(0, sound.lastIndexOf('.')));
}
nb = nb.setSound(Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId));
}
}
if (notification.containsKey("subtitle")) {
nb = nb.setSubText(notification.getString("subtitle"));
@ -126,7 +201,8 @@ public class RNFirebaseNotificationManager {
nb = nb.setCategory(notification.getString("category"));
}
if (notification.containsKey("color")) {
nb = nb.setColor(notification.getInt("color"));
String color = notification.getString("color");
nb = nb.setColor(Color.parseColor(color));
}
if (notification.containsKey("colorized")) {
nb = nb.setColorized(notification.getBoolean("colorized"));
@ -135,8 +211,12 @@ public class RNFirebaseNotificationManager {
nb = nb.setContentInfo(notification.getString("contentInfo"));
}
if (notification.containsKey("defaults")) {
// TODO: Bitwise ?
nb = nb.setDefaults()
int[] defaultsArray = notification.getIntArray("defaults");
int defaults = 0;
for (int d : defaultsArray) {
defaults |= d;
}
nb = nb.setDefaults(defaults);
}
if (notification.containsKey("group")) {
nb = nb.setGroup(notification.getString("group"));
@ -183,12 +263,11 @@ public class RNFirebaseNotificationManager {
Bundle progress = notification.getBundle("lights");
nb = nb.setProgress(progress.getInt("max"), progress.getInt("progress"), progress.getBoolean("indeterminate"));
}
if (notification.containsKey("publicVersion")) {
// TODO: Build notification
// TODO: Public version of notification
/* if (notification.containsKey("publicVersion")) {
nb = nb.setPublicVersion();
}
} */
if (notification.containsKey("remoteInputHistory")) {
// TODO: Build notification
nb = nb.setRemoteInputHistory(notification.getStringArray("remoteInputHistory"));
}
if (notification.containsKey("shortcutId")) {
@ -198,7 +277,18 @@ public class RNFirebaseNotificationManager {
nb = nb.setShowWhen(notification.getBoolean("showWhen"));
}
if (notification.containsKey("smallIcon")) {
nb = nb.setSmallIcon(notification.getInt("smallIcon"));
Bundle smallIcon = notification.getBundle("smallIcon");
int smallIconResourceId = getResourceId("mipmap", smallIcon.getString("icon"));
if (smallIconResourceId == 0) {
smallIconResourceId = getResourceId("drawable", smallIcon.getString("icon"));
}
if (smallIconResourceId != 0) {
if (smallIcon.containsKey("level")) {
nb = nb.setSmallIcon(smallIconResourceId, smallIcon.getInt("level"));
} else {
nb = nb.setSmallIcon(smallIconResourceId);
}
}
}
if (notification.containsKey("sortKey")) {
nb = nb.setSortKey(notification.getString("sortKey"));
@ -221,148 +311,109 @@ public class RNFirebaseNotificationManager {
if (notification.containsKey("when")) {
nb = nb.setWhen(notification.getLong("when"));
}
// TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh)
// TODO: style: Style; // Need to figure out if this can work
//icon
String smallIcon = bundle.getString("icon", "ic_launcher");
int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
notification.setSmallIcon(smallIconResId);
//big text
String bigText = bundle.getString("big_text");
// TODO: Big text / Big picture
/* String bigText = bundle.getString("big_text");
if(bigText != null){
notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
}
String picture = bundle.getString("picture");
if(picture!=null){
NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
//sound
String soundName = bundle.getString("sound", "default");
if (!soundName.equalsIgnoreCase("default")) {
int soundResourceId = res.getIdentifier(soundName, "raw", packageName);
if(soundResourceId == 0){
soundName = soundName.substring(0, soundName.lastIndexOf('.'));
soundResourceId = res.getIdentifier(soundName, "raw", packageName);
Bitmap pictureBitmap = getBitmap(picture);
if (pictureBitmap != null) {
bigPicture.bigPicture(pictureBitmap);
}
notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId));
bigPicture.setBigContentTitle(title);
bigPicture.setSummaryText(bundle.getString("body"));
notification.setStyle(bigPicture);
} */
// Create the notification intent
Intent intent = new Intent(context, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtras(notification);
if (notification.containsKey("clickAction")) {
intent.setAction(notification.getString("clickAction"));
}
//color
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String color = bundle.getString("color");
if (color != null) {
notification.setColor(Color.parseColor(color));
}
}
PendingIntent contentIntent = PendingIntent.getActivity(context, notificationId.hashCode(), intent,
PendingIntent.FLAG_UPDATE_CURRENT);
nb = nb.setContentIntent(contentIntent);
//lights
if (bundle.getBoolean("lights")) {
notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS);
}
Log.d(TAG, "broadcast intent before showing notification");
Intent i = new Intent("io.invertase.firebase.messaging.ReceiveLocalNotification");
i.putExtras(bundle);
mContext.sendOrderedBroadcast(i, null);
if(!mIsForeground || bundle.getBoolean("show_in_foreground")){
Intent intent = new Intent(mContext, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtras(bundle);
intent.setAction(bundle.getString("click_action"));
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();
}
// Build the notification and send it
Notification builtNotification = nb.build();
notificationManager.notify(notificationId.hashCode(), builtNotification);
} catch (Exception e) {
Log.e(TAG, "failed to send local notification", e);
}
}
public ArrayList<Bundle> getScheduledNotifications(){
ArrayList<Bundle> array = new ArrayList<>();
Map<String, ?> notifications = preferences.getAll();
for(String notificationId : notifications.keySet()){
try {
JSONObject json = new JSONObject((String)notifications.get(notificationId));
Bundle bundle = BundleJSONConverter.convertToBundle(json);
array.add(bundle);
} catch (JSONException e) {
Log.e(TAG, e.getMessage());
if (promise == null) {
Log.e(TAG, "Failed to send notification", e);
} else {
promise.reject("notification/display_notification_error", "Could not send notification", e);
}
}
return array;
}
public void removeAllDeliveredNotifications() {
notificationManager.cancelAll();
}
public void removeDeliveredNotification(String notificationId) {
notificationManager.cancel(notificationId.hashCode());
}
public void rescheduleNotifications() {
ArrayList<Bundle> bundles = getScheduledNotifications();
for(Bundle bundle: bundles){
scheduleNotification(bundle, null);
private Bitmap getBitmap(String image) {
if (image.startsWith("http://") || image.startsWith("https://")) {
return getBitmapFromUrl(image);
} else {
int largeIconResId = getResourceId("mipmap", image);
return BitmapFactory.decodeResource(context.getResources(), largeIconResId);
}
}
public void scheduleNotification(ReadableMap notification, ReadableMap schedule, Promise promise) {
Bundle notificationBundle = Arguments.toBundle(notification);
private Bitmap getBitmapFromUrl(String imageUrl) {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection();
connection.setDoInput(true);
connection.connect();
return BitmapFactory.decodeStream(connection.getInputStream());
} catch (IOException e) {
Log.e(TAG, "Failed to get bitmap for url: " + imageUrl, e);
return null;
}
}
scheduleNotification(notificationBundle, promise);
private Class getMainActivityClass() {
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
try {
return Class.forName(launchIntent.getComponent().getClassName());
} catch (ClassNotFoundException e) {
Log.e(TAG, "Failed to get main activity class", e);
return null;
}
}
private int getResourceId(String type, String image) {
return context.getResources().getIdentifier(image, type, context.getPackageName());
}
private void scheduleNotification(Bundle notification, Promise promise) {
// TODO
String notificationId = notification.getString("notificationId");
if (!notification.containsKey("notificationId")) {
if (promise != null) {
promise.reject("notification/schedule_notification_error", "Missing notificationId");
} else {
if (promise == null) {
Log.e(TAG, "Missing notificationId");
} else {
promise.reject("notification/schedule_notification_error", "Missing notificationId");
}
return;
}
// TODO: Schedule check
if (!notification.hasKey("schedule")) {
if (!notification.containsKey("schedule")) {
if (promise == null) {
Log.e(TAG, "Missing schedule information");
} else {
promise.reject("notification/schedule_notification_error", "Missing schedule information");
}
return;
}
/*
Long fireDate = Math.round(bundle.getDouble("fire_date"));
if (fireDate == 0) {
Log.e(TAG, "failed to schedule notification because fire date is missing");
return;
}*/
String notificationId = notification.getString("notificationId");
Bundle schedule = notification.getBundle("schedule");
long fireDate = schedule.getLong("fireDate");
// Scheduled alarms are cleared on restart
// We store them so that they can be re-scheduled when the phone restarts in RNFirebaseNotificationsRebootReceiver
@ -374,61 +425,47 @@ public class RNFirebaseNotificationManager {
return;
}
Intent notificationIntent = new Intent(context, RNFirebaseNotificationReceiver.class);
notificationIntent.putExtras(notification);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(),
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// TODO: Scheduling
Long interval = null;
switch (notification.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 (schedule.containsKey("interval")) {
Long interval = null;
switch (schedule.getString("interval")) {
case "minute":
interval = 60000L;
break;
case "hour":
interval = AlarmManager.INTERVAL_HOUR;
break;
case "day":
interval = AlarmManager.INTERVAL_DAY;
break;
case "week":
interval = AlarmManager.INTERVAL_DAY * 7;
break;
default:
Log.e(TAG, "Invalid interval: " + schedule.getString("interval"));
break;
}
if (interval == null) {
promise.reject("notification/schedule_notification_error", "Invalid interval");
return;
}
if(interval != null){
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent);
} else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}else {
alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}
}
private void cancelAlarm(String notificationId) {
Intent notificationIntent = new Intent(context, RNFirebaseNotificationManager.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent);
}
private Bitmap getBitmap(String image) {
if (image.startsWith("http://") || image.startsWith("https://")) {
return getBitmapFromUrl(image);
} else {
int largeIconResId = res.getIdentifier(image, "mipmap", packageName);
return BitmapFactory.decodeResource(res, largeIconResId);
if (schedule.containsKey("exact")
&& schedule.getBoolean("exact")
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}
}
}
private Bitmap getBitmapFromUrl(String imageUrl) {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection();
connection.setDoInput(true);
connection.connect();
return BitmapFactory.decodeStream(connection.getInputStream());
} catch (IOException e) {
Log.e(TAG, e.getMessage());
return null;
}
promise.resolve(null);
}
}

View File

@ -10,6 +10,6 @@ import android.content.Intent;
public class RNFirebaseNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
new RNFirebaseNotificationManager(context).displayNotification(intent.getExtras());
new RNFirebaseNotificationManager(context).displayScheduledNotification(intent.getExtras());
}
}

View File

@ -1,24 +1,41 @@
package io.invertase.firebase.notifications;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
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 java.util.ArrayList;
public class RNFirebaseNotifications extends ReactContextBaseJavaModule {
import io.invertase.firebase.Utils;
public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements LifecycleEventListener {
private static final String TAG = "RNFirebaseNotifications";
private RNFirebaseNotificationManager notificationManager;
public RNFirebaseNotifications(ReactApplicationContext context) {
super(context);
notificationManager = new RNFirebaseNotificationManager(context.getApplicationContext());
context.addLifecycleEventListener(this);
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
// Subscribe to scheduled notification events
localBroadcastManager.registerReceiver(new ScheduledNotificationReceiver(),
new IntentFilter(RNFirebaseNotificationManager.SCHEDULED_NOTIFICATION_EVENT));
}
@Override
@ -67,7 +84,46 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule {
}
@ReactMethod
public void scheduleNotification(ReadableMap notification, ReadableMap schedule, Promise promise) {
notificationManager.scheduleNotification(notification, schedule, promise);
public void scheduleNotification(ReadableMap notification, Promise promise) {
notificationManager.scheduleNotification(notification, promise);
}
//////////////////////////////////////////////////////////////////////
// Start LifecycleEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onHostResume() {
notificationManager.setIsForeground(true);
}
@Override
public void onHostPause() {
notificationManager.setIsForeground(false);
}
@Override
public void onHostDestroy() {
// Do nothing
}
//////////////////////////////////////////////////////////////////////
// End LifecycleEventListener methods
//////////////////////////////////////////////////////////////////////
private WritableMap parseNotificationBundle(Bundle notification) {
return Arguments.makeNativeMap(notification);
}
private class ScheduledNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
Log.d(TAG, "Received new scheduled notification");
Bundle notification = intent.getBundleExtra("notification");
WritableMap messageMap = parseNotificationBundle(notification);
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_received", messageMap);
}
}
}
}

View File

@ -27,7 +27,6 @@ RCT_EXPORT_METHOD(cancelNotification:(NSString*) notificationId) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) {
NSDictionary *notificationInfo = notification.userInfo;
// TODO: NotificationId?
if ([notificationId isEqualToString:[notificationInfo valueForKey:@"notificationId"]]) {
[RCTSharedApplication() cancelLocalNotification:notification];
}
@ -46,12 +45,12 @@ RCT_EXPORT_METHOD(displayNotification:(NSDictionary*) notification
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
UILocalNotification* notif = [self buildUILocalNotification:notification];
UILocalNotification* notif = [self buildUILocalNotification:notification withSchedule:false];
[RCTSharedApplication() presentLocalNotificationNow:notif];
resolve(nil);
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNNotificationRequest* request = [self buildUNNotificationRequest:notification];
UNNotificationRequest* request = [self buildUNNotificationRequest:notification withSchedule:false];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
@ -123,18 +122,15 @@ RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId) {
}
RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
schedule:(NSDictionary*) schedule
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
UILocalNotification* notif = [self buildUILocalNotification:notification];
// TODO: Schedule
UILocalNotification* notif = [self buildUILocalNotification:notification withSchedule:true];
[RCTSharedApplication() scheduleLocalNotification:notif];
resolve(nil);
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNNotificationRequest* request = [self buildUNNotificationRequest:notification];
// TODO: Schedule
UNNotificationRequest* request = [self buildUNNotificationRequest:notification withSchedule:true];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
@ -146,7 +142,8 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
}
}
- (UILocalNotification*) buildUILocalNotification:(NSDictionary *) notification {
- (UILocalNotification*) buildUILocalNotification:(NSDictionary *) notification
withSchedule:(BOOL) withSchedule {
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
if (notification[@"body"]) {
localNotification.alertBody = notification[@"body"];
@ -179,11 +176,32 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
localNotification.alertLaunchImage = ios[@"launchImage"];
}
}
if (withSchedule) {
NSDictionary *schedule = notification[@"schedule"];
NSNumber *fireDateNumber = schedule[@"fireDate"];
NSDate *fireDate = [NSDate dateWithTimeIntervalSince1970:([fireDateNumber doubleValue] / 1000.0)];
localNotification.fireDate = fireDate;
NSString *interval = schedule[@"repeatInterval"];
if (interval) {
if ([interval isEqualToString:@"minute"]) {
localNotification.repeatInterval = NSCalendarUnitMinute;
} else if ([interval isEqualToString:@"hour"]) {
localNotification.repeatInterval = NSCalendarUnitHour;
} else if ([interval isEqualToString:@"day"]) {
localNotification.repeatInterval = NSCalendarUnitDay;
} else if ([interval isEqualToString:@"week"]) {
localNotification.repeatInterval = NSCalendarUnitWeekday;
}
}
}
return localNotification;
}
- (UNNotificationRequest*) buildUNNotificationRequest:(NSDictionary *) notification {
- (UNNotificationRequest*) buildUNNotificationRequest:(NSDictionary *) notification
withSchedule:(BOOL) withSchedule {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
if (notification[@"body"]) {
content.body = notification[@"body"];
@ -234,8 +252,34 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
}
}
// TODO: Scheduling
return [UNNotificationRequest requestWithIdentifier:notification[@"ios"][@"identifier"] content:content trigger:nil];
if (withSchedule) {
NSDictionary *schedule = notification[@"schedule"];
NSNumber *fireDateNumber = schedule[@"fireDate"];
NSString *interval = schedule[@"repeatInterval"];
NSDate *fireDate = [NSDate dateWithTimeIntervalSince1970:([fireDateNumber doubleValue] / 1000.0)];
NSCalendarUnit calendarUnit;
if (interval) {
if ([interval isEqualToString:@"minute"]) {
calendarUnit = NSCalendarUnitSecond;
} else if ([interval isEqualToString:@"hour"]) {
calendarUnit = NSCalendarUnitMinute | NSCalendarUnitSecond;
} else if ([interval isEqualToString:@"day"]) {
calendarUnit = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
} else if ([interval isEqualToString:@"week"]) {
calendarUnit = NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
}
} else {
// Needs to match exactly to the secpmd
calendarUnit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
}
NSDateComponents *components = [[NSCalendar currentCalendar] components:calendarUnit fromDate:fireDate];
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:interval];
return [UNNotificationRequest requestWithIdentifier:notification[@"notificationId"] content:content trigger:trigger];
} else {
return [UNNotificationRequest requestWithIdentifier:notification[@"notificationId"] content:content trigger:nil];
}
}
- (NSDictionary*) parseUILocalNotification:(UILocalNotification *) localNotification {
@ -278,7 +322,7 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
- (NSDictionary*) parseUNNotificationRequest:(UNNotificationRequest *) localNotification {
NSMutableDictionary *notification = [[NSMutableDictionary alloc] init];
notification[@"identifier"] = localNotification.identifier;
notification[@"notificationId"] = localNotification.identifier;
if (localNotification.content.body) {
notification[@"body"] = localNotification.content.body;

View File

@ -5,7 +5,7 @@
import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import type App from '../core/firebase-app';
import type App from '../core/app';
export const MODULE_NAME = 'RNFirebaseInstanceId';
export const NAMESPACE = 'instanceid';

View File

@ -17,17 +17,18 @@ type Progress = {
};
type SmallIcon = {
icon: number,
icon: string,
level?: number,
};
export type NativeAndroidNotification = {
export type NativeAndroidNotification = {|
// TODO actions: Action[],
autoCancel: boolean,
badgeIconType: BadgeIconTypeType,
category: CategoryType,
channelId: string,
color: number,
clickAction?: string,
color: string,
colorized: boolean,
contentInfo: string,
defaults: DefaultsType[],
@ -43,7 +44,7 @@ export type NativeAndroidNotification = {
people: string[],
priority: PriorityType,
progress: Progress,
publicVersion: Notification,
// publicVersion: Notification,
remoteInputHistory: string[],
shortcutId: string,
showWhen: boolean,
@ -56,7 +57,7 @@ export type NativeAndroidNotification = {
vibrate: number[],
visibility: VisibilityType,
when: number,
};
|};
export const BadgeIconType = {
Large: 2,
@ -122,7 +123,8 @@ export default class AndroidNotification {
_badgeIconType: BadgeIconTypeType;
_category: CategoryType;
_channelId: string;
_color: number;
_clickAction: string;
_color: string;
_colorized: boolean;
_contentInfo: string;
_defaults: DefaultsType[];
@ -139,11 +141,13 @@ export default class AndroidNotification {
_people: string[];
_priority: PriorityType;
_progress: Progress;
_publicVersion: Notification;
// _publicVersion: Notification;
_remoteInputHistory: string[];
_shortcutId: string;
_showWhen: boolean;
_smallIcon: SmallIcon;
_smallIcon: SmallIcon = {
icon: 'ic_launcher',
};
_sortKey: string;
// TODO: style: Style; // Need to figure out if this can work
_ticker: string;
@ -170,9 +174,7 @@ export default class AndroidNotification {
/**
*
* @param identifier
* @param identifier
* @param identifier
* @param person
* @returns {Notification}
*/
addPerson(person: string): Notification {
@ -228,7 +230,7 @@ export default class AndroidNotification {
* @param color
* @returns {Notification}
*/
setColor(color: number): Notification {
setColor(color: string): Notification {
this._color = color;
return this._notification;
}
@ -394,10 +396,10 @@ export default class AndroidNotification {
* @param publicVersion
* @returns {Notification}
*/
setPublicVersion(publicVersion: Notification): Notification {
/* setPublicVersion(publicVersion: Notification): Notification {
this._publicVersion = publicVersion;
return this._notification;
}
} */
/**
*
@ -435,7 +437,7 @@ export default class AndroidNotification {
* @param level
* @returns {Notification}
*/
setSmallIcon(icon: number, level?: number): Notification {
setSmallIcon(icon: string, level?: number): Notification {
this._smallIcon = {
icon,
level,
@ -517,6 +519,7 @@ export default class AndroidNotification {
badgeIconType: this._badgeIconType,
category: this._category,
channelId: this._channelId,
clickAction: this._clickAction,
color: this._color,
colorized: this._colorized,
contentInfo: this._contentInfo,
@ -533,7 +536,7 @@ export default class AndroidNotification {
people: this._people,
priority: this._priority,
progress: this._progress,
publicVersion: this._publicVersion,
// publicVersion: this._publicVersion,
remoteInputHistory: this._remoteInputHistory,
shortcutId: this._shortcutId,
showWhen: this._showWhen,

View File

@ -2,7 +2,6 @@
* @flow
* IOSNotification representation wrapper
*/
import { generatePushID } from '../../utils';
import type Notification from './Notification';
type AttachmentOptions = {|
@ -23,16 +22,15 @@ type Attachment = {|
url: string,
|};
export type NativeIOSNotification = {
export type NativeIOSNotification = {|
alertAction?: string,
attachments: Attachment[],
badge?: number,
category?: string,
hasAction?: boolean,
identifier?: string,
launchImage?: string,
threadIdentifier?: string,
};
|};
export default class IOSNotification {
_alertAction: string; // alertAction | N/A
@ -40,23 +38,20 @@ export default class IOSNotification {
_badge: number; // applicationIconBadgeNumber | badge
_category: string;
_hasAction: boolean; // hasAction | N/A
_identifier: string; // N/A | identifier
_launchImage: string; // alertLaunchImage | launchImageName
_notification: Notification;
_threadIdentifier: string; // N/A | threadIdentifier
constructor(notification: Notification) {
this._attachments = [];
// TODO: Is this the best way to generate an ID?
this._identifier = generatePushID();
this._notification = notification;
}
/**
*
* @param identifier
* @param identifier
* @param identifier
* @param url
* @param options
* @returns {Notification}
*/
addAttachment(
@ -112,16 +107,6 @@ export default class IOSNotification {
return this._notification;
}
/**
*
* @param identifier
* @returns {Notification}
*/
setIdentifier(identifier: string): Notification {
this._identifier = identifier;
return this._notification;
}
/**
*
* @param launchImage
@ -151,7 +136,6 @@ export default class IOSNotification {
badge: this._badge,
category: this._category,
hasAction: this._hasAction,
identifier: this._identifier,
launchImage: this._launchImage,
threadIdentifier: this._threadIdentifier,
};

View File

@ -4,16 +4,19 @@
*/
import AndroidNotification from './AndroidNotification';
import IOSNotification from './IOSNotification';
import { isObject } from '../../utils';
import { generatePushID, isObject } from '../../utils';
import type { NativeAndroidNotification } from './AndroidNotification';
import type { NativeIOSNotification } from './IOSNotification';
import type { Schedule } from './';
type NativeNotification = {|
android: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios: NativeIOSNotification,
notificationId: string,
schedule?: Schedule,
sound?: string,
subtitle?: string,
title: string,
@ -25,6 +28,7 @@ export default class Notification {
_body: string; // alertBody | body | contentText
_data: { [string]: string }; // userInfo | userInfo | extras
_ios: IOSNotification;
_notificationId: string;
_sound: string | void; // soundName | sound | sound
_subtitle: string | void; // N/A | subtitle | subText
_title: string; // alertTitle | title | contentTitle
@ -33,6 +37,8 @@ export default class Notification {
this._android = new AndroidNotification(this);
this._data = {};
this._ios = new IOSNotification(this);
// TODO: Is this the best way to generate an ID?
this._notificationId = generatePushID();
}
get android(): AndroidNotification {
@ -68,6 +74,16 @@ export default class Notification {
return this;
}
/**
*
* @param notificationId
* @returns {Notification}
*/
setNotificationId(notificationId: string): Notification {
this._notificationId = notificationId;
return this;
}
/**
*
* @param sound
@ -101,9 +117,13 @@ export default class Notification {
build(): NativeNotification {
// Android required fields: body, title, smallicon
// iOS required fields: TODO
if (!this.body) {
if (!this._body) {
throw new Error('Notification: Missing required `body` property');
} else if (!this.title) {
} else if (!this._notificationId) {
throw new Error(
'Notification: Missing required `notificationId` property'
);
} else if (!this._title) {
throw new Error('Notification: Missing required `title` property');
}
@ -112,6 +132,7 @@ export default class Notification {
body: this._body,
data: this._data,
ios: this._ios.build(),
notificationId: this._notificationId,
sound: this._sound,
subtitle: this._subtitle,
title: this._title,

View File

@ -17,7 +17,7 @@ import {
Visibility,
} from './AndroidNotification';
import type App from '../core/firebase-app';
import type App from '../core/app';
// TODO: Received notification type will be different from sent notification
type OnNotification = Notification => any;
@ -26,9 +26,10 @@ type OnNotificationObserver = {
next: OnNotification,
};
// TODO: Schedule type
type Schedule = {
build: () => Object,
export type Schedule = {
exact?: boolean,
fireDate: number,
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
};
const NATIVE_EVENTS = ['notifications_notification_received'];
@ -174,10 +175,9 @@ export default class Notifications extends ModuleBase {
`Notifications:scheduleNotification expects a 'Notification' but got type ${typeof notification}`
);
}
return getNativeModule(this).scheduleNotification(
notification.build(),
schedule.build()
);
const nativeNotification = notification.build();
nativeNotification.schedule = schedule;
return getNativeModule(this).scheduleNotification(nativeNotification);
}
}

View File

@ -98,7 +98,7 @@ def enableProguardInReleaseBuilds = false
android {
compileSdkVersion 26
buildToolsVersion '25.0.3'
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.reactnativefirebasedemo"
@ -163,7 +163,7 @@ dependencies {
compile('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {
transitive = true
}
compile "com.android.support:appcompat-v7:26.0.1"
compile "com.android.support:appcompat-v7:26.0.2"
compile "com.facebook.react:react-native:+" // From node_modules
}

View File

@ -29,10 +29,10 @@ allprojects {
subprojects {
ext {
compileSdk = 25
buildTools = "25.0.2"
compileSdk = 26
buildTools = "26.0.2"
minSdk = 16
targetSdk = 25
targetSdk = 26
}
afterEvaluate { project ->

View File

@ -164,7 +164,7 @@ PODS:
- React/Core
- React/fishhook
- React/RCTBlob
- RNFirebase (3.2.4):
- RNFirebase (3.2.5):
- React
- yoga (0.52.0.React)
@ -228,7 +228,7 @@ SPEC CHECKSUMS:
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03
React: 61a6bdf17a9ff16875c230e6ff278d9de274e16c
RNFirebase: 011e47909cf54070f72d50b8d61eb7b347774d29
RNFirebase: e3448c730955d51d06dee59a265011536abdd7c4
yoga: 646606bf554d54a16711f35596178522fbc00480
PODFILE CHECKSUM: 67c98bcb203cb992da590bcab6f690f727653ca5