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:
Anurag Shivarathri 2021-11-15 10:42:05 +05:30 committed by GitHub
parent e4dafb4d3b
commit 805b90205a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 319 additions and 106 deletions

View file

@ -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) {

View file

@ -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);

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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',

View file

@ -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();

View file

@ -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) => {

View file

@ -101,6 +101,7 @@ export default class ChannelAndroid extends ChannelBase {
openMainSidebar={this.openMainSidebar}
openSettingsSidebar={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
isGlobalThreads={viewingGlobalThreads}
/>
{component}
<NetworkIndicator/>

View file

@ -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,

View file

@ -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];
}
}
}