Explorar el Código

Merge pull request #2407 from zbettenbuk/new-welcome-screen

Add URL validation and larger distance behind the back button
master
Saúl Ibarra Corretgé hace 7 años
padre
commit
dd5ae49217
No account linked to committer's email address

+ 3
- 0
lang/main.json Ver fichero

@@ -539,6 +539,9 @@
539 539
         "tooltip": "Get access info about the meeting"
540 540
     },
541 541
     "profileModal": {
542
+        "alertOk": "OK",
543
+        "alertTitle": "Warning",
544
+        "alertURLText": "The entered server URL is invalid",
542 545
         "conferenceSection": "Conference",
543 546
         "displayName": "Display name",
544 547
         "email": "Email",

+ 7
- 15
react/features/app-settings/components/AbstractAppSettings.js Ver fichero

@@ -4,8 +4,6 @@ import { Component } from 'react';
4 4
 
5 5
 import { getProfile, updateProfile } from '../../base/profile';
6 6
 
7
-import { hideAppSettings } from '../actions';
8
-
9 7
 /**
10 8
  * The type of the React {@code Component} props of {@link AbstractAppSettings}
11 9
  */
@@ -34,7 +32,12 @@ type Props = {
34 32
     /**
35 33
      * Redux store dispatch function.
36 34
      */
37
-    dispatch: Dispatch<*>
35
+    dispatch: Dispatch<*>,
36
+
37
+    /**
38
+     * The i18n translate function.
39
+     */
40
+    t: Function
38 41
 };
39 42
 
40 43
 /**
@@ -44,6 +47,7 @@ type Props = {
44 47
  * @abstract
45 48
  */
46 49
 export class AbstractAppSettings extends Component<Props> {
50
+
47 51
     /**
48 52
      * Initializes a new {@code AbstractAppSettings} instance.
49 53
      *
@@ -56,7 +60,6 @@ export class AbstractAppSettings extends Component<Props> {
56 60
         this._onChangeDisplayName = this._onChangeDisplayName.bind(this);
57 61
         this._onChangeEmail = this._onChangeEmail.bind(this);
58 62
         this._onChangeServerURL = this._onChangeServerURL.bind(this);
59
-        this._onRequestClose = this._onRequestClose.bind(this);
60 63
         this._onStartAudioMutedChange
61 64
             = this._onStartAudioMutedChange.bind(this);
62 65
         this._onStartVideoMutedChange
@@ -108,17 +111,6 @@ export class AbstractAppSettings extends Component<Props> {
108 111
         });
109 112
     }
110 113
 
111
-    _onRequestClose: () => void;
112
-
113
-    /**
114
-     * Handles the back button.
115
-     *
116
-     * @returns {void}
117
-     */
118
-    _onRequestClose() {
119
-        this.props.dispatch(hideAppSettings());
120
-    }
121
-
122 114
     _onStartAudioMutedChange: (boolean) => void;
123 115
 
124 116
     /**

+ 113
- 5
react/features/app-settings/components/AppSettings.native.js Ver fichero

@@ -1,5 +1,8 @@
1
+// @flow
2
+
1 3
 import React from 'react';
2 4
 import {
5
+    Alert,
3 6
     Modal,
4 7
     ScrollView,
5 8
     Switch,
@@ -11,13 +14,14 @@ import { connect } from 'react-redux';
11 14
 
12 15
 import { ASPECT_RATIO_NARROW } from '../../base/aspect-ratio';
13 16
 import { translate } from '../../base/i18n';
14
-import { isIPad } from '../../base/react';
17
+import { getSafetyOffset, isIPad } from '../../base/react';
15 18
 
16 19
 import { _mapStateToProps, AbstractAppSettings } from './AbstractAppSettings';
17
-import BackButton from './BackButton';
18
-import FormRow from './FormRow';
19
-import FormSectionHeader from './FormSectionHeader';
20
-import { getSafetyOffset } from '../functions';
20
+import { hideAppSettings } from '../actions';
21
+import BackButton from './BackButton.native';
22
+import FormRow from './FormRow.native';
23
+import FormSectionHeader from './FormSectionHeader.native';
24
+import { normalizeUserInputURL } from '../functions';
21 25
 import styles, { HEADER_PADDING } from './styles';
22 26
 
23 27
 /**
@@ -26,6 +30,8 @@ import styles, { HEADER_PADDING } from './styles';
26 30
  * @extends AbstractAppSettings
27 31
  */
28 32
 class AppSettings extends AbstractAppSettings {
33
+    _urlField: Object;
34
+
29 35
     /**
30 36
      * Instantiates a new {@code AppSettings} instance.
31 37
      *
@@ -35,6 +41,10 @@ class AppSettings extends AbstractAppSettings {
35 41
         super(props);
36 42
 
37 43
         this._getSafetyPadding = this._getSafetyPadding.bind(this);
44
+        this._onBlurServerURL = this._onBlurServerURL.bind(this);
45
+        this._onRequestClose = this._onRequestClose.bind(this);
46
+        this._setURLFieldReference = this._setURLFieldReference.bind(this);
47
+        this._showURLAlert = this._showURLAlert.bind(this);
38 48
     }
39 49
 
40 50
     /**
@@ -97,8 +107,10 @@ class AppSettings extends AbstractAppSettings {
97 107
                         i18nLabel = 'profileModal.serverURL' >
98 108
                         <TextInput
99 109
                             autoCapitalize = 'none'
110
+                            onBlur = { this._onBlurServerURL }
100 111
                             onChangeText = { this._onChangeServerURL }
101 112
                             placeholder = { this.props._serverURL }
113
+                            ref = { this._setURLFieldReference }
102 114
                             value = { _profile.serverURL } />
103 115
                     </FormRow>
104 116
                     <FormRow
@@ -127,6 +139,8 @@ class AppSettings extends AbstractAppSettings {
127 139
         );
128 140
     }
129 141
 
142
+    _getSafetyPadding: () => Object;
143
+
130 144
     /**
131 145
      * Calculates header safety padding for mobile devices. See comment in
132 146
      * functions.js.
@@ -145,6 +159,100 @@ class AppSettings extends AbstractAppSettings {
145 159
 
146 160
         return undefined;
147 161
     }
162
+
163
+    _onBlurServerURL: () => void;
164
+
165
+    /**
166
+     * Handler the server URL lose focus event. Here we validate the server URL
167
+     * and update it to the normalized version, or show an error if incorrect.
168
+     *
169
+     * @private
170
+     * @returns {void}
171
+     */
172
+    _onBlurServerURL() {
173
+        this._processServerURL(false /* hideOnSuccess */);
174
+    }
175
+
176
+    _onChangeDisplayName: (string) => void;
177
+
178
+    _onChangeEmail: (string) => void;
179
+
180
+    _onChangeServerURL: (string) => void;
181
+
182
+    _onStartAudioMutedChange: (boolean) => void;
183
+
184
+    _onStartVideoMutedChange: (boolean) => void;
185
+
186
+    _onRequestClose: () => void;
187
+
188
+    /**
189
+     * Processes the server URL. It normalizes it and an error alert is
190
+     * displayed in case it's incorrect.
191
+     *
192
+     * @param {boolean} hideOnSuccess - True if the dialog should be hidden if
193
+     * normalization / validation succeeds, false otherwise.
194
+     * @private
195
+     * @returns {void}
196
+     */
197
+    _processServerURL(hideOnSuccess: boolean) {
198
+        const { serverURL } = this.props._profile;
199
+        const normalizedURL = normalizeUserInputURL(serverURL);
200
+
201
+        if (normalizedURL === null) {
202
+            this._showURLAlert();
203
+        } else {
204
+            this._onChangeServerURL(normalizedURL);
205
+            if (hideOnSuccess) {
206
+                this.props.dispatch(hideAppSettings());
207
+            }
208
+        }
209
+    }
210
+
211
+    /**
212
+     * Handles the back button.
213
+     * Also invokes normalizeUserInputURL to validate the URL entered
214
+     * by the user.
215
+     *
216
+     * @returns {void}
217
+     */
218
+    _onRequestClose() {
219
+        this._processServerURL(true /* hideOnSuccess */);
220
+    }
221
+
222
+    _setURLFieldReference: (React$ElementRef<*> | null) => void;
223
+
224
+    /**
225
+     *  Stores a reference to the URL field for later use.
226
+     *
227
+     * @protected
228
+     * @param {Object} component - The field component.
229
+     * @returns {void}
230
+     */
231
+    _setURLFieldReference(component) {
232
+        this._urlField = component;
233
+    }
234
+
235
+    _showURLAlert: () => void;
236
+
237
+    /**
238
+     * Shows an alert telling the user that the URL he/she entered was invalid.
239
+     *
240
+     * @returns {void}
241
+     */
242
+    _showURLAlert() {
243
+        const { t } = this.props;
244
+
245
+        Alert.alert(
246
+            t('profileModal.alertTitle'),
247
+            t('profileModal.alertURLText'),
248
+            [
249
+                {
250
+                    onPress: () => this._urlField.focus(),
251
+                    text: t('profileModal.alertOk')
252
+                }
253
+            ]
254
+        );
255
+    }
148 256
 }
149 257
 
150 258
 export default translate(connect(_mapStateToProps)(AppSettings));

+ 1
- 1
react/features/app-settings/components/FormRow.native.js Ver fichero

@@ -6,8 +6,8 @@ import { connect } from 'react-redux';
6 6
 
7 7
 import { ASPECT_RATIO_WIDE } from '../../base/aspect-ratio';
8 8
 import { translate } from '../../base/i18n';
9
+import { getSafetyOffset } from '../../base/react';
9 10
 
10
-import { getSafetyOffset } from '../functions';
11 11
 import styles, { ANDROID_UNDERLINE_COLOR, CONTAINER_PADDING } from './styles';
12 12
 
13 13
 /**

+ 1
- 1
react/features/app-settings/components/FormSectionHeader.native.js Ver fichero

@@ -6,8 +6,8 @@ import { connect } from 'react-redux';
6 6
 
7 7
 import { ASPECT_RATIO_WIDE } from '../../base/aspect-ratio';
8 8
 import { translate } from '../../base/i18n';
9
+import { getSafetyOffset } from '../../base/react';
9 10
 
10
-import { getSafetyOffset } from '../functions';
11 11
 import styles, { CONTAINER_PADDING } from './styles';
12 12
 
13 13
 /**

+ 37
- 0
react/features/app-settings/functions.js Ver fichero

@@ -0,0 +1,37 @@
1
+// @flow
2
+
3
+import { parseStandardURIString } from '../base/util';
4
+
5
+/**
6
+ * Normalizes a URL entered by the user.
7
+ * FIXME: Consider adding this to base/util/uri.
8
+ *
9
+ * @param {string} url - The URL to validate.
10
+ * @returns {string|null} - The normalized URL, or null if the URL is invalid.
11
+ */
12
+export function normalizeUserInputURL(url: string) {
13
+    /* eslint-disable no-param-reassign */
14
+
15
+    if (url) {
16
+        url = url.replace(/\s/g, '').toLowerCase();
17
+        const urlRegExp = new RegExp('^(\\w+://)?(.+)$');
18
+        const urlComponents = urlRegExp.exec(url);
19
+
20
+        if (!urlComponents[1] || !urlComponents[1].startsWith('http')) {
21
+            url = `https://${urlComponents[2]}`;
22
+        }
23
+
24
+        const parsedURI
25
+            = parseStandardURIString(url);
26
+
27
+        if (!parsedURI.host) {
28
+            return null;
29
+        }
30
+
31
+        return parsedURI.toString();
32
+    }
33
+
34
+    return url;
35
+
36
+    /* eslint-enable no-param-reassign */
37
+}

+ 0
- 22
react/features/app-settings/functions.native.js Ver fichero

@@ -1,22 +0,0 @@
1
-// @flow
2
-
3
-import { isIPhoneX, Platform } from '../base/react';
4
-
5
-const IPHONE_OFFSET = 20;
6
-const IPHONEX_OFFSET = 44;
7
-
8
-/**
9
- * Determines the offset to be used for the device. This uses a custom
10
- * implementation to minimize empty area around screen, especially on iPhone X.
11
- *
12
- * @returns {number}
13
- */
14
-export function getSafetyOffset() {
15
-    if (Platform.OS === 'android') {
16
-        // Android doesn't need offset, except the Essential phone. Should be
17
-        // addressed later with a generic solution.
18
-        return 0;
19
-    }
20
-
21
-    return isIPhoneX() ? IPHONEX_OFFSET : IPHONE_OFFSET;
22
-}

+ 0
- 0
react/features/app-settings/functions.web.js Ver fichero


+ 18
- 0
react/features/base/react/functions.native.js Ver fichero

@@ -6,6 +6,24 @@ import Platform from './Platform';
6 6
 
7 7
 const IPHONEX_HEIGHT = 812;
8 8
 const IPHONEX_WIDTH = 375;
9
+const IPHONE_OFFSET = 20;
10
+const IPHONEX_OFFSET = 44;
11
+
12
+/**
13
+ * Determines the offset to be used for the device. This uses a custom
14
+ * implementation to minimize empty area around screen, especially on iPhone X.
15
+ *
16
+ * @returns {number}
17
+ */
18
+export function getSafetyOffset() {
19
+    if (Platform.OS === 'android') {
20
+        // Android doesn't need offset, except the Essential phone. Should be
21
+        // addressed later with a generic solution.
22
+        return 0;
23
+    }
24
+
25
+    return isIPhoneX() ? IPHONEX_OFFSET : IPHONE_OFFSET;
26
+}
9 27
 
10 28
 /**
11 29
  * Determines if the device is an iPad or not.

Loading…
Cancelar
Guardar