Просмотр исходного кода

feat(notifications): native UI updates (#12798)

* feat(notifications): native notifications UI updates
factor2
Calinteodor 2 лет назад
Родитель
Сommit
f8af9c4fae
Аккаунт пользователя с таким Email не найден
28 измененных файлов: 547 добавлений и 483 удалений
  1. 24
    15
      react/features/base/ui/components/native/Button.tsx
  2. 1
    1
      react/features/base/ui/components/native/IconButton.tsx
  3. 10
    0
      react/features/base/ui/components/native/buttonStyles.ts
  4. 3
    1
      react/features/base/ui/constants.any.ts
  5. 16
    2
      react/features/chat/actions.native.ts
  6. 1
    23
      react/features/chat/actions.web.ts
  7. 1
    1
      react/features/chat/components/native/PrivateMessageButton.js
  8. 8
    3
      react/features/conference/components/native/Conference.js
  9. 1
    1
      react/features/conference/components/web/RaisedHandsCountLabel.tsx
  10. 0
    173
      react/features/lobby/components/native/KnockingParticipantList.js
  11. 0
    1
      react/features/lobby/components/native/index.js
  12. 51
    61
      react/features/lobby/middleware.ts
  13. 5
    0
      react/features/notifications/components/AbstractNotification.js
  14. 0
    99
      react/features/notifications/components/native/Notification.js
  15. 270
    0
      react/features/notifications/components/native/Notification.tsx
  16. 18
    27
      react/features/notifications/components/native/NotificationsContainer.js
  17. 68
    25
      react/features/notifications/components/native/styles.js
  18. 16
    22
      react/features/notifications/middleware.ts
  19. 17
    7
      react/features/notifications/reducer.ts
  20. 1
    0
      react/features/notifications/types.ts
  21. 1
    15
      react/features/participants-pane/actions.any.ts
  22. 19
    1
      react/features/participants-pane/actions.native.ts
  23. 13
    0
      react/features/participants-pane/actions.web.ts
  24. 0
    1
      react/features/participants-pane/components/native/ContextMenuLobbyParticipantReject.js
  25. 0
    1
      react/features/participants-pane/components/native/ParticipantItem.js
  26. 1
    1
      react/features/participants-pane/hooks.web.ts
  27. 1
    1
      react/features/toolbox/components/web/Toolbox.tsx
  28. 1
    1
      react/features/video-menu/components/web/PrivateMessageMenuButton.js

+ 24
- 15
react/features/base/ui/components/native/Button.tsx Просмотреть файл

@@ -13,10 +13,12 @@ import { IButtonProps } from '../types';
13 13
 import styles from './buttonStyles';
14 14
 
15 15
 export interface IProps extends IButtonProps {
16
-    color?: string;
16
+    color?: string | undefined;
17 17
     contentStyle?: Object | undefined;
18 18
     labelStyle?: Object | undefined;
19
+    mode?: any;
19 20
     style?: Object | undefined;
21
+    useRippleColor?: boolean;
20 22
 }
21 23
 
22 24
 const Button: React.FC<IProps> = ({
@@ -27,31 +29,36 @@ const Button: React.FC<IProps> = ({
27 29
     icon,
28 30
     labelKey,
29 31
     labelStyle,
32
+    mode = BUTTON_MODES.CONTAINED,
30 33
     onClick: onPress,
31 34
     style,
32
-    type
35
+    type,
36
+    useRippleColor = true
33 37
 }: IProps) => {
34 38
     const { t } = useTranslation();
35
-    const { CONTAINED } = BUTTON_MODES;
36 39
     const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
40
+    const { CONTAINED, TEXT } = BUTTON_MODES;
41
+
42
+    const rippleColor
43
+        = useRippleColor ? BaseTheme.palette.action03Active : 'transparent';
37 44
 
38 45
     let buttonLabelStyles;
39 46
     let buttonStyles;
40 47
     let color;
41
-    let mode;
42 48
 
43 49
     if (type === PRIMARY) {
44
-        buttonLabelStyles = styles.buttonLabelPrimary;
45
-        color = BaseTheme.palette.action01;
46
-        mode = CONTAINED;
50
+        buttonLabelStyles = mode === TEXT
51
+            ? styles.buttonLabelPrimaryText
52
+            : styles.buttonLabelPrimary;
53
+        color = mode === CONTAINED && BaseTheme.palette.action01;
47 54
     } else if (type === SECONDARY) {
48 55
         buttonLabelStyles = styles.buttonLabelSecondary;
49
-        color = BaseTheme.palette.action02;
50
-        mode = CONTAINED;
56
+        color = mode === CONTAINED && BaseTheme.palette.action02;
51 57
     } else if (type === DESTRUCTIVE) {
52
-        color = BaseTheme.palette.actionDanger;
53
-        buttonLabelStyles = styles.buttonLabelDestructive;
54
-        mode = CONTAINED;
58
+        buttonLabelStyles = mode === TEXT
59
+            ? styles.buttonLabelDestructiveText
60
+            : styles.buttonLabelDestructive;
61
+        color = mode === CONTAINED && BaseTheme.palette.actionDanger;
55 62
     } else {
56 63
         color = buttonColor;
57 64
         buttonLabelStyles = styles.buttonLabel;
@@ -65,15 +72,17 @@ const Button: React.FC<IProps> = ({
65 72
     }
66 73
 
67 74
     if (type === TERTIARY) {
68
-        buttonLabelStyles
69
-            = disabled ? styles.buttonLabelTertiaryDisabled : styles.buttonLabelTertiary;
75
+        if (useRippleColor && disabled) {
76
+            buttonLabelStyles = styles.buttonLabelTertiaryDisabled;
77
+        }
78
+        buttonLabelStyles = styles.buttonLabelTertiary;
70 79
 
71 80
         return (
72 81
             <TouchableRipple
73 82
                 accessibilityLabel = { accessibilityLabel }
74 83
                 disabled = { disabled }
75 84
                 onPress = { onPress }
76
-                rippleColor = { BaseTheme.palette.action03Active }
85
+                rippleColor = { rippleColor }
77 86
                 style = { [
78 87
                     buttonStyles,
79 88
                     style

+ 1
- 1
react/features/base/ui/components/native/IconButton.tsx Просмотреть файл

@@ -36,7 +36,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
36 36
         iconButtonContainerStyles = styles.iconButtonContainerSecondary;
37 37
         rippleColor = BaseTheme.palette.action02;
38 38
     } else if (type === TERTIARY) {
39
-        color = BaseTheme.palette.icon01;
39
+        color = iconColor;
40 40
         iconButtonContainerStyles = styles.iconButtonContainer;
41 41
         rippleColor = BaseTheme.palette.action03;
42 42
     } else {

+ 10
- 0
react/features/base/ui/components/native/buttonStyles.ts Просмотреть файл

@@ -42,6 +42,11 @@ export default {
42 42
         color: BaseTheme.palette.text01
43 43
     },
44 44
 
45
+    buttonLabelPrimaryText: {
46
+        ...buttonLabel,
47
+        color: BaseTheme.palette.action01
48
+    },
49
+
45 50
     buttonLabelSecondary: {
46 51
         ...buttonLabel,
47 52
         color: BaseTheme.palette.text04
@@ -52,6 +57,11 @@ export default {
52 57
         color: BaseTheme.palette.text01
53 58
     },
54 59
 
60
+    buttonLabelDestructiveText: {
61
+        ...buttonLabel,
62
+        color: BaseTheme.palette.actionDanger
63
+    },
64
+
55 65
     buttonLabelTertiary: {
56 66
         ...buttonLabel,
57 67
         color: BaseTheme.palette.text01,

+ 3
- 1
react/features/base/ui/constants.any.ts Просмотреть файл

@@ -13,6 +13,8 @@ export enum BUTTON_TYPES {
13 13
  */
14 14
 export const BUTTON_MODES: {
15 15
     CONTAINED: 'contained';
16
+    TEXT: 'text';
16 17
 } = {
17
-    CONTAINED: 'contained'
18
+    CONTAINED: 'contained',
19
+    TEXT: 'text'
18 20
 };

+ 16
- 2
react/features/chat/actions.native.ts Просмотреть файл

@@ -1,3 +1,11 @@
1
+/* eslint-disable lines-around-comment, max-len */
2
+
3
+import { navigate }
4
+// @ts-ignore
5
+    from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
6
+// @ts-ignore
7
+import { screen } from '../mobile/navigation/routes';
8
+
1 9
 import { OPEN_CHAT } from './actionTypes';
2 10
 
3 11
 export * from './actions.any';
@@ -6,13 +14,19 @@ export * from './actions.any';
6 14
  * Displays the chat panel.
7 15
  *
8 16
  * @param {Object} participant - The recipient for the private chat.
17
+ * @param {boolean} disablePolls - Checks if polls are disabled.
9 18
  *
10 19
  * @returns {{
11
- *     participant: Participant,
20
+ *     participant: participant,
12 21
  *     type: OPEN_CHAT
13 22
  * }}
14 23
  */
15
-export function openChat(participant: Object) {
24
+export function openChat(participant: Object, disablePolls: boolean) {
25
+    if (disablePolls) {
26
+        navigate(screen.conference.chat);
27
+    }
28
+    navigate(screen.conference.chatandpolls.main);
29
+
16 30
     return {
17 31
         participant,
18 32
         type: OPEN_CHAT

+ 1
- 23
react/features/chat/actions.web.ts Просмотреть файл

@@ -1,7 +1,6 @@
1
-// @ts-expect-error
1
+// @ts-ignore
2 2
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
3 3
 import { IStore } from '../app/types';
4
-import { getParticipantById } from '../base/participants/functions';
5 4
 
6 5
 import { OPEN_CHAT } from './actionTypes';
7 6
 import { closeChat } from './actions.any';
@@ -26,27 +25,6 @@ export function openChat(participant?: Object) {
26 25
     };
27 26
 }
28 27
 
29
-/**
30
- * Displays the chat panel for a participant identified by an id.
31
- *
32
- * @param {string} id - The id of the participant.
33
- * @returns {{
34
- *     participant: Participant,
35
- *     type: OPEN_CHAT
36
- * }}
37
- */
38
-export function openChatById(id: string) {
39
-    return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
40
-        const participant = getParticipantById(getState(), id);
41
-
42
-        return dispatch({
43
-            participant,
44
-            type: OPEN_CHAT
45
-        });
46
-    };
47
-}
48
-
49
-
50 28
 /**
51 29
  * Toggles display of the chat panel.
52 30
  *

+ 1
- 1
react/features/chat/components/native/PrivateMessageButton.js Просмотреть файл

@@ -4,7 +4,7 @@ import { IconMessage, IconReply } from '../../../base/icons';
4 4
 import { getParticipantById } from '../../../base/participants';
5 5
 import { connect } from '../../../base/redux';
6 6
 import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
7
-import { handleLobbyChatInitialized, openChat } from '../../../chat/actions';
7
+import { handleLobbyChatInitialized, openChat } from '../../../chat/actions.native';
8 8
 import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
9 9
 import { screen } from '../../../mobile/navigation/routes';
10 10
 

+ 8
- 3
react/features/conference/components/native/Conference.js Просмотреть файл

@@ -24,7 +24,6 @@ import {
24 24
 import { CalleeInfoContainer } from '../../../invite';
25 25
 import { LargeVideo } from '../../../large-video';
26 26
 import { startKnocking } from '../../../lobby/actions.any';
27
-import { KnockingParticipantList } from '../../../lobby/components/native';
28 27
 import { getIsLobbyVisible } from '../../../lobby/functions';
29 28
 import { navigate }
30 29
     from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
@@ -433,7 +432,13 @@ class Conference extends AbstractConference<Props, State> {
433 432
 
434 433
                     <LonelyMeetingExperience />
435 434
 
436
-                    { _shouldDisplayTileView || <><Filmstrip /><Toolbox /></> }
435
+                    {
436
+                        _shouldDisplayTileView
437
+                        || <>
438
+                            <Filmstrip />
439
+                            <Toolbox />
440
+                        </>
441
+                    }
437 442
                 </View>
438 443
 
439 444
                 <SafeAreaView
@@ -463,10 +468,10 @@ class Conference extends AbstractConference<Props, State> {
463 468
                         <AlwaysOnLabels createOnPress = { this._createOnPress } />
464 469
                     </View>
465 470
                     { this._renderNotificationsContainer() }
466
-                    <KnockingParticipantList />
467 471
                 </SafeAreaView>
468 472
 
469 473
                 <TestConnectionInfo />
474
+
470 475
                 { this._renderConferenceNotification() }
471 476
 
472 477
                 {_shouldDisplayTileView && <Toolbox />}

+ 1
- 1
react/features/conference/components/web/RaisedHandsCountLabel.tsx Просмотреть файл

@@ -9,7 +9,7 @@ import Label from '../../../base/label/components/web/Label';
9 9
 // eslint-disable-next-line lines-around-comment
10 10
 // @ts-ignore
11 11
 import { Tooltip } from '../../../base/tooltip';
12
-import { open as openParticipantsPane } from '../../../participants-pane/actions';
12
+import { open as openParticipantsPane } from '../../../participants-pane/actions.web';
13 13
 
14 14
 const useStyles = makeStyles()(theme => {
15 15
     return {

+ 0
- 173
react/features/lobby/components/native/KnockingParticipantList.js Просмотреть файл

@@ -1,173 +0,0 @@
1
-import React, { PureComponent } from 'react';
2
-import { View } from 'react-native';
3
-
4
-import { translate } from '../../../base/i18n/functions';
5
-import { isLocalParticipantModerator } from '../../../base/participants/functions';
6
-import { connect } from '../../../base/redux';
7
-import Button from '../../../base/ui/components/native/Button';
8
-import { BUTTON_TYPES } from '../../../base/ui/constants.native';
9
-import { handleLobbyChatInitialized } from '../../../chat/actions.native';
10
-import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
11
-import { screen } from '../../../mobile/navigation/routes';
12
-import ParticipantItem
13
-    from '../../../participants-pane/components/native/ParticipantItem';
14
-import { setKnockingParticipantApproval } from '../../actions.native';
15
-import { getKnockingParticipants, getLobbyEnabled, showLobbyChatButton } from '../../functions';
16
-
17
-import styles from './styles';
18
-
19
-
20
-/**
21
- * Props type of the component.
22
- */
23
-export type Props = {
24
-
25
-    /**
26
-     * The list of participants.
27
-     */
28
-    _participants: Array<Object>,
29
-
30
-    /**
31
-     * True if the list should be rendered.
32
-     */
33
-    _visible: boolean,
34
-
35
-    /**
36
-     * True if the polls feature is disabled.
37
-     */
38
-    _isPollsDisabled: boolean,
39
-
40
-    /**
41
-     * Returns true if the lobby chat button should be shown.
42
-     */
43
-    _showChatButton: Function,
44
-
45
-    /**
46
-     * The Redux Dispatch function.
47
-     */
48
-    dispatch: Function
49
-};
50
-
51
-/**
52
- * Component to render a list for the actively knocking participants.
53
- */
54
-class KnockingParticipantList extends PureComponent<Props> {
55
-    /**
56
-     * Instantiates a new component.
57
-     *
58
-     * @param {Object} props - The read-only properties with which the new
59
-     * instance is to be initialized.
60
-     */
61
-    constructor(props: Props) {
62
-        super(props);
63
-
64
-        this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
65
-    }
66
-
67
-    /**
68
-     * Implements {@code PureComponent#render}.
69
-     *
70
-     * @inheritdoc
71
-     */
72
-    render() {
73
-        const { _participants, _visible, _showChatButton } = this.props;
74
-
75
-        if (!_visible) {
76
-            return null;
77
-        }
78
-
79
-        return (
80
-            <>
81
-                { _participants.map(p => (
82
-                    <View
83
-                        key = { p.id }
84
-                        style = { styles.knockingParticipantListEntry }>
85
-                        <ParticipantItem
86
-                            displayName = { p.name }
87
-                            isKnockingParticipant = { true }
88
-                            key = { p.id }
89
-                            participantID = { p.id }>
90
-                            <Button
91
-                                labelKey = { 'lobby.admit' }
92
-                                onClick = { this._onRespondToParticipant(p.id, true) }
93
-                                style = { styles.lobbyButtonAdmit }
94
-                                type = { BUTTON_TYPES.PRIMARY } />
95
-                            {
96
-                                _showChatButton(p)
97
-                                    ? (
98
-                                        <Button
99
-                                            labelKey = { 'lobby.chat' }
100
-                                            onClick = { this._onInitializeLobbyChat(p.id) }
101
-                                            style = { styles.lobbyButtonChat }
102
-                                            type = { BUTTON_TYPES.SECONDARY } />
103
-                                    ) : null
104
-                            }
105
-                            <Button
106
-                                labelKey = { 'lobby.reject' }
107
-                                onClick = { this._onRespondToParticipant(p.id, false) }
108
-                                style = { styles.lobbyButtonReject }
109
-                                type = { BUTTON_TYPES.DESTRUCTIVE } />
110
-                        </ParticipantItem>
111
-                    </View>
112
-                )) }
113
-            </>
114
-        );
115
-    }
116
-
117
-    _onRespondToParticipant: (string, boolean) => Function;
118
-
119
-    /**
120
-     * Function that constructs a callback for the response handler button.
121
-     *
122
-     * @param {string} id - The id of the knocking participant.
123
-     * @param {boolean} approve - The response for the knocking.
124
-     * @returns {Function}
125
-     */
126
-    _onRespondToParticipant(id, approve) {
127
-        return () => {
128
-            this.props.dispatch(setKnockingParticipantApproval(id, approve));
129
-        };
130
-    }
131
-
132
-    _onInitializeLobbyChat: (string) => Function;
133
-
134
-    /**
135
-     * Function that constructs a callback for the lobby chat button.
136
-     *
137
-     * @param {string} id - The id of the knocking participant.
138
-     * @returns {Function}
139
-     */
140
-    _onInitializeLobbyChat(id) {
141
-        return () => {
142
-            this.props.dispatch(handleLobbyChatInitialized(id));
143
-            if (this.props._isPollsDisabled) {
144
-                return navigate(screen.conference.chat);
145
-            }
146
-
147
-            navigate(screen.conference.chatandpolls.main);
148
-        };
149
-    }
150
-}
151
-
152
-/**
153
- * Maps part of the Redux state to the props of this component.
154
- *
155
- * @param {Object} state - The Redux state.
156
- * @returns {Props}
157
- */
158
-function _mapStateToProps(state): Object {
159
-    const lobbyEnabled = getLobbyEnabled(state);
160
-    const knockingParticipants = getKnockingParticipants(state);
161
-    const { disablePolls } = state['features/base/config'];
162
-
163
-    return {
164
-        _visible: lobbyEnabled && isLocalParticipantModerator(state),
165
-        _showChatButton: participant => showLobbyChatButton(participant)(state),
166
-        _isPollsDisabled: disablePolls,
167
-
168
-        // On mobile we only show a portion of the list for screen real estate reasons
169
-        _participants: knockingParticipants.slice(0, 2)
170
-    };
171
-}
172
-
173
-export default translate(connect(_mapStateToProps)(KnockingParticipantList));

+ 0
- 1
react/features/lobby/components/native/index.js Просмотреть файл

@@ -1,5 +1,4 @@
1 1
 // @flow
2 2
 
3
-export { default as KnockingParticipantList } from './KnockingParticipantList';
4 3
 export { default as LobbyScreen } from './LobbyScreen';
5 4
 export { default as LobbyChatScreen } from './LobbyChatScreen';

+ 51
- 61
react/features/lobby/middleware.ts Просмотреть файл

@@ -1,22 +1,40 @@
1
+/* eslint-disable lines-around-comment */
2
+
1 3
 import i18n from 'i18next';
2 4
 import { batch } from 'react-redux';
3 5
 import { AnyAction } from 'redux';
4 6
 
5 7
 import { IStore } from '../app/types';
6 8
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
7
-import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference/actionTypes';
9
+import {
10
+    CONFERENCE_FAILED,
11
+    CONFERENCE_JOINED
12
+} from '../base/conference/actionTypes';
8 13
 import { conferenceWillJoin } from '../base/conference/actions';
9
-import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
10
-import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/participants/functions';
14
+import {
15
+    JitsiConferenceErrors,
16
+    JitsiConferenceEvents
17
+} from '../base/lib-jitsi-meet';
18
+import {
19
+    getFirstLoadableAvatarUrl,
20
+    getParticipantDisplayName
21
+} from '../base/participants/functions';
11 22
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
12 23
 import StateListenerRegistry from '../base/redux/StateListenerRegistry';
13
-import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
24
+import {
25
+    playSound,
26
+    registerSound,
27
+    unregisterSound
28
+} from '../base/sounds/actions';
14 29
 import { isTestModeEnabled } from '../base/testing/functions';
15
-import { handleLobbyChatInitialized, removeLobbyChatParticipant } from '../chat/actions.any';
30
+import { BUTTON_TYPES } from '../base/ui/constants.any';
31
+// @ts-ignore
32
+import { openChat } from '../chat/actions';
16 33
 import {
17
-    hideNotification,
18
-    showNotification
19
-} from '../notifications/actions';
34
+    handleLobbyChatInitialized,
35
+    removeLobbyChatParticipant
36
+} from '../chat/actions.any';
37
+import { hideNotification, showNotification } from '../notifications/actions';
20 38
 import {
21 39
     LOBBY_NOTIFICATION_ID,
22 40
     NOTIFICATION_ICON,
@@ -28,7 +46,10 @@ import { open as openParticipantsPane } from '../participants-pane/actions';
28 46
 import { getParticipantsPaneOpen } from '../participants-pane/functions';
29 47
 import { shouldAutoKnock } from '../prejoin/functions';
30 48
 
31
-import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED, KNOCKING_PARTICIPANT_LEFT } from './actionTypes';
49
+import {
50
+    KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
51
+    KNOCKING_PARTICIPANT_LEFT
52
+} from './actionTypes';
32 53
 import {
33 54
     approveKnockingParticipant,
34 55
     hideLobbyScreen,
@@ -47,6 +68,7 @@ import { getKnockingParticipants, showLobbyChatButton } from './functions';
47 68
 import { KNOCKING_PARTICIPANT_FILE } from './sounds';
48 69
 import { IKnockingParticipant } from './types';
49 70
 
71
+
50 72
 MiddlewareRegistry.register(store => next => action => {
51 73
     switch (action.type) {
52 74
     case APP_WILL_MOUNT:
@@ -112,7 +134,7 @@ StateListenerRegistry.register(
112 134
 
113 135
                     const isParticipantsPaneVisible = getParticipantsPaneOpen(getState());
114 136
 
115
-                    if (navigator.product === 'ReactNative' || isParticipantsPaneVisible) {
137
+                    if (isParticipantsPaneVisible || navigator.product === 'ReactNative') {
116 138
                         return;
117 139
                     }
118 140
 
@@ -121,57 +143,6 @@ StateListenerRegistry.register(
121 143
                         getState
122 144
                     });
123 145
 
124
-                    let notificationTitle;
125
-                    let customActionNameKey;
126
-                    let customActionHandler;
127
-                    let descriptionKey;
128
-                    let icon;
129
-
130
-                    const knockingParticipants = getKnockingParticipants(getState());
131
-                    const firstParticipant = knockingParticipants[0];
132
-                    const showChat = showLobbyChatButton(firstParticipant)(getState());
133
-
134
-                    if (knockingParticipants.length > 1) {
135
-                        descriptionKey = 'notify.participantsWantToJoin';
136
-                        notificationTitle = i18n.t('notify.waitingParticipants', {
137
-                            waitingParticipants: knockingParticipants.length
138
-                        });
139
-                        icon = NOTIFICATION_ICON.PARTICIPANTS;
140
-                        customActionNameKey = [ 'notify.viewLobby' ];
141
-                        customActionHandler = [ () => batch(() => {
142
-                            dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
143
-                            dispatch(openParticipantsPane());
144
-                        }) ];
145
-                    } else {
146
-                        descriptionKey = 'notify.participantWantsToJoin';
147
-                        notificationTitle = firstParticipant.name;
148
-                        icon = NOTIFICATION_ICON.PARTICIPANT;
149
-                        customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
150
-                        customActionHandler = [ () => batch(() => {
151
-                            dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
152
-                            dispatch(approveKnockingParticipant(firstParticipant.id));
153
-                        }),
154
-                        () => batch(() => {
155
-                            dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
156
-                            dispatch(rejectKnockingParticipant(firstParticipant.id));
157
-                        }) ];
158
-                        if (showChat) {
159
-                            customActionNameKey.splice(1, 0, 'lobby.chat');
160
-                            customActionHandler.splice(1, 0, () => batch(() => {
161
-                                dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
162
-                                dispatch(handleLobbyChatInitialized(firstParticipant.id));
163
-                            }));
164
-                        }
165
-                    }
166
-                    dispatch(showNotification({
167
-                        title: notificationTitle,
168
-                        descriptionKey,
169
-                        uid: LOBBY_NOTIFICATION_ID,
170
-                        customActionNameKey,
171
-                        customActionHandler,
172
-                        icon
173
-                    }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
174
-
175 146
                     if (typeof APP !== 'undefined') {
176 147
                         APP.API.notifyKnockingParticipant({
177 148
                             id,
@@ -227,16 +198,20 @@ function _handleLobbyNotification(store: IStore) {
227 198
     let notificationTitle;
228 199
     let customActionNameKey;
229 200
     let customActionHandler;
201
+    let customActionType;
230 202
     let descriptionKey;
231 203
     let icon;
232 204
 
233 205
     if (knockingParticipants.length === 1) {
234 206
         const firstParticipant = knockingParticipants[0];
207
+        const { disablePolls } = getState()['features/base/config'];
208
+        const showChat = showLobbyChatButton(firstParticipant)(getState());
235 209
 
236 210
         descriptionKey = 'notify.participantWantsToJoin';
237 211
         notificationTitle = firstParticipant.name;
238 212
         icon = NOTIFICATION_ICON.PARTICIPANT;
239 213
         customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
214
+        customActionType = [ BUTTON_TYPES.PRIMARY, BUTTON_TYPES.DESTRUCTIVE ];
240 215
         customActionHandler = [ () => batch(() => {
241 216
             dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
242 217
             dispatch(approveKnockingParticipant(firstParticipant.id));
@@ -245,6 +220,18 @@ function _handleLobbyNotification(store: IStore) {
245 220
             dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
246 221
             dispatch(rejectKnockingParticipant(firstParticipant.id));
247 222
         }) ];
223
+
224
+        // This checks if lobby chat button is available
225
+        // and, if so, it adds it to the customActionNameKey array
226
+        if (showChat) {
227
+            customActionNameKey.splice(1, 0, 'lobby.chat');
228
+            customActionType.splice(1, 0, BUTTON_TYPES.SECONDARY);
229
+            customActionHandler.splice(1, 0, () => batch(() => {
230
+                dispatch(handleLobbyChatInitialized(firstParticipant.id));
231
+                // @ts-ignore
232
+                dispatch(openChat(disablePolls));
233
+            }));
234
+        }
248 235
     } else {
249 236
         descriptionKey = 'notify.participantsWantToJoin';
250 237
         notificationTitle = i18n.t('notify.waitingParticipants', {
@@ -252,16 +239,19 @@ function _handleLobbyNotification(store: IStore) {
252 239
         });
253 240
         icon = NOTIFICATION_ICON.PARTICIPANTS;
254 241
         customActionNameKey = [ 'notify.viewLobby' ];
242
+        customActionType = [ BUTTON_TYPES.PRIMARY ];
255 243
         customActionHandler = [ () => batch(() => {
256 244
             dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
257 245
             dispatch(openParticipantsPane());
258 246
         }) ];
259 247
     }
248
+
260 249
     dispatch(showNotification({
261 250
         title: notificationTitle,
262 251
         descriptionKey,
263 252
         uid: LOBBY_NOTIFICATION_ID,
264 253
         customActionNameKey,
254
+        customActionType,
265 255
         customActionHandler,
266 256
         icon
267 257
     }, NOTIFICATION_TIMEOUT_TYPE.STICKY));

+ 5
- 0
react/features/notifications/components/AbstractNotification.js Просмотреть файл

@@ -27,6 +27,11 @@ export type Props = {
27 27
      */
28 28
     customActionNameKey: string[],
29 29
 
30
+    /**
31
+     * The type of button.
32
+     */
33
+    customActionType: ?string[],
34
+
30 35
     /**
31 36
      * The text to display in the body of the notification. If not passed
32 37
      * in, the passed in descriptionKey will be used.

+ 0
- 99
react/features/notifications/components/native/Notification.js Просмотреть файл

@@ -1,99 +0,0 @@
1
-// @flow
2
-
3
-import React from 'react';
4
-import { Text, TouchableOpacity, View } from 'react-native';
5
-
6
-import { translate } from '../../../base/i18n';
7
-import { Icon, IconCloseLarge } from '../../../base/icons';
8
-import { replaceNonUnicodeEmojis } from '../../../chat/functions';
9
-import AbstractNotification, {
10
-    type Props
11
-} from '../AbstractNotification';
12
-
13
-import styles from './styles';
14
-
15
-/**
16
- * Default value for the maxLines prop.
17
- *
18
- * @type {number}
19
- */
20
-const DEFAULT_MAX_LINES = 2;
21
-
22
-/**
23
- * Implements a React {@link Component} to display a notification.
24
- *
25
- * @augments Component
26
- */
27
-class Notification extends AbstractNotification<Props> {
28
-    /**
29
-     * Implements React's {@link Component#render()}.
30
-     *
31
-     * @inheritdoc
32
-     * @returns {ReactElement}
33
-     */
34
-    render() {
35
-        return (
36
-            <View
37
-                pointerEvents = 'box-none'
38
-                style = { styles.notification }>
39
-                <View style = { styles.contentColumn }>
40
-                    <View
41
-                        pointerEvents = 'box-none'
42
-                        style = { styles.notificationContent }>
43
-                        {
44
-                            this._renderContent()
45
-                        }
46
-                    </View>
47
-                </View>
48
-                <TouchableOpacity onPress = { this._onDismissed }>
49
-                    <Icon
50
-                        src = { IconCloseLarge }
51
-                        style = { styles.dismissIcon } />
52
-                </TouchableOpacity>
53
-            </View>
54
-        );
55
-    }
56
-
57
-    /**
58
-     * Renders the notification's content. If the title or title key is present
59
-     * it will be just the title. Otherwise it will fallback to description.
60
-     *
61
-     * @returns {Array<ReactElement>}
62
-     * @private
63
-     */
64
-    _renderContent() {
65
-        const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey, concatText } = this.props;
66
-        const titleText = title || (titleKey && t(titleKey, titleArguments));
67
-        const description = this._getDescription();
68
-        const titleConcat = [];
69
-
70
-        if (concatText) {
71
-            titleConcat.push(titleText);
72
-        }
73
-
74
-        if (description && description.length) {
75
-            return [ ...titleConcat, ...description ].map((line, index) => (
76
-                <Text
77
-                    key = { index }
78
-                    numberOfLines = { maxLines }
79
-                    style = { styles.contentText }>
80
-                    { replaceNonUnicodeEmojis(line) }
81
-                </Text>
82
-            ));
83
-        }
84
-
85
-        return (
86
-            <Text
87
-                numberOfLines = { maxLines }
88
-                style = { styles.contentText } >
89
-                { titleText }
90
-            </Text>
91
-        );
92
-    }
93
-
94
-    _getDescription: () => Array<string>;
95
-
96
-    _onDismissed: () => void;
97
-}
98
-
99
-export default translate(Notification);

+ 270
- 0
react/features/notifications/components/native/Notification.tsx Просмотреть файл

@@ -0,0 +1,270 @@
1
+/* eslint-disable lines-around-comment */
2
+
3
+import React from 'react';
4
+import { WithTranslation } from 'react-i18next';
5
+import { Animated, Text, View } from 'react-native';
6
+
7
+import { translate } from '../../../base/i18n/functions';
8
+import {
9
+    Icon,
10
+    IconCloseLarge,
11
+    IconInfoCircle,
12
+    IconUsers,
13
+    IconWarning
14
+    // @ts-ignore
15
+} from '../../../base/icons';
16
+import { colors } from '../../../base/ui/Tokens';
17
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
18
+import Button from '../../../base/ui/components/native/Button';
19
+import IconButton from '../../../base/ui/components/native/IconButton';
20
+import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
21
+import { replaceNonUnicodeEmojis } from '../../../chat/functions';
22
+import { NOTIFICATION_ICON } from '../../constants';
23
+import AbstractNotification, {
24
+    type Props as AbstractNotificationProps
25
+    // @ts-ignore
26
+} from '../AbstractNotification';
27
+
28
+// @ts-ignore
29
+import styles from './styles';
30
+
31
+
32
+/**
33
+ * Secondary colors for notification icons.
34
+ *
35
+ * @type {{error, info, normal, success, warning}}
36
+ */
37
+
38
+const ICON_COLOR = {
39
+    error: colors.error06,
40
+    normal: colors.primary06,
41
+    success: colors.success05,
42
+    warning: colors.warning05
43
+};
44
+
45
+
46
+type Props = AbstractNotificationProps & WithTranslation & {
47
+    _participants: ArrayLike<any>;
48
+};
49
+
50
+
51
+/**
52
+ * Implements a React {@link Component} to display a notification.
53
+ *
54
+ * @augments Component
55
+ */
56
+class Notification extends AbstractNotification<Props> {
57
+
58
+    /**
59
+     * Initializes a new {@code Notification} instance.
60
+     *
61
+     * @inheritdoc
62
+     */
63
+    constructor(props: Props) {
64
+        super(props);
65
+
66
+        // @ts-ignore
67
+        this.state = {
68
+            notificationContainerAnimation: new Animated.Value(0)
69
+        };
70
+    }
71
+
72
+    /**
73
+     * Implements React's {@link Component#componentDidMount()}.
74
+     *
75
+     * @inheritdoc
76
+     */
77
+    componentDidMount() {
78
+        Animated.timing(
79
+            // @ts-ignore
80
+            this.state.notificationContainerAnimation,
81
+            {
82
+                toValue: 1,
83
+                duration: 500,
84
+                useNativeDriver: true
85
+            })
86
+            .start();
87
+    }
88
+
89
+    /**
90
+     * Creates action button configurations for the notification based on
91
+     * notification appearance.
92
+     *
93
+     * @private
94
+     * @returns {Object[]}
95
+     */
96
+    _mapAppearanceToButtons() {
97
+        const {
98
+            customActionHandler,
99
+            customActionNameKey,
100
+            customActionType
101
+            // @ts-ignore
102
+        } = this.props;
103
+
104
+        if (customActionNameKey?.length && customActionHandler?.length && customActionType?.length) {
105
+            return customActionNameKey?.map((customAction: string, index: number) => (
106
+                <Button
107
+                    accessibilityLabel = { customAction }
108
+                    key = { index }
109
+                    labelKey = { customAction }
110
+                    mode = { BUTTON_MODES.TEXT }
111
+                    // eslint-disable-next-line react/jsx-no-bind
112
+                    onClick = { () => {
113
+                        if (customActionHandler[index]()) {
114
+                            this._onDismissed();
115
+                        }
116
+                    } }
117
+                    style = { styles.btn }
118
+                    type = { customActionType[index] } />
119
+            ));
120
+        }
121
+
122
+        return [];
123
+    }
124
+
125
+    /**
126
+     * Returns the Icon type component to be used, based on icon or appearance.
127
+     *
128
+     * @returns {ReactElement}
129
+     */
130
+    _getIcon() {
131
+        const {
132
+            appearance,
133
+            icon
134
+            // @ts-ignore
135
+        } = this.props;
136
+
137
+        let src;
138
+
139
+        switch (icon || appearance) {
140
+        case NOTIFICATION_ICON.PARTICIPANT:
141
+            src = IconInfoCircle;
142
+            break;
143
+        case NOTIFICATION_ICON.PARTICIPANTS:
144
+            src = IconUsers;
145
+            break;
146
+        case NOTIFICATION_ICON.WARNING:
147
+            src = IconWarning;
148
+            break;
149
+        default:
150
+            src = IconInfoCircle;
151
+            break;
152
+        }
153
+
154
+        return src;
155
+    }
156
+
157
+    /**
158
+     * Creates an icon component depending on the configured notification
159
+     * appearance.
160
+     *
161
+     * @private
162
+     * @returns {ReactElement}
163
+     */
164
+    _mapAppearanceToIcon() {
165
+        // @ts-ignore
166
+        const { appearance } = this.props;
167
+        // @ts-ignore
168
+        const color = ICON_COLOR[appearance];
169
+
170
+        return (
171
+            <View style = { styles.iconContainer }>
172
+                <Icon
173
+                    color = { color }
174
+                    size = { 24 }
175
+                    src = { this._getIcon() } />
176
+            </View>
177
+        );
178
+    }
179
+
180
+    /**
181
+     * Implements React's {@link Component#render()}.
182
+     *
183
+     * @inheritdoc
184
+     * @returns {ReactElement}
185
+     */
186
+    render() {
187
+        // @ts-ignore
188
+        const { icon } = this.props;
189
+        const contentColumnStyles = icon === NOTIFICATION_ICON.PARTICIPANTS
190
+            ? styles.contentColumn : styles.interactiveContentColumn;
191
+
192
+        return (
193
+            <Animated.View
194
+                pointerEvents = 'box-none'
195
+                style = { [
196
+                    styles.notification,
197
+                    {
198
+                        // @ts-ignore
199
+                        opacity: this.state.notificationContainerAnimation
200
+                    }
201
+                ] }>
202
+                <View style = { contentColumnStyles }>
203
+                    { this._mapAppearanceToIcon() }
204
+                    <View
205
+                        pointerEvents = 'box-none'
206
+                        style = { styles.contentContainer }>
207
+                        { this._renderContent() }
208
+                    </View>
209
+                    <View style = { styles.btnContainer }>
210
+                        { this._mapAppearanceToButtons() }
211
+                    </View>
212
+                </View>
213
+                <IconButton
214
+                    color = { BaseTheme.palette.icon04 }
215
+                    onPress = { this._onDismissed }
216
+                    src = { IconCloseLarge }
217
+                    type = { BUTTON_TYPES.TERTIARY } />
218
+            </Animated.View>
219
+        );
220
+    }
221
+
222
+    /**
223
+     * Renders the notification's content. If the title or title key is present
224
+     * it will be just the title. Otherwise it will fallback to description.
225
+     *
226
+     * @returns {Array<ReactElement>}
227
+     * @private
228
+     */
229
+    _renderContent() {
230
+        // @ts-ignore
231
+        const { icon, t, title, titleArguments, titleKey } = this.props;
232
+        const titleText = title || (titleKey && t(titleKey, titleArguments));
233
+        const description = this._getDescription();
234
+        const descriptionStyles = icon === NOTIFICATION_ICON.PARTICIPANTS
235
+            ? styles.contentTextInteractive : styles.contentText;
236
+
237
+        if (description?.length) {
238
+            return (
239
+                <>
240
+                    <Text style = { styles.contentTextTitle }>
241
+                        { titleText }
242
+                    </Text>
243
+                    {
244
+                        description.map((line, index) => (
245
+                            <Text
246
+                                key = { index }
247
+                                style = { descriptionStyles }>
248
+                                { replaceNonUnicodeEmojis(line) }
249
+                            </Text>
250
+                        ))
251
+                    }
252
+                </>
253
+            );
254
+        }
255
+
256
+        return (
257
+            <Text style = { styles.contentTextTitle }>
258
+                { titleText }
259
+            </Text>
260
+        );
261
+    }
262
+
263
+    _getDescription: () => Array<string>;
264
+
265
+    _onDismissed: () => void;
266
+}
267
+
268
+
269
+// @ts-ignore
270
+export default translate(Notification);

+ 18
- 27
react/features/notifications/components/native/NotificationsContainer.js Просмотреть файл

@@ -1,14 +1,13 @@
1 1
 // @flow
2 2
 
3 3
 import React, { Component } from 'react';
4
-import { View } from 'react-native';
5 4
 
6 5
 import { connect } from '../../../base/redux';
7 6
 import { hideNotification } from '../../actions';
8 7
 import { areThereNotifications } from '../../functions';
9 8
 
10 9
 import Notification from './Notification';
11
-import styles from './styles';
10
+
12 11
 
13 12
 type Props = {
14 13
 
@@ -21,12 +20,7 @@ type Props = {
21 20
     /**
22 21
      * Invoked to update the redux store in order to remove notifications.
23 22
      */
24
-     dispatch: Function,
25
-
26
-    /**
27
-     * Any custom styling applied to the notifications container.
28
-     */
29
-    style: Object
23
+     dispatch: Function
30 24
 };
31 25
 
32 26
 /**
@@ -65,7 +59,7 @@ class NotificationsContainer extends Component<Props> {
65 59
     }
66 60
 
67 61
     /**
68
-     * Sets a timeout for the first notification (if applicable).
62
+     * Sets a timeout (if applicable).
69 63
      *
70 64
      * @inheritdoc
71 65
      */
@@ -173,25 +167,22 @@ class NotificationsContainer extends Component<Props> {
173 167
     render() {
174 168
         const { _notifications } = this.props;
175 169
 
176
-        // Currently the native container displays only the topmost notification
177
-        const theNotification = _notifications[0];
178
-
179
-        if (!theNotification) {
180
-            return null;
181
-        }
182
-
183 170
         return (
184
-            <View
185
-                pointerEvents = 'box-none'
186
-                style = { [
187
-                    styles.notificationContainer,
188
-                    this.props.style
189
-                ] } >
190
-                <Notification
191
-                    { ...theNotification.props }
192
-                    onDismissed = { this._onDismissed }
193
-                    uid = { theNotification.uid } />
194
-            </View>
171
+            <>
172
+                {
173
+                    _notifications.map((notification, index) => {
174
+                        const { props, uid } = notification;
175
+
176
+                        return (
177
+                            <Notification
178
+                                { ...props }
179
+                                key = { index }
180
+                                onDismissed = { this._onDismissed }
181
+                                uid = { uid } />
182
+                        );
183
+                    })
184
+                }
185
+            </>
195 186
         );
196 187
     }
197 188
 

+ 68
- 25
react/features/notifications/components/native/styles.js Просмотреть файл

@@ -1,6 +1,17 @@
1 1
 // @flow
2 2
 
3
-import { BoxModel, ColorPalette } from '../../../base/styles';
3
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
4
+
5
+const contentColumn = {
6
+    flex: 1,
7
+    flexDirection: 'column',
8
+    marginLeft: BaseTheme.spacing[2]
9
+};
10
+
11
+const contentText = {
12
+    color: BaseTheme.palette.text04,
13
+    marginLeft: BaseTheme.spacing[6]
14
+};
4 15
 
5 16
 /**
6 17
  * The styles of the React {@code Components} of the feature notifications.
@@ -10,52 +21,84 @@ export default {
10 21
     /**
11 22
      * The content (left) column of the notification.
12 23
      */
24
+    interactiveContentColumn: {
25
+        ...contentColumn
26
+    },
27
+
13 28
     contentColumn: {
14
-        justifyContent: 'center',
15
-        flex: 1,
16
-        flexDirection: 'column',
17
-        paddingLeft: 1.5 * BoxModel.padding
29
+        ...contentColumn,
30
+        justifyContent: 'center'
18 31
     },
19 32
 
20 33
     /**
21 34
      * Test style of the notification.
22 35
      */
36
+
37
+    contentContainer: {
38
+        marginTop: BaseTheme.spacing[2]
39
+    },
40
+
23 41
     contentText: {
24
-        alignSelf: 'flex-start',
25
-        color: ColorPalette.white
42
+        ...contentText,
43
+        marginVertical: BaseTheme.spacing[1]
44
+    },
45
+
46
+    contentTextInteractive: {
47
+        ...contentText,
48
+        marginTop: BaseTheme.spacing[1]
49
+    },
50
+
51
+    contentTextTitle: {
52
+        ...contentText,
53
+        fontWeight: 'bold',
54
+        marginTop: BaseTheme.spacing[1]
26 55
     },
27 56
 
28 57
     /**
29 58
      * Dismiss icon style.
30 59
      */
31 60
     dismissIcon: {
32
-        color: ColorPalette.white,
33
-        fontSize: 20,
34
-        padding: 1.5 * BoxModel.padding
61
+        color: BaseTheme.palette.icon04,
62
+        fontSize: 20
35 63
     },
36 64
 
37
-    /**
38
-     * Outermost view of a single notification.
39
-     */
40 65
     notification: {
41
-        backgroundColor: '#768898',
66
+        display: 'flex',
67
+        backgroundColor: BaseTheme.palette.ui12,
68
+        borderRadius: BaseTheme.shape.borderRadius,
42 69
         flexDirection: 'row',
43
-        minHeight: 48,
44
-        marginTop: 0.5 * BoxModel.margin
45
-    },
46
-
47
-    /**
48
-     * Outermost container of a list of notifications.
49
-     */
50
-    notificationContainer: {
51
-        flexGrow: 0,
52
-        justifyContent: 'flex-end'
70
+        maxHeight: 104,
71
+        height: 'auto',
72
+        marginBottom: BaseTheme.spacing[3],
73
+        marginHorizontal: BaseTheme.spacing[2]
53 74
     },
54 75
 
55 76
     /**
56 77
      * Wrapper for the message.
57 78
      */
58 79
     notificationContent: {
59
-        flexDirection: 'column'
80
+        alignItems: 'center',
81
+        flexDirection: 'row'
82
+    },
83
+
84
+    participantName: {
85
+        color: BaseTheme.palette.text04,
86
+        overflow: 'hidden'
87
+    },
88
+
89
+    iconContainer: {
90
+        left: BaseTheme.spacing[1],
91
+        position: 'absolute',
92
+        top: BaseTheme.spacing[2]
93
+    },
94
+
95
+    btn: {
96
+        marginLeft: BaseTheme.spacing[4]
97
+    },
98
+
99
+    btnContainer: {
100
+        display: 'flex',
101
+        flexDirection: 'row',
102
+        marginLeft: BaseTheme.spacing[1]
60 103
     }
61 104
 };

+ 16
- 22
react/features/notifications/middleware.ts Просмотреть файл

@@ -89,41 +89,35 @@ MiddlewareRegistry.register(store => next => action => {
89 89
 
90 90
     switch (action.type) {
91 91
     case CLEAR_NOTIFICATIONS: {
92
-        if (navigator.product !== 'ReactNative') {
93
-            const _notifications = getNotifications(state);
92
+        const _notifications = getNotifications(state);
94 93
 
95
-            for (const notification of _notifications) {
96
-                if (timers.has(notification.uid)) {
97
-                    const timeout = timers.get(notification.uid);
94
+        for (const notification of _notifications) {
95
+            if (timers.has(notification.uid)) {
96
+                const timeout = timers.get(notification.uid);
98 97
 
99
-                    clearTimeout(timeout);
100
-                    timers.delete(notification.uid);
101
-                }
98
+                clearTimeout(timeout);
99
+                timers.delete(notification.uid);
102 100
             }
103
-            timers.clear();
104 101
         }
102
+        timers.clear();
105 103
         break;
106 104
     }
107 105
     case SHOW_NOTIFICATION: {
108
-        if (navigator.product !== 'ReactNative') {
109
-            if (timers.has(action.uid)) {
110
-                const timer = timers.get(action.uid);
111
-
112
-                clearTimeout(timer);
113
-                timers.delete(action.uid);
114
-            }
106
+        if (timers.has(action.uid)) {
107
+            const timer = timers.get(action.uid);
115 108
 
116
-            createTimeoutId(action, dispatch);
109
+            clearTimeout(timer);
110
+            timers.delete(action.uid);
117 111
         }
112
+
113
+        createTimeoutId(action, dispatch);
118 114
         break;
119 115
     }
120 116
     case HIDE_NOTIFICATION: {
121
-        if (navigator.product !== 'ReactNative') {
122
-            const timer = timers.get(action.uid);
117
+        const timer = timers.get(action.uid);
123 118
 
124
-            clearTimeout(timer);
125
-            timers.delete(action.uid);
126
-        }
119
+        clearTimeout(timer);
120
+        timers.delete(action.uid);
127 121
         break;
128 122
     }
129 123
     case PARTICIPANT_JOINED: {

+ 17
- 7
react/features/notifications/reducer.ts Просмотреть файл

@@ -90,12 +90,25 @@ ReducerRegistry.register<INotificationsState>('features/notifications',
90 90
  * queue.
91 91
  */
92 92
 function _insertNotificationByPriority(notifications: INotification[], notification: INotification) {
93
+
94
+    // Create a copy to avoid mutation.
95
+    const copyOfNotifications = notifications.slice();
96
+
97
+    // Get the index of any queued notification that has the same id as the new notification
98
+    let insertAtLocation = copyOfNotifications.findIndex(
99
+            (queuedNotification: INotification) =>
100
+                queuedNotification?.uid === notification?.uid
101
+    );
102
+
103
+    if (insertAtLocation !== -1) {
104
+        copyOfNotifications.splice(insertAtLocation, 1, notification);
105
+
106
+        return copyOfNotifications;
107
+    }
108
+
93 109
     const newNotificationPriority
94 110
         = NOTIFICATION_TYPE_PRIORITIES[notification.props.appearance ?? ''] || 0;
95 111
 
96
-    // Default to putting the new notification at the end of the queue.
97
-    let insertAtLocation = notifications.length;
98
-
99 112
     // Find where to insert the new notification based on priority. Do not
100 113
     // insert at the front of the queue so that the user can finish acting on
101 114
     // any notification currently being read.
@@ -103,7 +116,7 @@ function _insertNotificationByPriority(notifications: INotification[], notificat
103 116
         const queuedNotification = notifications[i];
104 117
         const queuedNotificationPriority
105 118
             = NOTIFICATION_TYPE_PRIORITIES[queuedNotification.props.appearance ?? '']
106
-                || 0;
119
+            || 0;
107 120
 
108 121
         if (queuedNotificationPriority < newNotificationPriority) {
109 122
             insertAtLocation = i;
@@ -111,9 +124,6 @@ function _insertNotificationByPriority(notifications: INotification[], notificat
111 124
         }
112 125
     }
113 126
 
114
-    // Create a copy to avoid mutation and insert the notification.
115
-    const copyOfNotifications = notifications.slice();
116
-
117 127
     copyOfNotifications.splice(insertAtLocation, 0, notification);
118 128
 
119 129
     return copyOfNotifications;

+ 1
- 0
react/features/notifications/types.ts Просмотреть файл

@@ -5,6 +5,7 @@ export interface INotificationProps {
5 5
     concatText?: boolean;
6 6
     customActionHandler?: Function[];
7 7
     customActionNameKey?: string[];
8
+    customActionType?: string[];
8 9
     description?: string | React.ReactNode;
9 10
     descriptionArguments?: Object;
10 11
     descriptionKey?: string;

+ 1
- 15
react/features/participants-pane/actions.any.ts Просмотреть файл

@@ -1,7 +1,4 @@
1
-import {
2
-    PARTICIPANTS_PANE_CLOSE,
3
-    PARTICIPANTS_PANE_OPEN
4
-} from './actionTypes';
1
+import { PARTICIPANTS_PANE_CLOSE } from './actionTypes';
5 2
 
6 3
 /**
7 4
  * Action to close the participants pane.
@@ -13,14 +10,3 @@ export const close = () => {
13 10
         type: PARTICIPANTS_PANE_CLOSE
14 11
     };
15 12
 };
16
-
17
-/**
18
- * Action to open the participants pane.
19
- *
20
- * @returns {Object}
21
- */
22
-export const open = () => {
23
-    return {
24
-        type: PARTICIPANTS_PANE_OPEN
25
-    };
26
-};

+ 19
- 1
react/features/participants-pane/actions.native.ts Просмотреть файл

@@ -1,6 +1,11 @@
1 1
 /* eslint-disable lines-around-comment */
2 2
 import { IStore } from '../app/types';
3 3
 import { openSheet } from '../base/dialog/actions';
4
+import { navigate }
5
+// @ts-ignore
6
+    from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
7
+// @ts-ignore
8
+import { screen } from '../mobile/navigation/routes';
4 9
 // @ts-ignore
5 10
 import { SharedVideoMenu } from '../video-menu';
6 11
 // @ts-ignore
@@ -11,7 +16,7 @@ import ConnectionStatusComponent
11 16
 // @ts-ignore
12 17
 import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
13 18
 
14
-import { SET_VOLUME } from './actionTypes';
19
+import { PARTICIPANTS_PANE_OPEN, SET_VOLUME } from './actionTypes';
15 20
 import RoomParticipantMenu from './components/native/RoomParticipantMenu';
16 21
 
17 22
 export * from './actions.any';
@@ -88,3 +93,16 @@ export function showRoomParticipantMenu(room: Object, participantJid: string, pa
88 93
         participantJid,
89 94
         participantName });
90 95
 }
96
+
97
+/**
98
+ * Action to open the participants pane.
99
+ *
100
+ * @returns {Object}
101
+ */
102
+export const open = () => {
103
+    navigate(screen.conference.participants);
104
+
105
+    return {
106
+        type: PARTICIPANTS_PANE_OPEN
107
+    };
108
+};

+ 13
- 0
react/features/participants-pane/actions.web.ts Просмотреть файл

@@ -1 +1,14 @@
1
+import { PARTICIPANTS_PANE_OPEN } from './actionTypes';
2
+
1 3
 export * from './actions.any';
4
+
5
+/**
6
+ * Action to open the participants pane.
7
+ *
8
+ * @returns {Object}
9
+ */
10
+export const open = () => {
11
+    return {
12
+        type: PARTICIPANTS_PANE_OPEN
13
+    };
14
+};

+ 0
- 1
react/features/participants-pane/components/native/ContextMenuLobbyParticipantReject.js Просмотреть файл

@@ -35,7 +35,6 @@ const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
35 35
         <View
36 36
             style = { styles.contextMenuItemSectionAvatar }>
37 37
             <Avatar
38
-                className = 'participant-avatar'
39 38
                 participantId = { p.id }
40 39
                 size = { 24 } />
41 40
             <Text style = { styles.contextMenuItemName }>

+ 0
- 1
react/features/participants-pane/components/native/ParticipantItem.js Просмотреть файл

@@ -95,7 +95,6 @@ function ParticipantItem({
95 95
                 onPress = { onPress }
96 96
                 style = { styles.participantContent }>
97 97
                 <Avatar
98
-                    className = 'participant-avatar'
99 98
                     displayName = { displayName }
100 99
                     participantId = { participantID }
101 100
                     size = { 32 } />

+ 1
- 1
react/features/participants-pane/hooks.web.ts Просмотреть файл

@@ -1,7 +1,7 @@
1 1
 import { useCallback, useState } from 'react';
2 2
 import { useDispatch } from 'react-redux';
3 3
 
4
-import { handleLobbyChatInitialized } from '../chat/actions.any';
4
+import { handleLobbyChatInitialized } from '../chat/actions.web';
5 5
 import { approveKnockingParticipant, rejectKnockingParticipant } from '../lobby/actions.web';
6 6
 
7 7
 interface IDrawerParticipant {

+ 1
- 1
react/features/toolbox/components/web/Toolbox.tsx Просмотреть файл

@@ -53,7 +53,7 @@ import { NoiseSuppressionButton } from '../../../noise-suppression/components';
53 53
 import {
54 54
     close as closeParticipantsPane,
55 55
     open as openParticipantsPane
56
-} from '../../../participants-pane/actions';
56
+} from '../../../participants-pane/actions.web';
57 57
 // @ts-ignore
58 58
 import { ParticipantsPaneButton } from '../../../participants-pane/components/web';
59 59
 import { getParticipantsPaneOpen } from '../../../participants-pane/functions';

+ 1
- 1
react/features/video-menu/components/web/PrivateMessageMenuButton.js Просмотреть файл

@@ -8,7 +8,7 @@ import { IconMessage } from '../../../base/icons';
8 8
 import { getParticipantById } from '../../../base/participants';
9 9
 import { connect } from '../../../base/redux';
10 10
 import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
11
-import { openChat } from '../../../chat/';
11
+import { openChat } from '../../../chat/actions.web';
12 12
 import {
13 13
     type Props as AbstractProps
14 14
 } from '../../../chat/components/web/PrivateMessageButton';

Загрузка…
Отмена
Сохранить