feat(notifications): native UI updates (#12798)
* feat(notifications): native notifications UI updates
This commit is contained in:
parent
9fa426d97f
commit
f8af9c4fae
|
@ -13,10 +13,12 @@ import { IButtonProps } from '../types';
|
|||
import styles from './buttonStyles';
|
||||
|
||||
export interface IProps extends IButtonProps {
|
||||
color?: string;
|
||||
color?: string | undefined;
|
||||
contentStyle?: Object | undefined;
|
||||
labelStyle?: Object | undefined;
|
||||
mode?: any;
|
||||
style?: Object | undefined;
|
||||
useRippleColor?: boolean;
|
||||
}
|
||||
|
||||
const Button: React.FC<IProps> = ({
|
||||
|
@ -27,31 +29,36 @@ const Button: React.FC<IProps> = ({
|
|||
icon,
|
||||
labelKey,
|
||||
labelStyle,
|
||||
mode = BUTTON_MODES.CONTAINED,
|
||||
onClick: onPress,
|
||||
style,
|
||||
type
|
||||
type,
|
||||
useRippleColor = true
|
||||
}: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { CONTAINED } = BUTTON_MODES;
|
||||
const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
const { CONTAINED, TEXT } = BUTTON_MODES;
|
||||
|
||||
const rippleColor
|
||||
= useRippleColor ? BaseTheme.palette.action03Active : 'transparent';
|
||||
|
||||
let buttonLabelStyles;
|
||||
let buttonStyles;
|
||||
let color;
|
||||
let mode;
|
||||
|
||||
if (type === PRIMARY) {
|
||||
buttonLabelStyles = styles.buttonLabelPrimary;
|
||||
color = BaseTheme.palette.action01;
|
||||
mode = CONTAINED;
|
||||
buttonLabelStyles = mode === TEXT
|
||||
? styles.buttonLabelPrimaryText
|
||||
: styles.buttonLabelPrimary;
|
||||
color = mode === CONTAINED && BaseTheme.palette.action01;
|
||||
} else if (type === SECONDARY) {
|
||||
buttonLabelStyles = styles.buttonLabelSecondary;
|
||||
color = BaseTheme.palette.action02;
|
||||
mode = CONTAINED;
|
||||
color = mode === CONTAINED && BaseTheme.palette.action02;
|
||||
} else if (type === DESTRUCTIVE) {
|
||||
color = BaseTheme.palette.actionDanger;
|
||||
buttonLabelStyles = styles.buttonLabelDestructive;
|
||||
mode = CONTAINED;
|
||||
buttonLabelStyles = mode === TEXT
|
||||
? styles.buttonLabelDestructiveText
|
||||
: styles.buttonLabelDestructive;
|
||||
color = mode === CONTAINED && BaseTheme.palette.actionDanger;
|
||||
} else {
|
||||
color = buttonColor;
|
||||
buttonLabelStyles = styles.buttonLabel;
|
||||
|
@ -65,15 +72,17 @@ const Button: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
if (type === TERTIARY) {
|
||||
buttonLabelStyles
|
||||
= disabled ? styles.buttonLabelTertiaryDisabled : styles.buttonLabelTertiary;
|
||||
if (useRippleColor && disabled) {
|
||||
buttonLabelStyles = styles.buttonLabelTertiaryDisabled;
|
||||
}
|
||||
buttonLabelStyles = styles.buttonLabelTertiary;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { BaseTheme.palette.action03Active }
|
||||
rippleColor = { rippleColor }
|
||||
style = { [
|
||||
buttonStyles,
|
||||
style
|
||||
|
|
|
@ -36,7 +36,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
|
|||
iconButtonContainerStyles = styles.iconButtonContainerSecondary;
|
||||
rippleColor = BaseTheme.palette.action02;
|
||||
} else if (type === TERTIARY) {
|
||||
color = BaseTheme.palette.icon01;
|
||||
color = iconColor;
|
||||
iconButtonContainerStyles = styles.iconButtonContainer;
|
||||
rippleColor = BaseTheme.palette.action03;
|
||||
} else {
|
||||
|
|
|
@ -42,6 +42,11 @@ export default {
|
|||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
buttonLabelPrimaryText: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.action01
|
||||
},
|
||||
|
||||
buttonLabelSecondary: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text04
|
||||
|
@ -52,6 +57,11 @@ export default {
|
|||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
buttonLabelDestructiveText: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.actionDanger
|
||||
},
|
||||
|
||||
buttonLabelTertiary: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text01,
|
||||
|
|
|
@ -13,6 +13,8 @@ export enum BUTTON_TYPES {
|
|||
*/
|
||||
export const BUTTON_MODES: {
|
||||
CONTAINED: 'contained';
|
||||
TEXT: 'text';
|
||||
} = {
|
||||
CONTAINED: 'contained'
|
||||
CONTAINED: 'contained',
|
||||
TEXT: 'text'
|
||||
};
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/* eslint-disable lines-around-comment, max-len */
|
||||
|
||||
import { navigate }
|
||||
// @ts-ignore
|
||||
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
@ -6,13 +14,19 @@ export * from './actions.any';
|
|||
* Displays the chat panel.
|
||||
*
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
* @param {boolean} disablePolls - Checks if polls are disabled.
|
||||
*
|
||||
* @returns {{
|
||||
* participant: Participant,
|
||||
* participant: participant,
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function openChat(participant: Object) {
|
||||
export function openChat(participant: Object, disablePolls: boolean) {
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
}
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
|
||||
return {
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
|
||||
import { IStore } from '../app/types';
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
import { closeChat } from './actions.any';
|
||||
|
@ -26,27 +25,6 @@ export function openChat(participant?: Object) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the chat panel for a participant identified by an id.
|
||||
*
|
||||
* @param {string} id - The id of the participant.
|
||||
* @returns {{
|
||||
* participant: Participant,
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function openChatById(id: string) {
|
||||
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
const participant = getParticipantById(getState(), id);
|
||||
|
||||
return dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles display of the chat panel.
|
||||
*
|
||||
|
|
|
@ -4,7 +4,7 @@ import { IconMessage, IconReply } from '../../../base/icons';
|
|||
import { getParticipantById } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../../chat/actions';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../../chat/actions.native';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { startKnocking } from '../../../lobby/actions.any';
|
||||
import { KnockingParticipantList } from '../../../lobby/components/native';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
|
@ -433,7 +432,13 @@ class Conference extends AbstractConference<Props, State> {
|
|||
|
||||
<LonelyMeetingExperience />
|
||||
|
||||
{ _shouldDisplayTileView || <><Filmstrip /><Toolbox /></> }
|
||||
{
|
||||
_shouldDisplayTileView
|
||||
|| <>
|
||||
<Filmstrip />
|
||||
<Toolbox />
|
||||
</>
|
||||
}
|
||||
</View>
|
||||
|
||||
<SafeAreaView
|
||||
|
@ -463,10 +468,10 @@ class Conference extends AbstractConference<Props, State> {
|
|||
<AlwaysOnLabels createOnPress = { this._createOnPress } />
|
||||
</View>
|
||||
{ this._renderNotificationsContainer() }
|
||||
<KnockingParticipantList />
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
||||
{ this._renderConferenceNotification() }
|
||||
|
||||
{_shouldDisplayTileView && <Toolbox />}
|
||||
|
|
|
@ -9,7 +9,7 @@ import Label from '../../../base/label/components/web/Label';
|
|||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
import { open as openParticipantsPane } from '../../../participants-pane/actions';
|
||||
import { open as openParticipantsPane } from '../../../participants-pane/actions.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import { connect } from '../../../base/redux';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { handleLobbyChatInitialized } from '../../../chat/actions.native';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import ParticipantItem
|
||||
from '../../../participants-pane/components/native/ParticipantItem';
|
||||
import { setKnockingParticipantApproval } from '../../actions.native';
|
||||
import { getKnockingParticipants, getLobbyEnabled, showLobbyChatButton } from '../../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Props type of the component.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The list of participants.
|
||||
*/
|
||||
_participants: Array<Object>,
|
||||
|
||||
/**
|
||||
* True if the list should be rendered.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Returns true if the lobby chat button should be shown.
|
||||
*/
|
||||
_showChatButton: Function,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render a list for the actively knocking participants.
|
||||
*/
|
||||
class KnockingParticipantList extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _participants, _visible, _showChatButton } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ _participants.map(p => (
|
||||
<View
|
||||
key = { p.id }
|
||||
style = { styles.knockingParticipantListEntry }>
|
||||
<ParticipantItem
|
||||
displayName = { p.name }
|
||||
isKnockingParticipant = { true }
|
||||
key = { p.id }
|
||||
participantID = { p.id }>
|
||||
<Button
|
||||
labelKey = { 'lobby.admit' }
|
||||
onClick = { this._onRespondToParticipant(p.id, true) }
|
||||
style = { styles.lobbyButtonAdmit }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
{
|
||||
_showChatButton(p)
|
||||
? (
|
||||
<Button
|
||||
labelKey = { 'lobby.chat' }
|
||||
onClick = { this._onInitializeLobbyChat(p.id) }
|
||||
style = { styles.lobbyButtonChat }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
) : null
|
||||
}
|
||||
<Button
|
||||
labelKey = { 'lobby.reject' }
|
||||
onClick = { this._onRespondToParticipant(p.id, false) }
|
||||
style = { styles.lobbyButtonReject }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } />
|
||||
</ParticipantItem>
|
||||
</View>
|
||||
)) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
|
||||
/**
|
||||
* Function that constructs a callback for the response handler button.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approve - The response for the knocking.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onRespondToParticipant(id, approve) {
|
||||
return () => {
|
||||
this.props.dispatch(setKnockingParticipantApproval(id, approve));
|
||||
};
|
||||
}
|
||||
|
||||
_onInitializeLobbyChat: (string) => Function;
|
||||
|
||||
/**
|
||||
* Function that constructs a callback for the lobby chat button.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onInitializeLobbyChat(id) {
|
||||
return () => {
|
||||
this.props.dispatch(handleLobbyChatInitialized(id));
|
||||
if (this.props._isPollsDisabled) {
|
||||
return navigate(screen.conference.chat);
|
||||
}
|
||||
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const lobbyEnabled = getLobbyEnabled(state);
|
||||
const knockingParticipants = getKnockingParticipants(state);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_visible: lobbyEnabled && isLocalParticipantModerator(state),
|
||||
_showChatButton: participant => showLobbyChatButton(participant)(state),
|
||||
_isPollsDisabled: disablePolls,
|
||||
|
||||
// On mobile we only show a portion of the list for screen real estate reasons
|
||||
_participants: knockingParticipants.slice(0, 2)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(KnockingParticipantList));
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
|
||||
export { default as KnockingParticipantList } from './KnockingParticipantList';
|
||||
export { default as LobbyScreen } from './LobbyScreen';
|
||||
export { default as LobbyChatScreen } from './LobbyChatScreen';
|
||||
|
|
|
@ -1,22 +1,40 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED
|
||||
} from '../base/conference/actionTypes';
|
||||
import { conferenceWillJoin } from '../base/conference/actions';
|
||||
import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/participants/functions';
|
||||
import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
getFirstLoadableAvatarUrl,
|
||||
getParticipantDisplayName
|
||||
} from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { isTestModeEnabled } from '../base/testing/functions';
|
||||
import { handleLobbyChatInitialized, removeLobbyChatParticipant } from '../chat/actions.any';
|
||||
import {
|
||||
hideNotification,
|
||||
showNotification
|
||||
} from '../notifications/actions';
|
||||
playSound,
|
||||
registerSound,
|
||||
unregisterSound
|
||||
} from '../base/sounds/actions';
|
||||
import { isTestModeEnabled } from '../base/testing/functions';
|
||||
import { BUTTON_TYPES } from '../base/ui/constants.any';
|
||||
// @ts-ignore
|
||||
import { openChat } from '../chat/actions';
|
||||
import {
|
||||
handleLobbyChatInitialized,
|
||||
removeLobbyChatParticipant
|
||||
} from '../chat/actions.any';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import {
|
||||
LOBBY_NOTIFICATION_ID,
|
||||
NOTIFICATION_ICON,
|
||||
|
@ -28,7 +46,10 @@ import { open as openParticipantsPane } from '../participants-pane/actions';
|
|||
import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
||||
import { shouldAutoKnock } from '../prejoin/functions';
|
||||
|
||||
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED, KNOCKING_PARTICIPANT_LEFT } from './actionTypes';
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
KNOCKING_PARTICIPANT_LEFT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
approveKnockingParticipant,
|
||||
hideLobbyScreen,
|
||||
|
@ -47,6 +68,7 @@ import { getKnockingParticipants, showLobbyChatButton } from './functions';
|
|||
import { KNOCKING_PARTICIPANT_FILE } from './sounds';
|
||||
import { IKnockingParticipant } from './types';
|
||||
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
|
@ -112,7 +134,7 @@ StateListenerRegistry.register(
|
|||
|
||||
const isParticipantsPaneVisible = getParticipantsPaneOpen(getState());
|
||||
|
||||
if (navigator.product === 'ReactNative' || isParticipantsPaneVisible) {
|
||||
if (isParticipantsPaneVisible || navigator.product === 'ReactNative') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -121,57 +143,6 @@ StateListenerRegistry.register(
|
|||
getState
|
||||
});
|
||||
|
||||
let notificationTitle;
|
||||
let customActionNameKey;
|
||||
let customActionHandler;
|
||||
let descriptionKey;
|
||||
let icon;
|
||||
|
||||
const knockingParticipants = getKnockingParticipants(getState());
|
||||
const firstParticipant = knockingParticipants[0];
|
||||
const showChat = showLobbyChatButton(firstParticipant)(getState());
|
||||
|
||||
if (knockingParticipants.length > 1) {
|
||||
descriptionKey = 'notify.participantsWantToJoin';
|
||||
notificationTitle = i18n.t('notify.waitingParticipants', {
|
||||
waitingParticipants: knockingParticipants.length
|
||||
});
|
||||
icon = NOTIFICATION_ICON.PARTICIPANTS;
|
||||
customActionNameKey = [ 'notify.viewLobby' ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(openParticipantsPane());
|
||||
}) ];
|
||||
} else {
|
||||
descriptionKey = 'notify.participantWantsToJoin';
|
||||
notificationTitle = firstParticipant.name;
|
||||
icon = NOTIFICATION_ICON.PARTICIPANT;
|
||||
customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(approveKnockingParticipant(firstParticipant.id));
|
||||
}),
|
||||
() => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(rejectKnockingParticipant(firstParticipant.id));
|
||||
}) ];
|
||||
if (showChat) {
|
||||
customActionNameKey.splice(1, 0, 'lobby.chat');
|
||||
customActionHandler.splice(1, 0, () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(handleLobbyChatInitialized(firstParticipant.id));
|
||||
}));
|
||||
}
|
||||
}
|
||||
dispatch(showNotification({
|
||||
title: notificationTitle,
|
||||
descriptionKey,
|
||||
uid: LOBBY_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionHandler,
|
||||
icon
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyKnockingParticipant({
|
||||
id,
|
||||
|
@ -227,16 +198,20 @@ function _handleLobbyNotification(store: IStore) {
|
|||
let notificationTitle;
|
||||
let customActionNameKey;
|
||||
let customActionHandler;
|
||||
let customActionType;
|
||||
let descriptionKey;
|
||||
let icon;
|
||||
|
||||
if (knockingParticipants.length === 1) {
|
||||
const firstParticipant = knockingParticipants[0];
|
||||
const { disablePolls } = getState()['features/base/config'];
|
||||
const showChat = showLobbyChatButton(firstParticipant)(getState());
|
||||
|
||||
descriptionKey = 'notify.participantWantsToJoin';
|
||||
notificationTitle = firstParticipant.name;
|
||||
icon = NOTIFICATION_ICON.PARTICIPANT;
|
||||
customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
|
||||
customActionType = [ BUTTON_TYPES.PRIMARY, BUTTON_TYPES.DESTRUCTIVE ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(approveKnockingParticipant(firstParticipant.id));
|
||||
|
@ -245,6 +220,18 @@ function _handleLobbyNotification(store: IStore) {
|
|||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(rejectKnockingParticipant(firstParticipant.id));
|
||||
}) ];
|
||||
|
||||
// This checks if lobby chat button is available
|
||||
// and, if so, it adds it to the customActionNameKey array
|
||||
if (showChat) {
|
||||
customActionNameKey.splice(1, 0, 'lobby.chat');
|
||||
customActionType.splice(1, 0, BUTTON_TYPES.SECONDARY);
|
||||
customActionHandler.splice(1, 0, () => batch(() => {
|
||||
dispatch(handleLobbyChatInitialized(firstParticipant.id));
|
||||
// @ts-ignore
|
||||
dispatch(openChat(disablePolls));
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
descriptionKey = 'notify.participantsWantToJoin';
|
||||
notificationTitle = i18n.t('notify.waitingParticipants', {
|
||||
|
@ -252,16 +239,19 @@ function _handleLobbyNotification(store: IStore) {
|
|||
});
|
||||
icon = NOTIFICATION_ICON.PARTICIPANTS;
|
||||
customActionNameKey = [ 'notify.viewLobby' ];
|
||||
customActionType = [ BUTTON_TYPES.PRIMARY ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(openParticipantsPane());
|
||||
}) ];
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
title: notificationTitle,
|
||||
descriptionKey,
|
||||
uid: LOBBY_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionType,
|
||||
customActionHandler,
|
||||
icon
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
|
|
@ -27,6 +27,11 @@ export type Props = {
|
|||
*/
|
||||
customActionNameKey: string[],
|
||||
|
||||
/**
|
||||
* The type of button.
|
||||
*/
|
||||
customActionType: ?string[],
|
||||
|
||||
/**
|
||||
* The text to display in the body of the notification. If not passed
|
||||
* in, the passed in descriptionKey will be used.
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCloseLarge } from '../../../base/icons';
|
||||
import { replaceNonUnicodeEmojis } from '../../../chat/functions';
|
||||
import AbstractNotification, {
|
||||
type Props
|
||||
} from '../AbstractNotification';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Default value for the maxLines prop.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
const DEFAULT_MAX_LINES = 2;
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class Notification extends AbstractNotification<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notification }>
|
||||
<View style = { styles.contentColumn }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notificationContent }>
|
||||
{
|
||||
this._renderContent()
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity onPress = { this._onDismissed }>
|
||||
<Icon
|
||||
src = { IconCloseLarge }
|
||||
style = { styles.dismissIcon } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the notification's content. If the title or title key is present
|
||||
* it will be just the title. Otherwise it will fallback to description.
|
||||
*
|
||||
* @returns {Array<ReactElement>}
|
||||
* @private
|
||||
*/
|
||||
_renderContent() {
|
||||
const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey, concatText } = this.props;
|
||||
const titleText = title || (titleKey && t(titleKey, titleArguments));
|
||||
const description = this._getDescription();
|
||||
const titleConcat = [];
|
||||
|
||||
if (concatText) {
|
||||
titleConcat.push(titleText);
|
||||
}
|
||||
|
||||
if (description && description.length) {
|
||||
return [ ...titleConcat, ...description ].map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
numberOfLines = { maxLines }
|
||||
style = { styles.contentText }>
|
||||
{ replaceNonUnicodeEmojis(line) }
|
||||
</Text>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
numberOfLines = { maxLines }
|
||||
style = { styles.contentText } >
|
||||
{ titleText }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>;
|
||||
|
||||
_onDismissed: () => void;
|
||||
}
|
||||
|
||||
export default translate(Notification);
|
|
@ -0,0 +1,270 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Animated, Text, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import {
|
||||
Icon,
|
||||
IconCloseLarge,
|
||||
IconInfoCircle,
|
||||
IconUsers,
|
||||
IconWarning
|
||||
// @ts-ignore
|
||||
} from '../../../base/icons';
|
||||
import { colors } from '../../../base/ui/Tokens';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import IconButton from '../../../base/ui/components/native/IconButton';
|
||||
import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { replaceNonUnicodeEmojis } from '../../../chat/functions';
|
||||
import { NOTIFICATION_ICON } from '../../constants';
|
||||
import AbstractNotification, {
|
||||
type Props as AbstractNotificationProps
|
||||
// @ts-ignore
|
||||
} from '../AbstractNotification';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Secondary colors for notification icons.
|
||||
*
|
||||
* @type {{error, info, normal, success, warning}}
|
||||
*/
|
||||
|
||||
const ICON_COLOR = {
|
||||
error: colors.error06,
|
||||
normal: colors.primary06,
|
||||
success: colors.success05,
|
||||
warning: colors.warning05
|
||||
};
|
||||
|
||||
|
||||
type Props = AbstractNotificationProps & WithTranslation & {
|
||||
_participants: ArrayLike<any>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class Notification extends AbstractNotification<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Notification} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// @ts-ignore
|
||||
this.state = {
|
||||
notificationContainerAnimation: new Animated.Value(0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
Animated.timing(
|
||||
// @ts-ignore
|
||||
this.state.notificationContainerAnimation,
|
||||
{
|
||||
toValue: 1,
|
||||
duration: 500,
|
||||
useNativeDriver: true
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates action button configurations for the notification based on
|
||||
* notification appearance.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_mapAppearanceToButtons() {
|
||||
const {
|
||||
customActionHandler,
|
||||
customActionNameKey,
|
||||
customActionType
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
|
||||
if (customActionNameKey?.length && customActionHandler?.length && customActionType?.length) {
|
||||
return customActionNameKey?.map((customAction: string, index: number) => (
|
||||
<Button
|
||||
accessibilityLabel = { customAction }
|
||||
key = { index }
|
||||
labelKey = { customAction }
|
||||
mode = { BUTTON_MODES.TEXT }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { () => {
|
||||
if (customActionHandler[index]()) {
|
||||
this._onDismissed();
|
||||
}
|
||||
} }
|
||||
style = { styles.btn }
|
||||
type = { customActionType[index] } />
|
||||
));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Icon type component to be used, based on icon or appearance.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_getIcon() {
|
||||
const {
|
||||
appearance,
|
||||
icon
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
|
||||
let src;
|
||||
|
||||
switch (icon || appearance) {
|
||||
case NOTIFICATION_ICON.PARTICIPANT:
|
||||
src = IconInfoCircle;
|
||||
break;
|
||||
case NOTIFICATION_ICON.PARTICIPANTS:
|
||||
src = IconUsers;
|
||||
break;
|
||||
case NOTIFICATION_ICON.WARNING:
|
||||
src = IconWarning;
|
||||
break;
|
||||
default:
|
||||
src = IconInfoCircle;
|
||||
break;
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an icon component depending on the configured notification
|
||||
* appearance.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_mapAppearanceToIcon() {
|
||||
// @ts-ignore
|
||||
const { appearance } = this.props;
|
||||
// @ts-ignore
|
||||
const color = ICON_COLOR[appearance];
|
||||
|
||||
return (
|
||||
<View style = { styles.iconContainer }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 24 }
|
||||
src = { this._getIcon() } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
// @ts-ignore
|
||||
const { icon } = this.props;
|
||||
const contentColumnStyles = icon === NOTIFICATION_ICON.PARTICIPANTS
|
||||
? styles.contentColumn : styles.interactiveContentColumn;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.notification,
|
||||
{
|
||||
// @ts-ignore
|
||||
opacity: this.state.notificationContainerAnimation
|
||||
}
|
||||
] }>
|
||||
<View style = { contentColumnStyles }>
|
||||
{ this._mapAppearanceToIcon() }
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.contentContainer }>
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
<View style = { styles.btnContainer }>
|
||||
{ this._mapAppearanceToButtons() }
|
||||
</View>
|
||||
</View>
|
||||
<IconButton
|
||||
color = { BaseTheme.palette.icon04 }
|
||||
onPress = { this._onDismissed }
|
||||
src = { IconCloseLarge }
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the notification's content. If the title or title key is present
|
||||
* it will be just the title. Otherwise it will fallback to description.
|
||||
*
|
||||
* @returns {Array<ReactElement>}
|
||||
* @private
|
||||
*/
|
||||
_renderContent() {
|
||||
// @ts-ignore
|
||||
const { icon, t, title, titleArguments, titleKey } = this.props;
|
||||
const titleText = title || (titleKey && t(titleKey, titleArguments));
|
||||
const description = this._getDescription();
|
||||
const descriptionStyles = icon === NOTIFICATION_ICON.PARTICIPANTS
|
||||
? styles.contentTextInteractive : styles.contentText;
|
||||
|
||||
if (description?.length) {
|
||||
return (
|
||||
<>
|
||||
<Text style = { styles.contentTextTitle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
{
|
||||
description.map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
style = { descriptionStyles }>
|
||||
{ replaceNonUnicodeEmojis(line) }
|
||||
</Text>
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.contentTextTitle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>;
|
||||
|
||||
_onDismissed: () => void;
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
export default translate(Notification);
|
|
@ -1,14 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { hideNotification } from '../../actions';
|
||||
import { areThereNotifications } from '../../functions';
|
||||
|
||||
import Notification from './Notification';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
|
@ -21,12 +20,7 @@ type Props = {
|
|||
/**
|
||||
* Invoked to update the redux store in order to remove notifications.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Any custom styling applied to the notifications container.
|
||||
*/
|
||||
style: Object
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -65,7 +59,7 @@ class NotificationsContainer extends Component<Props> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets a timeout for the first notification (if applicable).
|
||||
* Sets a timeout (if applicable).
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -173,25 +167,22 @@ class NotificationsContainer extends Component<Props> {
|
|||
render() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
// Currently the native container displays only the topmost notification
|
||||
const theNotification = _notifications[0];
|
||||
|
||||
if (!theNotification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.notificationContainer,
|
||||
this.props.style
|
||||
] } >
|
||||
<Notification
|
||||
{ ...theNotification.props }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { theNotification.uid } />
|
||||
</View>
|
||||
<>
|
||||
{
|
||||
_notifications.map((notification, index) => {
|
||||
const { props, uid } = notification;
|
||||
|
||||
return (
|
||||
<Notification
|
||||
{ ...props }
|
||||
key = { index }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { uid } />
|
||||
);
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
// @flow
|
||||
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const contentColumn = {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
};
|
||||
|
||||
const contentText = {
|
||||
color: BaseTheme.palette.text04,
|
||||
marginLeft: BaseTheme.spacing[6]
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature notifications.
|
||||
|
@ -10,52 +21,84 @@ export default {
|
|||
/**
|
||||
* The content (left) column of the notification.
|
||||
*/
|
||||
interactiveContentColumn: {
|
||||
...contentColumn
|
||||
},
|
||||
|
||||
contentColumn: {
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
paddingLeft: 1.5 * BoxModel.padding
|
||||
...contentColumn,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* Test style of the notification.
|
||||
*/
|
||||
|
||||
contentContainer: {
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
contentText: {
|
||||
alignSelf: 'flex-start',
|
||||
color: ColorPalette.white
|
||||
...contentText,
|
||||
marginVertical: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
contentTextInteractive: {
|
||||
...contentText,
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
contentTextTitle: {
|
||||
...contentText,
|
||||
fontWeight: 'bold',
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss icon style.
|
||||
*/
|
||||
dismissIcon: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 20,
|
||||
padding: 1.5 * BoxModel.padding
|
||||
color: BaseTheme.palette.icon04,
|
||||
fontSize: 20
|
||||
},
|
||||
|
||||
/**
|
||||
* Outermost view of a single notification.
|
||||
*/
|
||||
notification: {
|
||||
backgroundColor: '#768898',
|
||||
display: 'flex',
|
||||
backgroundColor: BaseTheme.palette.ui12,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
flexDirection: 'row',
|
||||
minHeight: 48,
|
||||
marginTop: 0.5 * BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* Outermost container of a list of notifications.
|
||||
*/
|
||||
notificationContainer: {
|
||||
flexGrow: 0,
|
||||
justifyContent: 'flex-end'
|
||||
maxHeight: 104,
|
||||
height: 'auto',
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
marginHorizontal: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for the message.
|
||||
*/
|
||||
notificationContent: {
|
||||
flexDirection: 'column'
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
participantName: {
|
||||
color: BaseTheme.palette.text04,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
|
||||
iconContainer: {
|
||||
left: BaseTheme.spacing[1],
|
||||
position: 'absolute',
|
||||
top: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
btn: {
|
||||
marginLeft: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
btnContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginLeft: BaseTheme.spacing[1]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -89,41 +89,35 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
switch (action.type) {
|
||||
case CLEAR_NOTIFICATIONS: {
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
const _notifications = getNotifications(state);
|
||||
const _notifications = getNotifications(state);
|
||||
|
||||
for (const notification of _notifications) {
|
||||
if (timers.has(notification.uid)) {
|
||||
const timeout = timers.get(notification.uid);
|
||||
for (const notification of _notifications) {
|
||||
if (timers.has(notification.uid)) {
|
||||
const timeout = timers.get(notification.uid);
|
||||
|
||||
clearTimeout(timeout);
|
||||
timers.delete(notification.uid);
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
timers.delete(notification.uid);
|
||||
}
|
||||
timers.clear();
|
||||
}
|
||||
timers.clear();
|
||||
break;
|
||||
}
|
||||
case SHOW_NOTIFICATION: {
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
if (timers.has(action.uid)) {
|
||||
const timer = timers.get(action.uid);
|
||||
|
||||
clearTimeout(timer);
|
||||
timers.delete(action.uid);
|
||||
}
|
||||
|
||||
createTimeoutId(action, dispatch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HIDE_NOTIFICATION: {
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
if (timers.has(action.uid)) {
|
||||
const timer = timers.get(action.uid);
|
||||
|
||||
clearTimeout(timer);
|
||||
timers.delete(action.uid);
|
||||
}
|
||||
|
||||
createTimeoutId(action, dispatch);
|
||||
break;
|
||||
}
|
||||
case HIDE_NOTIFICATION: {
|
||||
const timer = timers.get(action.uid);
|
||||
|
||||
clearTimeout(timer);
|
||||
timers.delete(action.uid);
|
||||
break;
|
||||
}
|
||||
case PARTICIPANT_JOINED: {
|
||||
|
|
|
@ -90,12 +90,25 @@ ReducerRegistry.register<INotificationsState>('features/notifications',
|
|||
* queue.
|
||||
*/
|
||||
function _insertNotificationByPriority(notifications: INotification[], notification: INotification) {
|
||||
|
||||
// Create a copy to avoid mutation.
|
||||
const copyOfNotifications = notifications.slice();
|
||||
|
||||
// Get the index of any queued notification that has the same id as the new notification
|
||||
let insertAtLocation = copyOfNotifications.findIndex(
|
||||
(queuedNotification: INotification) =>
|
||||
queuedNotification?.uid === notification?.uid
|
||||
);
|
||||
|
||||
if (insertAtLocation !== -1) {
|
||||
copyOfNotifications.splice(insertAtLocation, 1, notification);
|
||||
|
||||
return copyOfNotifications;
|
||||
}
|
||||
|
||||
const newNotificationPriority
|
||||
= NOTIFICATION_TYPE_PRIORITIES[notification.props.appearance ?? ''] || 0;
|
||||
|
||||
// Default to putting the new notification at the end of the queue.
|
||||
let insertAtLocation = notifications.length;
|
||||
|
||||
// Find where to insert the new notification based on priority. Do not
|
||||
// insert at the front of the queue so that the user can finish acting on
|
||||
// any notification currently being read.
|
||||
|
@ -103,7 +116,7 @@ function _insertNotificationByPriority(notifications: INotification[], notificat
|
|||
const queuedNotification = notifications[i];
|
||||
const queuedNotificationPriority
|
||||
= NOTIFICATION_TYPE_PRIORITIES[queuedNotification.props.appearance ?? '']
|
||||
|| 0;
|
||||
|| 0;
|
||||
|
||||
if (queuedNotificationPriority < newNotificationPriority) {
|
||||
insertAtLocation = i;
|
||||
|
@ -111,9 +124,6 @@ function _insertNotificationByPriority(notifications: INotification[], notificat
|
|||
}
|
||||
}
|
||||
|
||||
// Create a copy to avoid mutation and insert the notification.
|
||||
const copyOfNotifications = notifications.slice();
|
||||
|
||||
copyOfNotifications.splice(insertAtLocation, 0, notification);
|
||||
|
||||
return copyOfNotifications;
|
||||
|
|
|
@ -5,6 +5,7 @@ export interface INotificationProps {
|
|||
concatText?: boolean;
|
||||
customActionHandler?: Function[];
|
||||
customActionNameKey?: string[];
|
||||
customActionType?: string[];
|
||||
description?: string | React.ReactNode;
|
||||
descriptionArguments?: Object;
|
||||
descriptionKey?: string;
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
PARTICIPANTS_PANE_CLOSE,
|
||||
PARTICIPANTS_PANE_OPEN
|
||||
} from './actionTypes';
|
||||
import { PARTICIPANTS_PANE_CLOSE } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Action to close the participants pane.
|
||||
|
@ -13,14 +10,3 @@ export const close = () => {
|
|||
type: PARTICIPANTS_PANE_CLOSE
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to open the participants pane.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const open = () => {
|
||||
return {
|
||||
type: PARTICIPANTS_PANE_OPEN
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import { IStore } from '../app/types';
|
||||
import { openSheet } from '../base/dialog/actions';
|
||||
import { navigate }
|
||||
// @ts-ignore
|
||||
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
// @ts-ignore
|
||||
import { SharedVideoMenu } from '../video-menu';
|
||||
// @ts-ignore
|
||||
|
@ -11,7 +16,7 @@ import ConnectionStatusComponent
|
|||
// @ts-ignore
|
||||
import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
|
||||
|
||||
import { SET_VOLUME } from './actionTypes';
|
||||
import { PARTICIPANTS_PANE_OPEN, SET_VOLUME } from './actionTypes';
|
||||
import RoomParticipantMenu from './components/native/RoomParticipantMenu';
|
||||
|
||||
export * from './actions.any';
|
||||
|
@ -88,3 +93,16 @@ export function showRoomParticipantMenu(room: Object, participantJid: string, pa
|
|||
participantJid,
|
||||
participantName });
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to open the participants pane.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const open = () => {
|
||||
navigate(screen.conference.participants);
|
||||
|
||||
return {
|
||||
type: PARTICIPANTS_PANE_OPEN
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1 +1,14 @@
|
|||
import { PARTICIPANTS_PANE_OPEN } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Action to open the participants pane.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const open = () => {
|
||||
return {
|
||||
type: PARTICIPANTS_PANE_OPEN
|
||||
};
|
||||
};
|
||||
|
|
|
@ -35,7 +35,6 @@ const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
|
|||
<View
|
||||
style = { styles.contextMenuItemSectionAvatar }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
participantId = { p.id }
|
||||
size = { 24 } />
|
||||
<Text style = { styles.contextMenuItemName }>
|
||||
|
|
|
@ -95,7 +95,6 @@ function ParticipantItem({
|
|||
onPress = { onPress }
|
||||
style = { styles.participantContent }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
displayName = { displayName }
|
||||
participantId = { participantID }
|
||||
size = { 32 } />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { handleLobbyChatInitialized } from '../chat/actions.any';
|
||||
import { handleLobbyChatInitialized } from '../chat/actions.web';
|
||||
import { approveKnockingParticipant, rejectKnockingParticipant } from '../lobby/actions.web';
|
||||
|
||||
interface IDrawerParticipant {
|
||||
|
|
|
@ -53,7 +53,7 @@ import { NoiseSuppressionButton } from '../../../noise-suppression/components';
|
|||
import {
|
||||
close as closeParticipantsPane,
|
||||
open as openParticipantsPane
|
||||
} from '../../../participants-pane/actions';
|
||||
} from '../../../participants-pane/actions.web';
|
||||
// @ts-ignore
|
||||
import { ParticipantsPaneButton } from '../../../participants-pane/components/web';
|
||||
import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
|
||||
|
|
|
@ -8,7 +8,7 @@ import { IconMessage } from '../../../base/icons';
|
|||
import { getParticipantById } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
import { openChat } from '../../../chat/';
|
||||
import { openChat } from '../../../chat/actions.web';
|
||||
import {
|
||||
type Props as AbstractProps
|
||||
} from '../../../chat/components/web/PrivateMessageButton';
|
||||
|
|
Loading…
Reference in New Issue