Browse Source

feat: display filmstrip on the short side

Adds the ability to detect app area's aspect ratio on react-native
through the features/base/aspect-ratio.

Makes conference, filmstrip and toolbox react to the aspect ratio
changes and display filmstrip on the shorter side of the screen.
master
paweldomas 7 years ago
parent
commit
c0a7d6144a

+ 15
- 0
react/features/app/components/App.native.js View File

@@ -1,10 +1,12 @@
1 1
 /* global __DEV__ */
2 2
 
3 3
 import PropTypes from 'prop-types';
4
+import React from 'react';
4 5
 import { Linking } from 'react-native';
5 6
 
6 7
 import '../../analytics';
7 8
 import '../../authentication';
9
+import { AspectRatioDetector } from '../../base/aspect-ratio';
8 10
 import { Platform } from '../../base/react';
9 11
 import '../../mobile/audio-mode';
10 12
 import '../../mobile/background';
@@ -86,6 +88,19 @@ export class App extends AbstractApp {
86 88
         super.componentWillUnmount();
87 89
     }
88 90
 
91
+    /**
92
+     * Overrides the super method to inject {@link AspectRatioDetector} as
93
+     * the top most component.
94
+     *
95
+     * @override
96
+     */
97
+    _createElement(component, props) {
98
+        return (
99
+            <AspectRatioDetector>
100
+                {super._createElement(component, props)}
101
+            </AspectRatioDetector>);
102
+    }
103
+
89 104
     /**
90 105
      * Attempts to disable the use of React Native
91 106
      * {@link ExceptionsManager#handleException} on platforms and in

+ 9
- 0
react/features/base/aspect-ratio/actionTypes.js View File

@@ -0,0 +1,9 @@
1
+/**
2
+ * The type of (redux) action which signals that a new aspect ratio has been
3
+ * detected by the app.
4
+ * {
5
+ *      type: SET_ASPECT_RATIO,
6
+ *      aspectRatio: Symbol
7
+ * }
8
+ */
9
+export const SET_ASPECT_RATIO = Symbol('SET_ASPECT_RATIO');

+ 22
- 0
react/features/base/aspect-ratio/actions.js View File

@@ -0,0 +1,22 @@
1
+/* @flow */
2
+
3
+import { SET_ASPECT_RATIO } from './actionTypes';
4
+import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
5
+
6
+/**
7
+ * Calculates new aspect ratio for the app based on provided width and height
8
+ * values.
9
+ *
10
+ * @param {number} width - The width of the app's area used on the screen.
11
+ * @param {number} height - The height of the app's area used on the screen.
12
+ * @returns {{
13
+ *      type: SET_ASPECT_RATIO,
14
+ *      aspectRatio: Symbol
15
+ * }}
16
+ */
17
+export function calculateNewAspectRatio(width: number, height: number): Object {
18
+    return {
19
+        type: SET_ASPECT_RATIO,
20
+        aspectRatio: width > height ? ASPECT_RATIO_WIDE : ASPECT_RATIO_NARROW
21
+    };
22
+}

+ 68
- 0
react/features/base/aspect-ratio/components/AspectRatioAware.js View File

@@ -0,0 +1,68 @@
1
+// @flow
2
+import PropTypes from 'prop-types';
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { ASPECT_RATIO_NARROW } from '../constants';
7
+
8
+/**
9
+ * Decorates given React component class into {@link AspectRatioAwareWrapper}
10
+ * which provides the <tt>aspectRatio</tt> property updated on each Redux state
11
+ * change.
12
+ *
13
+ * @param {ReactClass} WrapperComponent - A React component class to be wrapped.
14
+ * @returns {AspectRatioAwareWrapper}
15
+ */
16
+export function AspectRatioAware(
17
+        WrapperComponent: ReactClass<*>): ReactClass<*> {
18
+    return connect(_mapStateToProps)(
19
+        class AspectRatioAwareWrapper extends Component {
20
+            /**
21
+             * Properties of the aspect ratio aware wrapper.
22
+             */
23
+            static propTypes = {
24
+                /**
25
+                 * Either {@link ASPECT_RATIO_NARROW} or
26
+                 * {@link ASPECT_RATIO_WIDE}.
27
+                 */
28
+                aspectRatio: PropTypes.symbol
29
+            }
30
+
31
+            /**
32
+             * Implement's React render method to wrap the nested component.
33
+             *
34
+             * @returns {XML}
35
+             */
36
+            render(): React$Element<*> {
37
+                return <WrapperComponent { ...this.props } />;
38
+            }
39
+        });
40
+}
41
+
42
+/**
43
+ * Maps Redux state to {@link AspectRatioAwareWrapper} properties.
44
+ *
45
+ * @param {Object} state - The Redux whole state.
46
+ * @returns {{
47
+ *      aspectRatio: Symbol
48
+ * }}
49
+ * @private
50
+ */
51
+function _mapStateToProps(state) {
52
+    return {
53
+        aspectRatio: state['features/base/aspect-ratio'].aspectRatio
54
+    };
55
+}
56
+
57
+/**
58
+ * Checks if given React component decorated in {@link AspectRatioAwareWrapper}
59
+ * has currently the {@link ASPECT_RATIO_NARROW} set in the aspect ratio
60
+ * property.
61
+ *
62
+ * @param {AspectRatioAwareWrapper} component - A
63
+ * {@link AspectRatioAwareWrapper} which has <tt>aspectRation</tt> property.
64
+ * @returns {boolean}
65
+ */
66
+export function isNarrowAspectRatio(component: ReactClass<*>) {
67
+    return component.props.aspectRatio === ASPECT_RATIO_NARROW;
68
+}

+ 74
- 0
react/features/base/aspect-ratio/components/AspectRatioDetector.native.js View File

@@ -0,0 +1,74 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { View } from 'react-native';
4
+import { connect } from 'react-redux';
5
+
6
+import { calculateNewAspectRatio } from '../actions';
7
+import styles from './styles';
8
+
9
+/**
10
+ * A root {@link View} which captures the 'onLayout' event and figures out
11
+ * the aspect ratio of the app.
12
+ */
13
+class AspectRatioDetector extends Component {
14
+    /**
15
+     * AspectRatioDetector component's property types.
16
+     *
17
+     * @static
18
+     */
19
+    static propTypes = {
20
+        /**
21
+         * The "onLayout" handler.
22
+         */
23
+        _onLayout: PropTypes.func,
24
+
25
+        /**
26
+         * Any nested components.
27
+         */
28
+        children: PropTypes.object
29
+    };
30
+
31
+    /**
32
+     * Renders the root view and it's children.
33
+     *
34
+     * @returns {Component}
35
+     */
36
+    render() {
37
+        return (
38
+            <View
39
+                onLayout = { this.props._onLayout }
40
+                style = { styles.aspectRatioDetectorStyle } >
41
+                {this.props.children}
42
+            </View>);
43
+    }
44
+}
45
+
46
+/**
47
+ * Maps dispatching of the aspect ratio actions to React component props.
48
+ *
49
+ * @param {Function} dispatch - Redux action dispatcher.
50
+ * @private
51
+ * @returns {{
52
+ *     _onLayout: Function
53
+ * }}
54
+ */
55
+function _mapDispatchToProps(dispatch) {
56
+    return {
57
+        /**
58
+         * Handles the "on layout" View's event and dispatches aspect ratio
59
+         * changed action.
60
+         *
61
+         * @param {{ width: number, height: number }} event - The "on layout"
62
+         * event structure passed by react-native.
63
+         * @returns {void}
64
+         * @private
65
+         */
66
+        _onLayout(event) {
67
+            const { width, height } = event.nativeEvent.layout;
68
+
69
+            dispatch(calculateNewAspectRatio(width, height));
70
+        }
71
+    };
72
+}
73
+
74
+export default connect(undefined, _mapDispatchToProps)(AspectRatioDetector);

+ 2
- 0
react/features/base/aspect-ratio/components/index.js View File

@@ -0,0 +1,2 @@
1
+export * from './AspectRatioAware';
2
+export { default as AspectRatioDetector } from './AspectRatioDetector';

+ 14
- 0
react/features/base/aspect-ratio/components/styles.js View File

@@ -0,0 +1,14 @@
1
+import { createStyleSheet, fixAndroidViewClipping } from '../../styles/index';
2
+
3
+/**
4
+ * The styles of the feature app.
5
+ */
6
+export default createStyleSheet({
7
+    /**
8
+     * The style for {@link AspectRatioDetector} root view used on react-native.
9
+     */
10
+    aspectRatioDetectorStyle: fixAndroidViewClipping({
11
+        alignSelf: 'stretch',
12
+        flex: 1
13
+    })
14
+});

+ 15
- 0
react/features/base/aspect-ratio/constants.js View File

@@ -0,0 +1,15 @@
1
+/**
2
+ * The aspect ratio constant indicates that the app area's width is smaller than
3
+ * the height.
4
+ *
5
+ * @type {Symbol}
6
+ */
7
+export const ASPECT_RATIO_NARROW = Symbol('ASPECT_RATIO_NARROW');
8
+
9
+/**
10
+ * Aspect ratio constant indicates that the app area's width is larger than
11
+ * the height.
12
+ *
13
+ * @type {Symbol}
14
+ */
15
+export const ASPECT_RATIO_WIDE = Symbol('ASPECT_RATIO_WIDE');

+ 6
- 0
react/features/base/aspect-ratio/index.js View File

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

+ 19
- 0
react/features/base/aspect-ratio/reducer.js View File

@@ -0,0 +1,19 @@
1
+import { ReducerRegistry, set } from '../redux';
2
+
3
+import { SET_ASPECT_RATIO } from './actionTypes';
4
+import { ASPECT_RATIO_NARROW } from './constants';
5
+
6
+const INITIAL_STATE = {
7
+    aspectRatio: ASPECT_RATIO_NARROW
8
+};
9
+
10
+ReducerRegistry.register(
11
+'features/base/aspect-ratio',
12
+(state = INITIAL_STATE, action) => {
13
+    switch (action.type) {
14
+    case SET_ASPECT_RATIO:
15
+        return set(state, 'aspectRatio', action.aspectRatio);
16
+    }
17
+
18
+    return state;
19
+});

+ 16
- 13
react/features/conference/components/Conference.native.js View File

@@ -184,15 +184,6 @@ class Conference extends Component {
184 184
                   */}
185 185
                 <LargeVideo />
186 186
 
187
-                {/*
188
-                  * The Filmstrip is in a stacking layer above the LargeVideo.
189
-                  * The LargeVideo and the Filmstrip form what the Web/React app
190
-                  * calls "videospace". Presumably, the name and grouping stem
191
-                  * from the fact that these two React Components depict the
192
-                  * videos of the conference's participants.
193
-                  */}
194
-                <Filmstrip />
195
-
196 187
                 {/*
197 188
                   * The overlays need to be bellow the Toolbox so that the user
198 189
                   * may tap the ToolbarButtons.
@@ -209,10 +200,22 @@ class Conference extends Component {
209 200
                         </View>
210 201
                 }
211 202
 
212
-                {/*
213
-                  * The Toolbox is in a stacking layer above the Filmstrip.
214
-                  */}
215
-                <Toolbox />
203
+                <View style = { styles.toolboxAndFilmstripContainer } >
204
+                    {/*
205
+                      * The Toolbox is in a stacking layer above the Filmstrip.
206
+                      */}
207
+                    <Toolbox />
208
+                    {/*
209
+                      * The Filmstrip is in a stacking layer above
210
+                      * the LargeVideo.
211
+                      * The LargeVideo and the Filmstrip form what the Web/React
212
+                      * app calls "videospace". Presumably, the name and
213
+                      * grouping stem from the fact that these two React
214
+                      * Components depict the videos of the conference's
215
+                      * participants.
216
+                      */}
217
+                    <Filmstrip />
218
+                </View>
216 219
 
217 220
                 {/*
218 221
                   * The dialogs are in the topmost stacking layers.

+ 14
- 0
react/features/conference/components/styles.js View File

@@ -38,5 +38,19 @@ export default createStyleSheet({
38 38
         // contrast and translucency.
39 39
         backgroundColor: ColorPalette.appBackground,
40 40
         opacity: 0.5
41
+    },
42
+
43
+    /**
44
+     * The style of the view which expands over the whole conference area and
45
+     * splits it between both the filmstrip and the toolbox.
46
+     */
47
+    toolboxAndFilmstripContainer: {
48
+        bottom: 0,
49
+        flexDirection: 'column',
50
+        left: 0,
51
+        justifyContent: 'flex-end',
52
+        position: 'absolute',
53
+        right: 0,
54
+        top: 0
41 55
     }
42 56
 });

+ 15
- 11
react/features/filmstrip/components/Filmstrip.native.js View File

@@ -5,6 +5,7 @@ import React, { Component } from 'react';
5 5
 import { ScrollView } from 'react-native';
6 6
 import { connect } from 'react-redux';
7 7
 
8
+import { AspectRatioAware, isNarrowAspectRatio } from '../../base/aspect-ratio';
8 9
 import { Container } from '../../base/react';
9 10
 
10 11
 import Thumbnail from './Thumbnail';
@@ -47,15 +48,16 @@ class Filmstrip extends Component<*> {
47 48
      * @returns {ReactElement}
48 49
      */
49 50
     render() {
51
+        const filmstripStyle
52
+            = isNarrowAspectRatio(this)
53
+                ? styles.filmstripNarrow : styles.filmstripWide;
54
+
50 55
         return (
51 56
             <Container
52
-                style = { styles.filmstrip }
53
-                visible = { this.props._visible }>
57
+                style = { filmstripStyle }
58
+                visible = { this.props._visible } >
54 59
                 <ScrollView
55
-
56
-                    contentContainerStyle
57
-                        = { styles.filmstripScrollViewContentContainer }
58
-                    horizontal = { true }
60
+                    horizontal = { isNarrowAspectRatio(this) }
59 61
                     showsHorizontalScrollIndicator = { false }
60 62
                     showsVerticalScrollIndicator = { false }>
61 63
                     {
@@ -121,6 +123,8 @@ class Filmstrip extends Component<*> {
121 123
  * }}
122 124
  */
123 125
 function _mapStateToProps(state) {
126
+    const participants = state['features/base/participants'];
127
+
124 128
     return {
125 129
         /**
126 130
          * The participants in the conference.
@@ -128,20 +132,20 @@ function _mapStateToProps(state) {
128 132
          * @private
129 133
          * @type {Participant[]}
130 134
          */
131
-        _participants: state['features/base/participants'],
135
+        _participants: participants,
132 136
 
133 137
         /**
134 138
          * The indicator which determines whether the filmstrip is visible.
135 139
          *
136 140
          * XXX The React Component Filmstrip is used on mobile only at the time
137
-         * of this writing and on mobile the filmstrip is visible when the
138
-         * toolbar is not.
141
+         * of this writing and on mobile the filmstrip is when there are at
142
+         * least 2 participants in the conference (including the local one).
139 143
          *
140 144
          * @private
141 145
          * @type {boolean}
142 146
          */
143
-        _visible: !state['features/toolbox'].visible
147
+        _visible: participants.length > 1
144 148
     };
145 149
 }
146 150
 
147
-export default connect(_mapStateToProps)(Filmstrip);
151
+export default connect(_mapStateToProps)(AspectRatioAware(Filmstrip));

+ 25
- 16
react/features/filmstrip/components/styles.js View File

@@ -1,6 +1,14 @@
1 1
 import { Platform } from '../../base/react';
2 2
 import { BoxModel, ColorPalette } from '../../base/styles';
3 3
 
4
+/**
5
+ * The base filmstrip style shared between narrow and wide versions.
6
+ */
7
+const filmstripBaseStyle = {
8
+    flexGrow: 0,
9
+    flexDirection: 'column'
10
+};
11
+
4 12
 /**
5 13
  * The styles of the feature filmstrip common to both Web and native.
6 14
  */
@@ -40,26 +48,28 @@ export default {
40 48
     },
41 49
 
42 50
     /**
43
-     * The style of the Container which represents the very filmstrip.
51
+     * The style of the narrow filmstrip version which displays thumbnails
52
+     * in a row at the bottom of the screen.
44 53
      */
45
-    filmstrip: {
54
+    filmstripNarrow: {
55
+        ...filmstripBaseStyle,
46 56
         alignItems: 'flex-end',
47
-        alignSelf: 'stretch',
48
-        bottom: BoxModel.margin,
49
-        flex: 1,
50
-        flexDirection: 'column',
51
-        left: 0,
52
-        position: 'absolute',
53
-        right: 0
57
+        height: 90,
58
+        marginBottom: BoxModel.margin,
59
+        marginLeft: BoxModel.margin,
60
+        marginRight: BoxModel.margin
54 61
     },
55 62
 
56 63
     /**
57
-     * The style of the content container of the ScrollView which is placed
58
-     * inside filmstrip and which contains the participants' thumbnails in order
59
-     * to allow scrolling through them if they do not fit within the display.
64
+     * The style of the wide version of the filmstrip which appears as a column
65
+     * on the short side of the screen.
60 66
      */
61
-    filmstripScrollViewContentContainer: {
62
-        paddingHorizontal: BoxModel.padding
67
+    filmstripWide: {
68
+        ...filmstripBaseStyle,
69
+        bottom: BoxModel.margin,
70
+        left: BoxModel.margin,
71
+        position: 'absolute',
72
+        top: BoxModel.margin
63 73
     },
64 74
 
65 75
     /**
@@ -86,8 +96,7 @@ export default {
86 96
         borderWidth: 1,
87 97
         flex: 1,
88 98
         justifyContent: 'center',
89
-        marginLeft: 2,
90
-        marginRight: 2,
99
+        margin: 2,
91 100
         overflow: 'hidden',
92 101
         position: 'relative'
93 102
     },

+ 17
- 5
react/features/toolbox/components/Toolbox.native.js View File

@@ -4,6 +4,7 @@ import { View } from 'react-native';
4 4
 import { connect } from 'react-redux';
5 5
 
6 6
 import { sendAnalyticsEvent } from '../../analytics';
7
+import { AspectRatioAware, isNarrowAspectRatio } from '../../base/aspect-ratio';
7 8
 import { toggleAudioOnly } from '../../base/conference';
8 9
 import {
9 10
     MEDIA_TYPE,
@@ -119,15 +120,25 @@ class Toolbox extends Component {
119 120
      * @returns {ReactElement}
120 121
      */
121 122
     render() {
123
+        if (!this.props._visible) {
124
+            return null;
125
+        }
126
+
122 127
         return (
123 128
             <Container
124
-                style = { styles.toolbarContainer }
125
-                visible = { this.props._visible }>
129
+                style = {
130
+                    isNarrowAspectRatio(this)
131
+                        ? styles.toolbarContainerNarrow
132
+                        : styles.toolbarContainerWide } >
126 133
                 {
127
-                    this._renderPrimaryToolbar()
134
+                    isNarrowAspectRatio(this)
135
+                        ? this._renderSecondaryToolbar()
136
+                        : this._renderPrimaryToolbar()
128 137
                 }
129 138
                 {
130
-                    this._renderSecondaryToolbar()
139
+                    isNarrowAspectRatio(this)
140
+                        ? this._renderPrimaryToolbar()
141
+                        : this._renderSecondaryToolbar()
131 142
                 }
132 143
             </Container>
133 144
         );
@@ -420,4 +431,5 @@ function _mapStateToProps(state) {
420 431
     };
421 432
 }
422 433
 
423
-export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbox);
434
+export default connect(_mapStateToProps, _mapDispatchToProps)(
435
+    AspectRatioAware(Toolbox));

+ 18
- 4
react/features/toolbox/components/styles.js View File

@@ -6,7 +6,7 @@ import { BoxModel, ColorPalette, createStyleSheet } from '../../base/styles';
6 6
  * @type {Object}
7 7
  */
8 8
 const _toolbar = {
9
-    flex: 1,
9
+    flex: 0,
10 10
     position: 'absolute'
11 11
 };
12 12
 
@@ -86,7 +86,7 @@ export default createStyleSheet({
86 86
      */
87 87
     primaryToolbar: {
88 88
         ..._toolbar,
89
-        bottom: 3 * BoxModel.margin,
89
+        bottom: 0,
90 90
         flexDirection: 'row',
91 91
         justifyContent: 'center',
92 92
         left: 0,
@@ -135,9 +135,23 @@ export default createStyleSheet({
135 135
 
136 136
     /**
137 137
      * The style of the root/top-level {@link Container} of {@link Toolbox}
138
-     * which contains {@link Toolbar}s.
138
+     * which contains {@link Toolbar}s. This is narrow layout version which
139
+     * spans from the top of the screen to the top of the filmstrip located at
140
+     * the bottom of the screen.
141
+     */
142
+    toolbarContainerNarrow: {
143
+        flexDirection: 'column',
144
+        flexGrow: 1
145
+    },
146
+
147
+    /**
148
+     * The style of the root/top-level {@link Container} of {@link Toolbox}
149
+     * which contains {@link Toolbar}s. This is wide layout version which
150
+     * spans from the top to the bottom of the screen and is located to
151
+     * the right of the filmstrip which is displayed as a column on the left
152
+     * side of the screen.
139 153
      */
140
-    toolbarContainer: {
154
+    toolbarContainerWide: {
141 155
         bottom: 0,
142 156
         left: 0,
143 157
         position: 'absolute',

Loading…
Cancel
Save