[MM-17145] [MM-18947] [MM-17110] [MM-14926] [MM-18646] Use patched v2.0.6 of react-native-notifications and fix Android badge number (#3382)

* Refactor custom push notification code

* Use react-native-notifications 2.0.6 and patch for scheduled notifs

* Fix patch

* iOS changes

* Fix delete

* Fix setting of badge number on Android

* Undo Reflect removal

* Undo removal of didReceiveRemoteNotification

* Use min importance for push notifs received while app is active

* Correctly set badge number after push notificaiton reply

* Fix tests

* Localize reply action text

* Add getDeliveredNotifications

* Fix identifier check and failing test

* Fix local push notif test for Android > 9
This commit is contained in:
Miguel Alatzar 2019-10-22 11:18:59 -07:00 committed by Elias Nahum
parent 8f0619d083
commit 5e5d3abd79
13 changed files with 772 additions and 315 deletions

View file

@ -20,7 +20,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Build;
import android.provider.Settings.System;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
@ -33,7 +32,6 @@ import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.helpers.ApplicationBadgeHelper;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
@ -44,6 +42,9 @@ public class CustomPushNotification extends PushNotification {
public static final String KEY_TEXT_REPLY = "CAN_REPLY";
public static final String NOTIFICATION_REPLIED_EVENT_NAME = "notificationReplied";
private NotificationChannel mHighImportanceChannel;
private NotificationChannel mMinImportanceChannel;
private static LinkedHashMap<String,Integer> channelIdToNotificationCount = new LinkedHashMap<String,Integer>();
private static LinkedHashMap<String,List<Bundle>> channelIdToNotification = new LinkedHashMap<String,List<Bundle>>();
private static AppLifecycleFacade lifecycleFacade;
@ -53,11 +54,12 @@ public class CustomPushNotification extends PushNotification {
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
this.context = context;
createNotificationChannels();
}
public static void clearNotification(Context mContext, int notificationId, String channelId) {
if (notificationId != -1) {
Object objCount = channelIdToNotificationCount.get(channelId);
Object objCount = channelIdToNotificationCount.get(channelId);
Integer count = -1;
if (objCount != null) {
@ -74,7 +76,6 @@ public class CustomPushNotification extends PushNotification {
if (count != -1) {
int total = CustomPushNotification.badgeCount - count;
int badgeCount = total < 0 ? 0 : total;
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), badgeCount);
CustomPushNotification.badgeCount = badgeCount;
}
}
@ -87,7 +88,6 @@ public class CustomPushNotification extends PushNotification {
if (mContext != null) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), 0);
}
}
@ -121,7 +121,7 @@ public class CustomPushNotification extends PushNotification {
}
synchronized (list) {
if (!"clear".equals(type)) {
String senderName = getSenderName(data.getString("sender_name"), data.getString("channel_name"), data.getString("message"));
String senderName = getSenderName(data);
data.putLong("time", new Date().getTime());
data.putString("sender_name", senderName);
data.putString("sender_id", data.getString("sender_id"));
@ -149,59 +149,142 @@ public class CustomPushNotification extends PushNotification {
digestNotification();
}
@Override
protected void postNotification(int id, Notification notification) {
boolean force = false;
Bundle bundle = notification.extras;
if (bundle != null) {
force = bundle.getBoolean("localTest");
}
if (!mAppLifecycleFacade.isAppVisible() || force) {
super.postNotification(id, notification);
}
}
@Override
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
// First, get a builder initialized with defaults from the core class.
final Notification.Builder notification = new Notification.Builder(mContext);
// If Android Oreo or above we need to register a channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String CHANNEL_ID = "channel_01";
String CHANNEL_NAME = "Mattermost notifications";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(true);
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
notification.setChannelId(CHANNEL_ID);
}
Bundle bundle = mNotificationProps.asBundle();
String version = bundle.getString("version");
addNotificationExtras(notification, bundle);
setNotificationIcons(notification, bundle);
setNotificationMessagingStyle(notification, bundle);
setNotificationChannel(notification, bundle);
setNotificationBadgeIconType(notification);
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
setNotificationSound(notification, notificationPreferences);
setNotificationVibrate(notification, notificationPreferences);
setNotificationBlink(notification, notificationPreferences);
String channelId = bundle.getString("channel_id");
String channelName = bundle.getString("channel_name");
String senderName = bundle.getString("sender_name");
String senderId = bundle.getString("sender_id");
String postId = bundle.getString("post_id");
String badge = bundle.getString("badge");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
setNotificationNumber(notification, channelId);
setNotificationDeleteIntent(notification, notificationId);
addNotificationReplyAction(notification, notificationId, bundle);
notification
.setContentIntent(intent)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true);
return notification;
}
private void addNotificationExtras(Notification.Builder notification, Bundle bundle) {
Bundle userInfoBundle = bundle.getBundle("userInfo");
if (userInfoBundle == null) {
userInfoBundle = new Bundle();
}
String channelId = bundle.getString("channel_id");
userInfoBundle.putString("channel_id", channelId);
notification.addExtras(userInfoBundle);
}
private void setNotificationIcons(Notification.Builder notification, Bundle bundle) {
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
int smallIconResId = getSmallIconResourceId(smallIcon);
notification.setSmallIcon(smallIconResId);
int largeIconResId = getLargeIconResourceId(largeIcon);
final Resources res = mContext.getResources();
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0 && (largeIconBitmap != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
notification.setLargeIcon(largeIconBitmap);
}
}
private int getSmallIconResourceId(String iconName) {
if (iconName == null) {
iconName = "ic_notification";
}
int resourceId = getIconResourceId(iconName);
if (resourceId == 0) {
iconName = "ic_launcher";
resourceId = getIconResourceId(iconName);
if (resourceId == 0) {
resourceId = android.R.drawable.ic_dialog_info;
}
}
return resourceId;
}
private int getLargeIconResourceId(String iconName) {
if (iconName == null) {
iconName = "ic_launcher";
}
return getIconResourceId(iconName);
}
private int getIconResourceId(String iconName) {
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
String defType = "mipmap";
return res.getIdentifier(iconName, defType, packageName);
}
private void setNotificationNumber(Notification.Builder notification, String channelId) {
Integer number = 1;
Object objCount = channelIdToNotificationCount.get(channelId);
if (objCount != null) {
number = (Integer)objCount;
}
notification.setNumber(number);
}
private void setNotificationMessagingStyle(Notification.Builder notification, Bundle bundle) {
Notification.MessagingStyle messagingStyle = getMessagingStyle(bundle);
notification.setStyle(messagingStyle);
}
private Notification.MessagingStyle getMessagingStyle(Bundle bundle) {
Notification.MessagingStyle messagingStyle;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle = new Notification.MessagingStyle("");
} else {
String senderId = bundle.getString("sender_id");
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
.build();
messagingStyle = new Notification.MessagingStyle(sender);
}
String conversationTitle = getConversationTitle(bundle);
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
addMessagingStyleMessages(messagingStyle, conversationTitle, bundle);
return messagingStyle;
}
private String getConversationTitle(Bundle bundle) {
String title = null;
String version = bundle.getString("version");
if (version != null && version.equals("v2")) {
title = channelName;
title = bundle.getString("channel_name");
} else {
title = bundle.getString("title");
}
@ -211,149 +294,100 @@ public class CustomPushNotification extends PushNotification {
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
}
Bundle b = bundle.getBundle("userInfo");
if (b == null) {
b = new Bundle();
}
b.putString("channel_id", channelId);
notification.addExtras(b);
int smallIconResId;
int largeIconResId;
if (smallIcon != null) {
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
} else {
smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
}
if (smallIconResId == 0) {
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
if (smallIconResId == 0) {
smallIconResId = android.R.drawable.ic_dialog_info;
}
}
if (largeIcon != null) {
largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
} else {
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
}
if (badge != null) {
int badgeCount = Integer.parseInt(badge);
CustomPushNotification.badgeCount = badgeCount;
notification.setNumber(badgeCount);
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
}
return title;
}
private void setMessagingStyleConversationTitle(Notification.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
String channelName = bundle.getString("channel_name");
String senderName = bundle.getString("sender_name");
if (android.text.TextUtils.isEmpty(senderName)) {
senderName = getSenderName(senderName, channelName, bundle.getString("message"));
senderName = getSenderName(bundle);
}
String personId = senderId;
if (!android.text.TextUtils.isEmpty(channelName)) {
personId = channelId;
if (conversationTitle != null && (!conversationTitle.startsWith("@") || channelName != senderName)) {
messagingStyle.setConversationTitle(conversationTitle);
}
}
Notification.MessagingStyle messagingStyle;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle = new Notification.MessagingStyle("");
} else {
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
.build();
messagingStyle = new Notification.MessagingStyle(sender);
}
if (title != null && (!title.startsWith("@") || channelName != senderName)) {
messagingStyle
.setConversationTitle(title);
}
private void addMessagingStyleMessages(Notification.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
List<Bundle> bundleList;
String channelId = bundle.getString("channel_id");
List<Bundle> bundleArray = channelIdToNotification.get(channelId);
List<Bundle> list;
if (bundleArray != null) {
list = new ArrayList<Bundle>(bundleArray);
bundleList = new ArrayList<Bundle>(bundleArray);
} else {
list = new ArrayList<Bundle>();
list.add(bundle);
bundleList = new ArrayList<Bundle>();
bundleList.add(bundle);
}
int listCount = list.size() - 1;
for (int i = listCount; i >= 0; i--) {
Bundle data = list.get(i);
int bundleCount = bundleList.size() - 1;
for (int i = bundleCount; i >= 0; i--) {
Bundle data = bundleList.get(i);
String message = data.getString("message");
String previousPersonName = getSenderName(data.getString("sender_name"), channelName, message);
String previousPersonId = data.getString("sender_id");
if (title == null || !android.text.TextUtils.isEmpty(previousPersonName)) {
message = removeSenderFromMessage(previousPersonName, channelName, message);
String senderId = data.getString("sender_id");
Bundle userInfoBundle = data.getBundle("userInfo");
String senderName = getSenderName(data);
if (userInfoBundle != null) {
boolean localPushNotificationTest = userInfoBundle.getBoolean("localTest");
if (localPushNotificationTest) {
senderName = "Test";
}
}
if (conversationTitle == null || !android.text.TextUtils.isEmpty(senderName.trim())) {
message = removeSenderNameFromMessage(message, senderName);
}
long timestamp = data.getLong("time");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle.addMessage(message, data.getLong("time"), previousPersonName);
messagingStyle.addMessage(message, timestamp, senderName);
} else {
Person sender = new Person.Builder()
.setKey(previousPersonId)
.setName(previousPersonName)
.build();
messagingStyle.addMessage(message, data.getLong("time"), sender);
.setKey(senderId)
.setName(senderName)
.build();
messagingStyle.addMessage(message, timestamp, sender);
}
}
}
notification
.setContentIntent(intent)
.setGroupSummary(true)
.setStyle(messagingStyle)
.setSmallIcon(smallIconResId)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true);
private void setNotificationChannel(Notification.Builder notification, Bundle bundle) {
// If Android Oreo or above we need to register a channel
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
NotificationChannel notificationChannel = mHighImportanceChannel;
boolean localPushNotificationTest = false;
Bundle userInfoBundle = bundle.getBundle("userInfo");
if (userInfoBundle != null) {
localPushNotificationTest = userInfoBundle.getBoolean("localTest");
}
if (mAppLifecycleFacade.isAppVisible() && !localPushNotificationTest) {
notificationChannel = mMinImportanceChannel;
}
notification.setChannelId(notificationChannel.getId());
}
private void setNotificationBadgeIconType(Notification.Builder notification) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setBadgeIconType(Notification.BADGE_ICON_SMALL);
}
}
// Let's add a delete intent when the notification is dismissed
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
delIntent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
notification.setDeleteIntent(deleteIntent);
private void setNotificationGroup(Notification.Builder notification) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setGroup(GROUP_KEY_MESSAGES);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && postId != null) {
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
replyIntent.setAction(KEY_TEXT_REPLY);
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel("Reply")
.build();
Notification.Action replyAction = new Notification.Action.Builder(
R.drawable.ic_notif_action_reply, "Reply", replyPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build();
notification
.setShowWhen(true)
.addAction(replyAction);
}
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
notification.setLargeIcon(largeIconBitmap);
.setGroup(GROUP_KEY_MESSAGES)
.setGroupSummary(true);
}
}
private void setNotificationSound(Notification.Builder notification, NotificationPreferences notificationPreferences) {
String soundUri = notificationPreferences.getNotificationSound();
if (soundUri != null) {
if (soundUri != "none") {
@ -363,65 +397,115 @@ public class CustomPushNotification extends PushNotification {
Uri defaultUri = System.DEFAULT_NOTIFICATION_URI;
notification.setSound(defaultUri, AudioManager.STREAM_NOTIFICATION);
}
}
private void setNotificationVibrate(Notification.Builder notification, NotificationPreferences notificationPreferences) {
boolean vibrate = notificationPreferences.getShouldVibrate();
if (vibrate) {
// use the system default for vibration
// Use the system default for vibration
notification.setDefaults(Notification.DEFAULT_VIBRATE);
}
}
private void setNotificationBlink(Notification.Builder notification, NotificationPreferences notificationPreferences) {
boolean blink = notificationPreferences.getShouldBlink();
if (blink) {
notification.setLights(Color.CYAN, 500, 500);
}
}
return notification;
private void setNotificationDeleteIntent(Notification.Builder notification, int notificationId) {
// Let's add a delete intent when the notification is dismissed
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
delIntent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
notification.setDeleteIntent(deleteIntent);
}
private void addNotificationReplyAction(Notification.Builder notification, int notificationId, Bundle bundle) {
String postId = bundle.getString("post_id");
if (postId == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return;
}
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
replyIntent.setAction(KEY_TEXT_REPLY);
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
mContext,
notificationId,
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel("Reply")
.build();
int icon = R.drawable.ic_notif_action_reply;
CharSequence title = "Reply";
Notification.Action replyAction = new Notification.Action.Builder(icon, title, replyPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build();
notification
.setShowWhen(true)
.addAction(replyAction);
}
private void notifyReceivedToJS() {
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
public static Integer getMessageCountInChannel(String channelId) {
Object objCount = channelIdToNotificationCount.get(channelId);
if (objCount != null) {
return (Integer)objCount;
}
return 1;
}
private void cancelNotification(Bundle data, int notificationId) {
final String channelId = data.getString("channel_id");
final String numberString = data.getString("badge");
final String badge = data.getString("badge");
CustomPushNotification.badgeCount = Integer.parseInt(numberString);
CustomPushNotification.badgeCount = Integer.parseInt(badge);
CustomPushNotification.clearNotification(mContext.getApplicationContext(), notificationId, channelId);
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
}
private String getSenderName(String senderName, String channelName, String message) {
private String getSenderName(Bundle bundle) {
String senderName = bundle.getString("sender_name");
if (senderName != null) {
return senderName;
} else if (channelName != null && channelName.startsWith("@")) {
}
String channelName = bundle.getString("channel_name");
if (channelName != null && channelName.startsWith("@")) {
return channelName;
}
String name = message.split(":")[0];
if (name != message) {
return name;
String message = bundle.getString("message");
if (message != null) {
String name = message.split(":")[0];
if (name != message) {
return name;
}
}
return " ";
}
private String removeSenderFromMessage(String senderName, String channelName, String message) {
String sender = String.format("%s", getSenderName(senderName, channelName, message));
return message.replaceFirst(sender, "").replaceFirst(": ", "").trim();
private String removeSenderNameFromMessage(String message, String senderName) {
return message.replaceFirst(senderName, "").replaceFirst(": ", "").trim();
}
private void notificationReceiptDelivery(String ackId, String type) {
ReceiptDelivery.send(context, ackId, type);
}
private void createNotificationChannels() {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mHighImportanceChannel = new NotificationChannel("channel_01", "High Importance", NotificationManager.IMPORTANCE_HIGH);
mHighImportanceChannel.setShowBadge(true);
notificationManager.createNotificationChannel(mHighImportanceChannel);
mMinImportanceChannel = new NotificationChannel("channel_02", "Min Importance", NotificationManager.IMPORTANCE_MIN);
mMinImportanceChannel.setShowBadge(true);
notificationManager.createNotificationChannel(mMinImportanceChannel);
}
}

View file

@ -6,7 +6,7 @@ import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.INotificationsDrawerApplication;
import com.wix.reactnativenotifications.helpers.PushNotificationHelper;
import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class CustomPushNotificationDrawer extends PushNotificationsDrawer {

View file

@ -36,7 +36,7 @@ project(':react-native-cookies').projectDir = new File(rootProject.projectDir, '
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':reactnativenotifications'
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
include ':app'
include ':react-native-svg'

View file

@ -94,9 +94,8 @@ class PushNotification {
NotificationsAndroid.cancelAllLocalNotifications();
}
setApplicationIconBadgeNumber(number) {
const count = number < 0 ? 0 : number;
NotificationsAndroid.setBadgesCount(count);
setApplicationIconBadgeNumber() {
// Not supported for Android
}
getNotification() {
@ -116,7 +115,6 @@ class PushNotification {
}
clearNotifications = () => {
this.setApplicationIconBadgeNumber(0);
this.cancelAllLocalNotifications(); // TODO: Only cancel the local notifications that belong to this server
}
}

View file

@ -2,15 +2,23 @@
// See LICENSE.txt for license information.
import {AppState} from 'react-native';
import NotificationsIOS, {NotificationAction, NotificationCategory} from 'react-native-notifications';
import NotificationsIOS, {
NotificationAction,
NotificationCategory,
DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT,
DEVICE_NOTIFICATION_RECEIVED_FOREGROUND_EVENT,
DEVICE_NOTIFICATION_OPENED_EVENT,
} from 'react-native-notifications';
import {getBadgeCount} from 'app/selectors/views';
import ephemeralStore from 'app/store/ephemeral_store';
import {getCurrentLocale} from 'app/selectors/i18n';
import {getLocalizedMessage} from 'app/i18n';
import {t} from 'app/utils/i18n';
const CATEGORY = 'CAN_REPLY';
const REPLY_ACTION = 'REPLY_ACTION';
let replyCategory;
const replies = new Set();
class PushNotification {
@ -21,24 +29,9 @@ class PushNotification {
this.onReply = null;
this.reduxStore = null;
NotificationsIOS.addEventListener('remoteNotificationsRegistered', this.onRemoteNotificationsRegistered);
NotificationsIOS.addEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground);
NotificationsIOS.addEventListener('notificationReceivedBackground', this.onNotificationReceivedBackground);
NotificationsIOS.addEventListener('notificationOpened', this.onNotificationOpened);
const replyAction = new NotificationAction({
activationMode: 'background',
title: 'Reply',
behavior: 'textInput',
authenticationRequired: true,
identifier: REPLY_ACTION,
}, this.handleReply);
replyCategory = new NotificationCategory({
identifier: CATEGORY,
actions: [replyAction],
context: 'default',
});
NotificationsIOS.addEventListener(DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT, this.onRemoteNotificationsRegistered);
NotificationsIOS.addEventListener(DEVICE_NOTIFICATION_RECEIVED_FOREGROUND_EVENT, this.onNotificationReceivedForeground);
NotificationsIOS.addEventListener(DEVICE_NOTIFICATION_OPENED_EVENT, this.onNotificationOpened);
}
handleNotification = (data, foreground, userInteraction) => {
@ -55,18 +48,14 @@ class PushNotification {
}
};
handleReply = (action, completed) => {
if (action.identifier === REPLY_ACTION) {
const data = action.notification.getData();
const text = action.text;
const badge = parseInt(action.notification._badge, 10) - 1; //eslint-disable-line no-underscore-dangle
handleReply = (notification, text, completion) => {
const data = notification.getData();
if (this.onReply && !replies.has(action.completionKey)) {
replies.add(action.completionKey);
this.onReply(data, text, badge, completed);
}
if (this.onReply && !replies.has(data.identifier)) {
replies.add(data.identifier);
this.onReply(data, text, completion);
} else {
completed();
completion();
}
};
@ -76,9 +65,39 @@ class PushNotification {
this.onNotification = options.onNotification;
this.onReply = options.onReply;
this.requestPermissions([replyCategory]);
this.requestNotificationReplyPermissions();
}
NotificationsIOS.consumeBackgroundQueue();
requestNotificationReplyPermissions = () => {
const replyCategory = this.createReplyCategory();
this.requestPermissions([replyCategory]);
}
createReplyCategory = () => {
const {getState} = this.reduxStore;
const state = getState();
const locale = getCurrentLocale(state);
const replyTitle = getLocalizedMessage(locale, t('mobile.push_notification_reply.title'));
const replyButton = getLocalizedMessage(locale, t('mobile.push_notification_reply.button'));
const replyPlaceholder = getLocalizedMessage(locale, t('mobile.push_notification_reply.placeholder'));
const replyAction = new NotificationAction({
activationMode: 'background',
title: replyTitle,
textInput: {
buttonTitle: replyButton,
placeholder: replyPlaceholder,
},
authenticationRequired: true,
identifier: REPLY_ACTION,
});
return new NotificationCategory({
identifier: CATEGORY,
actions: [replyAction],
context: 'default',
});
}
requestPermissions = (permissions) => {
@ -89,7 +108,7 @@ class PushNotification {
if (notification.date) {
const deviceNotification = {
fireDate: notification.date.toISOString(),
alertBody: notification.message,
body: notification.message,
alertAction: '',
userInfo: notification.userInfo,
};
@ -100,7 +119,7 @@ class PushNotification {
localNotification(notification) {
this.deviceNotification = {
alertBody: notification.message,
body: notification.message,
alertAction: '',
userInfo: notification.userInfo,
};
@ -138,12 +157,17 @@ class PushNotification {
this.handleNotification(info, true, false);
};
onNotificationOpened = (notification) => {
const info = {
...notification.getData(),
message: notification.getMessage(),
};
this.handleNotification(info, false, true);
onNotificationOpened = (notification, completion, action) => {
if (action.identifier === REPLY_ACTION) {
this.handleReply(notification, action.text, completion);
} else {
const info = {
...notification.getData(),
message: notification.getMessage(),
};
this.handleNotification(info, false, true);
completion();
}
};
onRemoteNotificationsRegistered = (deviceToken) => {
@ -165,6 +189,10 @@ class PushNotification {
this.deviceNotification = null;
}
getDeliveredNotifications(callback) {
NotificationsIOS.getDeliveredNotifications(callback);
}
clearChannelNotifications(channelId) {
NotificationsIOS.getDeliveredNotifications((notifications) => {
const ids = [];
@ -180,7 +208,7 @@ class PushNotification {
for (let i = 0; i < notifications.length; i++) {
const notification = notifications[i];
if (notification.userInfo.channel_id === channelId) {
if (notification.channel_id === channelId) {
ids.push(notification.identifier);
}
}

View file

@ -39,25 +39,25 @@ describe('PushNotification', () => {
// Three channel1 delivered notifications
{
identifier: 'channel1-1',
userInfo: {channel_id: channel1ID},
channel_id: channel1ID,
},
{
identifier: 'channel1-2',
userInfo: {channel_id: channel1ID},
channel_id: channel1ID,
},
{
identifier: 'channel1-3',
userInfo: {channel_id: channel1ID},
channel_id: channel1ID,
},
// Two channel2 delivered notifications
{
identifier: 'channel2-1',
userInfo: {channel_id: channel2ID},
channel_id: channel2ID,
},
{
identifier: 'channel2-2',
userInfo: {channel_id: channel2ID},
channel_id: channel2ID,
},
];
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
@ -73,8 +73,8 @@ describe('PushNotification', () => {
await NotificationsIOS.getDeliveredNotifications(async (deliveredNotifs) => {
expect(deliveredNotifs.length).toBe(2);
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.userInfo.channel_id === channel1ID);
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.userInfo.channel_id === channel2ID);
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel1ID);
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel2ID);
expect(channel1DeliveredNotifications.length).toBe(0);
expect(channel2DeliveredNotifications.length).toBe(2);
});

View file

@ -102,7 +102,7 @@ class PushNotificationUtils {
}
};
onPushNotificationReply = async (data, text, badge, completed) => {
onPushNotificationReply = async (data, text, completion) => {
const {dispatch, getState} = this.store;
const state = getState();
const credentials = await getAppCredentials(); // TODO Change to handle multiple servers
@ -144,23 +144,21 @@ class PushNotificationUtils {
channel_id: data.channel_id,
},
});
completed();
completion();
return;
}
if (badge >= 0) {
PushNotifications.setApplicationIconBadgeNumber(badge);
}
dispatch(markChannelViewedAndRead(data.channel_id));
this.replyNotificationData = null;
completed();
PushNotifications.getDeliveredNotifications((notifications) => {
PushNotifications.setApplicationIconBadgeNumber(notifications.length);
completion();
});
} else {
this.replyNotificationData = {
data,
text,
badge,
completed,
completion,
};
}
};

View file

@ -32,7 +32,6 @@ jest.mock('react-native-notifications', () => {
addEventListener: jest.fn(),
NotificationAction: jest.fn(),
NotificationCategory: jest.fn(),
consumeBackgroundQueue: jest.fn(),
localNotification: jest.fn(),
};
});
@ -49,9 +48,8 @@ describe('PushNotifications', () => {
const channelID = 'channel-id';
const data = {channel_id: channelID};
const text = 'text';
const badge = 1;
const completed = () => {};
await PushNotificationUtils.onPushNotificationReply(data, text, badge, completed);
const completion = () => {};
await PushNotificationUtils.onPushNotificationReply(data, text, completion);
const storeActions = store.getActions();
const receivedPost = storeActions.some((action) => action.type === PostTypes.RECEIVED_POST);

View file

@ -383,6 +383,9 @@
"mobile.post.retry": "Refresh",
"mobile.posts_view.moreMsg": "More New Messages Above",
"mobile.privacy_link": "Privacy Policy",
"mobile.push_notification_reply.button": "Send",
"mobile.push_notification_reply.placeholder": "Write a reply...",
"mobile.push_notification_reply.title": "Reply",
"mobile.reaction_header.all_emojis": "All",
"mobile.recent_mentions.empty_description": "Messages containing your username and other words that trigger mentions will appear here.",
"mobile.recent_mentions.empty_title": "No Recent Mentions",

View file

@ -26,7 +26,7 @@
@implementation AppDelegate
NSString* const NotificationClearAction = @"clear";
NSString* const NOTIFICATION_CLEAR_ACTION = @"clear";
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
os_log(OS_LOG_DEFAULT, "Mattermost will attach session from handleEventsForBackgroundURLSession!! identifier=%{public}@", identifier);
@ -58,18 +58,14 @@ NSString* const NotificationClearAction = @"clear";
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error: nil];
[RNNotifications startMonitorNotifications];
os_log(OS_LOG_DEFAULT, "Mattermost started!!");
return YES;
}
// Required to register for notifications
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[RNNotifications didRegisterUserNotificationSettings:notificationSettings];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
@ -79,7 +75,34 @@ NSString* const NotificationClearAction = @"clear";
[RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
}
-(void)cleanNotificationsFromChannel:(NSString *)channelId andUpdateBadge:(BOOL)updateBadge {
// Required for the notification event.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
UIApplicationState state = [UIApplication sharedApplication].applicationState;
NSString* action = [userInfo objectForKey:@"type"];
NSString* channelId = [userInfo objectForKey:@"channel_id"];
NSString* ackId = [userInfo objectForKey:@"ack_id"];
if (action && [action isEqualToString: NOTIFICATION_CLEAR_ACTION]) {
// If received a notification that a channel was read, remove all notifications from that channel (only with app in foreground/background)
[self cleanNotificationsFromChannel:channelId];
RuntimeUtils *utils = [[RuntimeUtils alloc] init];
[[UploadSession shared] notificationReceiptWithNotificationId:ackId receivedAt:round([[NSDate date] timeIntervalSince1970] * 1000.0) type:action];
[utils delayWithSeconds:0.2 closure:^(void) {
// This is to notify the NotificationCenter that something has changed.
completionHandler(UIBackgroundFetchResultNewData);
}];
return;
} else if (state == UIApplicationStateInactive) {
// When the notification is opened
[self cleanNotificationsFromChannel:channelId];
}
completionHandler(UIBackgroundFetchResultNoData);
}
-(void)cleanNotificationsFromChannel:(NSString *)channelId {
if ([UNUserNotificationCenter class]) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
@ -97,64 +120,10 @@ NSString* const NotificationClearAction = @"clear";
}
[center removeDeliveredNotificationsWithIdentifiers:notificationIds];
NSInteger removed = (NSInteger)[notificationIds count] + 1;
if (removed > 0 && updateBadge) {
NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;
NSInteger count = badge - removed;
if (count > 0) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
} else {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
}
}];
}
}
// Required for the notification event.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
UIApplicationState state = [UIApplication sharedApplication].applicationState;
NSString* action = [userInfo objectForKey:@"type"];
NSString* channelId = [userInfo objectForKey:@"channel_id"];
NSString* ackId = [userInfo objectForKey:@"ack_id"];
if (action && [action isEqualToString: NotificationClearAction]) {
// If received a notification that a channel was read, remove all notifications from that channel (only with app in foreground/background)
[self cleanNotificationsFromChannel:channelId andUpdateBadge:NO];
RuntimeUtils *utils = [[RuntimeUtils alloc] init];
[[UploadSession shared] notificationReceiptWithNotificationId:ackId receivedAt:round([[NSDate date] timeIntervalSince1970] * 1000.0) type:action];
[utils delayWithSeconds:0.2 closure:^(void) {
// This is to notify the NotificationCenter that something has changed.
completionHandler(UIBackgroundFetchResultNewData);
}];
return;
} else if (state == UIApplicationStateInactive) {
// When the notification is opened
[self cleanNotificationsFromChannel:channelId andUpdateBadge:NO];
}
[RNNotifications didReceiveRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNoData);
}
// Required for the localNotification event.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[RNNotifications didReceiveLocalNotification:notification];
}
// Required for the notification actions.
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
{
[RNNotifications handleActionWithIdentifier:identifier forLocalNotification:notification withResponseInfo:responseInfo completionHandler:completionHandler];
}
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
{
[RNNotifications handleActionWithIdentifier:identifier forRemoteNotification:userInfo withResponseInfo:responseInfo completionHandler:completionHandler];
}
// Required for deeplinking
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url

5
package-lock.json generated
View file

@ -16750,8 +16750,9 @@
}
},
"react-native-notifications": {
"version": "github:mattermost/react-native-notifications#1b7ec8513606b42237ab4674de9dacb4d1935e38",
"from": "github:mattermost/react-native-notifications#1b7ec8513606b42237ab4674de9dacb4d1935e38",
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/react-native-notifications/-/react-native-notifications-2.0.6.tgz",
"integrity": "sha512-NFx5ADlqfQYTFkKWvd/GxM8rxKf1lSWJZJY0jbydAOZAuhnKFR/CsH7Mpx6T+9pY5Z3rvu7UzBtVn9LTBx0jYg==",
"requires": {
"core-js": "^1.0.0",
"uuid": "^2.0.3"

View file

@ -48,7 +48,7 @@
"react-native-linear-gradient": "2.5.4",
"react-native-local-auth": "github:mattermost/react-native-local-auth#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab",
"react-native-navigation": "2.21.1",
"react-native-notifications": "github:mattermost/react-native-notifications#1b7ec8513606b42237ab4674de9dacb4d1935e38",
"react-native-notifications": "2.0.6",
"react-native-passcode-status": "1.1.1",
"react-native-permissions": "1.1.1",
"react-native-safe-area": "0.5.1",

View file

@ -0,0 +1,378 @@
diff --git a/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml b/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
index ffef75f..a4df210 100644
--- a/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
+++ b/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
@@ -32,6 +32,8 @@
<service
android:name=".gcm.FcmInstanceIdRefreshHandlerService"
android:exported="false" />
+
+ <receiver android:name="com.wix.reactnativenotifications.core.notification.PushNotificationPublisher" />
</application>
</manifest>
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
index 8fb5f01..74d6138 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
@@ -103,12 +103,26 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
pushNotification.onPostRequest(notificationId);
}
+ @ReactMethod
+ public void scheduleLocalNotification(ReadableMap notificationPropsMap, int notificationId) {
+ Log.d(LOGTAG, "Native method invocation: scheduleLocalNotification");
+ final Bundle notificationProps = Arguments.toBundle(notificationPropsMap);
+ final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps);
+ pushNotification.onScheduleRequest(notificationId);
+ }
+
@ReactMethod
public void cancelLocalNotification(int notificationId) {
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onNotificationClearRequest(notificationId);
}
+ @ReactMethod
+ public void cancelAllLocalNotifications() {
+ IPushNotificationsDrawer notificationDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
+ notificationDrawer.onCancelAllLocalNotifications();
+ }
+
@ReactMethod
public void isRegisteredForRemoteNotifications(Promise promise) {
boolean hasPermission = NotificationManagerCompat.from(getReactApplicationContext()).areNotificationsEnabled();
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java
new file mode 100644
index 0000000..c35076d
--- /dev/null
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java
@@ -0,0 +1,90 @@
+package com.wix.reactnativenotifications.core.helpers;
+
+import android.app.AlarmManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.content.Context;
+import android.content.Intent;
+import android.app.PendingIntent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
+import com.wix.reactnativenotifications.core.notification.PushNotificationPublisher;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
+
+public class ScheduleNotificationHelper {
+ public static ScheduleNotificationHelper sInstance;
+ public static final String PREFERENCES_KEY = "rn_push_notification";
+ static final String NOTIFICATION_ID = "notificationId";
+
+ private final SharedPreferences scheduledNotificationsPersistence;
+ protected final Context mContext;
+
+ private ScheduleNotificationHelper(Context context) {
+ this.mContext = context;
+ this.scheduledNotificationsPersistence = context.getSharedPreferences(ScheduleNotificationHelper.PREFERENCES_KEY, Context.MODE_PRIVATE);
+ }
+
+ public static ScheduleNotificationHelper getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new ScheduleNotificationHelper(context);
+ }
+ return sInstance;
+ }
+
+ public PendingIntent createPendingNotificationIntent(Bundle bundle) {
+ Integer notificationId = Integer.valueOf(bundle.getString("id"));
+ Intent notificationIntent = new Intent(mContext, PushNotificationPublisher.class);
+ notificationIntent.putExtra(ScheduleNotificationHelper.NOTIFICATION_ID, notificationId);
+ notificationIntent.putExtras(bundle);
+ return PendingIntent.getBroadcast(mContext, notificationId, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ public void schedulePendingNotificationIntent(PendingIntent intent, long fireDate) {
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, intent);
+ } else {
+ alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, intent);
+ }
+ }
+
+ public void cancelScheduledNotificationIntent(PendingIntent intent) {
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ alarmManager.cancel(intent);
+ }
+
+ public boolean savePreferences(String notificationId, PushNotificationProps notificationProps) {
+ SharedPreferences.Editor editor = scheduledNotificationsPersistence.edit();
+ editor.putString(notificationId, notificationProps.toString());
+ commit(editor);
+
+ return scheduledNotificationsPersistence.contains(notificationId);
+ }
+
+ public void removePreference(String notificationId) {
+ if (scheduledNotificationsPersistence.contains(notificationId)) {
+ // remove it from local storage
+ SharedPreferences.Editor editor = scheduledNotificationsPersistence.edit();
+ editor.remove(notificationId);
+ commit(editor);
+ } else {
+ Log.w(LOGTAG, "Unable to find notification " + notificationId);
+ }
+ }
+
+ public java.util.Set<String> getPreferencesKeys() {
+ return scheduledNotificationsPersistence.getAll().keySet();
+ }
+
+ private static void commit(SharedPreferences.Editor editor) {
+ if (Build.VERSION.SDK_INT < 9) {
+ editor.commit();
+ } else {
+ editor.apply();
+ }
+ }
+}
\ No newline at end of file
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
index 0d70024..47b962e 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
@@ -26,5 +26,20 @@ public interface IPushNotification {
*/
int onPostRequest(Integer notificationId);
+ /**
+ * Handle a request to schedule this notification.
+ *
+ * @param notificationId The specific ID to associated with the notification.
+ */
+ void onScheduleRequest(Integer notificationId);
+
+ /**
+ * Handle a request to post this scheduled notification.
+ *
+ * @param notificationId The specific ID to associated with the notification.
+ * @return The ID assigned to the notification.
+ */
+ int onPostScheduledRequest(Integer notificationId);
+
PushNotificationProps asProps();
}
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
index 5e4e3d2..871e157 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
@@ -1,5 +1,6 @@
package com.wix.reactnativenotifications.core.notification;
+import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -20,7 +21,9 @@ import com.wix.reactnativenotifications.core.InitialNotificationHolder;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
import com.wix.reactnativenotifications.core.ProxyService;
+import com.wix.reactnativenotifications.core.helpers.ScheduleNotificationHelper;
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_FOREGROUND_EVENT_NAME;
@@ -80,6 +83,41 @@ public class PushNotification implements IPushNotification {
return postNotification(notificationId);
}
+ @Override
+ public void onScheduleRequest(Integer notificationId) {
+ Bundle bundle = mNotificationProps.asBundle();
+
+ if (bundle.getString("message") == null) {
+ Log.e(LOGTAG, "No message specified for the scheduled notification");
+ return;
+ }
+
+ double date = bundle.getDouble("fireDate");
+ if (date == 0) {
+ Log.e(LOGTAG, "No date specified for the scheduled notification");
+ return;
+ }
+
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
+ String notificationIdStr = Integer.toString(notificationId);
+ boolean isSaved = helper.savePreferences(notificationIdStr, mNotificationProps);
+ if (!isSaved) {
+ Log.e(LOGTAG, "Failed to save preference for notificationId " + notificationIdStr);
+ }
+
+ PendingIntent pendingIntent = helper.createPendingNotificationIntent(bundle);
+ long fireDate = (long) date;
+ helper.schedulePendingNotificationIntent(pendingIntent, fireDate);
+ }
+
+ @Override
+ public int onPostScheduledRequest(Integer notificationId) {
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
+ helper.removePreference(String.valueOf(notificationId));
+
+ return postNotification(notificationId);
+ }
+
@Override
public PushNotificationProps asProps() {
return mNotificationProps.copy();
@@ -140,11 +178,12 @@ public class PushNotification implements IPushNotification {
}
protected Notification buildNotification(PendingIntent intent) {
- return getNotificationBuilder(intent).build();
+ Notification.Builder builder = getNotificationBuilder(intent);
+ Notification notification = builder.build();
+ return notification;
}
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
-
String CHANNEL_ID = "channel_01";
String CHANNEL_NAME = "Channel Name";
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java
new file mode 100644
index 0000000..5b64593
--- /dev/null
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java
@@ -0,0 +1,27 @@
+package com.wix.reactnativenotifications.core.notification;
+
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
+
+public class PushNotificationPublisher extends BroadcastReceiver {
+ final static String NOTIFICATION_ID = "notificationId";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(LOGTAG, "Received scheduled notification intent");
+ int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0);
+ long currentTime = System.currentTimeMillis();
+
+ Application applicationContext = (Application) context.getApplicationContext();
+ final IPushNotification pushNotification = PushNotification.get(applicationContext, intent.getExtras());
+
+ Log.i(LOGTAG, "PushNotificationPublisher: Prepare To Publish: " + notificationId + ", Now Time: " + currentTime);
+
+ pushNotification.onPostScheduledRequest(notificationId);
+ }
+}
\ No newline at end of file
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
index 3be3dc1..7027958 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
@@ -9,4 +9,5 @@ public interface IPushNotificationsDrawer {
void onNotificationOpened();
void onNotificationClearRequest(int id);
+ void onCancelAllLocalNotifications();
}
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
index 7b320e1..d95535b 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
@@ -2,10 +2,16 @@ package com.wix.reactnativenotifications.core.notificationdrawer;
import android.app.Activity;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.InitialNotificationHolder;
+import com.wix.reactnativenotifications.core.helpers.ScheduleNotificationHelper;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class PushNotificationsDrawer implements IPushNotificationsDrawer {
@@ -60,8 +66,41 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer {
notificationManager.cancel(id);
}
+ @Override
+ public void onCancelAllLocalNotifications() {
+ clearAll();
+ cancelAllScheduledNotifications();
+ }
+
protected void clearAll() {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
}
+
+ protected void cancelAllScheduledNotifications() {
+ Log.i(LOGTAG, "Cancelling all scheduled notifications");
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
+
+ for (String notificationId : helper.getPreferencesKeys()) {
+ cancelScheduledNotification(notificationId);
+ }
+ }
+
+ protected void cancelScheduledNotification(String notificationId) {
+ Log.i(LOGTAG, "Cancelling scheduled notification: " + notificationId);
+
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
+
+ // Remove it from the alarm manger schedule
+ Bundle bundle = new Bundle();
+ bundle.putString("id", notificationId);
+ PendingIntent pendingIntent = helper.createPendingNotificationIntent(bundle);
+ helper.cancelScheduledNotificationIntent(pendingIntent);
+
+ helper.removePreference(notificationId);
+
+ // Remove it from the notification center
+ final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(Integer.parseInt(notificationId));
+ }
}
diff --git a/node_modules/react-native-notifications/lib/src/index.android.js b/node_modules/react-native-notifications/lib/src/index.android.js
index 51376bf..a5d9540 100644
--- a/node_modules/react-native-notifications/lib/src/index.android.js
+++ b/node_modules/react-native-notifications/lib/src/index.android.js
@@ -67,9 +67,22 @@ export class NotificationsAndroid {
return id;
}
+ static scheduleLocalNotification(notification: Object) {
+ const id = Math.random() * 100000000 | 0; // Bitwise-OR forces value onto a 32bit limit
+ if (!notification.hasOwnProperty('id')) {
+ notification.id = id.toString();
+ }
+ RNNotifications.scheduleLocalNotification(notification, id);
+ return id;
+ }
+
static cancelLocalNotification(id) {
RNNotifications.cancelLocalNotification(id);
}
+
+ static cancelAllLocalNotifications() {
+ RNNotifications.cancelAllLocalNotifications();
+ }
}
export class PendingNotifications {