Browse Source

Add hint box with dynamic join button

master
zbettenbuk 7 years ago
parent
commit
e23d4317eb

+ 1
- 0
lang/main.json View File

@@ -49,6 +49,7 @@
49 49
         "appDescription": "Go ahead, video chat with the whole team. In fact, invite everyone you know. __app__ is a fully encrypted, 100% open source video conferencing solution that you can use all day, every day, for free — with no account needed.",
50 50
         "audioOnlyLabel": "Voice",
51 51
         "go": "GO",
52
+        "hintText": "Enter a room name you want to join to, or simply create a new room name, eg. MeetingWithJohn",
52 53
         "join": "JOIN",
53 54
         "privacy": "Privacy",
54 55
         "roomname": "Enter room name",

+ 11
- 2
react/features/base/react/components/native/LoadingIndicator.js View File

@@ -5,12 +5,21 @@ import { ActivityIndicator } from 'react-native';
5 5
 
6 6
 import { ColorPalette } from '../../../styles';
7 7
 
8
+type Props = {
9
+
10
+    /**
11
+     * Prop to set the size of the indicator. This is the same as the
12
+     * prop of the native component.
13
+     */
14
+    size: 'large' | 'small'
15
+};
16
+
8 17
 /**
9 18
  * An animated, large react-native {@link ActivityIndicator} which is considered
10 19
  * a suitable visualization of long-running processes with indeterminate amounts
11 20
  * of work to be done.
12 21
  */
13
-export default class LoadingIndicator extends Component<*> {
22
+export default class LoadingIndicator extends Component<Props> {
14 23
     /**
15 24
      * Implements React's {@link Component#render()}.
16 25
      *
@@ -22,7 +31,7 @@ export default class LoadingIndicator extends Component<*> {
22 31
             <ActivityIndicator
23 32
                 animating = { true }
24 33
                 color = { ColorPalette.white }
25
-                size = { 'large' }
34
+                size = { this.props.size || 'large' }
26 35
                 { ...this.props } />
27 36
         );
28 37
     }

+ 8
- 1
react/features/recent-list/components/AbstractRecentList.js View File

@@ -9,6 +9,11 @@ import { appNavigate } from '../../app';
9 9
  */
10 10
 type Props = {
11 11
 
12
+    /**
13
+     * Indicates if the list is disabled or not.
14
+     */
15
+    disabled: boolean,
16
+
12 17
     /**
13 18
      * The redux store's {@code dispatch} function.
14 19
      */
@@ -31,7 +36,9 @@ export default class AbstractRecentList extends Component<Props> {
31 36
      * @returns {void}
32 37
      */
33 38
     _onJoin(room) {
34
-        room && this.props.dispatch(appNavigate(room));
39
+        const { disabled, dispatch } = this.props;
40
+
41
+        !disabled && room && dispatch(appNavigate(room));
35 42
     }
36 43
 
37 44
     /**

+ 11
- 5
react/features/recent-list/components/RecentList.native.js View File

@@ -28,8 +28,8 @@ class RecentList extends AbstractRecentList {
28 28
      *
29 29
      * @inheritdoc
30 30
      */
31
-    constructor() {
32
-        super();
31
+    constructor(props) {
32
+        super(props);
33 33
 
34 34
         // Bind event handlers so they are only bound once per instance.
35 35
         this._getAvatarStyle = this._getAvatarStyle.bind(this);
@@ -47,16 +47,22 @@ class RecentList extends AbstractRecentList {
47 47
      * @returns {ReactElement}
48 48
      */
49 49
     render() {
50
-        if (!this.props || !this.props._recentList) {
50
+        const { _recentList, disabled } = this.props;
51
+
52
+        if (!_recentList) {
51 53
             return null;
52 54
         }
53 55
 
54 56
         const listViewDataSource
55 57
             = this.dataSource.cloneWithRows(
56
-                getRecentRooms(this.props._recentList));
58
+                getRecentRooms(_recentList));
57 59
 
58 60
         return (
59
-            <View style = { styles.container }>
61
+            <View
62
+                style = { [
63
+                    styles.container,
64
+                    disabled ? styles.containerDisabled : null
65
+                ] }>
60 66
                 <ListView
61 67
                     dataSource = { listViewDataSource }
62 68
                     enableEmptySections = { true }

+ 7
- 0
react/features/recent-list/components/styles.js View File

@@ -87,6 +87,13 @@ export default createStyleSheet({
87 87
         flex: 1
88 88
     },
89 89
 
90
+    /**
91
+     * Shows the container disabled.
92
+     */
93
+    containerDisabled: {
94
+        opacity: 0.2
95
+    },
96
+
90 97
     /**
91 98
      * Second line of the list (date). May be extended with server name later.
92 99
      */

+ 5
- 0
react/features/welcome/components/AbstractWelcomePage.js View File

@@ -14,6 +14,11 @@ import { generateRoomWithoutSeparator } from '../functions';
14 14
  */
15 15
 type Props = {
16 16
 
17
+    /**
18
+     * Boolean to indicate if the room field is focused or not.
19
+     */
20
+    _fieldFocused: boolean,
21
+
17 22
     /**
18 23
      * The user's profile.
19 24
      */

+ 94
- 5
react/features/welcome/components/WelcomePage.native.js View File

@@ -1,5 +1,6 @@
1 1
 import React from 'react';
2 2
 import {
3
+    Animated,
3 4
     Keyboard,
4 5
     SafeAreaView,
5 6
     Switch,
@@ -59,8 +60,13 @@ class WelcomePage extends AbstractWelcomePage {
59 60
     constructor(props) {
60 61
         super(props);
61 62
 
63
+        this.state.hintBoxAnimation = new Animated.Value(0);
64
+
65
+        this._getHintBoxStyle = this._getHintBoxStyle.bind(this);
66
+        this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
62 67
         this._onShowSideBar = this._onShowSideBar.bind(this);
63 68
         this._onStartAudioOnlyChange = this._onStartAudioOnlyChange.bind(this);
69
+        this._renderHintBox = this._renderHintBox.bind(this);
64 70
     }
65 71
 
66 72
     /**
@@ -124,7 +130,9 @@ class WelcomePage extends AbstractWelcomePage {
124 130
                             autoComplete = { false }
125 131
                             autoCorrect = { false }
126 132
                             autoFocus = { false }
133
+                            onBlur = { this._onFieldFocusChange(false) }
127 134
                             onChangeText = { this._onRoomChange }
135
+                            onFocus = { this._onFieldFocusChange(true) }
128 136
                             onSubmitEditing = { this._onJoin }
129 137
                             placeholder = { t('welcomepage.roomname') }
130 138
                             placeholderTextColor = { PLACEHOLDER_TEXT_COLOR }
@@ -133,9 +141,9 @@ class WelcomePage extends AbstractWelcomePage {
133 141
                             underlineColorAndroid = 'transparent'
134 142
                             value = { this.state.room } />
135 143
                         {
136
-                            this._renderJoinButton()
144
+                            this._renderHintBox()
137 145
                         }
138
-                        <RecentList />
146
+                        <RecentList disabled = { this.state._fieldFocused } />
139 147
                     </SafeAreaView>
140 148
                     <AppSettings />
141 149
                 </View>
@@ -144,6 +152,50 @@ class WelcomePage extends AbstractWelcomePage {
144 152
         );
145 153
     }
146 154
 
155
+    /**
156
+     * Constructs a style array to handle the hint box animation.
157
+     *
158
+     * @private
159
+     * @returns {Array<Object>}
160
+     */
161
+    _getHintBoxStyle() {
162
+        return [
163
+            styles.hintContainer,
164
+            {
165
+                opacity: this.state.hintBoxAnimation
166
+            }
167
+        ];
168
+    }
169
+
170
+    /**
171
+     * Callback for when the room field's focus changes so the hint box
172
+     * must be rendered or removed.
173
+     *
174
+     * @private
175
+     * @param {boolean} focused - The focused state of the field.
176
+     * @returns {Function}
177
+     */
178
+    _onFieldFocusChange(focused) {
179
+        return () => {
180
+            if (focused) {
181
+                this.setState({
182
+                    _fieldFocused: true
183
+                });
184
+            }
185
+
186
+            Animated.timing(this.state.hintBoxAnimation, {
187
+                duration: 300,
188
+                toValue: focused ? 1 : 0
189
+            }).start(animationState => {
190
+                if (animationState.finished && !focused) {
191
+                    this.setState({
192
+                        _fieldFocused: false
193
+                    });
194
+                }
195
+            });
196
+        };
197
+    }
198
+
147 199
     /**
148 200
      * Toggles the side bar.
149 201
      *
@@ -171,6 +223,36 @@ class WelcomePage extends AbstractWelcomePage {
171 223
         }));
172 224
     }
173 225
 
226
+    /**
227
+     * Renders the hint box if necessary.
228
+     *
229
+     * @private
230
+     * @returns {React$Node}
231
+     */
232
+    _renderHintBox() {
233
+        if (this.state._fieldFocused) {
234
+            const { t } = this.props;
235
+
236
+            return (
237
+                <Animated.View
238
+                    style = { this._getHintBoxStyle() }>
239
+                    <View style = { styles.hintTextContainer } >
240
+                        <Text>
241
+                            { t('welcomepage.hintText') }
242
+                        </Text>
243
+                    </View>
244
+                    <View style = { styles.hintButtonContainer } >
245
+                        {
246
+                            this._renderJoinButton()
247
+                        }
248
+                    </View>
249
+                </Animated.View>
250
+            );
251
+        }
252
+
253
+        return null;
254
+    }
255
+
174 256
     /**
175 257
      * Renders the join button.
176 258
      *
@@ -188,7 +270,9 @@ class WelcomePage extends AbstractWelcomePage {
188 270
             // modify non-native children.
189 271
             children = (
190 272
                 <View>
191
-                    <LoadingIndicator color = { styles.buttonText.color } />
273
+                    <LoadingIndicator
274
+                        color = { styles.buttonText.color }
275
+                        size = 'small' />
192 276
                 </View>
193 277
             );
194 278
         } else {
@@ -201,12 +285,17 @@ class WelcomePage extends AbstractWelcomePage {
201 285
 
202 286
         /* eslint-enable no-extra-parens */
203 287
 
288
+        const buttonDisabled = this._isJoinDisabled();
289
+
204 290
         return (
205 291
             <TouchableHighlight
206 292
                 accessibilityLabel = { 'Tap to Join.' }
207
-                disabled = { this._isJoinDisabled() }
293
+                disabled = { buttonDisabled }
208 294
                 onPress = { this._onJoin }
209
-                style = { styles.button }
295
+                style = { [
296
+                    styles.button,
297
+                    buttonDisabled ? styles.buttonDisabled : null
298
+                ] }
210 299
                 underlayColor = { ColorPalette.white }>
211 300
                 {
212 301
                     children

+ 41
- 4
react/features/welcome/components/styles.js View File

@@ -54,10 +54,17 @@ export default createStyleSheet({
54 54
         borderColor: ColorPalette.blue,
55 55
         borderRadius: 4,
56 56
         borderWidth: 1,
57
-        height: 40,
57
+        height: 30,
58 58
         justifyContent: 'center',
59
-        marginBottom: BoxModel.margin,
60
-        marginTop: BoxModel.margin
59
+        paddingHorizontal: 20
60
+    },
61
+
62
+    /**
63
+     * Renders the button visually disabled.
64
+     */
65
+    buttonDisabled: {
66
+        backgroundColor: '#cccccc',
67
+        borderColor: '#999999'
61 68
     },
62 69
 
63 70
     /**
@@ -66,7 +73,7 @@ export default createStyleSheet({
66 73
     buttonText: {
67 74
         alignSelf: 'center',
68 75
         color: ColorPalette.white,
69
-        fontSize: 18
76
+        fontSize: 14
70 77
     },
71 78
 
72 79
     /**
@@ -86,6 +93,36 @@ export default createStyleSheet({
86 93
         justifyContent: 'space-between'
87 94
     },
88 95
 
96
+    /**
97
+     * Container for the button on the hint box.
98
+     */
99
+    hintButtonContainer: {
100
+        flexDirection: 'row',
101
+        justifyContent: 'flex-end'
102
+    },
103
+
104
+    /**
105
+     * Container for the text on the hint box.
106
+     */
107
+    hintTextContainer: {
108
+        marginBottom: 2 * BoxModel.margin
109
+    },
110
+
111
+    /**
112
+     * Container for the hint box.
113
+     */
114
+    hintContainer: {
115
+        backgroundColor: ColorPalette.white,
116
+        borderColor: ColorPalette.white,
117
+        borderRadius: 4,
118
+        borderWidth: 1,
119
+        flexDirection: 'column',
120
+        marginVertical: 5,
121
+        overflow: 'hidden',
122
+        paddingHorizontal: BoxModel.padding,
123
+        paddingVertical: 2 * BoxModel.padding
124
+    },
125
+
89 126
     /**
90 127
      * Container for the items in the side bar.
91 128
      */

Loading…
Cancel
Save