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:
parent
76284a78d5
commit
094a34d05f
6 changed files with 174 additions and 12 deletions
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue