Enable replying from the push notification (#871)
* Enable replying from the push notification * Fix Android and iOS flow * Own review
This commit is contained in:
parent
3373aae455
commit
60087e6b4d
19 changed files with 324 additions and 72 deletions
|
|
@ -45,7 +45,12 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
<receiver android:name=".NotificationDismissReceiver" />
|
||||
<service android:name=".NotificationDismissService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service android:name=".NotificationReplyService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import android.os.Bundle;
|
|||
import android.os.Build;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.RemoteInput;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
||||
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
||||
import com.wix.reactnativenotifications.core.JsIOHelper;
|
||||
|
|
@ -27,18 +30,25 @@ public class CustomPushNotification extends PushNotification {
|
|||
public static final int MESSAGE_NOTIFICATION_ID = 435345;
|
||||
public static final String GROUP_KEY_MESSAGES = "mm_group_key_messages";
|
||||
public static final String NOTIFICATION_ID = "notificationId";
|
||||
public static final String KEY_TEXT_REPLY = "CAN_REPLY";
|
||||
public static final String NOTIFICATION_REPLIED_EVENT_NAME = "notificationReplied";
|
||||
|
||||
private static LinkedHashMap<String,Integer> channelIdToNotificationCount = new LinkedHashMap<String,Integer>();
|
||||
private static LinkedHashMap<String,ArrayList<Bundle>> channelIdToNotification = new LinkedHashMap<String,ArrayList<Bundle>>();
|
||||
private static AppLifecycleFacade lifecycleFacade;
|
||||
private static Context context;
|
||||
|
||||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public static void clearNotification(int notificationId) {
|
||||
public static void clearNotification(int notificationId, String channelId) {
|
||||
if (notificationId != -1) {
|
||||
String channelId = String.valueOf(notificationId);
|
||||
channelIdToNotificationCount.remove(channelId);
|
||||
channelIdToNotification.remove(channelId);
|
||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,8 +118,9 @@ public class CustomPushNotification extends PushNotification {
|
|||
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
|
||||
}
|
||||
|
||||
int notificationId = bundle.getString("channel_id").hashCode();
|
||||
String channelId = bundle.getString("channel_id");
|
||||
String postId = bundle.getString("post_id");
|
||||
int notificationId = channelId.hashCode();
|
||||
String message = bundle.getString("message");
|
||||
String subText = bundle.getString("subText");
|
||||
String numberString = bundle.getString("badge");
|
||||
|
|
@ -143,11 +154,7 @@ public class CustomPushNotification extends PushNotification {
|
|||
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), Integer.parseInt(numberString));
|
||||
}
|
||||
|
||||
int numMessages = 0;
|
||||
Object objCount = channelIdToNotificationCount.get(channelId);
|
||||
if (objCount != null) {
|
||||
numMessages = (Integer)objCount;
|
||||
}
|
||||
int numMessages = getMessageCountInChannel(channelId);
|
||||
|
||||
notification
|
||||
.setGroupSummary(true)
|
||||
|
|
@ -173,19 +180,40 @@ public class CustomPushNotification extends PushNotification {
|
|||
style.setBigContentTitle(title);
|
||||
notification.setStyle(style)
|
||||
.setContentTitle(summaryTitle);
|
||||
// .setNumber(numMessages);
|
||||
}
|
||||
|
||||
// Let's add a delete intent when the notification is dismissed
|
||||
Intent delIntent = new Intent(mContext, NotificationDismissReceiver.class);
|
||||
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
|
||||
delIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
PendingIntent deleteIntent = PendingIntent.getBroadcast(mContext, 0, delIntent, 0);
|
||||
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
|
||||
notification.setDeleteIntent(deleteIntent);
|
||||
|
||||
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, NotificationReplyService.class);
|
||||
replyIntent.setAction(KEY_TEXT_REPLY);
|
||||
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
replyIntent.putExtra("pushNotification", bundle);
|
||||
PendingIntent replyPendingIntent = PendingIntent.getService(mContext, 103, 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);
|
||||
|
|
@ -202,6 +230,15 @@ public class CustomPushNotification extends PushNotification {
|
|||
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 0;
|
||||
}
|
||||
|
||||
private void cancelNotification(Bundle data, int notificationId) {
|
||||
final String channelId = data.getString("channel_id");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
||||
|
||||
public class NotificationDismissReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
|
||||
CustomPushNotification.clearNotification(notificationId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.app.IntentService;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
|
||||
public class NotificationDismissService extends IntentService {
|
||||
|
||||
public NotificationDismissService() {
|
||||
super("notificationDismissService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
|
||||
int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
|
||||
String channelId = bundle.getString("channel_id");
|
||||
CustomPushNotification.clearNotification(notificationId, channelId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.RemoteInput;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.HeadlessJsTaskService;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
|
||||
|
||||
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
|
||||
public class NotificationReplyService extends HeadlessJsTaskService {
|
||||
|
||||
@Override
|
||||
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
|
||||
if (CustomPushNotification.KEY_TEXT_REPLY.equals(intent.getAction())) {
|
||||
CharSequence message = getReplyMessage(intent);
|
||||
|
||||
Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
|
||||
String channelId = bundle.getString("channel_id");
|
||||
bundle.putCharSequence("text", message);
|
||||
bundle.putInt("msg_count", CustomPushNotification.getMessageCountInChannel(channelId));
|
||||
|
||||
int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
|
||||
CustomPushNotification.clearNotification(notificationId, channelId);
|
||||
|
||||
return new HeadlessJsTaskConfig(
|
||||
"notificationReplied",
|
||||
Arguments.fromBundle(bundle),
|
||||
5000);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CharSequence getReplyMessage(Intent intent) {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput != null) {
|
||||
return remoteInput.getCharSequence(CustomPushNotification.KEY_TEXT_REPLY);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
BIN
android/app/src/main/res/drawable-hdpi/ic_notif_action_reply.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/ic_notif_action_reply.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 630 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notif_action_reply.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/ic_notif_action_reply.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 508 B |
Binary file not shown.
|
After Width: | Height: | Size: 925 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -91,7 +91,7 @@ export default class Badge extends PureComponent {
|
|||
setTimeout(() => {
|
||||
this.setNativeProps({
|
||||
style: {
|
||||
display: 'flex'
|
||||
opacity: 1
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
|
|
@ -125,7 +125,7 @@ export default class Badge extends PureComponent {
|
|||
>
|
||||
<View
|
||||
ref='badgeContainer'
|
||||
style={[styles.badge, this.props.style, {display: 'none'}]}
|
||||
style={[styles.badge, this.props.style, {opacity: 0}]}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
{this.renderText()}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'babel-polyfill';
|
|||
import Analytics from 'analytics-react-native';
|
||||
import Orientation from 'react-native-orientation';
|
||||
import {Provider} from 'react-redux';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
import {Navigation, NativeEventsReceiver} from 'react-native-navigation';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import {
|
||||
Alert,
|
||||
|
|
@ -23,6 +23,7 @@ import {General} from 'mattermost-redux/constants';
|
|||
import {setAppState, setDeviceToken, setServerVersion} from 'mattermost-redux/actions/general';
|
||||
import {markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {logError} from 'mattermost-redux/actions/errors';
|
||||
import {createPost} from 'mattermost-redux/actions/posts';
|
||||
import {logout} from 'mattermost-redux/actions/users';
|
||||
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
|
||||
import {Client, Client4} from 'mattermost-redux/client';
|
||||
|
|
@ -149,6 +150,7 @@ export default class Mattermost {
|
|||
PushNotifications.configure({
|
||||
onRegister: this.onRegisterDevice,
|
||||
onNotification: this.onPushNotification,
|
||||
onReply: this.onPushNotificationReply,
|
||||
popInitialNotification: true,
|
||||
requestPermissions: true
|
||||
});
|
||||
|
|
@ -158,24 +160,30 @@ export default class Mattermost {
|
|||
const {dispatch, getState} = store;
|
||||
const isActive = appState === 'active';
|
||||
setAppState(isActive)(dispatch, getState);
|
||||
try {
|
||||
if (!isActive && !this.inBackgroundSince) {
|
||||
this.inBackgroundSince = Date.now();
|
||||
} else if (isActive && this.inBackgroundSince && (Date.now() - this.inBackgroundSince) >= AUTHENTICATION_TIMEOUT) {
|
||||
this.inBackgroundSince = null;
|
||||
if (this.mdmEnabled) {
|
||||
const config = await mattermostManaged.getConfig();
|
||||
const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
|
||||
if (authNeeded) {
|
||||
const authenticated = await this.handleAuthentication(config.vendor);
|
||||
if (!authenticated) {
|
||||
mattermostManaged.quitApp();
|
||||
|
||||
if (isActive && this.shouldRelaunchonActive) {
|
||||
this.launchApp();
|
||||
this.shouldRelaunchonActive = false;
|
||||
} else {
|
||||
try {
|
||||
if (!isActive && !this.inBackgroundSince) {
|
||||
this.inBackgroundSince = Date.now();
|
||||
} else if (isActive && this.inBackgroundSince && (Date.now() - this.inBackgroundSince) >= AUTHENTICATION_TIMEOUT) {
|
||||
this.inBackgroundSince = null;
|
||||
if (this.mdmEnabled) {
|
||||
const config = await mattermostManaged.getConfig();
|
||||
const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
|
||||
if (authNeeded) {
|
||||
const authenticated = await this.handleAuthentication(config.vendor);
|
||||
if (!authenticated) {
|
||||
mattermostManaged.quitApp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// do nothing
|
||||
}
|
||||
} catch (error) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -347,11 +355,33 @@ export default class Mattermost {
|
|||
const state = store.getState();
|
||||
if (state.views.root.hydrationComplete) {
|
||||
this.unsubscribeFromStore();
|
||||
this.handleManagedConfig().then((shouldStart) => {
|
||||
if (shouldStart) {
|
||||
this.startApp();
|
||||
}
|
||||
});
|
||||
|
||||
const notification = PushNotifications.getNotification();
|
||||
if (notification) {
|
||||
// If we have a notification means that the app was started cause of a reply
|
||||
// and the app was not sitting in the background nor opened
|
||||
const {data, text, badge} = notification;
|
||||
this.onPushNotificationReply(data, text, badge);
|
||||
PushNotifications.resetNotification();
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// In case of Android we need to handle the bridge being initialized by HeadlessJS
|
||||
Promise.resolve(Navigation.isAppLaunched()).then((appLaunched) => {
|
||||
if (appLaunched) {
|
||||
this.launchApp(); // App is launched -> show UI
|
||||
} else {
|
||||
new NativeEventsReceiver().appLaunched(this.launchApp); // App hasn't been launched yet -> show the UI only when needed.
|
||||
}
|
||||
});
|
||||
} else if (AppState.currentState === 'background') {
|
||||
// for IOS replying from push notification starts the app in the background
|
||||
this.shouldRelaunchonActive = true;
|
||||
this.configurePushNotifications();
|
||||
this.startFakeApp();
|
||||
} else {
|
||||
this.launchApp();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -366,6 +396,7 @@ export default class Mattermost {
|
|||
} else {
|
||||
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
}
|
||||
|
||||
setDeviceToken(`${prefix}:${data.token}`)(dispatch, getState);
|
||||
this.isConfigured = true;
|
||||
};
|
||||
|
|
@ -407,6 +438,46 @@ export default class Mattermost {
|
|||
}
|
||||
};
|
||||
|
||||
onPushNotificationReply = (data, text, badge, completed) => {
|
||||
const {dispatch, getState} = store;
|
||||
const state = getState();
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
if (currentUserId) {
|
||||
// one thing to note is that for android it will reply to the last post in the stack
|
||||
const rootId = data.root_id || data.post_id;
|
||||
const post = {
|
||||
user_id: currentUserId,
|
||||
channel_id: data.channel_id,
|
||||
root_id: rootId,
|
||||
parent_id: rootId,
|
||||
message: text
|
||||
};
|
||||
|
||||
if (!Client4.getUrl()) {
|
||||
// Make sure the Client has the server url set
|
||||
Client4.setUrl(state.entities.general.credentials.url);
|
||||
}
|
||||
|
||||
if (!Client.getToken()) {
|
||||
// Make sure the Client has the server token set
|
||||
Client4.setToken(state.entities.general.credentials.token);
|
||||
}
|
||||
|
||||
createPost(post)(dispatch, getState);
|
||||
markChannelAsRead(data.channel_id)(dispatch, getState);
|
||||
|
||||
if (badge >= 0) {
|
||||
PushNotifications.setApplicationIconBadgeNumber(badge);
|
||||
}
|
||||
}
|
||||
|
||||
if (completed) {
|
||||
// You must call to completed(), otherwise the action will not be triggered
|
||||
completed();
|
||||
}
|
||||
};
|
||||
|
||||
resetBadgeAndVersion = () => {
|
||||
const {dispatch, getState} = store;
|
||||
Client4.serverVersion = '';
|
||||
|
|
@ -426,6 +497,14 @@ export default class Mattermost {
|
|||
this.startApp('fade');
|
||||
};
|
||||
|
||||
launchApp = () => {
|
||||
this.handleManagedConfig().then((shouldStart) => {
|
||||
if (shouldStart) {
|
||||
this.startApp();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
startFakeApp = async () => {
|
||||
return Navigation.startSingleScreenApp({
|
||||
screen: {
|
||||
|
|
@ -443,6 +522,10 @@ export default class Mattermost {
|
|||
};
|
||||
|
||||
startApp = (animationType = 'none') => {
|
||||
if (!this.isConfigured) {
|
||||
this.configurePushNotifications();
|
||||
}
|
||||
|
||||
Navigation.startSingleScreenApp({
|
||||
screen: {
|
||||
screen: 'Root',
|
||||
|
|
@ -457,9 +540,5 @@ export default class Mattermost {
|
|||
},
|
||||
animationType
|
||||
});
|
||||
|
||||
if (!this.isConfigured) {
|
||||
this.configurePushNotifications();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {AppState} from 'react-native';
|
||||
import {AppRegistry, AppState} from 'react-native';
|
||||
import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
|
||||
import Notification from 'react-native-notifications/notification.android';
|
||||
|
||||
class PushNotification {
|
||||
constructor() {
|
||||
this.onRegister = null;
|
||||
this.onNotification = null;
|
||||
this.onReply = null;
|
||||
this.deviceNotification = null;
|
||||
|
||||
NotificationsAndroid.setNotificationReceivedListener((notification) => {
|
||||
if (notification) {
|
||||
|
|
@ -22,6 +25,21 @@ class PushNotification {
|
|||
this.handleNotification(data, true);
|
||||
}
|
||||
});
|
||||
|
||||
AppRegistry.registerHeadlessTask('notificationReplied', () => async (deviceNotification) => {
|
||||
const notification = new Notification(deviceNotification);
|
||||
const data = notification.getData();
|
||||
|
||||
if (this.onReply && AppState.currentState === 'background') {
|
||||
this.onReply(data, data.text, parseInt(data.badge, 10) - parseInt(data.msg_count, 10));
|
||||
} else {
|
||||
this.deviceNotification = {
|
||||
data,
|
||||
text: data.text,
|
||||
badge: parseInt(data.badge, 10) - parseInt(data.msg_count, 10)
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleNotification = (data, userInteraction) => {
|
||||
|
|
@ -41,6 +59,7 @@ class PushNotification {
|
|||
configure(options) {
|
||||
this.onRegister = options.onRegister;
|
||||
this.onNotification = options.onNotification;
|
||||
this.onReply = options.onReply;
|
||||
|
||||
NotificationsAndroid.refreshToken();
|
||||
NotificationsAndroid.setRegistrationTokenUpdateListener((deviceToken) => {
|
||||
|
|
@ -80,6 +99,14 @@ class PushNotification {
|
|||
setApplicationIconBadgeNumber(number) {
|
||||
NotificationsAndroid.setBadgesCount(number);
|
||||
}
|
||||
|
||||
getNotification() {
|
||||
return this.deviceNotification;
|
||||
}
|
||||
|
||||
resetNotification() {
|
||||
this.deviceNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default new PushNotification();
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {AppState} from 'react-native';
|
||||
import NotificationsIOS from 'react-native-notifications';
|
||||
import NotificationsIOS, {NotificationAction, NotificationCategory} from 'react-native-notifications';
|
||||
|
||||
const CATEGORY = 'CAN_REPLY';
|
||||
const REPLY_ACTION = 'REPLY_ACTION';
|
||||
|
||||
let replyCategory;
|
||||
|
||||
class PushNotification {
|
||||
constructor() {
|
||||
this.onRegister = null;
|
||||
this.onNotification = null;
|
||||
this.onReply = null;
|
||||
|
||||
NotificationsIOS.addEventListener('notificationReceivedForeground', (notification) => {
|
||||
const info = {
|
||||
|
|
@ -32,6 +38,20 @@ class PushNotification {
|
|||
};
|
||||
this.handleNotification(info, false, true);
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
||||
handleNotification = (data, foreground, userInteraction) => {
|
||||
|
|
@ -48,9 +68,24 @@ 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
|
||||
|
||||
if (this.onReply) {
|
||||
this.onReply(data, text, badge, completed);
|
||||
}
|
||||
} else {
|
||||
completed();
|
||||
}
|
||||
};
|
||||
|
||||
configure(options) {
|
||||
this.onRegister = options.onRegister;
|
||||
this.onNotification = options.onNotification;
|
||||
this.onReply = options.onReply;
|
||||
|
||||
NotificationsIOS.addEventListener('remoteNotificationsRegistered', (deviceToken) => {
|
||||
if (this.onRegister) {
|
||||
|
|
@ -59,7 +94,7 @@ class PushNotification {
|
|||
});
|
||||
|
||||
if (options.requestPermissions) {
|
||||
this.requestPermissions();
|
||||
this.requestPermissions([replyCategory]);
|
||||
}
|
||||
|
||||
if (options.popInitialNotification) {
|
||||
|
|
@ -67,8 +102,8 @@ class PushNotification {
|
|||
}
|
||||
}
|
||||
|
||||
requestPermissions = () => {
|
||||
NotificationsIOS.requestPermissions();
|
||||
requestPermissions = (permissions) => {
|
||||
NotificationsIOS.requestPermissions(permissions);
|
||||
};
|
||||
|
||||
localNotificationSchedule(notification) {
|
||||
|
|
@ -91,6 +126,10 @@ class PushNotification {
|
|||
setApplicationIconBadgeNumber(number) {
|
||||
NotificationsIOS.setBadgesCount(number);
|
||||
}
|
||||
|
||||
getNotification() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default new PushNotification();
|
||||
|
|
|
|||
|
|
@ -350,7 +350,13 @@ platform :android do
|
|||
)
|
||||
|
||||
find_replace_string(
|
||||
path_to_file: './android/app/src/main/java/com/mattermost/rn/NotificationDismissReceiver.java',
|
||||
path_to_file: './android/app/src/main/java/com/mattermost/rn/NotificationDismissService.java',
|
||||
old_string: 'package com.mattermost.rnbeta;',
|
||||
new_string: 'package com.mattermost.rn;'
|
||||
)
|
||||
|
||||
find_replace_string(
|
||||
path_to_file: './android/app/src/main/java/com/mattermost/rn/NotificationReplyService.java',
|
||||
old_string: 'package com.mattermost.rnbeta;',
|
||||
new_string: 'package com.mattermost.rn;'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
|
||||
|
|
@ -1435,7 +1434,6 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -1453,7 +1451,6 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
|
|||
|
|
@ -60,4 +60,16 @@
|
|||
{
|
||||
[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];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<key>NSCameraUsageDescription</key>
|
||||
<string>Take a Photo or Video and upload it to your mattermost instance</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<string/>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Upload Photos and Videos to your Mattermost instance</string>
|
||||
<key>UIAppFonts</key>
|
||||
|
|
@ -57,16 +57,8 @@
|
|||
<string>MaterialIcons.ttf</string>
|
||||
<string>Octicons.ttf</string>
|
||||
<string>Zocial.ttf</string>
|
||||
<string>Entypo.ttf</string>
|
||||
<string>EvilIcons.ttf</string>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>Foundation.ttf</string>
|
||||
<string>Ionicons.ttf</string>
|
||||
<string>MaterialCommunityIcons.ttf</string>
|
||||
<string>MaterialIcons.ttf</string>
|
||||
<string>Octicons.ttf</string>
|
||||
<string>SimpleLineIcons.ttf</string>
|
||||
<string>Zocial.ttf</string>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
<string>OpenSans-BoldItalic.ttf</string>
|
||||
<string>OpenSans-ExtraBold.ttf</string>
|
||||
|
|
|
|||
|
|
@ -4560,8 +4560,8 @@ react-native-navigation@1.1.131:
|
|||
lodash "4.x.x"
|
||||
|
||||
react-native-notifications@enahum/react-native-notifications.git:
|
||||
version "1.1.11"
|
||||
resolved "https://codeload.github.com/enahum/react-native-notifications/tar.gz/a8450bcbfc6ae46ed4b97f6fe723aac44c8ad598"
|
||||
version "1.1.15"
|
||||
resolved "https://codeload.github.com/enahum/react-native-notifications/tar.gz/084d5e928d8149e711c1b6c384f3d87cc16072f9"
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
uuid "^2.0.3"
|
||||
|
|
|
|||
Loading…
Reference in a new issue