update dependencies (#4958)

* update dependencies

* revert keychain update

* Update dependencies & Fastlane

* set path agnostic for bash in scrips

* Fix open from push notification race

* patch react-native-localize
This commit is contained in:
Elias Nahum 2020-11-18 23:45:07 -03:00 committed by GitHub
parent 4f668f0fdf
commit b226d451f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 2848 additions and 2896 deletions

View file

@ -250,7 +250,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation project(':reactnativenotifications')
implementation 'com.google.firebase:firebase-messaging:17.3.4'
implementation "com.google.firebase:firebase-messaging:$firebaseVersion"
// For animated GIF support
implementation 'com.facebook.fresco:fresco:2.0.0'

View file

@ -26,8 +26,6 @@
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
<meta-data android:name="com.wix.reactnativenotifications.gcmSenderId" android:value="184930218130\"/>
<activity
android:name=".MainActivity"
android:label="@string/app_name"

View file

@ -300,11 +300,11 @@ public class CustomPushNotification extends PushNotification {
private Notification.MessagingStyle getMessagingStyle(Bundle bundle) {
Notification.MessagingStyle messagingStyle;
String senderId = bundle.getString("sender_id");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || senderId == null) {
messagingStyle = new Notification.MessagingStyle("");
} else {
String senderId = bundle.getString("sender_id");
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
@ -364,7 +364,7 @@ public class CustomPushNotification extends PushNotification {
int bundleCount = bundleList.size() - 1;
for (int i = bundleCount; i >= 0; i--) {
Bundle data = bundleList.get(i);
String message = data.getString("message");
String message = data.getString("message", data.getString("body"));
String senderId = data.getString("sender_id");
if (senderId == null) {
senderId = "sender_id";
@ -372,7 +372,7 @@ public class CustomPushNotification extends PushNotification {
Bundle userInfoBundle = data.getBundle("userInfo");
String senderName = getSenderName(data);
if (userInfoBundle != null) {
boolean localPushNotificationTest = userInfoBundle.getBoolean("localTest");
boolean localPushNotificationTest = userInfoBundle.getBoolean("test");
if (localPushNotificationTest) {
senderName = "Test";
}
@ -403,13 +403,15 @@ public class CustomPushNotification extends PushNotification {
NotificationChannel notificationChannel = mHighImportanceChannel;
boolean localPushNotificationTest = false;
boolean testNotification = false;
boolean localNotification = false;
Bundle userInfoBundle = bundle.getBundle("userInfo");
if (userInfoBundle != null) {
localPushNotificationTest = userInfoBundle.getBoolean("localTest");
testNotification = userInfoBundle.getBoolean("test");
localNotification = userInfoBundle.getBoolean("local");
}
if (mAppLifecycleFacade.isAppVisible() && !localPushNotificationTest) {
if (mAppLifecycleFacade.isAppVisible() && !testNotification && !localNotification) {
notificationChannel = mMinImportanceChannel;
}

View file

@ -8,6 +8,7 @@ buildscript {
targetSdkVersion = 29
supportLibVersion = "28.0.0"
kotlinVersion = "1.3.61"
firebaseVersion = "21.0.0"
RNNKotlinVersion = kotlinVersion
}
@ -20,7 +21,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.google.gms:google-services:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -1,5 +1,5 @@
rootProject.name = 'Mattermost'
include ':reactnativenotifications'
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/lib/android/app')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'

View file

@ -293,6 +293,8 @@ export function markChannelViewedAndRead(channelId, previousChannelId, markOnSer
const actions = markAsViewedAndReadBatch(state, channelId, previousChannelId, markOnServer);
dispatch(batchActions(actions, 'BATCH_MARK_CHANNEL_VIEWED_AND_READ'));
return {data: true};
};
}

View file

@ -13,7 +13,7 @@ import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
import {setAppCredentials} from 'app/init/credentials';
import PushNotifications from 'app/push_notifications';
import PushNotifications from '@init/push_notifications';
import {getDeviceTimezone} from 'app/utils/timezone';
import {setCSRFFromCookie} from 'app/utils/security';
import {loadConfigAndLicense} from 'app/actions/views/root';
@ -94,11 +94,11 @@ export function scheduleExpiredNotification(intl) {
});
if (expiresAt) {
PushNotifications.localNotificationSchedule({
date: new Date(expiresAt),
message,
PushNotifications.scheduleNotification({
fireDate: expiresAt,
body: message,
userInfo: {
localNotification: true,
local: true,
},
});
}

View file

@ -66,17 +66,17 @@ export function loadConfigAndLicense() {
export function loadFromPushNotification(notification) {
return async (dispatch, getState) => {
const state = getState();
const {data} = notification;
const {payload} = notification;
const {currentTeamId, teams, myMembers: myTeamMembers} = state.entities.teams;
const {channels} = state.entities.channels;
let channelId = '';
let teamId = currentTeamId;
if (data) {
channelId = data.channel_id;
if (payload) {
channelId = payload.channel_id;
// when the notification does not have a team id is because its from a DM or GM
teamId = data.team_id || currentTeamId;
teamId = payload.team_id || currentTeamId;
}
// load any missing data
@ -96,6 +96,8 @@ export function loadFromPushNotification(notification) {
}
dispatch(handleSelectTeamAndChannel(teamId, channelId));
return {data: true};
};
}

View file

@ -8,7 +8,7 @@ import {Client4} from '@mm-redux/client';
import {getConfig} from '@mm-redux/selectors/entities/general';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import PushNotifications from 'app/push_notifications';
import PushNotifications from '@init/push_notifications';
const sortByNewest = (a, b) => {
if (a.create_at > b.create_at) {
@ -56,11 +56,11 @@ export function scheduleExpiredNotification(intl) {
if (expiresAt) {
// eslint-disable-next-line no-console
console.log('Schedule Session Expiry Local Push Notification', expiresAt);
PushNotifications.localNotificationSchedule({
date: new Date(expiresAt),
message,
PushNotifications.scheduleNotification({
fireDate: expiresAt,
body: message,
userInfo: {
localNotification: true,
local: true,
},
});
}

View file

@ -24,7 +24,7 @@ import networkConnectionListener, {checkConnection} from '@utils/network';
import {t} from '@utils/i18n';
import mattermostBucket from 'app/mattermost_bucket';
import PushNotifications from 'app/push_notifications';
import PushNotifications from '@init/push_notifications';
const MAX_WEBSOCKET_RETRIES = 3;
const CONNECTION_RETRY_SECONDS = 5;

View file

@ -17,6 +17,7 @@ import {loadMe, logout} from '@actions/views/user';
import LocalConfig from '@assets/config';
import {NavigationTypes, ViewTypes} from '@constants';
import {getTranslations, resetMomentLocale} from '@i18n';
import PushNotifications from '@init/push_notifications';
import {setAppState, setServerVersion} from '@mm-redux/actions/general';
import {getTeams} from '@mm-redux/actions/teams';
import {autoUpdateTimezone} from '@mm-redux/actions/timezone';
@ -38,7 +39,6 @@ import {getDeviceTimezone} from '@utils/timezone';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import PushNotifications from 'app/push_notifications';
import {getAppCredentials, removeAppCredentials} from './credentials';
import emmProvider from './emm_provider';
@ -384,12 +384,12 @@ class GlobalEventHandler {
}
handleInAppNotification = (notification) => {
const {data} = notification;
const {payload} = notification;
const {getState} = Store.redux;
const state = getState();
const currentChannelId = getCurrentChannelId(state);
if (data && data.channel_id !== currentChannelId) {
if (payload?.channel_id !== currentChannelId) {
const screen = 'Notification';
const passProps = {
notification,

View file

@ -8,11 +8,11 @@ import semver from 'semver/preload';
import {MinServerVersion} from '@assets/config';
import * as I18n from '@i18n';
import PushNotification from '@init/push_notifications';
import EventEmitter from '@mm-redux/utils/event_emitter';
import Store from '@store/store';
import intitialState from '@store/initial_state';
import PushNotification from 'app/push_notifications';
import mattermostBucket from 'app/mattermost_bucket';
import GlobalEventHandler from './global_event_handler';
@ -29,14 +29,6 @@ jest.mock('@utils/error_handling', () => ({
},
}));
jest.mock('react-native-notifications', () => ({
addEventListener: jest.fn(),
cancelAllLocalNotifications: jest.fn(),
setBadgesCount: jest.fn(),
NotificationAction: jest.fn(),
NotificationCategory: jest.fn(),
}));
jest.mock('react-native-status-bar-size', () => ({
addEventListener: jest.fn(),
}));

View file

@ -1,189 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Platform} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {markChannelViewedAndRead, fetchPostActionWithRetry} from '@actions/views/channel';
import {dismissAllModals, popToRoot} from '@actions/navigation';
import {getPosts} from '@actions/views/post';
import {
createPostForNotificationReply,
loadFromPushNotification,
} from '@actions/views/root';
import {logout} from '@actions/views/user';
import {NavigationTypes, ViewTypes} from '@constants';
import {getLocalizedMessage} from '@i18n';
import {getCurrentServerUrl, getAppCredentials} from '@init/credentials';
import {setDeviceToken} from '@mm-redux/actions/general';
import {Client4} from '@mm-redux/client';
import {General} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {getCurrentLocale} from '@selectors/i18n';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
import {waitForHydration} from '@store/utils';
import {t} from '@utils/i18n';
import PushNotifications from 'app/push_notifications';
const NOTIFICATION_TYPE = {
CLEAR: 'clear',
MESSAGE: 'message',
SESSION: 'session',
};
class PushNotificationUtils {
constructor() {
this.configured = false;
this.replyNotificationData = null;
}
configure = async () => {
return PushNotifications.configure({
onRegister: this.onRegisterDevice,
onNotification: this.onPushNotification,
onReply: this.onPushNotificationReply,
popInitialNotification: true,
requestPermissions: true,
});
};
loadFromNotification = async (notification) => {
await Store.redux.dispatch(loadFromPushNotification(notification));
// if we have a componentId means that the app is already initialized
const componentId = EphemeralStore.getNavigationTopComponentId();
if (componentId) {
EventEmitter.emit(NavigationTypes.CLOSE_MAIN_SIDEBAR);
EventEmitter.emit(NavigationTypes.CLOSE_SETTINGS_SIDEBAR);
await dismissAllModals();
await popToRoot();
PushNotifications.resetNotification();
}
};
onPushNotification = async (deviceNotification) => {
const {dispatch} = Store.redux;
const {data, foreground, message, userInfo, userInteraction} = deviceNotification;
const notification = {
data,
message,
userInfo,
};
waitForHydration(Store.redux, () => {
switch (data.type) {
case NOTIFICATION_TYPE.CLEAR:
dispatch(markChannelViewedAndRead(data.channel_id, null, false));
break;
case NOTIFICATION_TYPE.MESSAGE:
// get the posts for the channel as soon as possible
dispatch(fetchPostActionWithRetry(getPosts(data.channel_id)));
if (foreground) {
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
} else if (userInteraction && !notification?.userInfo?.localNotification && !notification?.data?.localNotification) {
this.loadFromNotification(notification);
}
break;
case NOTIFICATION_TYPE.SESSION:
// eslint-disable-next-line no-console
console.log('Session expired notification');
dispatch(logout());
break;
}
});
};
onPushNotificationReply = async (data, text, completion) => {
const {dispatch, getState} = Store.redux;
const state = getState();
const credentials = await getAppCredentials(); // TODO Change to handle multiple servers
const url = await getCurrentServerUrl(); // TODO Change to handle multiple servers
const token = credentials.password;
const usernameParsed = credentials.username.split(',');
const [, currentUserId] = usernameParsed;
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(url);
}
if (!Client4.getToken()) {
// Make sure the Client has the server token set
Client4.setToken(token);
}
dispatch(fetchPostActionWithRetry(getPosts(data.channel_id)));
const result = await dispatch(createPostForNotificationReply(post));
if (result.error) {
const locale = getCurrentLocale(state);
PushNotifications.localNotification({
message: getLocalizedMessage(locale, t('mobile.reply_post.failed')),
userInfo: {
localNotification: true,
localTest: true,
channel_id: data.channel_id,
},
});
completion();
return;
}
this.replyNotificationData = null;
PushNotifications.getDeliveredNotifications((notifications) => {
PushNotifications.setApplicationIconBadgeNumber(notifications.length);
completion();
});
} else {
this.replyNotificationData = {
data,
text,
completion,
};
}
};
onRegisterDevice = (data) => {
const {dispatch} = Store.redux;
let prefix;
if (Platform.OS === 'ios') {
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
if (DeviceInfo.getBundleId().includes('rnbeta')) {
prefix = `${prefix}beta`;
}
} else {
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
}
EphemeralStore.deviceToken = `${prefix}:${data.token}`;
// TODO: Remove when realm is ready
waitForHydration(Store.redux, () => {
this.configured = true;
dispatch(setDeviceToken(EphemeralStore.deviceToken));
});
};
getNotification = () => {
return PushNotifications.getNotification();
};
}
export default new PushNotificationUtils();

View file

@ -3,37 +3,12 @@
/* eslint-disable no-import-assign */
import NotificationsIOS from 'react-native-notifications';
import {Notifications} from 'react-native-notifications';
import * as ViewSelectors from '@selectors/views';
import Store from '@store/store';
import PushNotification from './push_notifications.ios';
jest.mock('react-native-notifications', () => {
let badgesCount = 0;
let deliveredNotifications = [];
return {
getBadgesCount: jest.fn((callback) => callback(badgesCount)),
setBadgesCount: jest.fn((count) => {
badgesCount = count;
}),
addEventListener: jest.fn(),
setDeliveredNotifications: jest.fn((notifications) => {
deliveredNotifications = notifications;
}),
getDeliveredNotifications: jest.fn(async (callback) => {
await callback(deliveredNotifications);
}),
removeDeliveredNotifications: jest.fn((ids) => {
deliveredNotifications = deliveredNotifications.filter((n) => !ids.includes(n.identifier));
}),
cancelAllLocalNotifications: jest.fn(),
NotificationAction: jest.fn(),
NotificationCategory: jest.fn(),
};
});
import PushNotification from './push_notifications';
describe('PushNotification', () => {
const channel1ID = 'channel-1-id';
@ -66,18 +41,15 @@ describe('PushNotification', () => {
channel_id: channel2ID,
},
];
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
Notifications.setDeliveredNotifications(deliveredNotifications);
const notificationCount = deliveredNotifications.length;
expect(notificationCount).toBe(5);
NotificationsIOS.setBadgesCount(notificationCount);
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(notificationCount));
// Clear channel1 notifications
await PushNotification.clearChannelNotifications(channel1ID);
await NotificationsIOS.getDeliveredNotifications(async (deliveredNotifs) => {
await Notifications.ios.getDeliveredNotifications(async (deliveredNotifs) => {
expect(deliveredNotifs.length).toBe(2);
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel1ID);
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel2ID);
@ -92,33 +64,33 @@ describe('PushNotification', () => {
PushNotification.clearNotifications();
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(0);
expect(NotificationsIOS.setBadgesCount).toHaveBeenCalledWith(0);
expect(Notifications.ios.setBadgeCount).toHaveBeenCalledWith(0);
expect(cancelAllLocalNotifications).toHaveBeenCalled();
expect(NotificationsIOS.cancelAllLocalNotifications).toHaveBeenCalled();
expect(Notifications.cancelAllLocalNotifications).toHaveBeenCalled();
});
it('clearChannelNotifications should set app badge number from to delivered notification count when redux store is not set', () => {
it('clearChannelNotifications should set app badge number from to delivered notification count when redux store is not set', async () => {
Store.redux = null;
const setApplicationIconBadgeNumber = jest.spyOn(PushNotification, 'setApplicationIconBadgeNumber');
const deliveredNotifications = [{identifier: 1}, {identifier: 2}];
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
Notifications.setDeliveredNotifications(deliveredNotifications);
PushNotification.clearChannelNotifications();
await PushNotification.clearChannelNotifications();
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(deliveredNotifications.length);
});
it('clearChannelNotifications should set app badge number from redux store when set', () => {
it('clearChannelNotifications should set app badge number from redux store when set', async () => {
Store.redux = {
getState: jest.fn(),
};
const setApplicationIconBadgeNumber = jest.spyOn(PushNotification, 'setApplicationIconBadgeNumber');
const deliveredNotifications = [{identifier: 1}, {identifier: 2}];
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
Notifications.setDeliveredNotifications(deliveredNotifications);
const stateBadgeCount = 2 * deliveredNotifications.length;
ViewSelectors.getBadgeCount = jest.fn().mockReturnValue(stateBadgeCount);
PushNotification.clearChannelNotifications();
await PushNotification.clearChannelNotifications();
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(stateBadgeCount);
});
});

View file

@ -0,0 +1,254 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AppState, NativeModules, Platform} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {
Notification,
NotificationAction,
NotificationBackgroundFetchResult,
NotificationCategory,
NotificationCompletion,
Notifications,
NotificationTextInput,
Registered,
} from 'react-native-notifications';
import {dismissAllModals, popToRoot} from '@actions/navigation';
import {markChannelViewedAndRead, fetchPostActionWithRetry} from '@actions/views/channel';
import {getPosts} from '@actions/views/post';
import {loadFromPushNotification} from '@actions/views/root';
import {logout} from '@actions/views/user';
import {NavigationTypes, ViewTypes} from '@constants';
import {getLocalizedMessage} from '@i18n';
import {setDeviceToken} from '@mm-redux/actions/general';
import {General} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {getCurrentLocale} from '@selectors/i18n';
import {getBadgeCount} from '@selectors/views';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
import {waitForHydration} from '@store/utils';
import {t} from '@utils/i18n';
import type {DispatchFunc} from '@mm-redux/types/actions';
const CATEGORY = 'CAN_REPLY';
const REPLY_ACTION = 'REPLY_ACTION';
const AndroidNotificationPreferences = Platform.OS === 'android' ? NativeModules.NotificationPreferences : null;
const NOTIFICATION_TYPE = {
CLEAR: 'clear',
MESSAGE: 'message',
SESSION: 'session',
};
interface NotificationWithChannel extends Notification {
identifier: string;
channel_id: string;
}
class PushNotifications {
configured = false;
constructor() {
Notifications.registerRemoteNotifications();
Notifications.events().registerNotificationOpened(this.onNotificationOpened);
Notifications.events().registerRemoteNotificationsRegistered(this.onRemoteNotificationsRegistered);
Notifications.events().registerNotificationReceivedBackground(this.onNotificationReceivedBackground);
Notifications.events().registerNotificationReceivedForeground(this.onNotificationReceivedForeground);
this.getInitialNotification();
}
cancelAllLocalNotifications() {
Notifications.cancelAllLocalNotifications();
}
clearNotifications = () => {
this.setApplicationIconBadgeNumber(0);
// TODO: Only cancel the local notifications that belong to this server
this.cancelAllLocalNotifications();
};
clearChannelNotifications = async (channelId: string) => {
if (Platform.OS === 'android') {
const notifications = await AndroidNotificationPreferences.getDeliveredNotifications();
const notificationForChannel = notifications.find((n: NotificationWithChannel) => n.channel_id === channelId);
if (notificationForChannel) {
AndroidNotificationPreferences.removeDeliveredNotifications(notificationForChannel.identifier, channelId);
}
} else {
const ids: string[] = [];
const notifications = await Notifications.ios.getDeliveredNotifications();
let badgeCount = notifications.length;
if (Store.redux) {
const totalMentions = getBadgeCount(Store.redux.getState());
if (totalMentions > -1) {
badgeCount = totalMentions;
}
}
for (let i = 0; i < notifications.length; i++) {
const notification = notifications[i] as NotificationWithChannel;
if (notification.channel_id === channelId) {
ids.push(notification.identifier);
}
}
if (ids.length) {
Notifications.ios.removeDeliveredNotifications(ids);
}
this.setApplicationIconBadgeNumber(badgeCount);
}
}
createReplyCategory = () => {
const {getState} = Store.redux!;
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 replyTextInput: NotificationTextInput = {
buttonTitle: replyButton,
placeholder: replyPlaceholder,
};
const replyAction = new NotificationAction(REPLY_ACTION, 'background', replyTitle, true, replyTextInput);
return new NotificationCategory(CATEGORY, [replyAction]);
}
getInitialNotification = async () => {
const notification: NotificationWithData | undefined = await Notifications.getInitialNotification();
if (notification) {
EphemeralStore.setStartFromNotification(true);
notification.userInteraction = true;
// getInitialNotification may run before the store is set
// that is why we run on an interval until the store is available
// once we handle the notification the interval is cleared.
const interval = setInterval(() => {
if (Store.redux) {
clearInterval(interval);
this.handleNotification(notification);
}
}, 500);
}
}
handleNotification = (notification: NotificationWithData) => {
const {payload, foreground, userInteraction} = notification;
if (Store.redux && payload) {
const dispatch = Store.redux.dispatch as DispatchFunc;
waitForHydration(Store.redux, async () => {
switch (payload.type) {
case NOTIFICATION_TYPE.CLEAR:
dispatch(markChannelViewedAndRead(payload.channel_id, null, false));
break;
case NOTIFICATION_TYPE.MESSAGE:
// get the posts for the channel as soon as possible
dispatch(fetchPostActionWithRetry(getPosts(payload.channel_id)));
if (foreground) {
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
} else if (userInteraction && !payload.userInfo?.local) {
dispatch(loadFromPushNotification(notification));
const componentId = EphemeralStore.getNavigationTopComponentId();
if (componentId) {
EventEmitter.emit(NavigationTypes.CLOSE_MAIN_SIDEBAR);
EventEmitter.emit(NavigationTypes.CLOSE_SETTINGS_SIDEBAR);
await dismissAllModals();
await popToRoot();
}
}
break;
case NOTIFICATION_TYPE.SESSION:
// eslint-disable-next-line no-console
console.log('Session expired notification');
dispatch(logout());
break;
}
});
}
}
localNotification = (notification: Notification) => {
Notifications.postLocalNotification(notification);
}
onNotificationOpened = (notification: NotificationWithData, completion: () => void) => {
notification.userInteraction = true;
this.handleNotification(notification);
completion();
}
onNotificationReceivedBackground = (notification: NotificationWithData, completion: (response: NotificationBackgroundFetchResult) => void) => {
this.handleNotification(notification);
completion(NotificationBackgroundFetchResult.NO_DATA);
};
onNotificationReceivedForeground = (notification: NotificationWithData, completion: (response: NotificationCompletion) => void) => {
notification.foreground = AppState.currentState === 'active';
completion({alert: false, sound: true, badge: true});
this.handleNotification(notification);
};
onRemoteNotificationsRegistered = (event: Registered) => {
if (!this.configured) {
this.configured = true;
const {deviceToken} = event;
let prefix;
if (Platform.OS === 'ios') {
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
if (DeviceInfo.getBundleId().includes('rnbeta')) {
prefix = `${prefix}beta`;
}
} else {
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
}
EphemeralStore.deviceToken = `${prefix}:${deviceToken}`;
if (Store.redux) {
const dispatch = Store.redux.dispatch as DispatchFunc;
waitForHydration(Store.redux, () => {
this.requestNotificationReplyPermissions();
dispatch(setDeviceToken(EphemeralStore.deviceToken));
});
}
}
};
requestNotificationReplyPermissions = () => {
if (Platform.OS === 'ios') {
const replyCategory = this.createReplyCategory();
Notifications.setCategories([replyCategory]);
}
};
setApplicationIconBadgeNumber = (value: number) => {
if (Platform.OS === 'ios') {
const count = value < 0 ? 0 : value;
Notifications.ios.setBadgeCount(count);
}
};
scheduleNotification = (notification: Notification) => {
if (notification.fireDate) {
if (Platform.OS === 'ios') {
notification.fireDate = new Date(notification.fireDate).toISOString();
}
Notifications.postLocalNotification(notification);
}
};
}
export default new PushNotifications();

View file

@ -17,7 +17,6 @@ import emmProvider from '@init/emm_provider';
import '@init/device';
import '@init/fetch';
import globalEventHandler from '@init/global_event_handler';
import pushNotifications from '@init/push_notifications';
import {registerScreens} from '@screens';
import configureStore from '@store';
import EphemeralStore from '@store/ephemeral_store';
@ -37,7 +36,6 @@ const init = async () => {
const MMKVStorage = await getStorage();
const {store} = configureStore(MMKVStorage);
await pushNotifications.configure();
globalEventHandler.configure({
launchApp,
});

View file

@ -1,5 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PushNotifications from './push_notifications';
export default PushNotifications;

View file

@ -1,130 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AppState, NativeModules} from 'react-native';
import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
import EphemeralStore from '@store/ephemeral_store';
const {NotificationPreferences} = NativeModules;
class PushNotification {
constructor() {
this.onRegister = null;
this.onNotification = null;
this.deviceNotification = null;
this.deviceToken = null;
NotificationsAndroid.setRegistrationTokenUpdateListener((deviceToken) => {
this.deviceToken = deviceToken;
if (this.onRegister) {
this.onRegister({token: this.deviceToken});
}
});
NotificationsAndroid.setNotificationReceivedListener((notification) => {
if (notification) {
const data = notification.getData();
this.handleNotification(data, false);
}
});
NotificationsAndroid.setNotificationOpenedListener((notification) => {
if (notification) {
EphemeralStore.setStartFromNotification(true);
const data = notification.getData();
this.handleNotification(data, true);
}
});
}
handleNotification = (data, userInteraction) => {
const foreground = !userInteraction && AppState.currentState === 'active';
this.deviceNotification = {
data,
foreground,
message: data.message,
userInfo: data.userInfo,
userInteraction,
};
if (this.onNotification && (foreground || userInteraction)) {
this.onNotification(this.deviceNotification);
}
};
configure(options) {
this.onRegister = options.onRegister;
this.onNotification = options.onNotification;
if (this.onRegister && this.deviceToken) {
this.onRegister({token: this.deviceToken});
}
return new Promise((resolve) => {
if (!options.popInitialNotification) {
resolve();
return;
}
PendingNotifications.getInitialNotification().
then((notification) => {
if (notification) {
const data = notification.getData();
if (data) {
EphemeralStore.setStartFromNotification(true);
this.handleNotification(data, true);
}
}
}).
catch((err) => {
console.log('Android getInitialNotifiation() failed', err); //eslint-disable-line no-console
}).
finally(() => {
resolve();
});
});
}
localNotificationSchedule(notification) {
if (notification.date) {
notification.fireDate = notification.date.getTime();
Reflect.deleteProperty(notification, 'date');
NotificationsAndroid.scheduleLocalNotification(notification);
}
}
localNotification(notification) {
NotificationsAndroid.localNotification(notification);
}
cancelAllLocalNotifications() {
NotificationsAndroid.cancelAllLocalNotifications();
}
setApplicationIconBadgeNumber() {
// Not supported for Android
}
getNotification() {
return this.deviceNotification;
}
resetNotification() {
this.deviceNotification = null;
}
async clearChannelNotifications(channelId) {
const notifications = await NotificationPreferences.getDeliveredNotifications();
const notificationForChannel = notifications.find((n) => n.channel_id === channelId);
if (notificationForChannel) {
NotificationPreferences.removeDeliveredNotifications(notificationForChannel.identifier, channelId);
}
}
clearNotifications = () => {
this.cancelAllLocalNotifications(); // TODO: Only cancel the local notifications that belong to this server
}
}
export default new PushNotification();

View file

@ -1,237 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AppState} from 'react-native';
import NotificationsIOS, {
NotificationAction,
NotificationCategory,
DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT,
DEVICE_NOTIFICATION_RECEIVED_FOREGROUND_EVENT,
DEVICE_NOTIFICATION_OPENED_EVENT,
} from 'react-native-notifications';
import {getLocalizedMessage} from '@i18n';
import {getCurrentLocale} from '@selectors/i18n';
import {getBadgeCount} from '@selectors/views';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
import {t} from '@utils/i18n';
const CATEGORY = 'CAN_REPLY';
const REPLY_ACTION = 'REPLY_ACTION';
class PushNotification {
constructor() {
this.deviceNotification = null;
this.onRegister = null;
this.onNotification = null;
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) => {
this.deviceNotification = {
data,
foreground,
message: data.body || data.message,
userInfo: data.userInfo,
userInteraction,
};
if (this.onNotification) {
this.onNotification(this.deviceNotification);
}
};
configure(options) {
this.onRegister = options.onRegister;
this.onNotification = options.onNotification;
this.requestNotificationReplyPermissions();
return new Promise((resolve) => {
if (!options.popInitialNotification) {
resolve();
return;
}
NotificationsIOS.getInitialNotification().
then((notification) => {
if (notification) {
const data = notification.getData();
if (data) {
EphemeralStore.setStartFromNotification(true);
this.handleNotification(data, false, true);
}
}
}).
catch((err) => {
console.log('iOS getInitialNotifiation() failed', err); //eslint-disable-line no-console
}).
finally(() => {
resolve();
});
});
}
requestNotificationReplyPermissions = () => {
const replyCategory = this.createReplyCategory();
this.requestPermissions([replyCategory]);
}
createReplyCategory = () => {
const {getState} = Store.redux;
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) => {
NotificationsIOS.requestPermissions(permissions);
};
localNotificationSchedule(notification) {
if (notification.date) {
const deviceNotification = {
fireDate: notification.date.toISOString(),
body: notification.message,
alertAction: '',
userInfo: notification.userInfo,
};
NotificationsIOS.localNotification(deviceNotification);
}
}
localNotification(notification) {
this.deviceNotification = {
body: notification.message,
alertAction: '',
userInfo: notification.userInfo,
};
NotificationsIOS.localNotification(this.deviceNotification);
}
cancelAllLocalNotifications() {
NotificationsIOS.cancelAllLocalNotifications();
}
onNotificationReceivedBackground = (notification) => {
const userInteraction = AppState.currentState === 'active';
// mark the app as started as soon as possible
if (userInteraction) {
EphemeralStore.setStartFromNotification(true);
}
const data = notification.getData();
const info = {
...data,
message: data.body || notification.getMessage(),
};
if (!userInteraction) {
this.handleNotification(info, false, userInteraction);
}
};
onNotificationReceivedForeground = (notification) => {
const data = notification.getData();
const info = {
...data,
message: data.body || notification.getMessage(),
};
this.handleNotification(info, true, false);
};
onNotificationOpened = (notification, completion) => {
const data = notification.getData();
const info = {
...data,
message: data.body || notification.getMessage(),
};
this.handleNotification(info, false, true);
completion();
};
onRemoteNotificationsRegistered = (deviceToken) => {
if (this.onRegister) {
this.onRegister({token: deviceToken});
}
};
setApplicationIconBadgeNumber(number) {
const count = number < 0 ? 0 : number;
NotificationsIOS.setBadgesCount(count);
}
getNotification() {
return this.deviceNotification;
}
resetNotification() {
this.deviceNotification = null;
}
getDeliveredNotifications(callback) {
NotificationsIOS.getDeliveredNotifications(callback);
}
clearChannelNotifications(channelId) {
NotificationsIOS.getDeliveredNotifications((notifications) => {
const ids = [];
let badgeCount = notifications.length;
if (Store.redux) {
const totalMentions = getBadgeCount(Store.redux.getState());
if (totalMentions > -1) {
badgeCount = totalMentions;
}
}
for (let i = 0; i < notifications.length; i++) {
const notification = notifications[i];
if (notification.channel_id === channelId) {
ids.push(notification.identifier);
}
}
if (ids.length) {
NotificationsIOS.removeDeliveredNotifications(ids);
}
this.setApplicationIconBadgeNumber(badgeCount);
});
}
clearNotifications = () => {
this.setApplicationIconBadgeNumber(0);
this.cancelAllLocalNotifications(); // TODO: Only cancel the local notifications that belong to this server
}
}
export default new PushNotification();

View file

@ -19,7 +19,7 @@ import {preventDoubleTap} from '@utils/tap';
import {setNavigatorStyles} from '@utils/theme';
import tracker from '@utils/time_tracker';
import PushNotifications from 'app/push_notifications';
import PushNotifications from '@init/push_notifications';
import telemetry from 'app/telemetry';
export let ClientUpgradeListener;

View file

@ -11,7 +11,7 @@ import {intlShape} from 'react-intl';
import Badge from '@components/badge';
import CompassIcon from '@components/compass_icon';
import PushNotifications from 'app/push_notifications';
import PushNotifications from '@init/push_notifications';
import {preventDoubleTap} from '@utils/tap';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {t} from '@utils/i18n';

View file

@ -2,41 +2,14 @@
// See LICENSE.txt for license information.
import React from 'react';
import NotificationsIOS from 'react-native-notifications';
import Badge from '@components/badge';
import Preferences from '@mm-redux/constants/preferences';
import Badge from 'app/components/badge';
import PushNotification from 'app/push_notifications/push_notifications.ios';
import PushNotification from '@init/push_notifications';
import {shallowWithIntl} from 'test/intl-test-helper';
import MainSidebarDrawerButton from './main_sidebar_drawer_button';
jest.mock('react-native-notifications', () => {
let badgesCount = 0;
let deliveredNotifications = {};
return {
getBadgesCount: jest.fn((callback) => callback(badgesCount)),
setBadgesCount: jest.fn((count) => {
badgesCount = count;
}),
addEventListener: jest.fn(),
setDeliveredNotifications: jest.fn((notifications) => {
deliveredNotifications = notifications;
}),
getDeliveredNotifications: jest.fn(async (callback) => {
await callback(deliveredNotifications);
}),
removeDeliveredNotifications: jest.fn((ids) => {
deliveredNotifications = deliveredNotifications.filter((n) => !ids.includes(n.identifier));
}),
cancelAllLocalNotifications: jest.fn(),
NotificationAction: jest.fn(),
NotificationCategory: jest.fn(),
};
});
describe('MainSidebarDrawerButton', () => {
const baseProps = {
openSidebar: jest.fn(),
@ -45,7 +18,7 @@ describe('MainSidebarDrawerButton', () => {
visible: false,
};
afterEach(() => NotificationsIOS.setBadgesCount(0));
afterEach(() => PushNotification.setApplicationIconBadgeNumber(0));
test('should match, full snapshot', () => {
const wrapper = shallowWithIntl(
@ -73,7 +46,6 @@ describe('MainSidebarDrawerButton', () => {
<MainSidebarDrawerButton {...props}/>,
);
expect(setApplicationIconBadgeNumber).not.toBeCalled();
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(0));
});
test('should set app icon badge on mount', () => {
@ -87,7 +59,6 @@ describe('MainSidebarDrawerButton', () => {
<MainSidebarDrawerButton {...props}/>,
);
expect(setApplicationIconBadgeNumber).toHaveBeenCalledTimes(1);
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(1));
});
test('should set app icon badge update', () => {
@ -100,12 +71,10 @@ describe('MainSidebarDrawerButton', () => {
const wrapper = shallowWithIntl(
<MainSidebarDrawerButton {...props}/>,
);
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(0));
wrapper.setProps({badgeCount: 2});
expect(setApplicationIconBadgeNumber).toHaveBeenCalledTimes(1);
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(2);
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(2));
});
test('should set remove icon badge on update', () => {
@ -120,11 +89,9 @@ describe('MainSidebarDrawerButton', () => {
);
wrapper.setProps({badgeCount: 2});
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(2);
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(2));
wrapper.setProps({badgeCount: -1});
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(-1);
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(0));
});
test('Should be accessible', () => {

View file

@ -1,68 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Gallery should match snapshot 1`] = `
<React.Fragment>
<GalleryViewer
files={
Array [
Object {
"caption": "Caption 1",
"data": "data",
"source": "source",
},
Object {
"caption": "Caption 2",
"data": "data",
"source": "source",
},
]
}
footerVisible={true}
height={400}
initialIndex={0}
isLandscape={false}
onClose={[Function]}
onPageSelected={[Function]}
onTap={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
width={300}
/>
<InjectIntl(Footer)
file={
Object {
"caption": "Caption 1",
"data": "data",
"source": "source",
}
}
/>
</React.Fragment>
`;

View file

@ -32,7 +32,7 @@ import {
pinchGestureHandler,
useValue,
vec,
} from 'react-native-redash';
} from 'react-native-redash/lib/module/v1';
import {Platform} from 'react-native';
import {State} from 'react-native-gesture-handler';

View file

@ -67,7 +67,7 @@ export default class Gallery extends PureComponent {
sharedElementTransitions.push({
fromId: `gallery-${file.id}`,
toId: `image-${file.id}`,
interpolation: 'accelerateDecelerate',
interpolation: {mode: 'accelerateDecelerate'},
});
}

View file

@ -5,7 +5,7 @@ import React, {useEffect, useMemo, useRef, useState} from 'react';
import {Platform, StyleSheet, View} from 'react-native';
import {PanGestureHandler, PinchGestureHandler, State, TapGestureHandler, TapGestureHandlerStateChangeEvent} from 'react-native-gesture-handler';
import Animated, {abs, add, and, call, clockRunning, cond, divide, eq, floor, greaterOrEq, greaterThan, multiply, neq, not, onChange, set, sub, useCode} from 'react-native-reanimated';
import {clamp, snapPoint, timing, useClock, usePanGestureHandler, usePinchGestureHandler, useTapGestureHandler, useValue, vec} from 'react-native-redash';
import {clamp, snapPoint, timing, useClock, usePanGestureHandler, usePinchGestureHandler, useTapGestureHandler, useValue, vec} from 'react-native-redash/lib/module/v1';
import {isImage, isVideo} from '@utils/file';
import {calculateDimensions} from '@utils/images';
import {makeStyleSheetFromTheme} from '@utils/theme';

View file

@ -12,9 +12,9 @@ import {gestureHandlerRootHOC} from 'react-native-gesture-handler';
import RootWrapper from '@components/root';
let store;
const withGestures = (screen) => {
const withGestures = (screen, styles) => {
if (Platform.OS === 'android') {
return gestureHandlerRootHOC(screen);
return gestureHandlerRootHOC(screen, styles);
}
return screen;
@ -204,7 +204,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
}
if (screen) {
Navigation.registerComponent(screenName, () => withGestures(withReduxProvider(screen)), extraStyles, () => screen);
Navigation.registerComponent(screenName, () => withGestures(withReduxProvider(screen), extraStyles), () => screen);
}
});

View file

@ -19,7 +19,7 @@ import NotificationTitle from './notification_title';
interface NotificationProps {
componentId: string;
notification: PushNotificationData;
notification: NotificationWithData;
}
interface SlideAnimation extends Animatable.CustomAnimation {
@ -78,7 +78,7 @@ const Notification = ({componentId, notification}: NotificationProps) => {
EventEmitter.emit(NavigationTypes.CLOSE_SETTINGS_SIDEBAR);
dismiss();
if (!notification.userInfo?.localNotification) {
if (!notification.payload?.userInfo?.local) {
dispatch(loadFromPushNotification(notification));
}
};
@ -112,10 +112,7 @@ const Notification = ({componentId, notification}: NotificationProps) => {
return () => EventEmitter.off(NavigationTypes.NAVIGATION_SHOW_OVERLAY, dismiss);
}, []);
let message = notification.message;
if (typeof notification.message === 'object') {
message = notification.message.body;
}
const message = notification.payload?.body || notification.payload?.message;
return (
<PanGestureHandler
@ -136,13 +133,13 @@ const Notification = ({componentId, notification}: NotificationProps) => {
activeOpacity={1}
>
<NotificationIcon
fromWebhook={notification.data?.from_webhook === 'true'}
senderId={notification.data?.sender_id || ''}
useUserIcon={notification.data?.use_user_icon === 'true'}
overrideIconUrl={notification.data?.override_icon_url}
fromWebhook={notification.payload?.from_webhook === 'true'}
senderId={notification.payload?.sender_id || ''}
useUserIcon={notification.payload?.use_user_icon === 'true'}
overrideIconUrl={notification.payload?.override_icon_url}
/>
<View style={styles.titleContainer}>
<NotificationTitle channelName={notification.data?.channel_name || ''}/>
<NotificationTitle channelName={notification.payload?.channel_name || ''}/>
<View style={styles.flex}>
<Text
numberOfLines={1}

View file

@ -16,16 +16,15 @@ import {
import deepEqual from 'deep-equal';
import PropTypes from 'prop-types';
import FormattedText from '@components/formatted_text';
import RadioButtonGroup from '@components/radio_button';
import StatusBar from '@components/status_bar';
import PushNotifications from '@init/push_notifications';
import {RequestStatus} from '@mm-redux/constants';
import FormattedText from 'app/components/formatted_text';
import RadioButtonGroup from 'app/components/radio_button';
import SectionItem from '@screens/settings/section_item';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {getNotificationProps} from '@utils/notify_props';
import NotificationPreferences from 'app/notification_preferences';
import PushNotifications from 'app/push_notifications';
import StatusBar from 'app/components/status_bar';
import SectionItem from 'app/screens/settings/section_item';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getNotificationProps} from 'app/utils/notify_props';
import NotificationSettingsMobileBase from './notification_settings_mobile_base';
@ -698,13 +697,13 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase {
const {intl} = this.props;
PushNotifications.localNotification({
message: intl.formatMessage({
body: intl.formatMessage({
id: 'mobile.notification_settings_mobile.test_push',
defaultMessage: 'This is a test push notification',
}),
userInfo: {
localNotification: true,
localTest: true,
local: true,
test: true,
},
});
};

View file

@ -8,6 +8,7 @@ const SAVE_STATE_ACTIONS = [
'CONNECTION_CHANGED',
'DATA_CLEANUP',
'LOGIN',
'BATCH_LOAD_ME',
'Offline/STATUS_CHANGED',
'persist/REHYDRATE',
'RECEIVED_APP_STATE',

View file

@ -2,35 +2,31 @@
// See LICENSE.txt for license information.
interface NotificationUserInfo {
localNotification: boolean;
localTest: boolean;
channel_id?: string;
local: boolean;
test?: boolean;
}
interface NotificationData {
body?: string;
channel_id: string;
channel_name?: string;
from_webhook?: string;
post_id: string;
root_id?: string;
type: string;
use_user_icon?: string;
message?: string;
override_icon_url?: string;
override_username?: string;
version: string;
post_id: string;
root_id?: string;
sender_id?: string;
}
interface NotificationMessage {
body: string;
}
interface PushNotificationData {
date?: Date;
data?: NotificationData;
fireDate?: string;
foreground: boolean;
message: NotificationMessage | string;
team_id?: string;
type: string;
use_user_icon?: string;
userInfo?: NotificationUserInfo;
userInteraction: boolean;
version: string;
}
interface NotificationWithData extends Notification {
identifier: string;
payload?: NotificationData;
foreground?: boolean;
userInteraction?: boolean;
}

View file

@ -94,14 +94,14 @@ export function openGalleryAtIndex(index, files) {
sharedElementTransitions.push({
fromId: `image-${file.id}`,
toId: `gallery-${file.id}`,
interpolation: 'overshoot',
interpolation: {mode: 'overshoot'},
});
} else {
contentPush.y = {
from: windowHeight,
to: 0,
duration: 300,
interpolation: 'decelerate',
interpolation: {mode: 'decelerate'},
};
if (Platform.OS === 'ios') {

View file

@ -422,7 +422,6 @@
"mobile.rename_channel.name_maxLength": "URL must be less than {maxLength, number} characters",
"mobile.rename_channel.name_minLength": "URL must be {minLength, number} or more characters",
"mobile.rename_channel.name_required": "URL is required",
"mobile.reply_post.failed": "Message failed to send.",
"mobile.request.invalid_response": "Received invalid response from the server.",
"mobile.reset_status.alert_cancel": "Cancel",
"mobile.reset_status.alert_ok": "Ok",

801
detox/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,15 +8,15 @@
"@babel/plugin-transform-modules-commonjs": "7.12.1",
"@babel/plugin-transform-runtime": "7.12.1",
"@babel/preset-env": "7.12.1",
"axios": "0.20.0",
"babel-jest": "26.6.0",
"axios": "0.21.0",
"babel-jest": "26.6.3",
"babel-plugin-module-resolver": "4.0.0",
"deepmerge": "4.2.2",
"detox": "17.10.3",
"detox": "17.12.0",
"form-data": "3.0.0",
"jest": "26.6.0",
"jest-circus": "26.6.0",
"jest-cli": "26.6.0",
"jest": "26.6.3",
"jest-circus": "26.6.3",
"jest-cli": "26.6.3",
"jest-html-reporters": "2.1.0",
"jest-junit": "12.0.0",
"sanitize-filename": "1.6.3",

View file

@ -6,8 +6,8 @@ GEM
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.388.0)
aws-sdk-core (3.109.1)
aws-partitions (1.393.0)
aws-sdk-core (3.109.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
@ -15,7 +15,7 @@ GEM
aws-sdk-kms (1.39.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.83.1)
aws-sdk-s3 (1.84.1)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
@ -34,7 +34,7 @@ GEM
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.0)
emoji_regex (3.2.1)
excon (0.78.0)
faraday (1.1.0)
multipart-post (>= 1.2, < 3)
@ -45,7 +45,7 @@ GEM
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.0)
fastlane (2.165.0)
fastlane (2.167.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
@ -123,7 +123,7 @@ GEM
json (2.3.1)
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.10.1)
mini_magick (4.11.0)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
multi_json (1.15.0)

View file

@ -1,4 +1,5 @@
#import <UIKit/UIKit.h>
#import "RNNotifications.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>

View file

@ -4,7 +4,6 @@
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
#import <ReactNativeNavigation/ReactNativeNavigation.h>
#import "RNNotifications.h"
#import <UploadAttachments/UploadAttachments-Swift.h>
#import <UserNotifications/UserNotifications.h>
#import "Mattermost-Swift.h"

View file

@ -54,10 +54,10 @@ NSString *const ReplyActionID = @"REPLY_ACTION";
NSString *completionKey = response.notification.request.identifier;
NSDictionary *parsedResponse = [RNNotificationParser parseNotificationResponse:response];
NSString *message = [parsedResponse valueForKeyPath:@"action.text"];
NSString *channelId = [parsedResponse valueForKeyPath:@"payload.channel_id"];
NSString *rootId = [parsedResponse valueForKeyPath:@"payload.root_id"];
NSString *channelId = [parsedResponse valueForKeyPath:@"notification.channel_id"];
NSString *rootId = [parsedResponse valueForKeyPath:@"notification.root_id"];
if (rootId == nil) {
rootId = [parsedResponse valueForKeyPath:@"payload.post_id"];
rootId = [parsedResponse valueForKeyPath:@"notification.post_id"];
}
NSDictionary *post = @{
@ -114,17 +114,17 @@ NSString *const ReplyActionID = @"REPLY_ACTION";
[self setNotificationCenter:notificationCenter];
}
NSString *id = [[NSUUID UUID] UUIDString];;
NSNumber *id = [NSNumber numberWithInteger:[NSDate timeIntervalSinceReferenceDate] * 10000];
NSDictionary *notification = @{
@"body": @"Message failed to send.",
@"alertAction": @"",
@"userInfo": @{
@"localNotification": @YES,
@"localTest": @YES,
@"local": @YES,
@"test": @NO,
@"channel_id": channelId
}
};
[notificationCenter sendLocalNotification:notification withId:id];
[notificationCenter postLocalNotification:notification withId:id];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler();

View file

@ -1,7 +1,7 @@
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '10.0'
platform :ios, '11.0'
target 'Mattermost' do
# Pods for Mattermost

View file

@ -32,9 +32,9 @@ PODS:
- libwebp/mux (1.1.0):
- libwebp/demux
- libwebp/webp (1.1.0)
- MMKV (1.2.2):
- MMKVCore (~> 1.2.2)
- MMKVCore (1.2.2)
- MMKV (1.2.4):
- MMKVCore (~> 1.2.4)
- MMKVCore (1.2.4)
- Permission-Camera (2.2.2):
- RNPermissions
- Permission-PhotoLibrary (2.2.2):
@ -212,7 +212,7 @@ PODS:
- React
- react-native-cookies (3.2.0):
- React
- react-native-document-picker (4.0.0):
- react-native-document-picker (4.1.0):
- React-Core
- react-native-hw-keyboard-event (0.0.4):
- React
@ -220,18 +220,18 @@ PODS:
- React-Core
- react-native-local-auth (1.6.0):
- React
- react-native-mmkv-storage (0.3.7):
- MMKV (= 1.2.2)
- react-native-mmkv-storage (0.4.1):
- MMKV (= 1.2.4)
- React
- react-native-netinfo (5.9.7):
- React-Core
- react-native-notifications (2.1.7):
- React
- react-native-notifications (3.4.0):
- React-Core
- react-native-passcode-status (1.1.2):
- React
- react-native-safe-area (0.5.1):
- React
- react-native-safe-area-context (3.1.8):
- react-native-safe-area-context (3.1.9):
- React-Core
- react-native-video (5.1.0-alpha8):
- React
@ -300,31 +300,35 @@ PODS:
- React-Core (= 0.63.3)
- React-cxxreact (= 0.63.3)
- React-jsi (= 0.63.3)
- ReactNativeExceptionHandler (2.10.8):
- React
- ReactNativeExceptionHandler (2.10.9):
- React-Core
- ReactNativeKeyboardTrackingView (5.7.0):
- React
- ReactNativeNavigation (7.1.0):
- ReactNativeNavigation (7.3.0):
- React-Core
- React-RCTImage
- React-RCTText
- ReactNativeNavigation/Core (= 7.3.0)
- ReactNativeNavigation/Core (7.3.0):
- React-Core
- React-RCTImage
- React-RCTText
- ReactNativeNavigation/Core (= 7.1.0)
- rn-fetch-blob (0.12.0):
- React-Core
- RNCAsyncStorage (1.12.1):
- React-Core
- RNCClipboard (1.5.0):
- RNCClipboard (1.5.1):
- React-Core
- RNCMaskedView (0.1.10):
- React
- RNDeviceInfo (7.0.2):
- RNDeviceInfo (7.1.0):
- React-Core
- RNDevMenu (4.0.2):
- React-Core
- React-Core/DevSupport
- React-RCTNetwork
- RNFastImage (8.3.2):
- React
- RNFastImage (8.3.4):
- React-Core
- SDWebImage (~> 5.8)
- SDWebImageWebPCoder (~> 0.6.1)
- RNFileViewer (2.1.4):
@ -333,38 +337,38 @@ PODS:
- React
- RNKeychain (4.0.5):
- React
- RNLocalize (1.4.2):
- RNLocalize (2.0.0):
- React-Core
- RNPermissions (2.2.2):
- React-Core
- RNReactNativeHapticFeedback (1.11.0):
- React-Core
- RNReanimated (1.13.1):
- React
- RNReanimated (1.13.2):
- React-Core
- RNRudderSdk (1.0.0):
- React
- Rudder
- RNScreens (2.11.0):
- React
- RNSentry (1.9.0):
- RNScreens (2.15.0):
- React-Core
- Sentry (~> 5.2.0)
- RNShare (3.8.1):
- RNSentry (2.0.0):
- React-Core
- Sentry (~> 6.0.3)
- RNShare (4.1.0):
- React-Core
- RNSVG (12.1.0):
- React
- RNVectorIcons (7.1.0):
- React
- Rudder (1.0.4)
- SDWebImage (5.8.4):
- SDWebImage/Core (= 5.8.4)
- SDWebImage/Core (5.8.4)
- Rudder (1.0.9)
- SDWebImage (5.9.4):
- SDWebImage/Core (= 5.9.4)
- SDWebImage/Core (5.9.4)
- SDWebImageWebPCoder (0.6.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.7)
- Sentry (5.2.1):
- Sentry/Core (= 5.2.1)
- Sentry/Core (5.2.1)
- Sentry (6.0.7):
- Sentry/Core (= 6.0.7)
- Sentry/Core (6.0.7)
- Swime (3.0.6)
- XCDYouTubeKit (2.8.2)
- Yoga (1.14.0)
@ -606,8 +610,8 @@ SPEC CHECKSUMS:
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
jail-monkey: 80c9e34da2cd54023e5ad08bf7051ec75bd43d5b
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
MMKV: b14909757d8b70e2aa89ff1c6d56a7b239e10a29
MMKVCore: 6225324fe5006026bc86ef8641d567e00c6582f3
MMKV: f15696ad598023bf6cbb17eadee13efc185c5028
MMKVCore: 9a9c10e4faf3b3068367fb2efe5419722f3d2e8a
Permission-Camera: 375576c57ea625c88137d2cc52ba6dfeb21db036
Permission-PhotoLibrary: bb20a7ac8aec0f7459558e5fbe2bd0a6f2744be3
RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047
@ -623,16 +627,16 @@ SPEC CHECKSUMS:
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
react-native-cameraroll: 9d2b7c1707204d2040d2812f960c6cebdbd9f670
react-native-cookies: 854d59c4135c70b92a02ca4930e68e4e2eb58150
react-native-document-picker: b3e78a8f7fef98b5cb069f20fc35797d55e68e28
react-native-document-picker: 37b870da56cf33b8432212842e3f374b9fe98889
react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53
react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
react-native-local-auth: 359af242caa1e5c501ac9dfe33b1e238ad8f08c6
react-native-mmkv-storage: a452e035494ecb7f641ec192b2dccdb50021a826
react-native-mmkv-storage: ceaec36b6967c680e60f3be5e91ba3f66d488697
react-native-netinfo: e36c1bb6df27ab84aa933679b3f5bbf9d180b18f
react-native-notifications: 24706907104a0f00c35c4bde7e0ca76a50f730e1
react-native-notifications: 1e4912c8f11741799c823f263beaf9f7f829955b
react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a
react-native-safe-area: e8230b0017d76c00de6b01e2412dcf86b127c6a3
react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6
react-native-safe-area-context: 86612d2c9a9e94e288319262d10b5f93f0b395f5
react-native-video: 8d97379f3c2322a569f1e27542ad1a646cad9444
react-native-webview: 0d1c2b4e7ffb0543a74fa0512f2f8dc5fb0e49e2
React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa
@ -645,38 +649,38 @@ SPEC CHECKSUMS:
React-RCTText: 65a6de06a7389098ce24340d1d3556015c38f746
React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454
ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3
ReactNativeExceptionHandler: 8025d98049c25f186835a3af732dd7c9974d6dce
ReactNativeExceptionHandler: f1638ffd507ef1b1794af25884fa0eb30dd5c551
ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
ReactNativeNavigation: 65025dab27b404053678593b2450ed7a022e3173
ReactNativeNavigation: c2dcda2b86e32090c443182206c915107d03106f
rn-fetch-blob: 17961aec08caae68bb8fc0e5b40f93b3acfa6932
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
RNCClipboard: 8f9f12fabf3c06e976f19f87a62c89e28dfedfca
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNDeviceInfo: a37a15a98822c31c3982cb28eba6d336ba4d0968
RNDeviceInfo: 2c198acf2cef2dc8e5bf2b502fdde0e7e5ef3520
RNDevMenu: 9f80d65b80ba1fa84e5361d017b8c854a2f05005
RNFastImage: e19ba191922e7dab9d932a4d59d62d76660aa222
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
RNFileViewer: 83cc066ad795b1f986791d03b56fe0ee14b6a69f
RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNLocalize: 4071198b59b461f3b74eebc5fee8c50f13e39e79
RNLocalize: dc432c370fe76ad48cb380386232ca68d534a52a
RNPermissions: 067727df624665d4a6c8e2cffcc172ba608b96ed
RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285
RNReanimated: dd8c286ab5dd4ba36d3a7fef8bff7e08711b5476
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNRudderSdk: 5d99b1a5a582ab55d6213b38018d35e98818af63
RNScreens: 0e91da98ab26d5d04c7b59a9b6bd694124caf88c
RNSentry: 1adaa43b01c6a3ab5091d4d1add66b7c58558898
RNShare: 26eb3a3a3a83f059318cafb0b09547e099969058
RNScreens: 2ad555d4d9fa10b91bb765ca07fe9b29d59573f0
RNSentry: cb4ef23400fb6690f6ac8f6acef0c023a8a4f37d
RNShare: 106a76243ac90f43ddb9028dcb78ade406b8adff
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59
Rudder: 57a48709b714c4746a96f4d4d00f33e78a62a5ee
SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9
Rudder: 90ed801a09c73017184e6fc901370be1754eb182
SDWebImage: b69257f4ab14e9b6a2ef53e910fdf914d8f757c1
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
Sentry: 7d075cae43a9a518fdd51e258b6212f0527c51cd
Sentry: e2c691627ae1dc0029acebbd1b0b4af4df12af73
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
XCDYouTubeKit: 79baadb0560673a67c771eba45f83e353fd12c1f
Yoga: 7d13633d129fd179e01b8953d38d47be90db185a
YoutubePlayer-in-WKWebView: af2f5929fc78882d94bfdfeea999b661b78d9717
PODFILE CHECKSUM: 1aabd16b6a9ed2037b4d7443027d222817790468
PODFILE CHECKSUM: 876d4bb13b09f8d0961bb18b2338fc5936aa5e63
COCOAPODS: 1.10.0.rc.1

2473
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,29 +7,29 @@
"license": "Apache 2.0",
"private": true,
"dependencies": {
"@babel/runtime": "7.12.1",
"@babel/runtime": "7.12.5",
"@react-native-community/async-storage": "1.12.1",
"@react-native-community/cameraroll": "4.0.1",
"@react-native-community/clipboard": "1.5.0",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "5.9.7",
"@react-navigation/native": "5.7.6",
"@react-navigation/stack": "5.9.3",
"@react-navigation/native": "5.8.9",
"@react-navigation/stack": "5.12.6",
"@rudderstack/rudder-sdk-react-native": "1.0.3",
"@sentry/react-native": "1.9.0",
"@sentry/react-native": "2.0.0",
"analytics-react-native": "1.2.0",
"commonmark": "github:mattermost/commonmark.js#f6ab98dede6ce4b4e7adea140ac77249bfb2d6ce",
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#3a2ac19cab725ad28b170fdc1d397dddedcf87eb",
"core-js": "3.6.5",
"core-js": "3.7.0",
"deep-equal": "2.0.4",
"deepmerge": "4.2.2",
"emoji-regex": "9.2.0",
"form-data": "3.0.0",
"fuse.js": "6.4.2",
"fuse.js": "6.4.3",
"intl": "1.2.5",
"jail-monkey": "2.3.3",
"mime-db": "1.45.0",
"moment-timezone": "0.5.31",
"moment-timezone": "0.5.32",
"prop-types": "15.7.2",
"react": "16.13.1",
"react-intl": "2.8.0",
@ -39,11 +39,11 @@
"react-native-button": "3.0.1",
"react-native-calendars": "1.403.0",
"react-native-cookies": "github:mattermost/react-native-cookies#b35bafc388ae09c83bd875e887daf6a0755e0b40",
"react-native-device-info": "7.0.2",
"react-native-document-picker": "4.0.0",
"react-native-device-info": "7.1.0",
"react-native-document-picker": "4.1.0",
"react-native-elements": "2.3.2",
"react-native-exception-handler": "2.10.8",
"react-native-fast-image": "8.3.2",
"react-native-exception-handler": "2.10.9",
"react-native-fast-image": "8.3.4",
"react-native-file-viewer": "2.1.4",
"react-native-gesture-handler": "1.8.0",
"react-native-haptic-feedback": "1.11.0",
@ -54,19 +54,19 @@
"react-native-keychain": "4.0.5",
"react-native-linear-gradient": "2.5.6",
"react-native-local-auth": "1.6.0",
"react-native-localize": "1.4.2",
"react-native-mmkv-storage": "0.3.7",
"react-native-navigation": "7.1.0",
"react-native-notifications": "2.1.7",
"react-native-localize": "2.0.0",
"react-native-mmkv-storage": "0.4.1",
"react-native-navigation": "7.3.0",
"react-native-notifications": "3.4.0",
"react-native-passcode-status": "1.1.2",
"react-native-permissions": "2.2.2",
"react-native-reanimated": "1.13.1",
"react-native-redash": "14.2.4",
"react-native-reanimated": "1.13.2",
"react-native-redash": "15.11.1",
"react-native-safe-area": "0.5.1",
"react-native-safe-area-context": "3.1.8",
"react-native-screens": "2.11.0",
"react-native-safe-area-context": "3.1.9",
"react-native-screens": "2.15.0",
"react-native-section-list-get-item-layout": "2.2.3",
"react-native-share": "3.8.1",
"react-native-share": "4.1.0",
"react-native-slider": "0.11.0",
"react-native-status-bar-size": "0.3.3",
"react-native-svg": "12.1.0",
@ -74,7 +74,7 @@
"react-native-video": "5.1.0-alpha8",
"react-native-webview": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
"react-native-youtube": "2.0.1",
"react-redux": "7.2.1",
"react-redux": "7.2.2",
"redux": "4.0.5",
"redux-action-buffer": "1.2.0",
"redux-batched-actions": "0.5.0",
@ -97,20 +97,20 @@
"@babel/preset-env": "7.12.1",
"@babel/register": "7.12.1",
"@react-native-community/eslint-config": "2.0.0",
"@storybook/addon-knobs": "6.0.26",
"@storybook/addon-knobs": "6.0.28",
"@storybook/addon-ondevice-knobs": "5.3.23",
"@storybook/react-native": "5.3.23",
"@storybook/react-native-server": "5.3.23",
"@testing-library/react-native": "7.1.0",
"@types/enzyme": "3.10.7",
"@types/enzyme": "3.10.8",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/jest": "26.0.15",
"@types/moment-timezone": "0.5.30",
"@types/react": "16.9.53",
"@types/react-native": "0.63.30",
"@types/react": "16.9.56",
"@types/react-native": "0.63.35",
"@types/react-native-share": "3.3.0",
"@types/react-native-video": "5.0.3",
"@types/react-redux": "7.1.9",
"@types/react-redux": "7.1.11",
"@types/react-test-renderer": "16.9.3",
"@types/shallow-equals": "1.0.0",
"@types/tinycolor2": "1.4.2",
@ -118,33 +118,33 @@
"@typescript-eslint/eslint-plugin": "3.10.1",
"@typescript-eslint/parser": "3.10.1",
"babel-eslint": "10.1.0",
"babel-jest": "26.6.1",
"babel-loader": "8.1.0",
"babel-jest": "26.6.3",
"babel-loader": "8.2.1",
"babel-plugin-module-resolver": "4.0.0",
"babel-plugin-transform-remove-console": "6.9.4",
"deep-freeze": "0.0.1",
"detox": "17.10.3",
"detox": "17.12.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.5",
"enzyme-to-json": "3.6.1",
"eslint": "7.11.0",
"eslint": "7.13.0",
"eslint-plugin-header": "3.1.0",
"eslint-plugin-jest": "24.1.0",
"eslint-plugin-jest": "24.1.3",
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee",
"eslint-plugin-react": "7.21.5",
"harmony-reflect": "1.6.1",
"husky": "4.3.0",
"isomorphic-fetch": "3.0.0",
"jest": "26.6.1",
"jest-cli": "26.6.1",
"jest": "26.6.3",
"jest-cli": "26.6.3",
"jest-enzyme": "7.1.2",
"jetifier": "1.6.6",
"jsdom-global": "3.0.2",
"metro-react-native-babel-preset": "0.63.0",
"metro-react-native-babel-preset": "0.64.0",
"mmjstool": "github:mattermost/mattermost-utilities#519b99a4e51e6c67a0dbd46a6efdff27dc835aaa",
"mock-async-storage": "2.2.0",
"mock-socket": "9.0.3",
"nock": "13.0.4",
"nock": "13.0.5",
"nyc": "15.1.0",
"patch-package": "6.2.2",
"react-dom": "16.13.1",
@ -154,8 +154,8 @@
"redux-mock-store": "1.5.4",
"redux-persist-node-storage": "2.0.0",
"socketcluster": "16.0.1",
"ts-jest": "26.4.1",
"typescript": "4.0.3",
"ts-jest": "26.4.4",
"typescript": "4.0.5",
"underscore": "1.11.0",
"util": "0.12.3"
},

View file

@ -18,12 +18,10 @@ module.exports = [
'app/init/emm_provider.js',
'app/init/fetch.js',
'app/init/global_event_handler.js',
'app/init/push_notifications.js',
'app/init/push_notifications.ts',
'app/mattermost.js',
'app/mattermost_managed/index.js',
'app/mattermost_managed/mattermost-managed.android.js',
'app/push_notifications/index.js',
'app/push_notifications/push_notifications.android.js',
'app/screens/index.js',
'app/store/ephemeral_store.js',
'app/store/helpers.ts',

View file

@ -18,12 +18,10 @@ module.exports = [
'./node_modules/app/init/emm_provider.js',
'./node_modules/app/init/fetch.js',
'./node_modules/app/init/global_event_handler.js',
'./node_modules/app/init/push_notifications.js',
'./node_modules/app/init/push_notifications.ts',
'./node_modules/app/mattermost.js',
'./node_modules/app/mattermost_managed/index.js',
'./node_modules/app/mattermost_managed/mattermost-managed.android.js',
'./node_modules/app/push_notifications/index.js',
'./node_modules/app/push_notifications/push_notifications.android.js',
'./node_modules/app/screens/index.js',
'./node_modules/app/store/ephemeral_store.js',
'./node_modules/app/store/helpers.ts',

View file

@ -18,12 +18,10 @@ module.exports = [
'./node_modules/app/init/emm_provider.js',
'./node_modules/app/init/fetch.js',
'./node_modules/app/init/global_event_handler.js',
'./node_modules/app/init/push_notifications.js',
'./node_modules/app/init/push_notifications.ts',
'./node_modules/app/mattermost.js',
'./node_modules/app/mattermost_managed/index.js',
'./node_modules/app/mattermost_managed/mattermost-managed.ios.js',
'./node_modules/app/push_notifications/index.js',
'./node_modules/app/push_notifications/push_notifications.ios.js',
'./node_modules/app/screens/index.js',
'./node_modules/app/store/ephemeral_store.js',
'./node_modules/app/store/helpers.ts',

View file

@ -1,13 +0,0 @@
diff --git a/node_modules/mime-db/db.json b/node_modules/mime-db/db.json
index e69f352..b21d9da 100644
--- a/node_modules/mime-db/db.json
+++ b/node_modules/mime-db/db.json
@@ -2004,7 +2004,7 @@
},
"application/vnd.apple.keynote": {
"source": "iana",
- "extensions": ["keynote"]
+ "extensions": ["key"]
},
"application/vnd.apple.mpegurl": {
"source": "iana",

View file

@ -0,0 +1,41 @@
diff --git a/node_modules/react-native-localize/android/src/main/java/com/zoontek/rnlocalize/RNLocalizeModule.java b/node_modules/react-native-localize/android/src/main/java/com/zoontek/rnlocalize/RNLocalizeModule.java
index 73fcd14..8c9e447 100644
--- a/node_modules/react-native-localize/android/src/main/java/com/zoontek/rnlocalize/RNLocalizeModule.java
+++ b/node_modules/react-native-localize/android/src/main/java/com/zoontek/rnlocalize/RNLocalizeModule.java
@@ -48,7 +48,6 @@ public class RNLocalizeModule extends ReactContextBaseJavaModule implements Life
private final List<String> USES_RTL_LAYOUT =
Arrays.asList("ar", "ckb", "fa", "he", "ks", "lrc", "mzn", "ps", "ug", "ur", "yi");
- private @Nullable final BroadcastReceiver mBroadcastReceiver;
private boolean mMainActivityVisible = true;
private boolean mEmitChangeOnResume = false;
@@ -62,7 +61,7 @@ public class RNLocalizeModule extends ReactContextBaseJavaModule implements Life
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mBroadcastReceiver = new BroadcastReceiver() {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != null) {
@@ -72,7 +71,7 @@ public class RNLocalizeModule extends ReactContextBaseJavaModule implements Life
};
reactContext.addLifecycleEventListener(this);
- reactContext.registerReceiver(mBroadcastReceiver, filter);
+ reactContext.registerReceiver(receiver, filter);
}
@Override
@@ -112,9 +111,7 @@ public class RNLocalizeModule extends ReactContextBaseJavaModule implements Life
}
@Override
- public void onHostDestroy() {
- getReactApplicationContext().unregisterReceiver(mBroadcastReceiver);
- }
+ public void onHostDestroy() {}
private void emitLocalizationDidChange() {
if (getReactApplicationContext().hasActiveCatalystInstance()) {

View file

@ -1,19 +1,22 @@
diff --git a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
index fe0ee2b..5bdde1d 100644
index 2875bda..8235771 100644
--- a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
+++ b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
@@ -45,7 +45,10 @@ - (id)init
@@ -45,10 +45,10 @@ - (id)init
{
self = [super init];
if (self) {
- [MMKV initialize];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
- NSString *libraryPath = (NSString *) [paths firstObject];
- NSString *rootDir = [libraryPath stringByAppendingPathComponent:@"mmkv"];
- [MMKV initializeMMKV:rootDir];
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSString *APP_GROUP_ID = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"];
+ NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID].path;
+ [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo];
secureStorage = [[SecureStorage alloc]init];
IdStore = [[IDStore alloc] initWithMMKV:[MMKV mmkvWithID:@"mmkvIdStore"]];
mmkvMap = [NSMutableDictionary dictionary];
diff --git a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m
index 70f3a01..07e1af0 100644
--- a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m

View file

@ -44,27 +44,11 @@ index 3bfbc0b..cdb0d05 100644
}
});
}
diff --git a/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m b/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m
index f7aba70..f8d3f38 100644
--- a/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m
+++ b/node_modules/react-native-navigation/lib/ios/RNNCommandsHandler.m
@@ -312,10 +312,9 @@ - (void)dismissAllModals:(NSDictionary *)mergeOptions commandId:(NSString*)comma
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self->_eventEmitter sendOnNavigationCommandCompletion:dismissAllModals commandId:commandId];
- completion();
}];
RNNNavigationOptions* options = [[RNNNavigationOptions alloc] initWithDict:mergeOptions];
- [_modalManager dismissAllModalsAnimated:[options.animations.dismissModal.enable getWithDefaultValue:YES] completion:nil];
+ [_modalManager dismissAllModalsAnimated:[options.animations.dismissModal.enable getWithDefaultValue:YES] completion:completion];
[CATransaction commit];
}
diff --git a/node_modules/react-native-navigation/lib/ios/RNNViewLocation.m b/node_modules/react-native-navigation/lib/ios/RNNViewLocation.m
index f98f7a1..26ecb83 100644
index f5187d9..d97e766 100644
--- a/node_modules/react-native-navigation/lib/ios/RNNViewLocation.m
+++ b/node_modules/react-native-navigation/lib/ios/RNNViewLocation.m
@@ -31,11 +31,7 @@ - (CGFloat)getCornerRadius:(UIView *)view {
@@ -32,11 +32,7 @@ - (CGFloat)getClippedCornerRadius:(UIView *)view {
- (CATransform3D)getTransform:(UIView *)view {
if (view) {

View file

@ -1,449 +0,0 @@
diff --git a/node_modules/react-native-notifications/RNNotifications/RNNotificationsStore.m b/node_modules/react-native-notifications/RNNotifications/RNNotificationsStore.m
index d5e953a..08d4d46 100644
--- a/node_modules/react-native-notifications/RNNotifications/RNNotificationsStore.m
+++ b/node_modules/react-native-notifications/RNNotifications/RNNotificationsStore.m
@@ -40,7 +40,9 @@ - (void)setPresentationCompletionHandler:(void (^)(UNNotificationPresentationOpt
- (void)completeAction:(NSString *)completionKey {
void (^completionHandler)() = (void (^)())[_actionCompletionHandlers valueForKey:completionKey];
if (completionHandler) {
- completionHandler();
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionHandler();
+ });
[_actionCompletionHandlers removeObjectForKey:completionKey];
}
}
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 7053040..ad63eff 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
@@ -11,6 +11,7 @@
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
@@ -29,7 +30,11 @@
<service
android:name=".fcm.FcmInstanceIdRefreshHandlerService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
+ <receiver android:name=".core.notification.PushNotificationPublisher"
+ android:enabled="true"
+ android:exported="false" />
</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 7b47aed..200cca8 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
@@ -99,12 +99,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 cancelDeliveredNotification(String tag, int notificationId) {
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
@@ -126,6 +140,6 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
final Context appContext = getReactApplicationContext().getApplicationContext();
final Intent tokenFetchIntent = new Intent(appContext, FcmInstanceIdRefreshHandlerService.class);
tokenFetchIntent.putExtra(extraFlag, true);
- appContext.startService(tokenFetchIntent);
+ FcmInstanceIdRefreshHandlerService.enqueueWork(appContext, tokenFetchIntent);
}
}
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 524ff07..a9f28e0 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;
@@ -31,7 +34,7 @@ public class PushNotification implements IPushNotification {
final protected AppLifecycleFacade mAppLifecycleFacade;
final protected AppLaunchHelper mAppLaunchHelper;
final protected JsIOHelper mJsIOHelper;
- final protected PushNotificationProps mNotificationProps;
+ protected PushNotificationProps mNotificationProps;
final protected AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() {
@Override
public void onAppVisible() {
@@ -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,7 +178,9 @@ 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) {
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..58ff887
--- /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);
+ }
+}
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 e22cd62..48aa1cd 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
@@ -11,4 +11,5 @@ public interface IPushNotificationsDrawer {
void onNotificationClearRequest(int id);
void onNotificationClearRequest(String tag, int id);
void onAllNotificationsClearRequest();
+ 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 dea6958..2c0f1c7 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 {
@@ -72,8 +78,41 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer {
notificationManager.cancelAll();
}
+ @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/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmInstanceIdRefreshHandlerService.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmInstanceIdRefreshHandlerService.java
index dd2cc9a..f1ef15a 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmInstanceIdRefreshHandlerService.java
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/fcm/FcmInstanceIdRefreshHandlerService.java
@@ -1,19 +1,22 @@
package com.wix.reactnativenotifications.fcm;
-import android.app.IntentService;
+import android.support.annotation.NonNull;
+import android.support.v4.app.JobIntentService;
+import android.content.Context;
import android.content.Intent;
-public class FcmInstanceIdRefreshHandlerService extends IntentService {
+public class FcmInstanceIdRefreshHandlerService extends JobIntentService {
public static String EXTRA_IS_APP_INIT = "isAppInit";
public static String EXTRA_MANUAL_REFRESH = "doManualRefresh";
+ static final int JOB_ID = 1000;
- public FcmInstanceIdRefreshHandlerService() {
- super(FcmInstanceIdRefreshHandlerService.class.getSimpleName());
+ public static void enqueueWork(Context context, Intent work) {
+ enqueueWork(context, FcmInstanceIdRefreshHandlerService.class, JOB_ID, work);
}
@Override
- protected void onHandleIntent(Intent intent) {
+ protected void onHandleWork(@NonNull Intent intent) {
IFcmToken fcmToken = FcmToken.get(this);
if (fcmToken == null) {
return;
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 ac2fe5c..18bee18 100644
--- a/node_modules/react-native-notifications/lib/src/index.android.js
+++ b/node_modules/react-native-notifications/lib/src/index.android.js
@@ -67,10 +67,23 @@ 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();
+ }
+
static cancelDeliveredNotification(tag, id) {
RNNotifications.cancelDeliveredNotification(tag, id);
}

View file

@ -0,0 +1,499 @@
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/AndroidManifest.xml b/node_modules/react-native-notifications/lib/android/app/src/main/AndroidManifest.xml
index abd988a..4ac4725 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/AndroidManifest.xml
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wix.reactnativenotifications">
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<!--
@@ -22,6 +23,9 @@
android:name=".fcm.FcmInstanceIdRefreshHandlerService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
+ <receiver android:name=".core.notification.PushNotificationPublisher"
+ android:enabled="true"
+ android:exported="false" />
</application>
</manifest>
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
index db9eaba..af65d0e 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
@@ -100,7 +100,12 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
if(BuildConfig.DEBUG) Log.d(LOGTAG, "Native method invocation: postLocalNotification");
final Bundle notificationProps = Arguments.toBundle(notificationPropsMap);
final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps);
- pushNotification.onPostRequest(notificationId);
+ double date = notificationProps.getDouble("fireDate", 0);
+ if (date == 0) {
+ pushNotification.onPostRequest(notificationId);
+ } else {
+ pushNotification.onScheduleRequest(notificationId);
+ }
}
@ReactMethod
@@ -109,6 +114,12 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
notificationsDrawer.onNotificationClearRequest(notificationId);
}
+ @ReactMethod
+ public void cancelAllLocalNotifications() {
+ IPushNotificationsDrawer notificationDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
+ notificationDrawer.onCancelAllLocalNotifications();
+ }
+
@ReactMethod
public void setCategories(ReadableArray categories) {
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java
new file mode 100644
index 0000000..dde4a2c
--- /dev/null
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java
@@ -0,0 +1,89 @@
+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(Integer notificationId, Bundle bundle) {
+ 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();
+ }
+ }
+}
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
index 0d70024..b9e6c88 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
@@ -26,5 +26,21 @@ 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/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
index f6ac8ec..08154fc 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
+++ b/node_modules/react-native-notifications/lib/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;
@@ -8,6 +9,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+import android.util.Log;
import com.facebook.react.bridge.ReactContext;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
@@ -18,7 +20,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_BACKGROUND_EVENT_NAME;
@@ -29,7 +33,7 @@ public class PushNotification implements IPushNotification {
final protected AppLifecycleFacade mAppLifecycleFacade;
final protected AppLaunchHelper mAppLaunchHelper;
final protected JsIOHelper mJsIOHelper;
- final protected PushNotificationProps mNotificationProps;
+ protected PushNotificationProps mNotificationProps;
final protected AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() {
@Override
public void onAppVisible() {
@@ -78,6 +82,42 @@ public class PushNotification implements IPushNotification {
return postNotification(notificationId);
}
+ @Override
+ public void onScheduleRequest(Integer notificationId) {
+ Bundle bundle = mNotificationProps.asBundle();
+
+ if (bundle.getString("body") == null) {
+ Log.e(LOGTAG, "No message specified for the scheduled notification");
+ return;
+ }
+
+ double date = bundle.getDouble("fireDate", 0);
+ 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(notificationId, 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,7 +180,9 @@ 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) {
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java
new file mode 100644
index 0000000..58ff887
--- /dev/null
+++ b/node_modules/react-native-notifications/lib/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);
+ }
+}
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
index e22cd62..48aa1cd 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
@@ -11,4 +11,5 @@ public interface IPushNotificationsDrawer {
void onNotificationClearRequest(int id);
void onNotificationClearRequest(String tag, int id);
void onAllNotificationsClearRequest();
+ void onCancelAllLocalNotifications();
}
diff --git a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
index a14089f..1262e6d 100644
--- a/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
+++ b/node_modules/react-native-notifications/lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
@@ -2,9 +2,15 @@ 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.helpers.ScheduleNotificationHelper;
+
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class PushNotificationsDrawer implements IPushNotificationsDrawer {
@@ -62,4 +68,37 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
}
+
+ @Override
+ public void onCancelAllLocalNotifications() {
+ onAllNotificationsClearRequest();
+ cancelAllScheduledNotifications();
+ }
+
+ 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(Integer.parseInt(notificationId), 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/dist/DTO/Notification.d.ts b/node_modules/react-native-notifications/lib/dist/DTO/Notification.d.ts
index 7b2b3b1..3a2f872 100644
--- a/node_modules/react-native-notifications/lib/dist/DTO/Notification.d.ts
+++ b/node_modules/react-native-notifications/lib/dist/DTO/Notification.d.ts
@@ -1,4 +1,5 @@
export declare class Notification {
+ fireDate?: number | string;
identifier: string;
payload: any;
constructor(payload: object);
diff --git a/node_modules/react-native-notifications/lib/dist/DTO/Notification.js b/node_modules/react-native-notifications/lib/dist/DTO/Notification.js
index ad7fc1a..a04ec6b 100644
--- a/node_modules/react-native-notifications/lib/dist/DTO/Notification.js
+++ b/node_modules/react-native-notifications/lib/dist/DTO/Notification.js
@@ -5,6 +5,7 @@ class Notification {
constructor(payload) {
this.payload = payload;
this.identifier = this.payload.identifier;
+ this.fireDate = undefined;
}
get title() {
return this.payload.title;
diff --git a/node_modules/react-native-notifications/lib/dist/Notifications.d.ts b/node_modules/react-native-notifications/lib/dist/Notifications.d.ts
index 34e8170..10600d2 100644
--- a/node_modules/react-native-notifications/lib/dist/Notifications.d.ts
+++ b/node_modules/react-native-notifications/lib/dist/Notifications.d.ts
@@ -23,7 +23,7 @@ export declare class NotificationsRoot {
/**
* postLocalNotification
*/
- postLocalNotification(notification: Notification, id: number): number;
+ postLocalNotification(notification: Notification, id?: number): number;
/**
* getInitialNotification
*/
@@ -36,6 +36,10 @@ export declare class NotificationsRoot {
* cancelLocalNotification
*/
cancelLocalNotification(notificationId: string): void;
+ /**
+ * cancelAllLocalNotifications
+ */
+ cancelAllLocalNotifications(): void;
/**
* removeAllDeliveredNotifications
*/
diff --git a/node_modules/react-native-notifications/lib/dist/Notifications.js b/node_modules/react-native-notifications/lib/dist/Notifications.js
index 15eea09..48b3d23 100644
--- a/node_modules/react-native-notifications/lib/dist/Notifications.js
+++ b/node_modules/react-native-notifications/lib/dist/Notifications.js
@@ -55,6 +55,12 @@ class NotificationsRoot {
cancelLocalNotification(notificationId) {
return this.commands.cancelLocalNotification(notificationId);
}
+ /**
+ * cancelAllLocalNotifications
+ */
+ cancelAllLocalNotifications() {
+ this.commands.cancelAllLocalNotifications();
+ }
/**
* removeAllDeliveredNotifications
*/
diff --git a/node_modules/react-native-notifications/lib/dist/interfaces/NotificationCategory.d.ts b/node_modules/react-native-notifications/lib/dist/interfaces/NotificationCategory.d.ts
index 0e78cb5..ae90bd1 100644
--- a/node_modules/react-native-notifications/lib/dist/interfaces/NotificationCategory.d.ts
+++ b/node_modules/react-native-notifications/lib/dist/interfaces/NotificationCategory.d.ts
@@ -13,5 +13,5 @@ export declare class NotificationAction {
title: string;
authenticationRequired: boolean;
textInput?: NotificationTextInput;
- constructor(identifier: string, activationMode: 'foreground' | 'authenticationRequired' | 'destructive', title: string, authenticationRequired: boolean, textInput?: NotificationTextInput);
+ constructor(identifier: string, activationMode: 'background' | 'foreground' | 'authenticationRequired' | 'destructive', title: string, authenticationRequired: boolean, textInput?: NotificationTextInput);
}
diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m
index 1fbd2a5..ab44587 100644
--- a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m
+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m
@@ -58,7 +58,7 @@ - (void)getDeliveredNotifications:(RCTResponseSenderBlock)callback {
for (UNNotification *notification in notifications) {
[formattedNotifications addObject:[RCTConvert UNNotificationPayload:notification]];
}
- callback(@[formattedNotifications]);
+ callback(formattedNotifications);
}];
}
diff --git a/node_modules/react-native-notifications/lib/src/Notifications.ts b/node_modules/react-native-notifications/lib/src/Notifications.ts
index 44956df..551fac2 100644
--- a/node_modules/react-native-notifications/lib/src/Notifications.ts
+++ b/node_modules/react-native-notifications/lib/src/Notifications.ts
@@ -54,7 +54,7 @@ export class NotificationsRoot {
/**
* postLocalNotification
*/
- public postLocalNotification(notification: Notification, id: number) {
+ public postLocalNotification(notification: Notification, id?: number) {
return this.commands.postLocalNotification(notification, id);
}
@@ -79,6 +79,13 @@ export class NotificationsRoot {
return this.commands.cancelLocalNotification(notificationId);
}
+ /**
+ * cancelAllLocalNotifications
+ */
+ public cancelAllLocalNotifications() {
+ this.commands.cancelAllLocalNotifications();
+ }
+
/**
* removeAllDeliveredNotifications
*/
diff --git a/node_modules/react-native-notifications/lib/src/NotificationsIOS.ts b/node_modules/react-native-notifications/lib/src/NotificationsIOS.ts
index b4218ab..38388c5 100644
--- a/node_modules/react-native-notifications/lib/src/NotificationsIOS.ts
+++ b/node_modules/react-native-notifications/lib/src/NotificationsIOS.ts
@@ -52,13 +52,6 @@ export class NotificationsIOS {
return this.commands.setBadgeCount(count);
}
- /**
- * cancelAllLocalNotifications
- */
- public cancelAllLocalNotifications() {
- this.commands.cancelAllLocalNotifications();
- }
-
/**
* checkPermissions
*/

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
function execute() {
cd fastlane && NODE_ENV=production bundle exec fastlane $1 $2
@ -60,7 +60,7 @@ function setup() {
echo "Installing Fastane"
if !gem list bundler -i --version 2.1.4 > /dev/null 2>&1; then
gem install bundler --versio 2.1.4
gem install bundler --version 2.1.4
fi
cd fastlane && bundle install && cd .. || exit 1
fi

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
echo Cleaning started

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
if [[ "$OSTYPE" == "darwin"* ]]; then
if !gem list bundler -i --version 2.1.4 > /dev/null 2>&1; then

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
jsfiles=$(git diff --cached --name-only --diff-filter=ACM | grep -E '.js$|.ts$|.tsx$')

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
mkdir -p tmp
cp assets/base/i18n/en.json tmp/en.json

View file

@ -228,6 +228,37 @@ jest.mock('react-native-navigation', () => {
};
});
jest.mock('react-native-notifications', () => {
let deliveredNotifications = [];
return {
Notifications: {
registerRemoteNotifications: jest.fn(),
addEventListener: jest.fn(),
setDeliveredNotifications: jest.fn((notifications) => {
deliveredNotifications = notifications;
}),
cancelAllLocalNotifications: jest.fn(),
NotificationAction: jest.fn(),
NotificationCategory: jest.fn(),
events: () => ({
registerNotificationOpened: jest.fn(),
registerRemoteNotificationsRegistered: jest.fn(),
registerNotificationReceivedBackground: jest.fn(),
registerNotificationReceivedForeground: jest.fn(),
}),
ios: {
getDeliveredNotifications: jest.fn().mockImplementation(() => Promise.resolve(deliveredNotifications)),
removeDeliveredNotifications: jest.fn((ids) => {
// eslint-disable-next-line max-nested-callbacks
deliveredNotifications = deliveredNotifications.filter((n) => !ids.includes(n.identifier));
}),
setBadgeCount: jest.fn(),
},
},
};
});
jest.mock('react-native-share', () => ({
default: jest.fn(),
}));

View file

@ -48,7 +48,10 @@
"@telemetry/*": ["/app/telemetry/*"],
"@utils/*": ["app/utils/*"],
"@websocket": ["app/client/websocket"],
"*": ["./*", "node_modules/*"]
"*": ["./*", "node_modules/*"],
"react-native-redash/lib/module/v1": [
"./node_modules/react-native-redash/lib/typescript/v1/index.d.ts"
]
}
},
"include": ["app/**/*", "share_extensionn/**/*", "test/**/*", "detox/**/*", "types/**/*"],