Kaynağa Gözat

feat(mobile): add 1 liner notifications

Adds 1 liner notifications to mobile. Only the title is displayed. In
case the title is missing there's a fallback to the description.
j8
paweldomas 6 yıl önce
ebeveyn
işleme
c7979a3944

+ 3
- 3
lang/main.json Dosyayı Görüntüle

@@ -458,9 +458,9 @@
458 458
     },
459 459
     "me": "me",
460 460
     "notify": {
461
-        "connectedOneMember": "__name__ connected",
462
-        "connectedThreePlusMembers": "__name__ and __count__ others connected",
463
-        "connectedTwoMembers": "__first__ and __second__ connected",
461
+        "connectedOneMember": "__name__ joined the meeting",
462
+        "connectedThreePlusMembers": "__name__ and __count__ others joined the meeting",
463
+        "connectedTwoMembers": "__first__ and __second__ joined the meeting",
464 464
         "disconnected": "disconnected",
465 465
         "focus": "Conference focus",
466 466
         "focusFail": "__component__ not available - retry in __ms__ sec",

+ 42
- 0
react/features/conference/components/AbstractConference.js Dosyayı Görüntüle

@@ -1,5 +1,10 @@
1 1
 // @flow
2 2
 
3
+import React, { Component } from 'react';
4
+
5
+import { NotificationsContainer } from '../../notifications/components';
6
+
7
+import { shouldDisplayNotifications } from '../functions';
3 8
 import { shouldDisplayTileView } from '../../video-layout';
4 9
 
5 10
 /**
@@ -7,6 +12,14 @@ import { shouldDisplayTileView } from '../../video-layout';
7 12
  */
8 13
 export type AbstractProps = {
9 14
 
15
+    /**
16
+     * Set to {@code true} when the notifications are to be displayed.
17
+     *
18
+     * @protected
19
+     * @type {boolean}
20
+     */
21
+    _notificationsVisible: boolean,
22
+
10 23
     /**
11 24
      * Conference room name.
12 25
      *
@@ -24,6 +37,34 @@ export type AbstractProps = {
24 37
     _shouldDisplayTileView: boolean
25 38
 };
26 39
 
40
+/**
41
+ * A container to hold video status labels, including recording status and
42
+ * current large video quality.
43
+ *
44
+ * @extends Component
45
+ */
46
+export class AbstractConference<P: AbstractProps, S>
47
+    extends Component<P, S> {
48
+
49
+    /**
50
+     * Renders the {@code LocalRecordingLabel}.
51
+     *
52
+     * @param {Object} props - The properties to be passed to
53
+     * the {@code NotificationsContainer}.
54
+     * @protected
55
+     * @returns {React$Element}
56
+     */
57
+    renderNotificationsContainer(props: ?Object) {
58
+        if (this.props._notificationsVisible) {
59
+            return (
60
+                React.createElement(NotificationsContainer, props)
61
+            );
62
+        }
63
+
64
+        return null;
65
+    }
66
+}
67
+
27 68
 /**
28 69
  * Maps (parts of) the redux state to the associated props of the {@link Labels}
29 70
  * {@code Component}.
@@ -34,6 +75,7 @@ export type AbstractProps = {
34 75
  */
35 76
 export function abstractMapStateToProps(state: Object) {
36 77
     return {
78
+        _notificationsVisible: shouldDisplayNotifications(state),
37 79
         _room: state['features/base/conference'].room,
38 80
         _shouldDisplayTileView: shouldDisplayTileView(state)
39 81
     };

+ 46
- 5
react/features/conference/components/native/Conference.js Dosyayı Görüntüle

@@ -1,8 +1,8 @@
1 1
 // @flow
2 2
 
3
-import React, { Component } from 'react';
3
+import React from 'react';
4 4
 
5
-import { BackHandler, StatusBar, View } from 'react-native';
5
+import { BackHandler, SafeAreaView, StatusBar, View } from 'react-native';
6 6
 import { connect as reactReduxConnect } from 'react-redux';
7 7
 
8 8
 import { appNavigate } from '../../../app';
@@ -10,6 +10,7 @@ import { connect, disconnect } from '../../../base/connection';
10 10
 import { getParticipantCount } from '../../../base/participants';
11 11
 import { Container, LoadingIndicator, TintedView } from '../../../base/react';
12 12
 import {
13
+    isNarrowAspectRatio,
13 14
     makeAspectRatioAware
14 15
 } from '../../../base/responsive-ui';
15 16
 import { TestConnectionInfo } from '../../../base/testing';
@@ -17,6 +18,7 @@ import { createDesiredLocalTracks } from '../../../base/tracks';
17 18
 import { ConferenceNotification } from '../../../calendar-sync';
18 19
 import { Chat } from '../../../chat';
19 20
 import {
21
+    FILMSTRIP_SIZE,
20 22
     Filmstrip,
21 23
     isFilmstripVisible,
22 24
     TileView
@@ -26,7 +28,10 @@ import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
26 28
 import { Captions } from '../../../subtitles';
27 29
 import { setToolboxVisible, Toolbox } from '../../../toolbox';
28 30
 
29
-import { abstractMapStateToProps } from '../AbstractConference';
31
+import {
32
+    AbstractConference,
33
+    abstractMapStateToProps
34
+} from '../AbstractConference';
30 35
 import DisplayNameLabel from './DisplayNameLabel';
31 36
 import Labels from './Labels';
32 37
 import NavigationBar from './NavigationBar';
@@ -134,7 +139,7 @@ type Props = AbstractProps & {
134 139
 /**
135 140
  * The conference page of the mobile (i.e. React Native) application.
136 141
  */
137
-class Conference extends Component<Props> {
142
+class Conference extends AbstractConference<Props, *> {
138 143
     /**
139 144
      * Initializes a new Conference instance.
140 145
      *
@@ -296,7 +301,12 @@ class Conference extends Component<Props> {
296 301
                     }
297 302
                 </View>
298 303
 
299
-                <NavigationBar />
304
+                <SafeAreaView
305
+                    pointerEvents = 'box-none'
306
+                    style = { styles.navBarSafeView }>
307
+                    <NavigationBar />
308
+                    { this.renderNotificationsContainer() }
309
+                </SafeAreaView>
300 310
 
301 311
                 <TestConnectionInfo />
302 312
 
@@ -341,6 +351,37 @@ class Conference extends Component<Props> {
341 351
                 ? <ConferenceNotification />
342 352
                 : undefined);
343 353
     }
354
+
355
+    /**
356
+     * Renders a container for notifications to be displayed by the
357
+     * base/notifications feature.
358
+     *
359
+     * @private
360
+     * @returns {React$Element}
361
+     */
362
+    renderNotificationsContainer() {
363
+        const notificationsStyle = {};
364
+
365
+        // In the landscape mode (wide) there's problem with notifications being
366
+        // shadowed by the filmstrip rendered on the right. This makes the "x"
367
+        // button not clickable. In order to avoid that a margin of the
368
+        // filmstrip's size is added to the right.
369
+        //
370
+        // Pawel: after many attempts I failed to make notifications adjust to
371
+        // their contents width because of column and rows being used in the
372
+        // flex layout. The only option that seemed to limit the notification's
373
+        // size was explicit 'width' value which is not better than the margin
374
+        // added here.
375
+        if (this.props._filmstripVisible && !isNarrowAspectRatio(this)) {
376
+            notificationsStyle.marginRight = FILMSTRIP_SIZE;
377
+        }
378
+
379
+        return super.renderNotificationsContainer(
380
+            {
381
+                style: notificationsStyle
382
+            }
383
+        );
384
+    }
344 385
 }
345 386
 
346 387
 /**

+ 4
- 1
react/features/conference/components/native/Labels.js Dosyayı Görüntüle

@@ -21,6 +21,7 @@ import AbstractLabels, {
21 21
     _abstractMapStateToProps,
22 22
     type Props as AbstractLabelsProps
23 23
 } from '../AbstractLabels';
24
+import { shouldDisplayNotifications } from '../../functions';
24 25
 import styles from './styles';
25 26
 
26 27
 /**
@@ -363,7 +364,9 @@ function _mapStateToProps(state) {
363 364
     return {
364 365
         ..._abstractMapStateToProps(state),
365 366
         _reducedUI: state['features/base/responsive-ui'].reducedUI,
366
-        _visible: !isToolboxVisible(state) && !shouldDisplayTileView(state)
367
+        _visible: !isToolboxVisible(state)
368
+            && !shouldDisplayTileView(state)
369
+            && !shouldDisplayNotifications(state)
367 370
     };
368 371
 }
369 372
 

+ 23
- 29
react/features/conference/components/native/NavigationBar.js Dosyayı Görüntüle

@@ -40,39 +40,33 @@ class NavigationBar extends Component<Props> {
40 40
             return null;
41 41
         }
42 42
 
43
-        return (
43
+        return [
44
+            <LinearGradient
45
+                colors = { NAVBAR_GRADIENT_COLORS }
46
+                key = { 1 }
47
+                pointerEvents = 'none'
48
+                style = { styles.gradient }>
49
+                <SafeAreaView>
50
+                    <View style = { styles.gradientStretch } />
51
+                </SafeAreaView>
52
+            </LinearGradient>,
44 53
             <View
54
+                key = { 2 }
45 55
                 pointerEvents = 'box-none'
46
-                style = { styles.navBarContainer }>
47
-                <LinearGradient
48
-                    colors = { NAVBAR_GRADIENT_COLORS }
49
-                    pointerEvents = 'none'
50
-                    style = { styles.gradient }>
51
-                    <SafeAreaView>
52
-                        <View style = { styles.gradientStretch } />
53
-                    </SafeAreaView>
54
-                </LinearGradient>
55
-                <SafeAreaView
56
+                style = { styles.navBarWrapper }>
57
+                <PictureInPictureButton
58
+                    styles = { styles.navBarButton } />
59
+                <View
56 60
                     pointerEvents = 'box-none'
57
-                    style = { styles.navBarSafeView }>
58
-                    <View
59
-                        pointerEvents = 'box-none'
60
-                        style = { styles.navBarWrapper }>
61
-                        <PictureInPictureButton
62
-                            styles = { styles.navBarButton } />
63
-                        <View
64
-                            pointerEvents = 'box-none'
65
-                            style = { styles.roomNameWrapper }>
66
-                            <Text
67
-                                numberOfLines = { 1 }
68
-                                style = { styles.roomName }>
69
-                                { this.props._meetingName }
70
-                            </Text>
71
-                        </View>
72
-                    </View>
73
-                </SafeAreaView>
61
+                    style = { styles.roomNameWrapper }>
62
+                    <Text
63
+                        numberOfLines = { 1 }
64
+                        style = { styles.roomName }>
65
+                        { this.props._meetingName }
66
+                    </Text>
67
+                </View>
74 68
             </View>
75
-        );
69
+        ];
76 70
     }
77 71
 
78 72
 }

+ 4
- 0
react/features/conference/components/native/styles.js Dosyayı Görüntüle

@@ -37,6 +37,10 @@ export default createStyleSheet({
37 37
     },
38 38
 
39 39
     gradient: {
40
+        position: 'absolute',
41
+        top: 0,
42
+        left: 0,
43
+        right: 0,
40 44
         flex: 1
41 45
     },
42 46
 

+ 7
- 5
react/features/conference/components/web/Conference.js Dosyayı Görüntüle

@@ -1,7 +1,7 @@
1 1
 // @flow
2 2
 
3 3
 import _ from 'lodash';
4
-import React, { Component } from 'react';
4
+import React from 'react';
5 5
 import { connect as reactReduxConnect } from 'react-redux';
6 6
 
7 7
 import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
@@ -13,7 +13,6 @@ import { Chat } from '../../../chat';
13 13
 import { Filmstrip } from '../../../filmstrip';
14 14
 import { CalleeInfoContainer } from '../../../invite';
15 15
 import { LargeVideo } from '../../../large-video';
16
-import { NotificationsContainer } from '../../../notifications';
17 16
 import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
18 17
 
19 18
 import {
@@ -28,7 +27,10 @@ import { maybeShowSuboptimalExperienceNotification } from '../../functions';
28 27
 import Labels from './Labels';
29 28
 import { default as Notice } from './Notice';
30 29
 import { default as Subject } from './Subject';
31
-import { abstractMapStateToProps } from '../AbstractConference';
30
+import {
31
+    AbstractConference,
32
+    abstractMapStateToProps
33
+} from '../AbstractConference';
32 34
 
33 35
 import type { AbstractProps } from '../AbstractConference';
34 36
 
@@ -87,7 +89,7 @@ type Props = AbstractProps & {
87 89
 /**
88 90
  * The conference page of the Web application.
89 91
  */
90
-class Conference extends Component<Props> {
92
+class Conference extends AbstractConference<Props, *> {
91 93
     _onFullScreenChange: Function;
92 94
     _onShowToolbar: Function;
93 95
     _originalOnShowToolbar: Function;
@@ -218,7 +220,7 @@ class Conference extends Component<Props> {
218 220
                 { filmstripOnly || <Toolbox /> }
219 221
                 { filmstripOnly || <Chat /> }
220 222
 
221
-                <NotificationsContainer />
223
+                { this.renderNotificationsContainer() }
222 224
 
223 225
                 <CalleeInfoContainer />
224 226
             </div>

+ 25
- 2
react/features/conference/functions.js Dosyayı Görüntüle

@@ -1,7 +1,13 @@
1
-import { getName } from '../app';
2 1
 import { translateToHTML } from '../base/i18n';
3 2
 import { browser } from '../base/lib-jitsi-meet';
4
-import { showWarningNotification } from '../notifications';
3
+import { toState } from '../base/redux';
4
+
5
+import { getName } from '../app';
6
+import {
7
+    areThereNotifications,
8
+    showWarningNotification
9
+} from '../notifications';
10
+import { getOverlayToRender } from '../overlay';
5 11
 
6 12
 /**
7 13
  * Shows the suboptimal experience notification if needed.
@@ -36,3 +42,20 @@ export function maybeShowSuboptimalExperienceNotification(dispatch, t) {
36 42
         );
37 43
     }
38 44
 }
45
+
46
+/**
47
+ * Tells whether or not the notifications should be displayed within
48
+ * the conference feature based on the current Redux state.
49
+ *
50
+ * @param {Object|Function} stateful - The redux store state.
51
+ * @returns {boolean}
52
+ */
53
+export function shouldDisplayNotifications(stateful) {
54
+    const state = toState(stateful);
55
+    const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
56
+    const { calleeInfoVisible } = state['features/invite'];
57
+
58
+    return areThereNotifications(state)
59
+            && !isAnyOverlayVisible
60
+            && !calleeInfoVisible;
61
+}

+ 4
- 7
react/features/notifications/components/AbstractNotificationsContainer.js Dosyayı Görüntüle

@@ -2,9 +2,8 @@
2 2
 
3 3
 import { Component } from 'react';
4 4
 
5
-import { getOverlayToRender } from '../../overlay';
6
-
7 5
 import { hideNotification } from '../actions';
6
+import { areThereNotifications } from '../functions';
8 7
 
9 8
 export type Props = {
10 9
 
@@ -165,12 +164,10 @@ export default class AbstractNotificationsContainer<P: Props>
165 164
  * }}
166 165
  */
167 166
 export function _abstractMapStateToProps(state: Object) {
168
-    const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
169
-    const { enabled, notifications } = state['features/notifications'];
170
-    const { calleeInfoVisible } = state['features/invite'];
167
+    const { notifications } = state['features/notifications'];
168
+    const _visible = areThereNotifications(state);
171 169
 
172 170
     return {
173
-        _notifications: enabled && !isAnyOverlayVisible && !calleeInfoVisible
174
-            ? notifications : []
171
+        _notifications: _visible ? notifications : []
175 172
     };
176 173
 }

+ 92
- 0
react/features/notifications/components/Notification.native.js Dosyayı Görüntüle

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

+ 67
- 0
react/features/notifications/components/NotificationsContainer.native.js Dosyayı Görüntüle

@@ -0,0 +1,67 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { View } from 'react-native';
5
+import { connect } from 'react-redux';
6
+
7
+import AbstractNotificationsContainer, {
8
+    _abstractMapStateToProps,
9
+    type Props as AbstractProps
10
+} from './AbstractNotificationsContainer';
11
+import Notification from './Notification';
12
+import styles from './styles';
13
+
14
+type Props = AbstractProps & {
15
+
16
+    /**
17
+     * Any custom styling applied to the notifications container.
18
+     */
19
+    style: Object
20
+};
21
+
22
+/**
23
+ * Implements a React {@link Component} which displays notifications and handles
24
+ * automatic dismissmal after a notification is shown for a defined timeout
25
+ * period.
26
+ *
27
+ * @extends {Component}
28
+ */
29
+class NotificationsContainer
30
+    extends AbstractNotificationsContainer<Props> {
31
+
32
+    /**
33
+     * Implements React's {@link Component#render()}.
34
+     *
35
+     * @inheritdoc
36
+     */
37
+    render() {
38
+        const { _notifications } = this.props;
39
+
40
+        if (!_notifications || !_notifications.length) {
41
+            return null;
42
+        }
43
+
44
+        return (
45
+            <View
46
+                pointerEvents = 'box-none'
47
+                style = { [
48
+                    styles.notificationContainer,
49
+                    this.props.style
50
+                ] } >
51
+                {
52
+                    _notifications.map(
53
+                        ({ props, uid }) => (
54
+                            <Notification
55
+                                { ...props }
56
+                                key = { uid }
57
+                                onDismissed = { this._onDismissed }
58
+                                uid = { uid } />))
59
+                }
60
+            </View>
61
+        );
62
+    }
63
+
64
+    _onDismissed: number => void;
65
+}
66
+
67
+export default connect(_abstractMapStateToProps)(NotificationsContainer);

+ 61
- 0
react/features/notifications/components/styles.js Dosyayı Görüntüle

@@ -0,0 +1,61 @@
1
+// @flow
2
+
3
+import { BoxModel, createStyleSheet, ColorPalette } from '../../base/styles';
4
+
5
+/**
6
+ * The styles of the React {@code Components} of the feature notifications.
7
+ */
8
+export default createStyleSheet({
9
+
10
+    /**
11
+     * The content (left) column of the notification.
12
+     */
13
+    contentColumn: {
14
+        justifyContent: 'center',
15
+        flex: 1,
16
+        flexDirection: 'column',
17
+        paddingLeft: 1.5 * BoxModel.padding
18
+    },
19
+
20
+    /**
21
+     * Test style of the notification.
22
+     */
23
+    contentText: {
24
+        alignSelf: 'flex-start',
25
+        color: ColorPalette.white
26
+    },
27
+
28
+    /**
29
+     * Dismiss icon style.
30
+     */
31
+    dismissIcon: {
32
+        color: ColorPalette.white,
33
+        fontSize: 20,
34
+        padding: 1.5 * BoxModel.padding
35
+    },
36
+
37
+    /**
38
+     * Outermost view of a single notification.
39
+     */
40
+    notification: {
41
+        backgroundColor: '#768898',
42
+        flexDirection: 'row',
43
+        height: 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'
53
+    },
54
+
55
+    /**
56
+     * Wrapper for the message.
57
+     */
58
+    notificationContent: {
59
+        flexDirection: 'column'
60
+    }
61
+});

+ 15
- 0
react/features/notifications/functions.js Dosyayı Görüntüle

@@ -0,0 +1,15 @@
1
+import { toState } from '../base/redux';
2
+
3
+/**
4
+ * Tells whether or not the notifications are enabled and if there are any
5
+ * notifications to be displayed based on the current Redux state.
6
+ *
7
+ * @param {Object|Function} stateful - The redux store state.
8
+ * @returns {boolean}
9
+ */
10
+export function areThereNotifications(stateful) {
11
+    const state = toState(stateful);
12
+    const { enabled, notifications } = state['features/notifications'];
13
+
14
+    return enabled && notifications.length > 0;
15
+}

+ 1
- 0
react/features/notifications/index.js Dosyayı Görüntüle

@@ -1,6 +1,7 @@
1 1
 export * from './actions';
2 2
 export * from './actionTypes';
3 3
 export * from './components';
4
+export * from './functions';
4 5
 
5 6
 import './middleware';
6 7
 import './reducer';

Loading…
İptal
Kaydet