forked from Ivasoft/mattermost-mobile
* Include in-app notifications in iOS badge number * Make foreground notifications key a const
This commit is contained in:
committed by
Miguel Alatzar
parent
1f8e853e41
commit
ce325a4ab1
@@ -2,12 +2,14 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {AppState} from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import NotificationsIOS, {NotificationAction, NotificationCategory} from 'react-native-notifications';
|
||||
|
||||
import ephemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
const CATEGORY = 'CAN_REPLY';
|
||||
const REPLY_ACTION = 'REPLY_ACTION';
|
||||
export const FOREGROUND_NOTIFICATIONS_KEY = '@FOREGROUND_NOTIFICATIONS';
|
||||
|
||||
let replyCategory;
|
||||
const replies = new Set();
|
||||
@@ -51,6 +53,8 @@ class PushNotification {
|
||||
if (this.onNotification) {
|
||||
this.onNotification(this.deviceNotification);
|
||||
}
|
||||
|
||||
this.trackForegroundNotification(data.channel_id);
|
||||
};
|
||||
|
||||
handleReply = (action, completed) => {
|
||||
@@ -130,6 +134,10 @@ class PushNotification {
|
||||
message: notification.getMessage(),
|
||||
};
|
||||
this.handleNotification(info, true, false);
|
||||
|
||||
NotificationsIOS.getBadgesCount((count) => {
|
||||
this.setApplicationIconBadgeNumber(count + 1);
|
||||
});
|
||||
};
|
||||
|
||||
onNotificationOpened = (notification) => {
|
||||
@@ -160,10 +168,22 @@ class PushNotification {
|
||||
}
|
||||
|
||||
clearChannelNotifications(channelId) {
|
||||
NotificationsIOS.getDeliveredNotifications((notifications) => {
|
||||
const ids = [];
|
||||
let badgeCount = notifications.length;
|
||||
NotificationsIOS.getDeliveredNotifications(async (notifications) => {
|
||||
let foregroundNotifications;
|
||||
try {
|
||||
const value = await AsyncStorage.getItem(FOREGROUND_NOTIFICATIONS_KEY);
|
||||
foregroundNotifications = JSON.parse(value) || {};
|
||||
} catch (e) {
|
||||
foregroundNotifications = {};
|
||||
}
|
||||
|
||||
Reflect.deleteProperty(foregroundNotifications, channelId);
|
||||
AsyncStorage.setItem(FOREGROUND_NOTIFICATIONS_KEY, JSON.stringify(foregroundNotifications));
|
||||
|
||||
const foregroundCount = Object.values(foregroundNotifications).reduce((a, b) => a + b, 0);
|
||||
let badgeCount = notifications.length + foregroundCount;
|
||||
|
||||
const ids = [];
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
const notification = notifications[i];
|
||||
|
||||
@@ -180,6 +200,16 @@ class PushNotification {
|
||||
this.setApplicationIconBadgeNumber(badgeCount);
|
||||
});
|
||||
}
|
||||
|
||||
trackForegroundNotification = async (channelId) => {
|
||||
const value = await AsyncStorage.getItem(FOREGROUND_NOTIFICATIONS_KEY);
|
||||
const foregroundNotifications = value ? JSON.parse(value) : {};
|
||||
if (!foregroundNotifications.hasOwnProperty(channelId)) {
|
||||
foregroundNotifications[channelId] = 0;
|
||||
}
|
||||
foregroundNotifications[channelId] += 1;
|
||||
await AsyncStorage.setItem(FOREGROUND_NOTIFICATIONS_KEY, JSON.stringify(foregroundNotifications));
|
||||
}
|
||||
}
|
||||
|
||||
export default new PushNotification();
|
||||
|
||||
136
app/push_notifications/push_notifications.ios.test.js
Normal file
136
app/push_notifications/push_notifications.ios.test.js
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import NotificationsIOS from 'react-native-notifications';
|
||||
import PushNotification, {FOREGROUND_NOTIFICATIONS_KEY} 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));
|
||||
}),
|
||||
NotificationAction: jest.fn(),
|
||||
NotificationCategory: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('PushNotification', () => {
|
||||
const channel1ID = 'channel-1-id';
|
||||
const channel2ID = 'channel-2-id';
|
||||
const notification = {
|
||||
getData: jest.fn(),
|
||||
getMessage: jest.fn(),
|
||||
};
|
||||
|
||||
it('should track foreground notifications for channel', async () => {
|
||||
let item = await AsyncStorage.getItem(FOREGROUND_NOTIFICATIONS_KEY);
|
||||
expect(item).toBe(null);
|
||||
|
||||
await PushNotification.trackForegroundNotification(channel1ID);
|
||||
item = await AsyncStorage.getItem(FOREGROUND_NOTIFICATIONS_KEY);
|
||||
expect(item).not.toBe(null);
|
||||
let foregroundNotifications = JSON.parse(item);
|
||||
expect(foregroundNotifications[channel1ID]).toBe(1);
|
||||
expect(foregroundNotifications[channel2ID]).toBe(undefined);
|
||||
|
||||
await PushNotification.trackForegroundNotification(channel1ID);
|
||||
item = await AsyncStorage.getItem(FOREGROUND_NOTIFICATIONS_KEY);
|
||||
expect(item).not.toBe(null);
|
||||
foregroundNotifications = JSON.parse(item);
|
||||
expect(foregroundNotifications[channel1ID]).toBe(2);
|
||||
expect(foregroundNotifications[channel2ID]).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should increment badge number when foreground notification is received', () => {
|
||||
const setApplicationIconBadgeNumber = jest.spyOn(PushNotification, 'setApplicationIconBadgeNumber');
|
||||
|
||||
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(0));
|
||||
|
||||
PushNotification.onNotificationReceivedForeground(notification);
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(1);
|
||||
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(1));
|
||||
|
||||
PushNotification.onNotificationReceivedForeground(notification);
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(2);
|
||||
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(2));
|
||||
});
|
||||
|
||||
it('should clear channel notifications and set correct badge number', async () => {
|
||||
const deliveredNotifications = [
|
||||
|
||||
// Three channel1 delivered notifications
|
||||
{
|
||||
identifier: 'channel1-1',
|
||||
userInfo: {channel_id: channel1ID},
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-2',
|
||||
userInfo: {channel_id: channel1ID},
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-3',
|
||||
userInfo: {channel_id: channel1ID},
|
||||
},
|
||||
|
||||
// Two channel2 delivered notifications
|
||||
{
|
||||
identifier: 'channel2-1',
|
||||
userInfo: {channel_id: channel2ID},
|
||||
},
|
||||
{
|
||||
identifier: 'channel2-2',
|
||||
userInfo: {channel_id: channel2ID},
|
||||
},
|
||||
];
|
||||
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
|
||||
|
||||
const foregroundNotifications = {
|
||||
[channel1ID]: 1,
|
||||
[channel2ID]: 1,
|
||||
};
|
||||
await AsyncStorage.setItem(FOREGROUND_NOTIFICATIONS_KEY, JSON.stringify(foregroundNotifications));
|
||||
|
||||
const notificationCount = deliveredNotifications.length + Object.values(foregroundNotifications).reduce((a, b) => a + b);
|
||||
expect(notificationCount).toBe(7);
|
||||
|
||||
NotificationsIOS.setBadgesCount(notificationCount);
|
||||
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(notificationCount));
|
||||
|
||||
// Clear channel1 notifications
|
||||
const setApplicationIconBadgeNumber = jest.spyOn(PushNotification, 'setApplicationIconBadgeNumber');
|
||||
await PushNotification.clearChannelNotifications(channel1ID);
|
||||
|
||||
NotificationsIOS.getDeliveredNotifications(async (deliveredNotifs) => {
|
||||
expect(deliveredNotifs.length).toBe(2);
|
||||
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.userInfo.channel_id === channel1ID);
|
||||
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.userInfo.channel_id === channel2ID);
|
||||
expect(channel1DeliveredNotifications.length).toBe(0);
|
||||
expect(channel2DeliveredNotifications.length).toBe(2);
|
||||
|
||||
const item = await AsyncStorage.getItem(FOREGROUND_NOTIFICATIONS_KEY);
|
||||
const foregroundNotifs = JSON.parse(item);
|
||||
const channel1ForegroundNotifications = foregroundNotifs[channel1ID];
|
||||
const channel2ForegroundNotifications = foregroundNotifs[channel2ID];
|
||||
expect(channel1ForegroundNotifications.length).toBe(0);
|
||||
expect(channel2ForegroundNotifications.length).toBe(1);
|
||||
|
||||
const badgeNumber = channel2DeliveredNotifications.lenth + channel2ForegroundNotifications.length;
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(badgeNumber);
|
||||
});
|
||||
});
|
||||
});
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -7674,8 +7674,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"optional": true
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -7696,8 +7695,7 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"optional": true
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
@@ -7717,20 +7715,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||
"optional": true
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"optional": true
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
|
||||
"optional": true
|
||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -7847,8 +7842,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"optional": true
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -7881,8 +7875,7 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"optional": true
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
@@ -7986,8 +7979,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
|
||||
"optional": true
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -8084,8 +8076,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"optional": true
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -8183,14 +8174,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"optional": true
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
|
||||
"optional": true
|
||||
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13854,6 +13843,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mock-async-storage": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mock-async-storage/-/mock-async-storage-2.2.0.tgz",
|
||||
"integrity": "sha512-HpMe9KNKxlyiRyOXFBrEOSQ1Idcoz1k5W5J+6C8zYzjasSxFJlrA5wVVjPR4U6zJJtrbuky1c0s1o3whzgghSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"deepmerge": "^3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"deepmerge": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz",
|
||||
"integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.22.2",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"jsdom-global": "3.0.2",
|
||||
"metro-react-native-babel-preset": "0.54.0",
|
||||
"mmjstool": "github:mattermost/mattermost-utilities",
|
||||
"mock-async-storage": "2.2.0",
|
||||
"nyc": "14.1.1",
|
||||
"react-dom": "16.8.6",
|
||||
"react-test-renderer": "16.8.6",
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import MockAsyncStorage from 'mock-async-storage';
|
||||
import {configure} from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
configure({adapter: new Adapter()});
|
||||
|
||||
const mockImpl = new MockAsyncStorage();
|
||||
jest.mock('@react-native-community/async-storage', () => mockImpl);
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
jest.mock('NativeModules', () => {
|
||||
|
||||
Reference in New Issue
Block a user