From 094a34d05ff1b9b87520e37fc39c7e06e93c97d1 Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Thu, 28 Oct 2021 11:06:03 +0300 Subject: [PATCH] MM-37934: adds CRT channel notification preferences (#5744) * MM-37934: adds CRT channel notification preferences Adds push notification preferences per channel for CRT replies. These are only enabled when push notifications for root messages are set to 'mention', in any other case we don't show the switch. * Adds another test case Co-authored-by: Mattermod --- ...annel_notification_preference.test.js.snap | 3 +- ...channel_notification_preference.android.js | 46 +++++++++++++++- .../channel_notification_preference.ios.js | 30 +++++++++- .../channel_notification_preference.test.js | 49 ++++++++++++++++- .../channel_notification_preference_base.js | 55 +++++++++++++++++-- .../channel_notification_preference/index.js | 3 +- 6 files changed, 174 insertions(+), 12 deletions(-) diff --git a/app/screens/channel_notification_preference/__snapshots__/channel_notification_preference.test.js.snap b/app/screens/channel_notification_preference/__snapshots__/channel_notification_preference.test.js.snap index 911dd5ac5..68c1b2274 100644 --- a/app/screens/channel_notification_preference/__snapshots__/channel_notification_preference.test.js.snap +++ b/app/screens/channel_notification_preference/__snapshots__/channel_notification_preference.test.js.snap @@ -24,10 +24,11 @@ exports[`ChannelNotificationPreference should match snapshot 1`] = ` id="channel_notifications.preference.header" style={ Object { + "backgroundColor": "rgba(63,67,80,0.03)", "color": "rgba(63,67,80,0.56)", "fontSize": 13, - "marginTop": 10, "padding": 16, + "paddingTop": 26, "textTransform": "uppercase", } } diff --git a/app/screens/channel_notification_preference/channel_notification_preference.android.js b/app/screens/channel_notification_preference/channel_notification_preference.android.js index 60611635c..e48bd3c45 100644 --- a/app/screens/channel_notification_preference/channel_notification_preference.android.js +++ b/app/screens/channel_notification_preference/channel_notification_preference.android.js @@ -4,12 +4,14 @@ import React from 'react'; import { ScrollView, + Switch, View, } from 'react-native'; import FormattedText from '@components/formatted_text'; import RadioButtonGroup from '@components/radio_button'; import StatusBar from '@components/status_bar'; +import {ViewTypes} from '@constants'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import ChannelNotificationPreferenceBase from './channel_notification_preference_base'; @@ -34,7 +36,8 @@ export default class ChannelNotificationPreferenceAndroid extends ChannelNotific } render() { - const {theme} = this.props; + const {theme, isCollapsedThreadsEnabled} = this.props; + const {notificationLevel, notificationThreadsLevel} = this.state; const style = getStyleSheet(theme); const options = this.getRadioItems(); @@ -59,6 +62,27 @@ export default class ChannelNotificationPreferenceAndroid extends ChannelNotific onSelect={this.handlePress} options={options} /> + + {isCollapsedThreadsEnabled && notificationLevel === ViewTypes.NotificationLevels.MENTION && ( + <> + + + + + + + )} ); @@ -80,5 +104,25 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { marginTop: 10, padding: 16, }, + switchContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + paddingRight: 16, + }, + switchHeader: { + color: changeOpacity(theme.centerChannelColor, 0.56), + fontSize: 13, + marginTop: 10, + marginBottom: 16, + }, + switchLabel: { + color: theme.centerChannelColor, + flex: 1, + fontSize: 17, + paddingRight: 10, + textAlignVertical: 'center', + includeFontPadding: false, + }, }; }); diff --git a/app/screens/channel_notification_preference/channel_notification_preference.ios.js b/app/screens/channel_notification_preference/channel_notification_preference.ios.js index 2b6f58af4..36b4292ef 100644 --- a/app/screens/channel_notification_preference/channel_notification_preference.ios.js +++ b/app/screens/channel_notification_preference/channel_notification_preference.ios.js @@ -10,6 +10,7 @@ import {SafeAreaView} from 'react-native-safe-area-context'; import FormattedText from '@components/formatted_text'; import StatusBar from '@components/status_bar'; +import {ViewTypes} from '@constants'; import SectionItem from '@screens/settings/section_item'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -17,7 +18,8 @@ import ChannelNotificationPreferenceBase from './channel_notification_preference export default class ChannelNotificationPreferenceIos extends ChannelNotificationPreferenceBase { render() { - const {theme} = this.props; + const {theme, isCollapsedThreadsEnabled} = this.props; + const {notificationLevel, notificationThreadsLevel} = this.state; const style = getStyleSheet(theme); const items = this.getItems(); @@ -60,6 +62,29 @@ export default class ChannelNotificationPreferenceIos extends ChannelNotificatio /> ), )} + {isCollapsedThreadsEnabled && notificationLevel === ViewTypes.NotificationLevels.MENTION && ( + + + + + )} + description={} + action={this.handleThreadsPress} + actionType='toggle' + selected={notificationThreadsLevel === ViewTypes.NotificationLevels.ALL} + theme={theme} + /> + + )} @@ -76,11 +101,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), }, header: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.03), color: changeOpacity(theme.centerChannelColor, 0.56), fontSize: 13, textTransform: 'uppercase', - marginTop: 10, padding: 16, + paddingTop: 26, }, divider: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), diff --git a/app/screens/channel_notification_preference/channel_notification_preference.test.js b/app/screens/channel_notification_preference/channel_notification_preference.test.js index e76ec3fd8..2b637b099 100644 --- a/app/screens/channel_notification_preference/channel_notification_preference.test.js +++ b/app/screens/channel_notification_preference/channel_notification_preference.test.js @@ -10,7 +10,7 @@ import {shallowWithIntlMessages} from '@test/intl-test-helper'; import ChannelNotificationPreference from './channel_notification_preference'; -function makeProps(pushNotificationLevel) { +function makeProps(pushNotificationLevel, pushThreadsNotificationLevel, isCollapsedThreadsEnabled = false) { return { actions: { updateChannelNotifyProps: jest.fn(), @@ -22,8 +22,10 @@ function makeProps(pushNotificationLevel) { userId: 'user_id', notifyProps: { push: pushNotificationLevel, + push_threads: pushThreadsNotificationLevel, }, theme: Preferences.THEMES.denim, + isCollapsedThreadsEnabled, }; } @@ -62,7 +64,7 @@ describe('ChannelNotificationPreference', () => { checkNotificationSelected(ViewTypes.NotificationLevels.NONE, 3); }); - test('should save on click', () => { + test('should save on click, when CRT off', () => { const props = makeProps('default'); const wrapper = shallowWithIntlMessages( , @@ -78,4 +80,47 @@ describe('ChannelNotificationPreference', () => { {push: ViewTypes.NotificationLevels.NONE}, ); }); + + test('should save on click, when CRT on', () => { + const props = makeProps('default', undefined, true); + const wrapper = shallowWithIntlMessages( + , + ); + + // click on 'Never' -- last item + wrapper.find(SectionItem).at(3).dive().simulate('press'); + + expect(props.actions.updateChannelNotifyProps).toHaveBeenCalledTimes(1); + expect(props.actions.updateChannelNotifyProps).toBeCalledWith( + props.userId, + props.channelId, + { + push: ViewTypes.NotificationLevels.NONE, + push_threads: ViewTypes.NotificationLevels.ALL, + }, + ); + }); + + test('should show push_threads switch, when CRT is on and notifyLevel is mention', () => { + const props = makeProps('all', undefined, true); + const wrapper = shallowWithIntlMessages( + , + ); + + expect(wrapper.find(SectionItem)).toHaveLength(4); + + wrapper.setState({ + notificationLevel: 'mention', + }); + + expect(wrapper.find(SectionItem)).toHaveLength(5); + }); + + test('should show push_threads switch, when CRT is off and notifyLevel is mention', () => { + const props = makeProps('mention', undefined, false); + const wrapper = shallowWithIntlMessages( + , + ); + expect(wrapper.find(SectionItem)).toHaveLength(4); + }); }); diff --git a/app/screens/channel_notification_preference/channel_notification_preference_base.js b/app/screens/channel_notification_preference/channel_notification_preference_base.js index 2f7f23ff0..16f2a92f0 100644 --- a/app/screens/channel_notification_preference/channel_notification_preference_base.js +++ b/app/screens/channel_notification_preference/channel_notification_preference_base.js @@ -20,6 +20,7 @@ export default class ChannelNotificationPreferenceBase extends PureComponent { notifyProps: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, userId: PropTypes.string.isRequired, + isCollapsedThreadsEnabled: PropTypes.bool.isRequired, }; static contextTypes = { @@ -31,6 +32,7 @@ export default class ChannelNotificationPreferenceBase extends PureComponent { this.state = { notificationLevel: props.notifyProps?.push || ViewTypes.NotificationLevels.DEFAULT, + notificationThreadsLevel: props.notifyProps?.push_threads || ViewTypes.NotificationLevels.ALL, }; } @@ -67,9 +69,54 @@ export default class ChannelNotificationPreferenceBase extends PureComponent { }]; } + handleSubmit = (push, push_threads) => { + const {actions, channelId, userId, isCollapsedThreadsEnabled} = this.props; + + const props = { + push, + }; + + if (isCollapsedThreadsEnabled) { + props.push_threads = push_threads; + } + + return actions.updateChannelNotifyProps(userId, channelId, props); + }; + + handleThreadsPress = preventDoubleTap(async (value) => { + const newNotificationLevel = value ? ViewTypes.NotificationLevels.ALL : ViewTypes.NotificationLevels.MENTION; + const {notificationThreadsLevel, notificationLevel} = this.state; + + if (newNotificationLevel === notificationThreadsLevel) { + // tapped on current selection. + return; + } + + this.setState({ + notificationThreadsLevel: newNotificationLevel, + }); + + const {error} = await this.handleSubmit(notificationLevel, newNotificationLevel); + if (error) { + const {intl} = this.context; + alertErrorWithFallback( + intl, + error, + { + id: t('channel_notifications.preference.save_error'), + defaultMessage: "We couldn't save notification preference. Please check your connection and try again.", + }, + ); + + // restore old value. + this.setState({ + notificationThreadsLevel, + }); + } + }); + handlePress = preventDoubleTap(async (newNotificationLevel) => { - const {actions, channelId, userId} = this.props; - const {notificationLevel} = this.state; + const {notificationLevel, notificationThreadsLevel} = this.state; if (newNotificationLevel === notificationLevel) { // tapped on current selection. @@ -80,9 +127,7 @@ export default class ChannelNotificationPreferenceBase extends PureComponent { notificationLevel: newNotificationLevel, }); - const props = {push: newNotificationLevel}; - - const {error} = await actions.updateChannelNotifyProps(userId, channelId, props); + const {error} = await this.handleSubmit(newNotificationLevel, notificationThreadsLevel); if (error) { const {intl} = this.context; alertErrorWithFallback( diff --git a/app/screens/channel_notification_preference/index.js b/app/screens/channel_notification_preference/index.js index 42dad93f2..4b72e7afb 100644 --- a/app/screens/channel_notification_preference/index.js +++ b/app/screens/channel_notification_preference/index.js @@ -5,7 +5,7 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import {updateChannelNotifyProps} from '@mm-redux/actions/channels'; -import {getTheme} from '@mm-redux/selectors/entities/preferences'; +import {getTheme, isCollapsedThreadsEnabled} from '@mm-redux/selectors/entities/preferences'; import {getCurrentUser} from '@mm-redux/selectors/entities/users'; import {isLandscape} from '@selectors/device'; @@ -15,6 +15,7 @@ function mapStateToProps(state) { return { globalNotifyProps: getCurrentUser(state)?.notify_props, isLandscape: isLandscape(state), + isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state), theme: getTheme(state), }; }