MM-36687, MM-38302, MM-37598 Fix push notifications with CRT (#5669)
* initalised * Removed unused packages * Android: Added groupId for supporting both threadId & channelId * Fixed ios condition check * Removed commented code * Removed unwanted condition * Removed unused variable * CRT reduced chunk size to 30, Android global threads showing GlobalThreads & iOS is_crt_enabled field is expected to be a boolean * Update android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Misc fixes Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
e4dafb4d3b
commit
805b90205a
11 changed files with 319 additions and 106 deletions
|
|
@ -15,7 +15,6 @@ import androidx.annotation.Nullable;
|
|||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -30,7 +29,6 @@ import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_
|
|||
|
||||
import com.mattermost.react_native_interface.ResolvePromise;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CustomPushNotification extends PushNotification {
|
||||
|
|
@ -61,7 +59,7 @@ public class CustomPushNotification extends PushNotification {
|
|||
editor.apply();
|
||||
}
|
||||
|
||||
Map<String, List<Integer>> inputMap = new HashMap<>();
|
||||
Map<String, Map<String, JSONObject>> inputMap = new HashMap<>();
|
||||
saveNotificationsMap(context, inputMap);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
|
|
@ -69,55 +67,70 @@ public class CustomPushNotification extends PushNotification {
|
|||
}
|
||||
}
|
||||
|
||||
public static void cancelNotification(Context context, String channelId, Integer notificationId) {
|
||||
public static void cancelNotification(Context context, String channelId, String rootId, Integer notificationId, Boolean isCRTEnabled) {
|
||||
if (!android.text.TextUtils.isEmpty(channelId)) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
|
||||
List<Integer> notifications = notificationsInChannel.get(channelId);
|
||||
final String notificationIdStr = notificationId.toString();
|
||||
final Boolean isThreadNotification = isCRTEnabled && !android.text.TextUtils.isEmpty(rootId);
|
||||
final String groupId = isThreadNotification ? rootId : channelId;
|
||||
Map<String, Map<String, JSONObject>> notificationsInChannel = loadNotificationsMap(context);
|
||||
Map<String, JSONObject> notifications = notificationsInChannel.get(groupId);
|
||||
if (notifications == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
||||
notificationManager.cancel(notificationId);
|
||||
notifications.remove(notificationId);
|
||||
notifications.remove(notificationIdStr);
|
||||
final StatusBarNotification[] statusNotifications = notificationManager.getActiveNotifications();
|
||||
boolean hasMore = false;
|
||||
for (final StatusBarNotification status : statusNotifications) {
|
||||
if (status.getNotification().extras.getString("channel_id").equals(channelId)) {
|
||||
hasMore = true;
|
||||
Bundle bundle = status.getNotification().extras;
|
||||
if (isThreadNotification) {
|
||||
hasMore = bundle.getString("root_id").equals(rootId);
|
||||
} else if (isCRTEnabled) {
|
||||
hasMore = !bundle.getString("root_id").equals(rootId);
|
||||
} else {
|
||||
hasMore = bundle.getString("channel_id").equals(channelId);
|
||||
}
|
||||
if (hasMore) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMore) {
|
||||
notificationsInChannel.remove(channelId);
|
||||
notificationsInChannel.remove(groupId);
|
||||
} else {
|
||||
notificationsInChannel.put(groupId, notifications);
|
||||
}
|
||||
|
||||
saveNotificationsMap(context, notificationsInChannel);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearChannelNotifications(Context context, String channelId) {
|
||||
public static void clearChannelNotifications(Context context, String channelId, String rootId, Boolean isCRTEnabled) {
|
||||
if (!android.text.TextUtils.isEmpty(channelId)) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
|
||||
List<Integer> notifications = notificationsInChannel.get(channelId);
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
|
||||
// rootId is available only when CRT is enabled & clearing the thread
|
||||
final boolean isClearThread = isCRTEnabled && !android.text.TextUtils.isEmpty(rootId);
|
||||
|
||||
Map<String, Map<String, JSONObject>> notificationsInChannel = loadNotificationsMap(context);
|
||||
String groupId = isClearThread ? rootId : channelId;
|
||||
Map<String, JSONObject> notifications = notificationsInChannel.get(groupId);
|
||||
|
||||
if (notifications == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
notificationsInChannel.remove(channelId);
|
||||
notificationsInChannel.remove(groupId);
|
||||
saveNotificationsMap(context, notificationsInChannel);
|
||||
|
||||
for (final Integer notificationId : notifications) {
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
notifications.forEach(
|
||||
(notificationIdStr, post) -> notificationManager.cancel(Integer.valueOf(notificationIdStr))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearAllNotifications(Context context) {
|
||||
if (context != null) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
|
||||
Map<String, Map<String, JSONObject>> notificationsInChannel = loadNotificationsMap(context);
|
||||
notificationsInChannel.clear();
|
||||
saveNotificationsMap(context, notificationsInChannel);
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
|
|
@ -132,6 +145,8 @@ public class CustomPushNotification extends PushNotification {
|
|||
final String ackId = initialData.getString("ack_id");
|
||||
final String postId = initialData.getString("post_id");
|
||||
final String channelId = initialData.getString("channel_id");
|
||||
final String rootId = initialData.getString("root_id");
|
||||
final boolean isCRTEnabled = initialData.getString("is_crt_enabled") != null && initialData.getString("is_crt_enabled").equals("true");
|
||||
final boolean isIdLoaded = initialData.getString("id_loaded") != null && initialData.getString("id_loaded").equals("true");
|
||||
int notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
|
||||
if (postId != null) {
|
||||
|
|
@ -165,24 +180,41 @@ public class CustomPushNotification extends PushNotification {
|
|||
|
||||
if (type.equals(PUSH_TYPE_MESSAGE)) {
|
||||
if (channelId != null) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
|
||||
List<Integer> list = notificationsInChannel.get(channelId);
|
||||
if (list == null) {
|
||||
list = Collections.synchronizedList(new ArrayList(0));
|
||||
}
|
||||
try {
|
||||
|
||||
list.add(0, notificationId);
|
||||
if (list.size() > 1) {
|
||||
createSummary = false;
|
||||
}
|
||||
JSONObject post = new JSONObject();
|
||||
if (!android.text.TextUtils.isEmpty(rootId)) {
|
||||
post.put("root_id", rootId);
|
||||
}
|
||||
if (!android.text.TextUtils.isEmpty(postId)) {
|
||||
post.put("post_id", postId);
|
||||
}
|
||||
|
||||
if (createSummary) {
|
||||
// Add the summary notification id as well
|
||||
list.add(0, notificationId + 1);
|
||||
}
|
||||
final Boolean isThreadNotification = isCRTEnabled && post.has("root_id");
|
||||
final String groupId = isThreadNotification ? rootId : channelId;
|
||||
|
||||
Map<String, Map<String, JSONObject>> notificationsInChannel = loadNotificationsMap(mContext);
|
||||
Map<String, JSONObject> notifications = notificationsInChannel.get(groupId);
|
||||
if (notifications == null) {
|
||||
notifications = Collections.synchronizedMap(new HashMap<String, JSONObject>());
|
||||
}
|
||||
|
||||
notificationsInChannel.put(channelId, list);
|
||||
saveNotificationsMap(mContext, notificationsInChannel);
|
||||
if (notifications.size() > 0) {
|
||||
createSummary = false;
|
||||
}
|
||||
|
||||
notifications.put(String.valueOf(notificationId), post);
|
||||
|
||||
if (createSummary) {
|
||||
// Add the summary notification id as well
|
||||
notifications.put(String.valueOf(notificationId + 1), new JSONObject());
|
||||
}
|
||||
|
||||
notificationsInChannel.put(groupId, notifications);
|
||||
saveNotificationsMap(mContext, notificationsInChannel);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +222,7 @@ public class CustomPushNotification extends PushNotification {
|
|||
}
|
||||
break;
|
||||
case PUSH_TYPE_CLEAR:
|
||||
clearChannelNotifications(mContext, channelId);
|
||||
clearChannelNotifications(mContext, channelId, rootId, isCRTEnabled);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -205,22 +237,11 @@ public class CustomPushNotification extends PushNotification {
|
|||
|
||||
Bundle data = mNotificationProps.asBundle();
|
||||
final String channelId = data.getString("channel_id");
|
||||
final String postId = data.getString("post_id");
|
||||
Integer notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
|
||||
|
||||
if (postId != null) {
|
||||
notificationId = postId.hashCode();
|
||||
}
|
||||
final String rootId = data.getString("root_id");
|
||||
final Boolean isCRTEnabled = data.getBoolean("is_crt_enabled");
|
||||
|
||||
if (channelId != null) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
|
||||
List<Integer> notifications = notificationsInChannel.get(channelId);
|
||||
if (notifications == null) {
|
||||
return;
|
||||
}
|
||||
notifications.remove(notificationId);
|
||||
saveNotificationsMap(mContext, notificationsInChannel);
|
||||
clearChannelNotifications(mContext, channelId);
|
||||
clearChannelNotifications(mContext, channelId, rootId, isCRTEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +274,7 @@ public class CustomPushNotification extends PushNotification {
|
|||
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
|
||||
}
|
||||
|
||||
private static void saveNotificationsMap(Context context, Map<String, List<Integer>> inputMap) {
|
||||
private static void saveNotificationsMap(Context context, Map<String, Map<String, JSONObject>> inputMap) {
|
||||
SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE);
|
||||
if (pSharedPref != null && context != null) {
|
||||
JSONObject json = new JSONObject(inputMap);
|
||||
|
|
@ -265,23 +286,41 @@ public class CustomPushNotification extends PushNotification {
|
|||
}
|
||||
}
|
||||
|
||||
private static Map<String, List<Integer>> loadNotificationsMap(Context context) {
|
||||
Map<String, List<Integer>> outputMap = new HashMap<>();
|
||||
/**
|
||||
* Map Structure
|
||||
*
|
||||
* {
|
||||
* channel_id1 | thread_id1: {
|
||||
* notification_id1: {
|
||||
* post_id: 'p1',
|
||||
* root_id: 'r1',
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
private static Map<String, Map<String, JSONObject>> loadNotificationsMap(Context context) {
|
||||
Map<String, Map<String, JSONObject>> outputMap = new HashMap<>();
|
||||
if (context != null) {
|
||||
SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE);
|
||||
try {
|
||||
if (pSharedPref != null) {
|
||||
String jsonString = pSharedPref.getString(NOTIFICATIONS_IN_CHANNEL, (new JSONObject()).toString());
|
||||
JSONObject json = new JSONObject(jsonString);
|
||||
Iterator<String> keysItr = json.keys();
|
||||
while (keysItr.hasNext()) {
|
||||
String key = keysItr.next();
|
||||
JSONArray array = json.getJSONArray(key);
|
||||
List<Integer> values = new ArrayList<>();
|
||||
for (int i = 0; i < array.length(); ++i) {
|
||||
values.add(array.getInt(i));
|
||||
|
||||
// Can be a channel_id or thread_id
|
||||
Iterator<String> groupIdsItr = json.keys();
|
||||
while (groupIdsItr.hasNext()) {
|
||||
String groupId = groupIdsItr.next();
|
||||
JSONObject notificationsJSONObj = json.getJSONObject(groupId);
|
||||
Map<String, JSONObject> notifications = new HashMap<>();
|
||||
Iterator<String> notificationIdKeys = notificationsJSONObj.keys();
|
||||
while(notificationIdKeys.hasNext()) {
|
||||
String notificationId = notificationIdKeys.next();
|
||||
JSONObject post = notificationsJSONObj.getJSONObject(notificationId);
|
||||
notifications.put(notificationId, post);
|
||||
}
|
||||
outputMap.put(key, values);
|
||||
outputMap.put(groupId, notifications);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,16 @@ public class CustomPushNotificationHelper {
|
|||
userInfoBundle = new Bundle();
|
||||
}
|
||||
|
||||
String postId = bundle.getString("post_id");
|
||||
if (postId != null) {
|
||||
userInfoBundle.putString("post_id", postId);
|
||||
}
|
||||
|
||||
String rootId = bundle.getString("root_id");
|
||||
if (rootId != null) {
|
||||
userInfoBundle.putString("root_id", rootId);
|
||||
}
|
||||
|
||||
String channelId = bundle.getString("channel_id");
|
||||
if (channelId != null) {
|
||||
userInfoBundle.putString("channel_id", channelId);
|
||||
|
|
@ -145,13 +155,17 @@ public class CustomPushNotificationHelper {
|
|||
|
||||
String channelId = bundle.getString("channel_id");
|
||||
String postId = bundle.getString("post_id");
|
||||
String rootId = bundle.getString("root_id");
|
||||
int notificationId = postId != null ? postId.hashCode() : MESSAGE_NOTIFICATION_ID;
|
||||
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(context);
|
||||
|
||||
Boolean is_crt_enabled = bundle.getString("is_crt_enabled") != null && bundle.getString("is_crt_enabled").equals("true");
|
||||
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
|
||||
|
||||
addNotificationExtras(notification, bundle);
|
||||
setNotificationIcons(context, notification, bundle);
|
||||
setNotificationMessagingStyle(context, notification, bundle);
|
||||
setNotificationGroup(notification, channelId, createSummary);
|
||||
setNotificationGroup(notification, groupId, createSummary);
|
||||
setNotificationBadgeType(notification);
|
||||
setNotificationSound(notification, notificationPreferences);
|
||||
setNotificationVibrate(notification, notificationPreferences);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ public class NotificationDismissService extends IntentService {
|
|||
final Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
|
||||
final String channelId = bundle.getString("channel_id");
|
||||
final String postId = bundle.getString("post_id");
|
||||
final String rootId = bundle.getString("root_id");
|
||||
final Boolean isCRTEnabled = bundle.getString("is_crt_enabled") != null && bundle.getString("is_crt_enabled").equals("true");
|
||||
|
||||
int notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
|
||||
if (postId != null) {
|
||||
notificationId = postId.hashCode();
|
||||
|
|
@ -26,7 +29,7 @@ public class NotificationDismissService extends IntentService {
|
|||
notificationId = channelId.hashCode();
|
||||
}
|
||||
|
||||
CustomPushNotification.cancelNotification(context, channelId, notificationId);
|
||||
CustomPushNotification.cancelNotification(context, channelId, rootId, notificationId, isCRTEnabled);
|
||||
Log.i("ReactNative", "Dismiss notification");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ public class NotificationPreferencesModule extends ReactContextBaseJavaModule {
|
|||
WritableMap map = Arguments.createMap();
|
||||
Notification n = sbn.getNotification();
|
||||
Bundle bundle = n.extras;
|
||||
String postId = bundle.getString("post_id");
|
||||
map.putString("post_id", postId);
|
||||
String rootId = bundle.getString("root_id");
|
||||
map.putString("root_id", rootId);
|
||||
String channelId = bundle.getString("channel_id");
|
||||
map.putString("channel_id", channelId);
|
||||
result.pushMap(map);
|
||||
|
|
@ -126,8 +130,9 @@ public class NotificationPreferencesModule extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeDeliveredNotifications(String channelId) {
|
||||
public void removeDeliveredNotifications(String channelId, String rootId, Boolean isCRTEnabled) {
|
||||
final Context context = mApplication.getApplicationContext();
|
||||
CustomPushNotification.clearChannelNotifications(context, channelId);
|
||||
CustomPushNotification.clearChannelNotifications(context, channelId, rootId, isCRTEnabled);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,7 +390,7 @@ export function markAsViewedAndReadBatch(state, channelId, prevChannelId = '', m
|
|||
type: ChannelTypes.SET_UNREAD_MSG_COUNT,
|
||||
data: {
|
||||
channelId,
|
||||
count: unreadMessageCount,
|
||||
count: isCollapsedThreadsEnabled(state) ? unreadMessageCountRoot : unreadMessageCount,
|
||||
},
|
||||
}, {
|
||||
type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export default {
|
|||
...ViewTypes,
|
||||
RequiredServer,
|
||||
POST_VISIBILITY_CHUNK_SIZE: 60,
|
||||
CRT_CHUNK_SIZE: 60,
|
||||
CRT_CHUNK_SIZE: 30,
|
||||
FEATURE_TOGGLE_PREFIX: 'feature_enabled_',
|
||||
EMBED_PREVIEW: 'embed_preview',
|
||||
LINK_PREVIEW_DISPLAY: 'link_previews',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import {Notifications} from 'react-native-notifications';
|
||||
|
||||
import * as Preferences from '@mm-redux/selectors/entities/preferences';
|
||||
import * as ViewSelectors from '@selectors/views';
|
||||
import Store from '@store/store';
|
||||
|
||||
|
|
@ -49,13 +50,118 @@ describe('PushNotification', () => {
|
|||
// Clear channel1 notifications
|
||||
await PushNotification.clearChannelNotifications(channel1ID);
|
||||
|
||||
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);
|
||||
expect(channel1DeliveredNotifications.length).toBe(0);
|
||||
expect(channel2DeliveredNotifications.length).toBe(2);
|
||||
});
|
||||
const deliveredNotifs = await Notifications.ios.getDeliveredNotifications();
|
||||
expect(deliveredNotifs.length).toBe(2);
|
||||
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel1ID);
|
||||
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel2ID);
|
||||
expect(channel1DeliveredNotifications.length).toBe(0);
|
||||
expect(channel2DeliveredNotifications.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should clear root posts only from the channel notifications when CRT is enabled', async () => {
|
||||
Store.redux = {
|
||||
getState: jest.fn(),
|
||||
};
|
||||
|
||||
Preferences.isCollapsedThreadsEnabled = jest.fn().mockImplementation(() => true);
|
||||
ViewSelectors.getBadgeCount = jest.fn().mockReturnValue(5);
|
||||
|
||||
const deliveredNotifications = [
|
||||
|
||||
// Three channel1 delivered notifications
|
||||
{
|
||||
identifier: 'channel1-1',
|
||||
channel_id: channel1ID,
|
||||
root_id: 'root-id-1',
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-2',
|
||||
channel_id: channel1ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-3',
|
||||
channel_id: channel1ID,
|
||||
},
|
||||
|
||||
// Two channel2 delivered notifications
|
||||
{
|
||||
identifier: 'channel2-1',
|
||||
channel_id: channel2ID,
|
||||
root_id: 'root-id-2',
|
||||
},
|
||||
{
|
||||
identifier: 'channel2-2',
|
||||
channel_id: channel2ID,
|
||||
},
|
||||
];
|
||||
Notifications.setDeliveredNotifications(deliveredNotifications);
|
||||
|
||||
const notificationCount = deliveredNotifications.length;
|
||||
expect(notificationCount).toBe(5);
|
||||
|
||||
// Clear channel1 notifications
|
||||
await PushNotification.clearChannelNotifications(channel1ID);
|
||||
|
||||
const deliveredNotifs = await Notifications.ios.getDeliveredNotifications();
|
||||
expect(deliveredNotifs.length).toBe(3);
|
||||
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel1ID);
|
||||
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel2ID);
|
||||
expect(channel1DeliveredNotifications.length).toBe(1);
|
||||
expect(channel2DeliveredNotifications.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should clear all thread notifications', async () => {
|
||||
Store.redux = null;
|
||||
|
||||
ViewSelectors.getBadgeCount = jest.fn().mockReturnValue(5);
|
||||
|
||||
const root1ID = 'root-1-id';
|
||||
const root2ID = 'root-2-id';
|
||||
const root3ID = 'root-3-id';
|
||||
const deliveredNotifications = [
|
||||
|
||||
// Three channel1 delivered notifications
|
||||
{
|
||||
identifier: 'channel1-1',
|
||||
channel_id: channel1ID,
|
||||
root_id: root1ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-2',
|
||||
channel_id: channel1ID,
|
||||
root_id: root1ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-3',
|
||||
channel_id: channel1ID,
|
||||
root_id: root2ID,
|
||||
},
|
||||
|
||||
// Two channel2 delivered notifications
|
||||
{
|
||||
identifier: 'channel2-2',
|
||||
channel_id: channel2ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel2-2',
|
||||
channel_id: channel2ID,
|
||||
root_id: root3ID,
|
||||
},
|
||||
];
|
||||
Notifications.setDeliveredNotifications(deliveredNotifications);
|
||||
|
||||
const notificationCount = deliveredNotifications.length;
|
||||
expect(notificationCount).toBe(5);
|
||||
|
||||
// Clear channel1 notifications
|
||||
await PushNotification.clearChannelNotifications(channel1ID, root1ID);
|
||||
|
||||
const deliveredNotifs = await Notifications.ios.getDeliveredNotifications();
|
||||
expect(deliveredNotifs.length).toBe(3);
|
||||
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel1ID);
|
||||
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel2ID);
|
||||
expect(channel1DeliveredNotifications.length).toBe(1);
|
||||
expect(channel2DeliveredNotifications.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should clear all notifications', async () => {
|
||||
|
|
@ -63,7 +169,7 @@ describe('PushNotification', () => {
|
|||
const cancelAllLocalNotifications = jest.spyOn(PushNotification, 'cancelAllLocalNotifications');
|
||||
|
||||
PushNotification.clearNotifications();
|
||||
await expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(0);
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(0);
|
||||
expect(Notifications.ios.setBadgeCount).toHaveBeenCalledWith(0);
|
||||
expect(cancelAllLocalNotifications).toHaveBeenCalled();
|
||||
expect(Notifications.cancelAllLocalNotifications).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ const NOTIFICATION_TYPE = {
|
|||
interface NotificationWithChannel extends Notification {
|
||||
identifier: string;
|
||||
channel_id: string;
|
||||
post_id: string;
|
||||
root_id: string;
|
||||
}
|
||||
|
||||
class PushNotifications {
|
||||
|
|
@ -61,6 +63,13 @@ class PushNotifications {
|
|||
this.getInitialNotification();
|
||||
}
|
||||
|
||||
getNotifications = async (): Promise<NotificationWithChannel[]> => {
|
||||
if (Platform.OS === 'android') {
|
||||
return AndroidNotificationPreferences.getDeliveredNotifications();
|
||||
}
|
||||
return Notifications.ios.getDeliveredNotifications() as Promise<NotificationWithChannel[]>;
|
||||
}
|
||||
|
||||
cancelAllLocalNotifications() {
|
||||
Notifications.cancelAllLocalNotifications();
|
||||
}
|
||||
|
|
@ -75,34 +84,54 @@ class PushNotifications {
|
|||
}
|
||||
};
|
||||
|
||||
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(channelId);
|
||||
}
|
||||
} else {
|
||||
const ids: string[] = [];
|
||||
const notifications = await Notifications.ios.getDeliveredNotifications();
|
||||
clearChannelNotifications = async (channelId: string, rootId?: string) => {
|
||||
const notifications = await this.getNotifications();
|
||||
|
||||
//set the badge count to the total amount of notifications present in the not-center
|
||||
let badgeCount = notifications.length;
|
||||
let collapsedThreadsEnabled = false;
|
||||
if (Store.redux) {
|
||||
collapsedThreadsEnabled = isCollapsedThreadsEnabled(Store.redux.getState());
|
||||
}
|
||||
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
const notification = notifications[i] as NotificationWithChannel;
|
||||
if (notification.channel_id === channelId) {
|
||||
ids.push(notification.identifier);
|
||||
badgeCount--;
|
||||
const clearThreads = Boolean(rootId);
|
||||
|
||||
const notificationIds: string[] = [];
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
const notification = notifications[i];
|
||||
if (notification.channel_id === channelId) {
|
||||
let doesNotificationMatch = true;
|
||||
if (clearThreads) {
|
||||
doesNotificationMatch = notification.root_id === rootId;
|
||||
} else if (collapsedThreadsEnabled) {
|
||||
// Do not match when CRT is enabled BUT post is not a root post
|
||||
doesNotificationMatch = !notification.root_id;
|
||||
}
|
||||
|
||||
if (doesNotificationMatch) {
|
||||
notificationIds.push(notification.identifier || notification.post_id);
|
||||
|
||||
// For Android, We just need one matching notification to clear the notifications
|
||||
if (Platform.OS === 'android') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.length) {
|
||||
Notifications.ios.removeDeliveredNotifications(ids);
|
||||
}
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
//set the badge count to the total amount of notifications present in the not-center
|
||||
const badgeCount = notifications.length - notificationIds.length;
|
||||
this.setBadgeCountByMentions(badgeCount);
|
||||
}
|
||||
|
||||
if (!notificationIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
AndroidNotificationPreferences.removeDeliveredNotifications(channelId, rootId, collapsedThreadsEnabled);
|
||||
} else {
|
||||
Notifications.ios.removeDeliveredNotifications(notificationIds);
|
||||
}
|
||||
}
|
||||
|
||||
setBadgeCountByMentions = (initialBadge = 0) => {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ export default class ChannelAndroid extends ChannelBase {
|
|||
openMainSidebar={this.openMainSidebar}
|
||||
openSettingsSidebar={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
isGlobalThreads={viewingGlobalThreads}
|
||||
/>
|
||||
{component}
|
||||
<NetworkIndicator/>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {popTopScreen, mergeNavigationOptions} from '@actions/navigation';
|
|||
import DeletedPost from '@components/deleted_post';
|
||||
import Loading from '@components/loading';
|
||||
import {TYPING_HEIGHT, TYPING_VISIBLE} from '@constants/post_draft';
|
||||
import PushNotifications from '@init/push_notifications';
|
||||
import {General, RequestStatus} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
|
|
@ -175,7 +176,6 @@ export default class ThreadBase extends PureComponent {
|
|||
|
||||
markThreadRead(hasNewPost = false) {
|
||||
const {thread} = this.props;
|
||||
|
||||
if (this.props.collapsedThreadsEnabled && thread?.is_following) {
|
||||
// Update lastViewedAt on marking thread as read on openining the screen.
|
||||
if (!hasNewPost) {
|
||||
|
|
@ -186,6 +186,7 @@ export default class ThreadBase extends PureComponent {
|
|||
}
|
||||
|
||||
if (hasNewPost || this.hasUnreadPost()) {
|
||||
PushNotifications.clearChannelNotifications(this.props.channelId, thread.id);
|
||||
this.props.actions.updateThreadRead(
|
||||
this.props.currentUserId,
|
||||
this.props.rootId,
|
||||
|
|
|
|||
|
|
@ -82,15 +82,17 @@ NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge";
|
|||
// Required for the notification event.
|
||||
|
||||
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
UIApplicationState state = [UIApplication sharedApplication].applicationState;
|
||||
NSString* action = [userInfo objectForKey:@"type"];
|
||||
NSString* channelId = [userInfo objectForKey:@"channel_id"];
|
||||
NSString* rootId = [userInfo objectForKey:@"root_id"];
|
||||
NSString* ackId = [userInfo objectForKey:@"ack_id"];
|
||||
BOOL isCRTEnabled = [userInfo objectForKey:@"is_crt_enabled"];
|
||||
|
||||
RuntimeUtils *utils = [[RuntimeUtils alloc] init];
|
||||
|
||||
if ((action && [action isEqualToString: NOTIFICATION_CLEAR_ACTION]) || (state == UIApplicationStateInactive)) {
|
||||
|
||||
if (action && [action isEqualToString: NOTIFICATION_CLEAR_ACTION]) {
|
||||
// If received a notification that a channel was read, remove all notifications from that channel (only with app in foreground/background)
|
||||
[self cleanNotificationsFromChannel:channelId];
|
||||
[self cleanNotificationsFromChannel:channelId :rootId :isCRTEnabled];
|
||||
}
|
||||
|
||||
[[UploadSession shared] notificationReceiptWithNotificationId:ackId receivedAt:round([[NSDate date] timeIntervalSince1970] * 1000.0) type:action];
|
||||
|
|
@ -100,7 +102,7 @@ NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge";
|
|||
}];
|
||||
}
|
||||
|
||||
-(void)cleanNotificationsFromChannel:(NSString *)channelId {
|
||||
-(void)cleanNotificationsFromChannel:(NSString *)channelId :(NSString *)rootId :(BOOL)isCRTEnabled {
|
||||
if ([UNUserNotificationCenter class]) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
|
||||
|
|
@ -111,9 +113,22 @@ NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge";
|
|||
UNNotificationContent *notificationContent = [notificationRequest content];
|
||||
NSString *identifier = [notificationRequest identifier];
|
||||
NSString* cId = [[notificationContent userInfo] objectForKey:@"channel_id"];
|
||||
|
||||
NSString* pId = [[notificationContent userInfo] objectForKey:@"post_id"];
|
||||
NSString* rId = [[notificationContent userInfo] objectForKey:@"root_id"];
|
||||
if ([cId isEqualToString: channelId]) {
|
||||
[notificationIds addObject:identifier];
|
||||
BOOL doesNotificationMatch = true;
|
||||
if (isCRTEnabled) {
|
||||
// Check if it is a thread notification
|
||||
if (rootId != nil) {
|
||||
doesNotificationMatch = [pId isEqualToString: rootId] || [rId isEqualToString: rootId];
|
||||
} else {
|
||||
// With CRT ON, remove notifications without rootId
|
||||
doesNotificationMatch = rId == nil;
|
||||
}
|
||||
}
|
||||
if (doesNotificationMatch) {
|
||||
[notificationIds addObject:identifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue