feat(notification-settings) Update Sounds tab in Settings Dialog (#12990)

Rename from Sounds to Notifications
Move Notifications settings from More tab to this tab
This commit is contained in:
Robert Pintilii 2023-03-03 12:53:39 +02:00 committed by GitHub
parent d550254f31
commit 036286a1d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 318 additions and 273 deletions

View File

@ -995,6 +995,7 @@
"more": "More",
"name": "Name",
"noDevice": "None",
"notifications": "Notifications",
"participantJoined": "Participant Joined",
"participantKnocking": "Participant entered lobby",
"participantLeft": "Participant Left",
@ -1005,7 +1006,6 @@
"selectCamera": "Camera",
"selectMic": "Microphone",
"selfView": "Self view",
"sounds": "Sounds",
"speakers": "Speakers",
"startAudioMuted": "Everyone starts muted",
"startReactionsMuted": "Mute reaction sounds for everyone",

View File

@ -21,8 +21,8 @@ import { LogoutDialog, SettingsDialog } from './components';
import {
getModeratorTabProps,
getMoreTabProps,
getProfileTabProps,
getSoundsTabProps
getNotificationsTabProps,
getProfileTabProps
} from './functions';
/**
@ -96,17 +96,6 @@ export function submitMoreTab(newState: any) {
}));
}
const enabledNotifications = newState.enabledNotifications;
if (enabledNotifications !== currentState.enabledNotifications) {
dispatch(updateSettings({
userSelectedNotifications: {
...getState()['features/base/settings'].userSelectedNotifications,
...enabledNotifications
}
}));
}
if (newState.currentFramerate !== currentState.currentFramerate) {
const frameRate = parseInt(newState.currentFramerate, 10);
@ -183,9 +172,9 @@ export function submitProfileTab(newState: any) {
* @param {Object} newState - The new settings.
* @returns {Function}
*/
export function submitSoundsTab(newState: any) {
export function submitNotificationsTab(newState: any) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const currentState = getSoundsTabProps(getState());
const currentState = getNotificationsTabProps(getState());
const shouldNotUpdateReactionSounds = getModeratorTabProps(getState()).startReactionsMuted;
const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage)
|| (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined)
@ -209,6 +198,17 @@ export function submitSoundsTab(newState: any) {
}
dispatch(updateSettings(settingsToUpdate));
}
const enabledNotifications = newState.enabledNotifications;
if (enabledNotifications !== currentState.enabledNotifications) {
dispatch(updateSettings({
userSelectedNotifications: {
...getState()['features/base/settings'].userSelectedNotifications,
...enabledNotifications
}
}));
}
};
}

View File

@ -27,11 +27,6 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
*/
desktopShareFramerates: Array<number>;
/**
* The types of enabled notifications that can be configured and their specific visibility.
*/
enabledNotifications: Object;
/**
* Whether or not follow me is currently active (enabled by some other participant).
*/
@ -47,11 +42,6 @@ export type Props = AbstractDialogTabProps & WithTranslation & {
*/
showModeratorSettings: boolean;
/**
* Whether or not to display notifications settings.
*/
showNotificationsSettings: boolean;
/**
* Whether or not to show prejoin screen.
*/
@ -90,7 +80,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
// Bind event handler so it is only bound once for every instance.
this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this);
this._onEnabledNotificationsChanged = this._onEnabledNotificationsChanged.bind(this);
this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this);
this._onKeyboardShortcutEnableChanged = this._onKeyboardShortcutEnableChanged.bind(this);
this._renderMaxStageParticipantsSelect = this._renderMaxStageParticipantsSelect.bind(this);
@ -143,24 +132,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
super._onChange({ showPrejoinPage: checked });
}
/**
* Callback invoked to select if the given type of
* notifications should be shown.
*
* @param {Object} e - The key event to handle.
* @param {string} type - The type of the notification.
*
* @returns {void}
*/
_onEnabledNotificationsChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>, type: any) {
super._onChange({
enabledNotifications: {
...this.props.enabledNotifications,
[type]: checked
}
});
}
/**
* Callback invoked to select if global keyboard shortcuts
* should be enabled.
@ -269,37 +240,6 @@ class MoreTab extends AbstractDialogTab<Props, any> {
);
}
/**
* Returns the React Element for modifying the enabled notifications settings.
*
* @private
* @returns {ReactElement}
*/
_renderNotificationsSettings() {
const { t, enabledNotifications } = this.props;
return (
<div
className = 'settings-sub-pane-element'
key = 'notifications'>
<span className = 'checkbox-label'>
{ t('notify.displayNotifications') }
</span>
{
Object.keys(enabledNotifications).map(key => (
<Checkbox
checked = { Boolean(enabledNotifications[key as keyof typeof enabledNotifications]) }
key = { key }
label = { t(key) }
name = { `show-${key}` }
/* eslint-disable-next-line react/jsx-no-bind */
onChange = { e => this._onEnabledNotificationsChanged(e, key) } />
))
}
</div>
);
}
/**
* Returns the React Element for the max stage participants dropdown.
*
@ -357,14 +297,13 @@ class MoreTab extends AbstractDialogTab<Props, any> {
* @returns {ReactElement}
*/
_renderSettingsLeft() {
const { showNotificationsSettings, showPrejoinSettings } = this.props;
const { showPrejoinSettings } = this.props;
return (
<div
className = 'settings-sub-pane left'
key = 'settings-sub-pane-left'>
{ showPrejoinSettings && this._renderPrejoinScreenSettings() }
{ showNotificationsSettings && this._renderNotificationsSettings() }
{ this._renderKeyboardShortcutCheckbox() }
</div>
);

View File

@ -0,0 +1,265 @@
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import AbstractDialogTab, {
IProps as AbstractDialogTabProps } from '../../../base/dialog/components/web/AbstractDialogTab';
import { translate } from '../../../base/i18n/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import Checkbox from '../../../base/ui/components/web/Checkbox';
/**
* The type of the React {@code Component} props of {@link NotificationsTab}.
*/
export interface IProps extends AbstractDialogTabProps, WithTranslation {
/**
* CSS classes object.
*/
classes: any;
/**
* Array of disabled sounds ids.
*/
disabledSounds: string[];
/**
* Whether or not the reactions feature is enabled.
*/
enableReactions: Boolean;
/**
* The types of enabled notifications that can be configured and their specific visibility.
*/
enabledNotifications: Object;
/**
* Whether or not moderator muted the sounds.
*/
moderatorMutedSoundsReactions: Boolean;
/**
* Whether or not to display notifications settings.
*/
showNotificationsSettings: boolean;
/**
* Whether sound settings should be displayed or not.
*/
showSoundsSettings: boolean;
/**
* Whether or not the sound for the incoming message should play.
*/
soundsIncomingMessage: Boolean;
/**
* Whether or not the sound for the participant joined should play.
*/
soundsParticipantJoined: Boolean;
/**
* Whether or not the sound for the participant entering the lobby should play.
*/
soundsParticipantKnocking: Boolean;
/**
* Whether or not the sound for the participant left should play.
*/
soundsParticipantLeft: Boolean;
/**
* Whether or not the sound for reactions should play.
*/
soundsReactions: Boolean;
/**
* Whether or not the sound for the talk while muted notification should play.
*/
soundsTalkWhileMuted: Boolean;
}
const styles = (theme: Theme) => {
return {
container: {
display: 'flex',
width: '100%'
},
column: {
flex: 1,
'&:first-child:not(:last-child)': {
marginRight: theme.spacing(3)
}
},
title: {
...withPixelLineHeight(theme.typography.heading6),
color: `${theme.palette.text01} !important`,
marginBottom: theme.spacing(3)
},
checkbox: {
marginBottom: theme.spacing(3)
}
};
};
/**
* React {@code Component} for modifying the local user's sound settings.
*
* @augments Component
*/
class NotificationsTab extends AbstractDialogTab<IProps, any> {
/**
* Initializes a new {@code SoundsTab} instance.
*
* @param {IProps} props - The React {@code Component} props to initialize
* the new {@code SoundsTab} instance with.
*/
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._onChange = this._onChange.bind(this);
this._onEnabledNotificationsChanged = this._onEnabledNotificationsChanged.bind(this);
}
/**
* Changes a sound setting state.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onChange({ target }: React.ChangeEvent<HTMLInputElement>) {
super._onChange({ [target.name]: target.checked });
}
/**
* Callback invoked to select if the given type of
* notifications should be shown.
*
* @param {Object} e - The key event to handle.
* @param {string} type - The type of the notification.
*
* @returns {void}
*/
_onEnabledNotificationsChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>, type: any) {
super._onChange({
enabledNotifications: {
...this.props.enabledNotifications,
[type]: checked
}
});
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
classes,
disabledSounds,
enabledNotifications,
showNotificationsSettings,
showSoundsSettings,
soundsIncomingMessage,
soundsParticipantJoined,
soundsParticipantKnocking,
soundsParticipantLeft,
soundsTalkWhileMuted,
soundsReactions,
enableReactions,
moderatorMutedSoundsReactions,
t
} = this.props;
return (
<div
className = { classes.container }
key = 'sounds'>
{showSoundsSettings && (
<div className = { classes.column }>
<h2 className = { classes.title }>
{t('settings.playSounds')}
</h2>
{enableReactions && <Checkbox
checked = { soundsReactions && !disabledSounds.includes('REACTION_SOUND') }
className = { classes.checkbox }
disabled = { Boolean(moderatorMutedSoundsReactions
|| disabledSounds.includes('REACTION_SOUND')) }
label = { t('settings.reactions') }
name = 'soundsReactions'
onChange = { this._onChange } />
}
<Checkbox
checked = { soundsIncomingMessage && !disabledSounds.includes('INCOMING_MSG_SOUND') }
className = { classes.checkbox }
disabled = { disabledSounds.includes('INCOMING_MSG_SOUND') }
label = { t('settings.incomingMessage') }
name = 'soundsIncomingMessage'
onChange = { this._onChange } />
<Checkbox
checked = { soundsParticipantJoined
&& !disabledSounds.includes('PARTICIPANT_JOINED_SOUND') }
className = { classes.checkbox }
disabled = { disabledSounds.includes('PARTICIPANT_JOINED_SOUND') }
label = { t('settings.participantJoined') }
name = 'soundsParticipantJoined'
onChange = { this._onChange } />
<Checkbox
checked = { soundsParticipantLeft && !disabledSounds.includes('PARTICIPANT_LEFT_SOUND') }
className = { classes.checkbox }
disabled = { disabledSounds.includes('PARTICIPANT_LEFT_SOUND') }
label = { t('settings.participantLeft') }
name = 'soundsParticipantLeft'
onChange = { this._onChange } />
<Checkbox
checked = { soundsTalkWhileMuted && !disabledSounds.includes('TALK_WHILE_MUTED_SOUND') }
className = { classes.checkbox }
disabled = { disabledSounds.includes('TALK_WHILE_MUTED_SOUND') }
label = { t('settings.talkWhileMuted') }
name = 'soundsTalkWhileMuted'
onChange = { this._onChange } />
<Checkbox
checked = { soundsParticipantKnocking
&& !disabledSounds.includes('KNOCKING_PARTICIPANT_SOUND') }
className = { classes.checkbox }
disabled = { disabledSounds.includes('KNOCKING_PARTICIPANT_SOUND') }
label = { t('settings.participantKnocking') }
name = 'soundsParticipantKnocking'
onChange = { this._onChange } />
</div>
)}
{showNotificationsSettings && (
<div className = { classes.column }>
<h2 className = { classes.title }>
{t('notify.displayNotifications')}
</h2>
{
Object.keys(enabledNotifications).map(key => (
<Checkbox
checked = { Boolean(enabledNotifications[key as
keyof typeof enabledNotifications]) }
className = { classes.checkbox }
key = { key }
label = { t(key) }
name = { `show-${key}` }
/* eslint-disable-next-line react/jsx-no-bind */
onChange = { e => this._onEnabledNotificationsChanged(e, key) } />
))
}
</div>
)}
</div>
);
}
}
export default withStyles(styles)(translate(NotificationsTab));

View File

@ -18,23 +18,24 @@ import {
import {
submitModeratorTab,
submitMoreTab,
submitProfileTab,
submitSoundsTab
submitNotificationsTab,
submitProfileTab
} from '../../actions';
import { SETTINGS_TABS } from '../../constants';
import {
getModeratorTabProps,
getMoreTabProps,
getProfileTabProps,
getSoundsTabProps
getNotificationsMap,
getNotificationsTabProps,
getProfileTabProps
} from '../../functions';
// @ts-ignore
import CalendarTab from './CalendarTab';
import ModeratorTab from './ModeratorTab';
import MoreTab from './MoreTab';
import NotificationsTab from './NotificationsTab';
import ProfileTab from './ProfileTab';
import SoundsTab from './SoundsTab';
/* eslint-enable lines-around-comment */
/**
@ -148,7 +149,7 @@ const styles = (theme: Theme) => {
'& .settings-checkbox': {
display: 'flex',
marginBottom: theme.spacing(2)
marginBottom: theme.spacing(3)
},
'& .calendar-tab': {
@ -248,6 +249,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const showCalendarSettings
= configuredTabs.includes('calendar') && isCalendarEnabled(state);
const showSoundsSettings = configuredTabs.includes('sounds');
const enabledNotifications = getNotificationsMap(state);
const showNotificationsSettings = Object.keys(enabledNotifications).length > 0;
const tabs: IDialogTab[] = [];
if (showDeviceSettings) {
@ -276,6 +279,24 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
});
}
if (showSoundsSettings || showNotificationsSettings) {
tabs.push({
name: SETTINGS_TABS.NOTIFICATIONS,
component: NotificationsTab,
labelKey: 'settings.notifications',
propsUpdateFunction: (tabState: any, newProps: any) => {
return {
...newProps,
enabledNotifications: tabState?.enabledNotifications || {}
};
},
props: getNotificationsTabProps(state, showSoundsSettings),
className: `settings-pane ${classes.settingsDialog}`,
submit: submitNotificationsTab,
icon: IconBell
});
}
if (showModeratorSettings) {
tabs.push({
name: SETTINGS_TABS.MODERATOR,
@ -321,18 +342,6 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
});
}
if (showSoundsSettings) {
tabs.push({
name: SETTINGS_TABS.SOUNDS,
component: SoundsTab,
labelKey: 'settings.sounds',
props: getSoundsTabProps(state),
className: `settings-pane ${classes.settingsDialog} profile-pane`,
submit: submitSoundsTab,
icon: IconBell
});
}
if (showMoreTab) {
tabs.push({
name: SETTINGS_TABS.MORE,
@ -350,7 +359,6 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
currentLanguage: tabState?.currentLanguage,
hideSelfView: tabState?.hideSelfView,
showPrejoinPage: tabState?.showPrejoinPage,
enabledNotifications: tabState?.enabledNotifications || {},
maxStageParticipants: tabState?.maxStageParticipants
};
},

View File

@ -1,169 +0,0 @@
import React from 'react';
import { WithTranslation } from 'react-i18next';
// @ts-ignore
import { AbstractDialogTab } from '../../../base/dialog';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import type { Props as AbstractDialogTabProps } from '../../../base/dialog';
import { translate } from '../../../base/i18n/functions';
import Checkbox from '../../../base/ui/components/web/Checkbox';
/**
* The type of the React {@code Component} props of {@link SoundsTab}.
*/
export type Props = AbstractDialogTabProps & WithTranslation & {
/**
* Whether or not the reactions feature is enabled.
*/
enableReactions: Boolean;
/**
* Whether or not moderator muted the sounds.
*/
moderatorMutedSoundsReactions: Boolean;
/**
* Whether or not the sound for the incoming message should play.
*/
soundsIncomingMessage: Boolean;
/**
* Whether or not the sound for the participant joined should play.
*/
soundsParticipantJoined: Boolean;
/**
* Whether or not the sound for the participant entering the lobby should play.
*/
soundsParticipantKnocking: Boolean;
/**
* Whether or not the sound for the participant left should play.
*/
soundsParticipantLeft: Boolean;
/**
* Whether or not the sound for reactions should play.
*/
soundsReactions: Boolean;
/**
* Whether or not the sound for the talk while muted notification should play.
*/
soundsTalkWhileMuted: Boolean;
/**
* Invoked to obtain translated strings.
*/
t: Function;
};
/**
* React {@code Component} for modifying the local user's sound settings.
*
* @augments Component
*/
class SoundsTab extends AbstractDialogTab<Props> {
/**
* Initializes a new {@code SoundsTab} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* the new {@code SoundsTab} instance with.
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._onChange = this._onChange.bind(this);
}
/**
* Changes a sound setting state.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onChange({ target }: React.ChangeEvent<HTMLInputElement>) {
super._onChange({ [target.name]: target.checked });
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
disabledSounds,
soundsIncomingMessage,
soundsParticipantJoined,
soundsParticipantKnocking,
soundsParticipantLeft,
soundsTalkWhileMuted,
soundsReactions,
enableReactions,
moderatorMutedSoundsReactions,
t // @ts-ignore
} = this.props;
return (
<div
className = 'settings-sub-pane-element'
key = 'sounds'>
<h2 className = 'mock-atlaskit-label'>
{t('settings.playSounds')}
</h2>
{enableReactions && <Checkbox
checked = { soundsReactions && !disabledSounds.includes('REACTION_SOUND') }
className = 'settings-checkbox'
disabled = { moderatorMutedSoundsReactions || disabledSounds.includes('REACTION_SOUND') }
label = { t('settings.reactions') }
name = 'soundsReactions'
onChange = { this._onChange } />
}
<Checkbox
checked = { soundsIncomingMessage && !disabledSounds.includes('INCOMING_MSG_SOUND') }
className = 'settings-checkbox'
disabled = { disabledSounds.includes('INCOMING_MSG_SOUND') }
label = { t('settings.incomingMessage') }
name = 'soundsIncomingMessage'
onChange = { this._onChange } />
<Checkbox
checked = { soundsParticipantJoined && !disabledSounds.includes('PARTICIPANT_JOINED_SOUND') }
className = 'settings-checkbox'
disabled = { disabledSounds.includes('PARTICIPANT_JOINED_SOUND') }
label = { t('settings.participantJoined') }
name = 'soundsParticipantJoined'
onChange = { this._onChange } />
<Checkbox
checked = { soundsParticipantLeft && !disabledSounds.includes('PARTICIPANT_LEFT_SOUND') }
className = 'settings-checkbox'
disabled = { disabledSounds.includes('PARTICIPANT_LEFT_SOUND') }
label = { t('settings.participantLeft') }
name = 'soundsParticipantLeft'
onChange = { this._onChange } />
<Checkbox
checked = { soundsTalkWhileMuted && !disabledSounds.includes('TALK_WHILE_MUTED_SOUND') }
className = 'settings-checkbox'
disabled = { disabledSounds.includes('TALK_WHILE_MUTED_SOUND') }
label = { t('settings.talkWhileMuted') }
name = 'soundsTalkWhileMuted'
onChange = { this._onChange } />
<Checkbox
checked = { soundsParticipantKnocking && !disabledSounds.includes('KNOCKING_PARTICIPANT_SOUND') }
className = 'settings-checkbox'
disabled = { disabledSounds.includes('KNOCKING_PARTICIPANT_SOUND') }
label = { t('settings.participantKnocking') }
name = 'soundsParticipantKnocking'
onChange = { this._onChange } />
</div>
);
}
}
// @ts-ignore
export default translate(SoundsTab);

View File

@ -3,8 +3,8 @@ export const SETTINGS_TABS = {
DEVICES: 'devices_tab',
MORE: 'more_tab',
MODERATOR: 'moderator-tab',
PROFILE: 'profile_tab',
SOUNDS: 'sounds_tab'
NOTIFICATIONS: 'notifications_tab',
PROFILE: 'profile_tab'
};
/**

View File

@ -116,14 +116,11 @@ export function getNotificationsMap(stateful: IStateful) {
export function getMoreTabProps(stateful: IStateful) {
const state = toState(stateful);
const framerate = state['features/screen-share'].captureFrameRate ?? SS_DEFAULT_FRAME_RATE;
const enabledNotifications = getNotificationsMap(stateful);
const stageFilmstripEnabled = isStageFilmstripEnabled(state);
return {
currentFramerate: framerate,
desktopShareFramerates: SS_SUPPORTED_FRAMERATES,
enabledNotifications,
showNotificationsSettings: Object.keys(enabledNotifications).length > 0,
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
showPrejoinSettings: state['features/base/config'].prejoinConfig?.enabled,
maxStageParticipants: state['features/base/settings'].maxStageParticipants,
@ -225,10 +222,11 @@ export function getProfileTabProps(stateful: IStateful) {
*
* @param {(Function|Object)} stateful -The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state.
* @param {boolean} showSoundsSettings - Whether to show the sound settings or not.
* @returns {Object} - The properties for the "Sounds" tab from settings
* dialog.
*/
export function getSoundsTabProps(stateful: IStateful) {
export function getNotificationsTabProps(stateful: IStateful, showSoundsSettings?: boolean) {
const state = toState(stateful);
const {
soundsIncomingMessage,
@ -240,9 +238,12 @@ export function getSoundsTabProps(stateful: IStateful) {
} = state['features/base/settings'];
const enableReactions = isReactionsEnabled(state);
const moderatorMutedSoundsReactions = state['features/base/conference'].startReactionsMuted ?? false;
const enabledNotifications = getNotificationsMap(stateful);
return {
disabledSounds: state['features/base/config'].disabledSounds || [],
enabledNotifications,
showNotificationsSettings: Object.keys(enabledNotifications).length > 0,
soundsIncomingMessage,
soundsParticipantJoined,
soundsParticipantKnocking,
@ -250,7 +251,8 @@ export function getSoundsTabProps(stateful: IStateful) {
soundsTalkWhileMuted,
soundsReactions,
enableReactions,
moderatorMutedSoundsReactions
moderatorMutedSoundsReactions,
showSoundsSettings
};
}