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 <mattermod@users.noreply.github.com>
This commit is contained in:
Kyriakos Z 2021-10-28 11:06:03 +03:00 committed by GitHub
parent 76284a78d5
commit 094a34d05f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 174 additions and 12 deletions

View file

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

View file

@ -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 && (
<>
<FormattedText
id='mobile.notification_settings.push_threads.title_android'
defaultMessage='Thread reply notifications'
style={style.switchHeader}
/>
<View style={style.switchContainer}>
<FormattedText
id='mobile.notification_settings.push_threads.description'
defaultMessage={'Notify me about all replies to threads I\'m following'}
style={style.switchLabel}
/>
<Switch
onValueChange={this.handleThreadsPress}
value={notificationThreadsLevel === ViewTypes.NotificationLevels.ALL}
/>
</View>
</>
)}
</ScrollView>
</View>
);
@ -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,
},
};
});

View file

@ -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
/>
</View>),
)}
{isCollapsedThreadsEnabled && notificationLevel === ViewTypes.NotificationLevels.MENTION && (
<View>
<FormattedText
id='mobile.notification_settings.push_threads.title'
defaultMessage={'Thread reply notifications'}
style={style.header}
/>
<View style={style.divider}/>
<SectionItem
label={(
<FormattedText
id='mobile.notification_settings.push_threads.description'
defaultMessage={'Notify me about all replies to threads I\'m following'}
/>
)}
description={<View/>}
action={this.handleThreadsPress}
actionType='toggle'
selected={notificationThreadsLevel === ViewTypes.NotificationLevels.ALL}
theme={theme}
/>
</View>
)}
</SafeAreaView>
<View style={style.divider}/>
</View>
@ -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),

View file

@ -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(
<ChannelNotificationPreference {...props}/>,
@ -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(
<ChannelNotificationPreference {...props}/>,
);
// 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(
<ChannelNotificationPreference {...props}/>,
);
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(
<ChannelNotificationPreference {...props}/>,
);
expect(wrapper.find(SectionItem)).toHaveLength(4);
});
});

View file

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

View file

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