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