diff --git a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java index 14a2b9665..73468cb50 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java @@ -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> inputMap = new HashMap<>(); + Map> 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> notificationsInChannel = loadNotificationsMap(context); - List 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> notificationsInChannel = loadNotificationsMap(context); + Map 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> notificationsInChannel = loadNotificationsMap(context); - List 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> notificationsInChannel = loadNotificationsMap(context); + String groupId = isClearThread ? rootId : channelId; + Map 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> notificationsInChannel = loadNotificationsMap(context); + Map> 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> notificationsInChannel = loadNotificationsMap(mContext); - List 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> notificationsInChannel = loadNotificationsMap(mContext); + Map notifications = notificationsInChannel.get(groupId); + if (notifications == null) { + notifications = Collections.synchronizedMap(new HashMap()); + } - 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> notificationsInChannel = loadNotificationsMap(mContext); - List 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> inputMap) { + private static void saveNotificationsMap(Context context, Map> 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> loadNotificationsMap(Context context) { - Map> outputMap = new HashMap<>(); + /** + * Map Structure + * + * { + * channel_id1 | thread_id1: { + * notification_id1: { + * post_id: 'p1', + * root_id: 'r1', + * } + * } + * } + * + */ + private static Map> loadNotificationsMap(Context context) { + Map> 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 keysItr = json.keys(); - while (keysItr.hasNext()) { - String key = keysItr.next(); - JSONArray array = json.getJSONArray(key); - List 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 groupIdsItr = json.keys(); + while (groupIdsItr.hasNext()) { + String groupId = groupIdsItr.next(); + JSONObject notificationsJSONObj = json.getJSONObject(groupId); + Map notifications = new HashMap<>(); + Iterator 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) { diff --git a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotificationHelper.java b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotificationHelper.java index 956322935..1fbaff01f 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotificationHelper.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotificationHelper.java @@ -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); diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java index d5e56eabb..fca41de0a 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java @@ -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"); } } diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java index 8eb71dc0b..a842af639 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java @@ -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); } + } diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 241474ea3..8d77c1f59 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -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, diff --git a/app/constants/view.js b/app/constants/view.js index 821da633c..2312d5fc7 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -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', diff --git a/app/init/push_notifications.test.js b/app/init/push_notifications.test.js index c5d02cbac..9832073d0 100644 --- a/app/init/push_notifications.test.js +++ b/app/init/push_notifications.test.js @@ -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(); diff --git a/app/init/push_notifications.ts b/app/init/push_notifications.ts index a35064645..3bff019a5 100644 --- a/app/init/push_notifications.ts +++ b/app/init/push_notifications.ts @@ -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 => { + if (Platform.OS === 'android') { + return AndroidNotificationPreferences.getDeliveredNotifications(); + } + return Notifications.ios.getDeliveredNotifications() as Promise; + } + 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) => { diff --git a/app/screens/channel/channel.android.js b/app/screens/channel/channel.android.js index 8bf28789c..8ecd5181c 100644 --- a/app/screens/channel/channel.android.js +++ b/app/screens/channel/channel.android.js @@ -101,6 +101,7 @@ export default class ChannelAndroid extends ChannelBase { openMainSidebar={this.openMainSidebar} openSettingsSidebar={this.openSettingsSidebar} onPress={this.goToChannelInfo} + isGlobalThreads={viewingGlobalThreads} /> {component} diff --git a/app/screens/thread/thread_base.js b/app/screens/thread/thread_base.js index 0b946b38e..3b8d1bedb 100644 --- a/app/screens/thread/thread_base.js +++ b/app/screens/thread/thread_base.js @@ -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, diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index 001eeaa30..07ba3f89a 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -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 * _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]; + } } }