浏览代码

[RN] Add ExpandedLabel

master
Bettenbuk Zoltan 7 年前
父节点
当前提交
e5cc732b72
共有 27 个文件被更改,包括 860 次插入75 次删除
  1. 8
    0
      lang/main.json
  2. 2
    2
      package-lock.json
  3. 2
    1
      react/features/base/label/components/AbstractCircularLabel.js
  4. 114
    8
      react/features/base/label/components/CircularLabel.native.js
  5. 1
    1
      react/features/base/label/components/CircularLabel.web.js
  6. 135
    0
      react/features/base/label/components/ExpandedLabel.native.js
  7. 0
    0
      react/features/base/label/components/ExpandedLabel.web.js
  8. 1
    0
      react/features/base/label/components/index.js
  9. 51
    6
      react/features/base/label/components/styles.js
  10. 281
    26
      react/features/large-video/components/Labels.native.js
  11. 14
    8
      react/features/large-video/components/styles.js
  12. 10
    16
      react/features/recording/components/AbstractRecordingLabel.js
  13. 108
    0
      react/features/recording/components/RecordingExpandedLabel.native.js
  14. 0
    0
      react/features/recording/components/RecordingExpandedLabel.web.js
  15. 13
    2
      react/features/recording/components/RecordingLabel.native.js
  16. 1
    2
      react/features/recording/components/RecordingLabel.web.js
  17. 1
    0
      react/features/recording/components/index.js
  18. 5
    2
      react/features/recording/components/styles.js
  19. 14
    0
      react/features/recording/constants.js
  20. 29
    0
      react/features/recording/functions.js
  21. 25
    0
      react/features/transcribing/components/TranscribingExpandedLabel.native.js
  22. 0
    0
      react/features/transcribing/components/TranscribingExpandedLabel.web.js
  23. 3
    0
      react/features/transcribing/components/index.js
  24. 36
    0
      react/features/video-quality/components/VideoQualityExpandedLabel.native.js
  25. 0
    0
      react/features/video-quality/components/VideoQualityExpandedLabel.web.js
  26. 3
    0
      react/features/video-quality/components/index.js
  27. 3
    1
      react/features/video-quality/components/styles.js

+ 8
- 0
lang/main.json 查看文件

@@ -462,6 +462,9 @@
462 462
         "busyTitle": "All recorders are currently busy",
463 463
         "buttonTooltip": "Start / Stop recording",
464 464
         "error": "Recording failed. Please try again.",
465
+        "expandedOff": "Recording has stopped",
466
+        "expandedOn": "The meeting is currently being recorded.",
467
+        "expandedPending": "Recording is being started...",
465 468
         "failedToStart": "Recording failed to start",
466 469
         "live": "LIVE",
467 470
         "off": "Recording stopped",
@@ -483,6 +486,7 @@
483 486
         "pending" : "Preparing to transcribe the meeting...",
484 487
         "off" : "Transcribing stopped",
485 488
         "error": "Transcribing failed. Please try again.",
489
+        "expandedLabel": "Transcribing is currently on",
486 490
         "failedToStart": "Transcribing failed to start",
487 491
         "tr": "TR",
488 492
         "labelToolTip": "The meeting is being transcribed",
@@ -502,6 +506,9 @@
502 506
         "error": "Live Streaming failed. Please try again.",
503 507
         "errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
504 508
         "errorLiveStreamNotEnabled": "Live Streaming is not enabled on __email__. Please enable live streaming or log into an account with live streaming enabled.",
509
+        "expandedOff": "The live streaming has stopped",
510
+        "expandedOn": "The meeting is currently being streamed to YouTube.",
511
+        "expandedPending": "The live streaming is being started...",
505 512
         "failedToStart": "Live Streaming failed to start",
506 513
         "off": "Live Streaming stopped",
507 514
         "on": "Live Streaming",
@@ -546,6 +553,7 @@
546 553
     },
547 554
     "videoStatus": {
548 555
         "audioOnly": "AUD",
556
+        "audioOnlyExpanded": "You are in audio only mode. This mode saves bandwidth but you won't see videos of others.",
549 557
         "callQuality": "Call Quality",
550 558
         "hd": "HD",
551 559
         "hdTooltip": "Viewing high definition video",

+ 2
- 2
package-lock.json 查看文件

@@ -441,7 +441,7 @@
441 441
     },
442 442
     "@atlaskit/inline-dialog": {
443 443
       "version": "5.3.0",
444
-      "resolved": "http://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-5.3.0.tgz",
444
+      "resolved": "https://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-5.3.0.tgz",
445 445
       "integrity": "sha512-4bEeC5rZwtb4YO9BxW1UCJYCp/dyCVXqcygRW1BDnYVbveAI8wdym6qEi4BRvIwXCT4qgNhsVsqcxSrn0X6CKQ==",
446 446
       "requires": {
447 447
         "@atlaskit/layer": "^2.8.0",
@@ -451,7 +451,7 @@
451 451
       "dependencies": {
452 452
         "@atlaskit/layer": {
453 453
           "version": "2.9.1",
454
-          "resolved": "http://registry.npmjs.org/@atlaskit/layer/-/layer-2.9.1.tgz",
454
+          "resolved": "https://registry.npmjs.org/@atlaskit/layer/-/layer-2.9.1.tgz",
455 455
           "integrity": "sha512-nyIVGeS2OhuGR5gIMTYUfRmCG8z/9KMgUzTpbpsB70sH6+d4KSFhfkz+KhKNIa8gvKI6zBc+3UBYSlUW1t1qmQ==",
456 456
           "requires": {
457 457
             "styled-components": "1.4.6 - 3"

+ 2
- 1
react/features/base/label/components/AbstractCircularLabel.js 查看文件

@@ -12,6 +12,7 @@ export type Props = {
12 12
 /**
13 13
  * Abstract class for the {@code CircularLabel} component.
14 14
  */
15
-export default class AbstractCircularLabel<P: Props> extends Component<P> {
15
+export default class AbstractCircularLabel<P: Props, S: *>
16
+    extends Component<P, S> {
16 17
 
17 18
 }

+ 114
- 8
react/features/base/label/components/CircularLabel.native.js 查看文件

@@ -1,6 +1,6 @@
1 1
 // @flow
2 2
 import React from 'react';
3
-import { Text, View } from 'react-native';
3
+import { Animated, Text } from 'react-native';
4 4
 
5 5
 import { combineStyles, type StyleType } from '../../styles';
6 6
 
@@ -9,36 +9,142 @@ import AbstractCircularLabel, {
9 9
 } from './AbstractCircularLabel';
10 10
 import styles from './styles';
11 11
 
12
+/**
13
+ * Const for status string 'in progress'.
14
+ */
15
+const STATUS_IN_PROGRESS = 'in_progress';
16
+
17
+/**
18
+ * Const for status string 'off'.
19
+ */
20
+const STATUS_OFF = 'off';
21
+
12 22
 type Props = AbstractProps & {
13 23
 
24
+    /**
25
+     * Status of the label. This prop adds some additional styles based on its
26
+     * value. E.g. if status = off, it will render the label symbolising that
27
+     * the thing it displays (e.g. recording) is off.
28
+     */
29
+    status: ('in_progress' | 'off' | 'on'),
30
+
14 31
     /**
15 32
      * Style of the label.
16 33
      */
17 34
     style?: ?StyleType
18 35
 };
19 36
 
37
+type State = {
38
+
39
+    /**
40
+     * An animation object handling the opacity changes of the in progress
41
+     * label.
42
+     */
43
+    pulseAnimation: Object
44
+}
45
+
20 46
 /**
21 47
  * Renders a circular indicator to be used for status icons, such as recording
22 48
  * on, audio-only conference, video quality and similar.
23 49
  */
24
-export default class CircularLabel extends AbstractCircularLabel<Props> {
50
+export default class CircularLabel extends AbstractCircularLabel<Props, State> {
51
+    /**
52
+     * A reference to the started animation of this label.
53
+     */
54
+    animationReference: Object;
55
+
56
+    /**
57
+     * Instantiates a new instance of {@code CircularLabel}.
58
+     *
59
+     * @inheritdoc
60
+     */
61
+    constructor(props: Props) {
62
+        super(props);
63
+
64
+        this.state = {
65
+            pulseAnimation: new Animated.Value(0)
66
+        };
67
+
68
+        this._maybeToggleAnimation({}, props);
69
+    }
70
+
71
+    /**
72
+     * Implements {@code Component#componentWillReceiveProps}.
73
+     *
74
+     * @inheritdoc
75
+     */
76
+    componentWillReceiveProps(newProps: Props) {
77
+        this._maybeToggleAnimation(this.props, newProps);
78
+    }
79
+
25 80
     /**
26 81
      * Implements React {@link Component}'s render.
27 82
      *
28 83
      * @inheritdoc
29 84
      */
30 85
     render() {
31
-        const { label, style } = this.props;
86
+        const { status, label, style } = this.props;
87
+
88
+        let extraStyle = null;
89
+
90
+        switch (status) {
91
+        case STATUS_IN_PROGRESS:
92
+            extraStyle = {
93
+                opacity: this.state.pulseAnimation
94
+            };
95
+            break;
96
+        case STATUS_OFF:
97
+            extraStyle = styles.labelOff;
98
+            break;
99
+        }
32 100
 
33 101
         return (
34
-            <View
35
-                style = {
36
-                    combineStyles(styles.indicatorContainer, style)
37
-                }>
102
+            <Animated.View
103
+                style = { [
104
+                    combineStyles(styles.indicatorContainer, style),
105
+                    extraStyle
106
+                ] }>
38 107
                 <Text style = { styles.indicatorText }>
39 108
                     { label }
40 109
                 </Text>
41
-            </View>
110
+            </Animated.View>
42 111
         );
43 112
     }
113
+
114
+    /**
115
+     * Checks if the animation has to be started or stopped and acts
116
+     * accordingly.
117
+     *
118
+     * @param {Props} oldProps - The previous values of the Props.
119
+     * @param {Props} newProps - The new values of the Props.
120
+     * @returns {void}
121
+     */
122
+    _maybeToggleAnimation(oldProps, newProps) {
123
+        const { status: oldStatus } = oldProps;
124
+        const { status: newStatus } = newProps;
125
+        const { pulseAnimation } = this.state;
126
+
127
+        if (newStatus === STATUS_IN_PROGRESS
128
+                && oldStatus !== STATUS_IN_PROGRESS) {
129
+            // Animation must be started
130
+            this.animationReference = Animated.loop(Animated.sequence([
131
+                Animated.timing(pulseAnimation, {
132
+                    delay: 500,
133
+                    toValue: 1,
134
+                    useNativeDriver: true
135
+                }),
136
+                Animated.timing(pulseAnimation, {
137
+                    toValue: 0.3,
138
+                    useNativeDriver: true
139
+                })
140
+            ]));
141
+
142
+            this.animationReference.start();
143
+        } else if (this.animationReference
144
+                && newStatus !== STATUS_IN_PROGRESS
145
+                && oldStatus === STATUS_IN_PROGRESS) {
146
+            // Animation must be stopped
147
+            this.animationReference.stop();
148
+        }
149
+    }
44 150
 }

+ 1
- 1
react/features/base/label/components/CircularLabel.web.js 查看文件

@@ -25,7 +25,7 @@ type Props = AbstractProps & {
25 25
  *
26 26
  * @extends Component
27 27
  */
28
-export default class CircularLabel extends AbstractCircularLabel<Props> {
28
+export default class CircularLabel extends AbstractCircularLabel<Props, {}> {
29 29
     /**
30 30
      * Default values for {@code CircularLabel} component's properties.
31 31
      *

+ 135
- 0
react/features/base/label/components/ExpandedLabel.native.js 查看文件

@@ -0,0 +1,135 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { Animated, Text, View } from 'react-native';
5
+
6
+import styles, { DEFAULT_COLOR, LABEL_MARGIN, LABEL_SIZE } from './styles';
7
+
8
+export type Props = {
9
+
10
+    /**
11
+     * The position of the parent element (from right to left) to display the
12
+     * arrow.
13
+     */
14
+    parentPosition: number
15
+};
16
+
17
+type State = {
18
+
19
+    /**
20
+     * The opacity animation Object.
21
+     */
22
+    opacityAnimation: Object,
23
+
24
+    /**
25
+     * A boolean to descide to show or not show the arrow. This is required as
26
+     * we can't easily animate this transformed Component so we render it once
27
+     * the animation is done.
28
+     */
29
+    showArrow: boolean
30
+};
31
+
32
+/**
33
+ * Offset to the arrow to be rendered in the right position.
34
+ */
35
+const ARROW_OFFSET = 0;
36
+
37
+/**
38
+ * A react {@code Component} that implements an expanded label as tooltip-like
39
+ * component to explain the meaning of the {@code Label}.
40
+ */
41
+export default class ExpandedLabel<P: Props> extends Component<P, State> {
42
+    /**
43
+     * Instantiates a new {@code ExpandedLabel} instance.
44
+     *
45
+     * @inheritdoc
46
+     */
47
+    constructor(props: P) {
48
+        super(props);
49
+
50
+        this.state = {
51
+            opacityAnimation: new Animated.Value(0),
52
+            showArrow: false
53
+        };
54
+    }
55
+
56
+    /**
57
+     * Implements React {@code Component}'s componentDidMount.
58
+     *
59
+     * @inheritdoc
60
+     */
61
+    componentDidMount() {
62
+        Animated.decay(this.state.opacityAnimation, {
63
+            toValue: 1,
64
+            velocity: 1,
65
+            useNativeDriver: true
66
+        }).start(({ finished }) => {
67
+            finished && this.setState({
68
+                showArrow: true
69
+            });
70
+        });
71
+    }
72
+
73
+    /**
74
+     * Implements React {@code Component}'s render.
75
+     *
76
+     * @inheritdoc
77
+     */
78
+    render() {
79
+        const arrowPosition
80
+            = this.props.parentPosition - LABEL_MARGIN - (LABEL_SIZE / 2);
81
+
82
+        return (
83
+            <Animated.View
84
+                style = { [
85
+                    styles.expandedLabelWrapper,
86
+                    {
87
+                        opacity: this.state.opacityAnimation
88
+                    }
89
+                ] } >
90
+                <View
91
+                    style = { [
92
+                        styles.expandedLabelArrow,
93
+                        {
94
+                            backgroundColor: this._getColor() || DEFAULT_COLOR,
95
+                            marginRight: arrowPosition + ARROW_OFFSET
96
+                        }
97
+                    ] } />
98
+                <View
99
+                    style = { [
100
+                        styles.expandedLabelContainer,
101
+                        {
102
+                            backgroundColor: this._getColor() || DEFAULT_COLOR
103
+                        }
104
+                    ] }>
105
+                    <Text style = { styles.expandedLabelText }>
106
+                        { this._getLabel() }
107
+                    </Text>
108
+                </View>
109
+            </Animated.View>
110
+        );
111
+    }
112
+
113
+    /**
114
+     * Returns the label that needs to be rendered in the box. To be implemented
115
+     * by its overriding classes.
116
+     *
117
+     * @returns {string}
118
+     */
119
+    _getLabel: () => string
120
+
121
+    _getColor: () => string
122
+
123
+    /**
124
+     * Defines the color of the expanded label. This function returns a default
125
+     * value if implementing classes don't override it, but the goal is to have
126
+     * expanded labels matching to circular labels in color.
127
+     * If implementing classes return a falsy value, it also uses the default
128
+     * color.
129
+     *
130
+     * @returns {string}
131
+     */
132
+    _getColor() {
133
+        return DEFAULT_COLOR;
134
+    }
135
+}

+ 0
- 0
react/features/base/label/components/ExpandedLabel.web.js 查看文件


+ 1
- 0
react/features/base/label/components/index.js 查看文件

@@ -1 +1,2 @@
1 1
 export { default as CircularLabel } from './CircularLabel';
2
+export { default as ExpandedLabel } from './ExpandedLabel';

+ 51
- 6
react/features/base/label/components/styles.js 查看文件

@@ -1,30 +1,75 @@
1 1
 // @flow
2 2
 
3
-import { ColorPalette, createStyleSheet } from '../../styles';
3
+import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
4
+
5
+/**
6
+ * The default color of the {@code Label} and {@code ExpandedLabel}.
7
+ */
8
+export const DEFAULT_COLOR = '#808080';
9
+
10
+/**
11
+ * Margin of the {@Label} - to be reused when rendering the
12
+ * {@code ExpandedLabel}.
13
+ */
14
+export const LABEL_MARGIN = 5;
15
+
16
+/**
17
+ * Size of the {@Label} - to be reused when rendering the
18
+ * {@code ExpandedLabel}.
19
+ */
20
+export const LABEL_SIZE = 36;
4 21
 
5 22
 /**
6 23
  * The styles of the native base/label feature.
7 24
  */
8 25
 export default createStyleSheet({
9 26
 
27
+    expandedLabelArrow: {
28
+        backgroundColor: ColorPalette.blue,
29
+        height: 15,
30
+        transform: [ { rotate: '45deg' }, { translateX: 10 } ],
31
+        width: 15
32
+    },
33
+
34
+    expandedLabelContainer: {
35
+        backgroundColor: ColorPalette.blue,
36
+        borderColor: ColorPalette.blue,
37
+        borderRadius: 6,
38
+        marginHorizontal: BoxModel.margin,
39
+        padding: BoxModel.padding
40
+    },
41
+
42
+    expandedLabelText: {
43
+        color: ColorPalette.white
44
+    },
45
+
46
+    expandedLabelWrapper: {
47
+        alignItems: 'flex-end',
48
+        flexDirection: 'column'
49
+    },
50
+
10 51
     /**
11 52
      * The outermost view.
12 53
      */
13 54
     indicatorContainer: {
14 55
         alignItems: 'center',
15
-        backgroundColor: '#808080',
16
-        borderRadius: 18,
56
+        backgroundColor: DEFAULT_COLOR,
57
+        borderRadius: LABEL_SIZE / 2,
17 58
         borderWidth: 0,
18 59
         flex: 0,
19
-        height: 36,
60
+        height: LABEL_SIZE,
20 61
         justifyContent: 'center',
21
-        margin: 5,
62
+        margin: LABEL_MARGIN,
22 63
         opacity: 0.6,
23
-        width: 36
64
+        width: LABEL_SIZE
24 65
     },
25 66
 
26 67
     indicatorText: {
27 68
         color: ColorPalette.white,
28 69
         fontSize: 12
70
+    },
71
+
72
+    labelOff: {
73
+        opacity: 0.3
29 74
     }
30 75
 });

+ 281
- 26
react/features/large-video/components/Labels.native.js 查看文件

@@ -1,14 +1,19 @@
1 1
 // @flow
2 2
 
3 3
 import React from 'react';
4
-import { View } from 'react-native';
4
+import { TouchableOpacity, View } from 'react-native';
5 5
 import { connect } from 'react-redux';
6 6
 
7 7
 import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
8
+import {
9
+    RecordingExpandedLabel
10
+} from '../../recording';
8 11
 import {
9 12
     isNarrowAspectRatio,
10 13
     makeAspectRatioAware
11 14
 } from '../../base/responsive-ui';
15
+import { TranscribingExpandedLabel } from '../../transcribing';
16
+import { VideoQualityExpandedLabel } from '../../video-quality';
12 17
 
13 18
 import AbstractLabels, {
14 19
     _abstractMapStateToProps,
@@ -21,6 +26,11 @@ import styles from './styles';
21 26
  */
22 27
 type Props = AbstractLabelsProps & {
23 28
 
29
+    /**
30
+     * Function to translate i18n labels.
31
+     */
32
+    t: Function,
33
+
24 34
     /**
25 35
      * The indicator which determines whether the UI is reduced (to accommodate
26 36
      * smaller display areas).
@@ -30,10 +40,108 @@ type Props = AbstractLabelsProps & {
30 40
     _reducedUI: boolean
31 41
 };
32 42
 
43
+type State = {
44
+
45
+    /**
46
+     * Layout object of the outermost container. For stucture please see:
47
+     * https://facebook.github.io/react-native/docs/view#onlayout
48
+     */
49
+    containerLayout: ?Object,
50
+
51
+    /**
52
+     * Layout objects of the individual labels. This data type contains the same
53
+     * structure as the layout is defined here:
54
+     * https://facebook.github.io/react-native/docs/view#onlayout
55
+     * but keyed with the ID of the label its layout it contains. E.g.
56
+     *
57
+     * {
58
+     *   transcribing: {
59
+     *     { layout: { x, y, width, height } }
60
+     *   },
61
+     *   ...
62
+     * }
63
+     */
64
+    labelLayouts: Object,
65
+
66
+    /**
67
+     * Position of the label to render the {@code ExpandedLabel} to.
68
+     */
69
+    parentPosition: ?number,
70
+
71
+    /**
72
+     * String to show which {@code ExpandedLabel} to be shown. (Equals to the
73
+     * label IDs below.)
74
+     */
75
+    visibleExpandedLabel: ?string
76
+}
77
+
78
+const LABEL_ID_QUALITY = 'quality';
79
+const LABEL_ID_RECORDING = 'recording';
80
+const LABEL_ID_STREAMING = 'streaming';
81
+const LABEL_ID_TRANSCRIBING = 'transcribing';
82
+
83
+/**
84
+ * The {@code ExpandedLabel} components to be rendered for the individual
85
+ * {@code Label}s.
86
+ */
87
+const EXPANDED_LABELS = {
88
+    quality: VideoQualityExpandedLabel,
89
+    recording: {
90
+        component: RecordingExpandedLabel,
91
+        props: {
92
+            mode: JitsiRecordingConstants.mode.FILE
93
+        }
94
+    },
95
+    streaming: {
96
+        component: RecordingExpandedLabel,
97
+        props: {
98
+            mode: JitsiRecordingConstants.mode.STREAM
99
+        }
100
+    },
101
+    transcribing: TranscribingExpandedLabel
102
+};
103
+
104
+/**
105
+ * Timeout to hide the {@ExpandedLabel}.
106
+ */
107
+const EXPANDED_LABEL_TIMEOUT = 5000;
108
+
33 109
 /**
34 110
  * A container that renders the conference indicators, if any.
35 111
  */
36
-class Labels extends AbstractLabels<Props, *> {
112
+class Labels extends AbstractLabels<Props, State> {
113
+    /**
114
+     * Timeout for the expanded labels to disappear.
115
+     */
116
+    expandedLabelTimeout: TimeoutID;
117
+
118
+    /**
119
+     * Instantiates a new instance of {@code Labels}.
120
+     *
121
+     * @inheritdoc
122
+     */
123
+    constructor(props: Props) {
124
+        super(props);
125
+
126
+        this.state = {
127
+            containerLayout: undefined,
128
+            labelLayouts: {},
129
+            parentPosition: undefined,
130
+            visibleExpandedLabel: undefined
131
+        };
132
+
133
+        this._onTopViewLayout = this._onTopViewLayout.bind(this);
134
+    }
135
+
136
+    /**
137
+     * Implements React {@code Component}'s componentWillUnmount.
138
+     *
139
+     * @inheritdoc
140
+     */
141
+    componentWillUnmount() {
142
+        clearTimeout(this.expandedLabelTimeout);
143
+    }
144
+
37 145
     /**
38 146
      * Implements React {@code Component}'s render.
39 147
      *
@@ -46,34 +154,181 @@ class Labels extends AbstractLabels<Props, *> {
46 154
         return (
47 155
             <View
48 156
                 pointerEvents = 'box-none'
49
-                style = { [
50
-                    styles.indicatorContainer,
51
-                    wide && _filmstripVisible && styles.indicatorContainerWide
52
-                ] }>
53
-                {
54
-                    this._renderRecordingLabel(
55
-                        JitsiRecordingConstants.mode.FILE)
56
-                }
57
-                {
58
-                    this._renderRecordingLabel(
59
-                        JitsiRecordingConstants.mode.STREAM)
60
-                }
61
-                {
62
-                    this._renderTranscribingLabel()
63
-                }
64
-                {/*
65
-                  * Emil, Lyubomir, Nichole, and Zoli said that the Labels
66
-                  * should not be rendered in Picture-in-Picture. Saul argued
67
-                  * that the recording Labels should be rendered. As a temporary
68
-                  * compromise, don't render the VideoQualityLabel at least
69
-                  * because it's not that important.
70
-                  */
71
-                    _reducedUI || this._renderVideoQualityLabel()
72
-                }
157
+                style = { styles.labelWrapper }>
158
+                <View
159
+                    onLayout = { this._onTopViewLayout }
160
+                    pointerEvents = 'box-none'
161
+                    style = { [
162
+                        styles.indicatorContainer,
163
+                        wide && _filmstripVisible
164
+                            && styles.indicatorContainerWide
165
+                    ] }>
166
+                    <TouchableOpacity
167
+                        onLayout = { this._createOnLayout(LABEL_ID_RECORDING) }
168
+                        onPress = { this._createOnPress(LABEL_ID_RECORDING) } >
169
+                        {
170
+                            this._renderRecordingLabel(
171
+                                JitsiRecordingConstants.mode.FILE)
172
+                        }
173
+                    </TouchableOpacity>
174
+                    <TouchableOpacity
175
+                        onLayout = { this._createOnLayout(LABEL_ID_STREAMING) }
176
+                        onPress = { this._createOnPress(LABEL_ID_STREAMING) } >
177
+                        {
178
+                            this._renderRecordingLabel(
179
+                                JitsiRecordingConstants.mode.STREAM)
180
+                        }
181
+                    </TouchableOpacity>
182
+                    <TouchableOpacity
183
+                        onLayout = {
184
+                            this._createOnLayout(LABEL_ID_TRANSCRIBING)
185
+                        }
186
+                        onPress = {
187
+                            this._createOnPress(LABEL_ID_TRANSCRIBING)
188
+                        } >
189
+                        {
190
+                            this._renderTranscribingLabel()
191
+                        }
192
+                    </TouchableOpacity>
193
+                    {/*
194
+                      * Emil, Lyubomir, Nichole, and Zoli said that the Labels
195
+                      * should not be rendered in Picture-in-Picture. Saul
196
+                      * argued that the recording Labels should be rendered. As
197
+                      * a temporary compromise, don't render the
198
+                      * VideoQualityLabel at least because it's not that
199
+                      * important.
200
+                      */
201
+                        _reducedUI || (
202
+                            <TouchableOpacity
203
+                                onLayout = {
204
+                                    this._createOnLayout(LABEL_ID_QUALITY) }
205
+                                onPress = {
206
+                                    this._createOnPress(LABEL_ID_QUALITY) } >
207
+                                { this._renderVideoQualityLabel() }
208
+                            </TouchableOpacity>
209
+                        )
210
+                    }
211
+                </View>
212
+                <View
213
+                    style = { [
214
+                        styles.indicatorContainer,
215
+                        wide && _filmstripVisible
216
+                            && styles.indicatorContainerWide
217
+                    ] }>
218
+                    {
219
+                        this._renderExpandedLabel()
220
+                    }
221
+                </View>
73 222
             </View>
74 223
         );
75 224
     }
76 225
 
226
+    /**
227
+     * Creates a function to be invoked when the onLayout of the touchables are
228
+     * triggered.
229
+     *
230
+     * @param {string} label - The identifier of the label that's onLayout is
231
+     * triggered.
232
+     * @returns {Function}
233
+     */
234
+    _createOnLayout(label) {
235
+        return ({ nativeEvent: { layout } }) => {
236
+            const { labelLayouts } = this.state;
237
+            const updatedLayout = {};
238
+
239
+            updatedLayout[label] = layout;
240
+
241
+            this.setState({
242
+                labelLayouts: {
243
+                    ...labelLayouts,
244
+                    ...updatedLayout
245
+                }
246
+            });
247
+        };
248
+    }
249
+
250
+    /**
251
+     * Creates a function to be invoked when the onPress of the touchables are
252
+     * triggered.
253
+     *
254
+     * @param {string} label - The identifier of the label that's onLayout is
255
+     * triggered.
256
+     * @returns {Function}
257
+     */
258
+    _createOnPress(label) {
259
+        return () => {
260
+            const {
261
+                containerLayout,
262
+                labelLayouts
263
+            } = this.state;
264
+            let { visibleExpandedLabel } = this.state;
265
+
266
+            if (containerLayout) {
267
+                const labelLayout = labelLayouts[label];
268
+
269
+                // This calculation has to be changed if the labels are not
270
+                // positioned right anymore.
271
+                const right = containerLayout.width - labelLayout.x;
272
+
273
+                visibleExpandedLabel
274
+                    = visibleExpandedLabel === label ? undefined : label;
275
+
276
+                clearTimeout(this.expandedLabelTimeout);
277
+                this.setState({
278
+                    parentPosition: right,
279
+                    visibleExpandedLabel
280
+                });
281
+
282
+                if (visibleExpandedLabel) {
283
+                    this.expandedLabelTimeout = setTimeout(() => {
284
+                        this.setState({
285
+                            visibleExpandedLabel: undefined
286
+                        });
287
+                    }, EXPANDED_LABEL_TIMEOUT);
288
+                }
289
+            }
290
+        };
291
+    }
292
+
293
+    _onTopViewLayout: Object => void
294
+
295
+    /**
296
+     * Invoked when the View containing the {@code Label}s is laid out.
297
+     *
298
+     * @param {Object} layout - The native layout object.
299
+     * @returns {void}
300
+     */
301
+    _onTopViewLayout({ nativeEvent: { layout } }) {
302
+        this.setState({
303
+            containerLayout: layout
304
+        });
305
+    }
306
+
307
+    /**
308
+     * Rendes the expanded (explaining) label for the label that was touched.
309
+     *
310
+     * @returns {React$Element}
311
+     */
312
+    _renderExpandedLabel() {
313
+        const { parentPosition, visibleExpandedLabel } = this.state;
314
+
315
+        if (visibleExpandedLabel) {
316
+            const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel];
317
+
318
+            if (expandedLabel) {
319
+                const component = expandedLabel.component || expandedLabel;
320
+                const expandedLabelProps = expandedLabel.props || {};
321
+
322
+                return React.createElement(component, {
323
+                    ...expandedLabelProps,
324
+                    parentPosition
325
+                });
326
+            }
327
+        }
328
+
329
+        return null;
330
+    }
331
+
77 332
     _renderRecordingLabel: string => React$Element<*>;
78 333
 
79 334
     _renderTranscribingLabel: () => React$Element<*>

+ 14
- 8
react/features/large-video/components/styles.js 查看文件

@@ -9,13 +9,26 @@ import { FILMSTRIP_SIZE } from '../../filmstrip';
9 9
 export const AVATAR_SIZE = 200;
10 10
 
11 11
 export default createStyleSheet({
12
+
12 13
     /**
13 14
      * View that contains the indicators.
14 15
      */
15 16
     indicatorContainer: {
16 17
         flex: 1,
17 18
         flexDirection: 'row',
18
-        margin: BoxModel.margin,
19
+        justifyContent: 'flex-end',
20
+        margin: BoxModel.margin
21
+    },
22
+
23
+    /**
24
+     * Indicator container for wide aspect ratio.
25
+     */
26
+    indicatorContainerWide: {
27
+        marginRight: FILMSTRIP_SIZE + BoxModel.margin
28
+    },
29
+
30
+    labelWrapper: {
31
+        flexDirection: 'column',
19 32
         position: 'absolute',
20 33
         right: 0,
21 34
 
@@ -25,13 +38,6 @@ export default createStyleSheet({
25 38
         top: BoxModel.margin * 3
26 39
     },
27 40
 
28
-    /**
29
-     * Indicator container for wide aspect ratio.
30
-     */
31
-    indicatorContainerWide: {
32
-        right: FILMSTRIP_SIZE
33
-    },
34
-
35 41
     /**
36 42
      * Large video container style.
37 43
      */

+ 10
- 16
react/features/recording/components/AbstractRecordingLabel.js 查看文件

@@ -4,6 +4,8 @@ import { Component } from 'react';
4 4
 
5 5
 import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
6 6
 
7
+import { getSessionStatusToShow } from '../functions';
8
+
7 9
 /**
8 10
  * NOTE: Web currently renders multiple indicators if multiple recording
9 11
  * sessions are running. This is however may not be a good UX as it's not
@@ -12,13 +14,12 @@ import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
12 14
  * running. These boolean are shared across the two components to make it
13 15
  * easier to align web's behaviour to mobile's later if necessary.
14 16
  */
15
-export type Props = {
17
+type Props = {
16 18
 
17 19
     /**
18
-     * True if there is an active recording with the provided mode therefore the
19
-     * component must be rendered.
20
+     * The status of the highermost priority session.
20 21
      */
21
-    _visible: boolean,
22
+    _status: ?string,
22 23
 
23 24
     /**
24 25
      * The recording mode this indicator should display.
@@ -34,8 +35,8 @@ export type Props = {
34 35
 /**
35 36
  * Abstract class for the {@code RecordingLabel} component.
36 37
  */
37
-export default class AbstractRecordingLabel<P: Props>
38
-    extends Component<P> {
38
+export default class AbstractRecordingLabel
39
+    extends Component<Props> {
39 40
 
40 41
     /**
41 42
      * Implements React {@code Component}'s render.
@@ -43,7 +44,7 @@ export default class AbstractRecordingLabel<P: Props>
43 44
      * @inheritdoc
44 45
      */
45 46
     render() {
46
-        return this.props._visible ? this._renderLabel() : null;
47
+        return this.props._status ? this._renderLabel() : null;
47 48
     }
48 49
 
49 50
     _getLabelKey: () => ?string
@@ -84,20 +85,13 @@ export default class AbstractRecordingLabel<P: Props>
84 85
  * @param {Props} ownProps - The component's own props.
85 86
  * @private
86 87
  * @returns {{
87
- *     _visible: boolean
88
+ *     _status: ?string
88 89
  * }}
89 90
  */
90 91
 export function _mapStateToProps(state: Object, ownProps: Props) {
91 92
     const { mode } = ownProps;
92
-    const _recordingSessions = state['features/recording'].sessionDatas;
93
-    const _visible
94
-        = Array.isArray(_recordingSessions)
95
-        && _recordingSessions.some(
96
-            session => session.status === JitsiRecordingConstants.status.ON
97
-            && session.mode === mode
98
-        );
99 93
 
100 94
     return {
101
-        _visible
95
+        _status: getSessionStatusToShow(state, mode)
102 96
     };
103 97
 }

+ 108
- 0
react/features/recording/components/RecordingExpandedLabel.native.js 查看文件

@@ -0,0 +1,108 @@
1
+// @flow
2
+
3
+import { connect } from 'react-redux';
4
+
5
+import { translate } from '../../base/i18n';
6
+import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
7
+import {
8
+    ExpandedLabel,
9
+    type Props as AbstractProps
10
+} from '../../base/label';
11
+
12
+import { getSessionStatusToShow } from '../functions';
13
+
14
+import { LIVE_LABEL_COLOR, REC_LABEL_COLOR } from './styles';
15
+
16
+type Props = AbstractProps & {
17
+
18
+    /**
19
+     * The status of the highermost priority session.
20
+     */
21
+    _status: ?string,
22
+
23
+    /**
24
+     * The recording mode this indicator should display.
25
+     */
26
+    mode: string,
27
+
28
+    /**
29
+     * Function to be used to translate i18n labels.
30
+     */
31
+    t: Function
32
+}
33
+
34
+/**
35
+ * A react {@code Component} that implements an expanded label as tooltip-like
36
+ * component to explain the meaning of the {@code RecordingLabel}.
37
+ */
38
+class RecordingExpandedLabel extends ExpandedLabel<Props> {
39
+    /**
40
+     * Returns the color this expanded label should be rendered with.
41
+     *
42
+     * @returns {string}
43
+     */
44
+    _getColor() {
45
+        switch (this.props.mode) {
46
+        case JitsiRecordingConstants.mode.STREAM:
47
+            return LIVE_LABEL_COLOR;
48
+        case JitsiRecordingConstants.mode.FILE:
49
+            return REC_LABEL_COLOR;
50
+        default:
51
+            return null;
52
+        }
53
+    }
54
+
55
+    /**
56
+     * Returns the label specific text of this {@code ExpandedLabel}.
57
+     *
58
+     * @returns {string}
59
+     */
60
+    _getLabel() {
61
+        const { _status, mode, t } = this.props;
62
+        let postfix = 'recording', prefix = 'expandedOn'; // Default values.
63
+
64
+        switch (mode) {
65
+        case JitsiRecordingConstants.mode.STREAM:
66
+            prefix = 'liveStreaming';
67
+            break;
68
+        case JitsiRecordingConstants.mode.FILE:
69
+            prefix = 'recording';
70
+            break;
71
+        }
72
+
73
+        switch (_status) {
74
+        case JitsiRecordingConstants.status.OFF:
75
+            postfix = 'expandedOff';
76
+            break;
77
+        case JitsiRecordingConstants.status.PENDING:
78
+            postfix = 'expandedPending';
79
+            break;
80
+        case JitsiRecordingConstants.status.ON:
81
+            postfix = 'expandedOn';
82
+            break;
83
+        }
84
+
85
+        return t(`${prefix}.${postfix}`);
86
+    }
87
+}
88
+
89
+/**
90
+ * Maps (parts of) the Redux state to the associated
91
+ * {@code RecordingExpandedLabel}'s props.
92
+ *
93
+ * @param {Object} state - The Redux state.
94
+ * @param {Props} ownProps - The component's own props.
95
+ * @private
96
+ * @returns {{
97
+ *     _status: ?string
98
+ * }}
99
+ */
100
+function _mapStateToProps(state: Object, ownProps: Props) {
101
+    const { mode } = ownProps;
102
+
103
+    return {
104
+        _status: getSessionStatusToShow(state, mode)
105
+    };
106
+}
107
+
108
+export default translate(connect(_mapStateToProps)(RecordingExpandedLabel));

+ 0
- 0
react/features/recording/components/RecordingExpandedLabel.web.js 查看文件


+ 13
- 2
react/features/recording/components/RecordingLabel.native.js 查看文件

@@ -8,7 +8,6 @@ import { CircularLabel } from '../../base/label';
8 8
 import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
9 9
 
10 10
 import AbstractRecordingLabel, {
11
-    type Props,
12 11
     _mapStateToProps
13 12
 } from './AbstractRecordingLabel';
14 13
 import styles from './styles';
@@ -19,7 +18,7 @@ import styles from './styles';
19 18
  *
20 19
  * @extends {Component}
21 20
  */
22
-class RecordingLabel extends AbstractRecordingLabel<Props> {
21
+class RecordingLabel extends AbstractRecordingLabel {
23 22
 
24 23
     /**
25 24
      * Renders the platform specific label component.
@@ -41,9 +40,21 @@ class RecordingLabel extends AbstractRecordingLabel<Props> {
41 40
             return null;
42 41
         }
43 42
 
43
+        let status = 'on';
44
+
45
+        switch (this.props._status) {
46
+        case JitsiRecordingConstants.status.PENDING:
47
+            status = 'in_progress';
48
+            break;
49
+        case JitsiRecordingConstants.status.OFF:
50
+            status = 'off';
51
+            break;
52
+        }
53
+
44 54
         return (
45 55
             <CircularLabel
46 56
                 label = { this.props.t(this._getLabelKey()) }
57
+                status = { status }
47 58
                 style = { indicatorStyle } />
48 59
         );
49 60
     }

+ 1
- 2
react/features/recording/components/RecordingLabel.web.js 查看文件

@@ -7,7 +7,6 @@ import { CircularLabel } from '../../base/label';
7 7
 import { translate } from '../../base/i18n';
8 8
 
9 9
 import AbstractRecordingLabel, {
10
-    type Props,
11 10
     _mapStateToProps
12 11
 } from './AbstractRecordingLabel';
13 12
 
@@ -17,7 +16,7 @@ import AbstractRecordingLabel, {
17 16
  *
18 17
  * @extends {Component}
19 18
  */
20
-class RecordingLabel extends AbstractRecordingLabel<Props> {
19
+class RecordingLabel extends AbstractRecordingLabel {
21 20
     /**
22 21
      * Renders the platform specific label component.
23 22
      *

+ 1
- 0
react/features/recording/components/index.js 查看文件

@@ -9,3 +9,4 @@ export {
9 9
     StopRecordingDialog
10 10
 } from './Recording';
11 11
 export { default as RecordingLabel } from './RecordingLabel';
12
+export { default as RecordingExpandedLabel } from './RecordingExpandedLabel';

+ 5
- 2
react/features/recording/components/styles.js 查看文件

@@ -2,6 +2,9 @@
2 2
 
3 3
 import { ColorPalette, createStyleSheet } from '../../base/styles';
4 4
 
5
+export const LIVE_LABEL_COLOR = ColorPalette.blue;
6
+export const REC_LABEL_COLOR = ColorPalette.red;
7
+
5 8
 /**
6 9
  * The styles of the React {@code Components} of the feature recording.
7 10
  */
@@ -11,13 +14,13 @@ export default createStyleSheet({
11 14
      * Style for the recording indicator.
12 15
      */
13 16
     indicatorLive: {
14
-        backgroundColor: ColorPalette.blue
17
+        backgroundColor: LIVE_LABEL_COLOR
15 18
     },
16 19
 
17 20
     /**
18 21
      * Style for the recording indicator.
19 22
      */
20 23
     indicatorRecording: {
21
-        backgroundColor: ColorPalette.red
24
+        backgroundColor: REC_LABEL_COLOR
22 25
     }
23 26
 });

+ 14
- 0
react/features/recording/constants.js 查看文件

@@ -1,5 +1,7 @@
1 1
 // @flow
2 2
 
3
+import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
4
+
3 5
 /**
4 6
  * The identifier of the sound to be played when a recording or live streaming
5 7
  * session is stopped.
@@ -26,3 +28,15 @@ export const RECORDING_TYPES = {
26 28
     JIBRI: 'jibri',
27 29
     JIRECON: 'jirecon'
28 30
 };
31
+
32
+/**
33
+ * An array defining the priorities of the recording (or live streaming)
34
+ * statuses, where the index of the array is the priority itself.
35
+ *
36
+ * @type {Array<string>}
37
+ */
38
+export const RECORDING_STATUS_PRIORITIES = [
39
+    JitsiRecordingConstants.status.OFF,
40
+    JitsiRecordingConstants.status.PENDING,
41
+    JitsiRecordingConstants.status.ON
42
+];

+ 29
- 0
react/features/recording/functions.js 查看文件

@@ -2,6 +2,8 @@
2 2
 
3 3
 import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
4 4
 
5
+import { RECORDING_STATUS_PRIORITIES } from './constants';
6
+
5 7
 /**
6 8
  * Searches in the passed in redux state for an active recording session of the
7 9
  * passed in mode.
@@ -43,3 +45,30 @@ export function getSessionById(state: Object, id: string) {
43 45
     return state['features/recording'].sessionDatas.find(
44 46
         sessionData => sessionData.id === id);
45 47
 }
48
+
49
+/**
50
+ * Returns the recording session status that is to be shown in a label. E.g. if
51
+ * there is a session with the status OFF and one with PENDING, then the PENDING
52
+ * one will be shown, because that is likely more important for the user to see.
53
+ *
54
+ * @param {Object} state - The redux state to search in.
55
+ * @param {string} mode - The recording mode to get status for.
56
+ * @returns {string|undefined}
57
+ */
58
+export function getSessionStatusToShow(state: Object, mode: string): ?string {
59
+    const recordingSessions = state['features/recording'].sessionDatas;
60
+    let status;
61
+
62
+    if (Array.isArray(recordingSessions)) {
63
+        for (const session of recordingSessions) {
64
+            if (session.mode === mode
65
+                    && (!status
66
+                        || (RECORDING_STATUS_PRIORITIES.indexOf(session.status)
67
+                            > RECORDING_STATUS_PRIORITIES.indexOf(status)))) {
68
+                status = session.status;
69
+            }
70
+        }
71
+    }
72
+
73
+    return status;
74
+}

+ 25
- 0
react/features/transcribing/components/TranscribingExpandedLabel.native.js 查看文件

@@ -0,0 +1,25 @@
1
+// @flow
2
+
3
+import { translate } from '../../base/i18n';
4
+import { ExpandedLabel, type Props as AbstractProps } from '../../base/label';
5
+
6
+type Props = AbstractProps & {
7
+    t: Function
8
+}
9
+
10
+/**
11
+ * A react {@code Component} that implements an expanded label as tooltip-like
12
+ * component to explain the meaning of the {@code TranscribingLabel}.
13
+ */
14
+class TranscribingExpandedLabel extends ExpandedLabel<Props> {
15
+    /**
16
+     * Returns the label specific text of this {@code ExpandedLabel}.
17
+     *
18
+     * @returns {string}
19
+     */
20
+    _getLabel() {
21
+        return this.props.t('transcribing.expandedLabel');
22
+    }
23
+}
24
+
25
+export default translate(TranscribingExpandedLabel);

+ 0
- 0
react/features/transcribing/components/TranscribingExpandedLabel.web.js 查看文件


+ 3
- 0
react/features/transcribing/components/index.js 查看文件

@@ -1 +1,4 @@
1 1
 export { default as TranscribingLabel } from './TranscribingLabel';
2
+export {
3
+    default as TranscribingExpandedLabel
4
+} from './TranscribingExpandedLabel';

+ 36
- 0
react/features/video-quality/components/VideoQualityExpandedLabel.native.js 查看文件

@@ -0,0 +1,36 @@
1
+// @flow
2
+
3
+import { translate } from '../../base/i18n';
4
+import { ExpandedLabel, type Props as AbstractProps } from '../../base/label';
5
+
6
+import { AUD_LABEL_COLOR } from './styles';
7
+
8
+type Props = AbstractProps & {
9
+    t: Function
10
+}
11
+
12
+/**
13
+ * A react {@code Component} that implements an expanded label as tooltip-like
14
+ * component to explain the meaning of the {@code VideoQualityLabel}.
15
+ */
16
+class VideoQualityExpandedLabel extends ExpandedLabel<Props> {
17
+    /**
18
+     * Returns the color this expanded label should be rendered with.
19
+     *
20
+     * @returns {string}
21
+     */
22
+    _getColor() {
23
+        return AUD_LABEL_COLOR;
24
+    }
25
+
26
+    /**
27
+     * Returns the label specific text of this {@code ExpandedLabel}.
28
+     *
29
+     * @returns {string}
30
+     */
31
+    _getLabel() {
32
+        return this.props.t('videoStatus.audioOnlyExpanded');
33
+    }
34
+}
35
+
36
+export default translate(VideoQualityExpandedLabel);

+ 0
- 0
react/features/video-quality/components/VideoQualityExpandedLabel.web.js 查看文件


+ 3
- 0
react/features/video-quality/components/index.js 查看文件

@@ -3,3 +3,6 @@ export {
3 3
 } from './OverflowMenuVideoQualityItem';
4 4
 export { default as VideoQualityDialog } from './VideoQualityDialog';
5 5
 export { default as VideoQualityLabel } from './VideoQualityLabel';
6
+export {
7
+    default as VideoQualityExpandedLabel
8
+} from './VideoQualityExpandedLabel';

+ 3
- 1
react/features/video-quality/components/styles.js 查看文件

@@ -2,6 +2,8 @@
2 2
 
3 3
 import { ColorPalette, createStyleSheet } from '../../base/styles';
4 4
 
5
+export const AUD_LABEL_COLOR = ColorPalette.green;
6
+
5 7
 /**
6 8
  * The styles of the React {@code Components} of the feature video-quality.
7 9
  */
@@ -11,6 +13,6 @@ export default createStyleSheet({
11 13
      * Style for the audio-only indicator.
12 14
      */
13 15
     indicatorAudioOnly: {
14
-        backgroundColor: ColorPalette.green
16
+        backgroundColor: AUD_LABEL_COLOR
15 17
     }
16 18
 });

正在加载...
取消
保存