Browse Source

fix(prejoin) implement ux improvements for mobile (#9939)

master
Avram Tudor 3 years ago
parent
commit
adbb5f8ead
No account linked to committer's email address

+ 1
- 1
css/premeeting/_connection-status.scss View File

11
         background-color: rgba(0, 0, 0, 0.7);
11
         background-color: rgba(0, 0, 0, 0.7);
12
         align-items: center;
12
         align-items: center;
13
         display: flex;
13
         display: flex;
14
-        padding: 8px 12px;
14
+        padding: 14px 16px;
15
     }
15
     }
16
 
16
 
17
     &-circle {
17
     &-circle {

+ 13
- 10
css/premeeting/_device-status.scss View File

1
 .device {
1
 .device {
2
     &-status {
2
     &-status {
3
         align-items: center;
3
         align-items: center;
4
-        align-self: stretch;
5
         color: #fff;
4
         color: #fff;
6
         display: flex;
5
         display: flex;
7
         font-size: 14px;
6
         font-size: 14px;
8
-        font-weight: 400;
9
-        justify-content: center;
10
         line-height: 20px;
7
         line-height: 20px;
11
-        margin-top: 8px;
12
         padding: 6px;
8
         padding: 6px;
13
         text-align: center;
9
         text-align: center;
10
+
11
+        &-error {
12
+            align-items: flex-start;
13
+            background-color: #F8AE1A;
14
+            border-radius: 6px;
15
+            color: #040404;
16
+            padding: 12px 16px;
17
+            text-align: left;
18
+        }
19
+
20
+        span {
21
+            margin-left: 16px;
22
+        }
14
     }
23
     }
15
 
24
 
16
     &-icon {
25
     &-icon {
18
         background-repeat: no-repeat;
27
         background-repeat: no-repeat;
19
         display: inline-block;
28
         display: inline-block;
20
         height: 16px;
29
         height: 16px;
21
-        margin-right: 10px;
22
         width: 16px;
30
         width: 16px;
23
 
31
 
24
-        &--warning {
25
-            svg path {
26
-                fill: rgba(241, 173, 51, 1);
27
-            }
28
-        }
29
         &--ok {
32
         &--ok {
30
             svg path {
33
             svg path {
31
                 fill: #189b55;
34
                 fill: #189b55;

+ 4
- 9
css/premeeting/_prejoin.scss View File

3
         width: 100%;
3
         width: 100%;
4
     }
4
     }
5
 
5
 
6
-    &-checkbox-container {
7
-        margin-bottom: 16px;
8
-        width: 100%;
9
-        text-align: center;
10
-    }
11
-
12
     &-error {
6
     &-error {
13
-        color: white;
14
         background-color: #E04757;
7
         background-color: #E04757;
15
         border-radius: 6px;
8
         border-radius: 6px;
16
-        padding: 4px;
17
         box-sizing: border-box;
9
         box-sizing: border-box;
10
+        color: white;
11
+        font-size: 12px;
12
+        line-height: 16px;
18
         margin-bottom: 16px;
13
         margin-bottom: 16px;
19
         margin-top: -8px;
14
         margin-top: -8px;
20
-        font-size: 12px;
15
+        padding: 4px;
21
         text-align: center;
16
         text-align: center;
22
         width: 100%;
17
         width: 100%;
23
     }
18
     }

+ 30
- 79
css/premeeting/_premeeting-screens.scss View File

16
         cursor: pointer;
16
         cursor: pointer;
17
         display: inline-block;
17
         display: inline-block;
18
         font-size: 14px;
18
         font-size: 14px;
19
+        font-weight: 600;
19
         line-height: 24px;
20
         line-height: 24px;
20
         margin-bottom: 16px;
21
         margin-bottom: 16px;
21
         padding: 7px 16px;
22
         padding: 7px 16px;
128
 
129
 
129
             #new-toolbox {
130
             #new-toolbox {
130
                 bottom: 0;
131
                 bottom: 0;
131
-                margin-bottom: 16px;
132
                 position: relative;
132
                 position: relative;
133
                 transition: none;
133
                 transition: none;
134
 
134
 
135
+                .toolbox-content {
136
+                    margin-bottom: 4px;
137
+                }
138
+
139
+                .toolbox-content-items {
140
+                    background: transparent;
141
+                    border-radius: 0;
142
+                    box-shadow: none;
143
+                    display: flex;
144
+                    justify-content: space-evenly;
145
+                    padding: 8px 0;
146
+                }
147
+
135
                 .toolbox-content,
148
                 .toolbox-content,
136
                 .toolbox-content-wrapper,
149
                 .toolbox-content-wrapper,
137
                 .toolbox-content-items {
150
                 .toolbox-content-items {
163
             padding: 16px;
176
             padding: 16px;
164
             width: 100%;
177
             width: 100%;
165
 
178
 
179
+            &-controls {
180
+                input.field {
181
+                    font-size: 16px;
182
+                    padding: 14px 16px;
183
+                }
184
+            }
185
+
166
             .title {
186
             .title {
167
-                font-size: 20px;
168
-                line-height: 28px;
169
-                letter-spacing: -0.012;
170
-                margin-bottom: 24px;
187
+                display: none;
171
             }
188
             }
172
         }
189
         }
173
 
190
 
174
         .con-status {
191
         .con-status {
175
-            margin: 16px;
176
-            width: calc(100% - 32px);
192
+            margin: 0;
193
+            width: 100%;
194
+        }
195
+
196
+        .device-status-error {
197
+            border-radius: 0;
198
+            margin: 0 -16px;
177
         }
199
         }
178
 
200
 
179
         input.field {
201
         input.field {
183
 
205
 
184
         .action-btn {
206
         .action-btn {
185
             font-size: 16px;
207
             font-size: 16px;
208
+            margin-bottom: 8px;
186
             padding: 11px 16px;
209
             padding: 11px 16px;
187
         }
210
         }
188
-
189
-        .toolbox-content-items {
190
-            border-radius: 0;
191
-            display: flex;
192
-            justify-content: space-evenly;
193
-            padding: 8px 0;
194
-        }
195
     }
211
     }
196
 
212
 
197
     input::placeholder {
213
     input::placeholder {
227
     display: flex;
243
     display: flex;
228
     justify-content: center;
244
     justify-content: center;
229
 }
245
 }
230
-
231
-@mixin icon-container($bg, $fill) {
232
-    .toggle-button-icon-container {
233
-        background: $bg;
234
-
235
-        svg {
236
-            fill: $fill
237
-        }
238
-    }
239
-}
240
-
241
-.toggle-button {
242
-    border-radius: 6px;
243
-    cursor: pointer;
244
-    color: #fff;
245
-    font-size: 13px;
246
-    height: 40px;
247
-    margin: 0 auto;
248
-    transition: background 0.16s ease-out;
249
-
250
-    @include flex-centered();
251
-
252
-    svg {
253
-        fill: transparent;
254
-    }
255
-
256
-    label {
257
-        cursor: pointer;
258
-    }
259
-
260
-    &:hover {
261
-        background: rgba(255, 255, 255, 0.1);
262
-
263
-        .toggle-button-icon-container {
264
-            display: none;
265
-        }
266
-    }
267
-
268
-    &-container {
269
-        position: relative;
270
-
271
-        @include flex-centered();
272
-    }
273
-
274
-    &-icon-container {
275
-        border-radius: 50%;
276
-        left: -22px;
277
-        padding: 2px;
278
-        position: absolute;
279
-    }
280
-
281
-    &--toggled {
282
-        @include icon-container(white, #1C2025);
283
-
284
-        &:hover {
285
-            .toggle-button-icon-container {
286
-                display: block;
287
-            }
288
-        }
289
-
290
-        .toggle-button-icon-container {
291
-            display: block;
292
-        }
293
-    }
294
-}

+ 1
- 0
lang/main.json View File

696
         "errorDialOutFailed": "Could not dial out. Call failed",
696
         "errorDialOutFailed": "Could not dial out. Call failed",
697
         "errorDialOutStatus": "Error getting dial out status",
697
         "errorDialOutStatus": "Error getting dial out status",
698
         "errorMissingName": "Please enter your name to join the meeting",
698
         "errorMissingName": "Please enter your name to join the meeting",
699
+        "errorNoPermissions": "You need to enable microphone and camera access",
699
         "errorStatusCode": "Error dialing out, status code: {{status}}",
700
         "errorStatusCode": "Error dialing out, status code: {{status}}",
700
         "errorValidation": "Number validation failed",
701
         "errorValidation": "Number validation failed",
701
         "iWantToDialIn": "I want to dial in",
702
         "iWantToDialIn": "I want to dial in",

+ 3
- 0
react/features/base/icons/svg/exclamation-triangle.svg View File

1
+<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2332 14.3254L9.70838 0.910718C9.63451 0.77902 9.52397 0.670633 9.38965 0.598199C8.99846 0.387243 8.50693 0.52716 8.29177 0.910713L0.766813 14.3254C0.701168 14.4424 0.666748 14.5738 0.666748 14.7073C0.666748 15.1451 1.02867 15.4999 1.47512 15.4999H16.5249C16.6611 15.4999 16.7951 15.4662 16.9145 15.4018C17.3057 15.1909 17.4484 14.7089 17.2332 14.3254ZM2.84224 13.9147L9.00002 2.93733L15.1577 13.9147H2.84224ZM8.19177 11.5371C8.19177 11.0993 8.54663 10.7445 8.98437 10.7445H9.01591C9.45365 10.7445 9.80851 11.0993 9.80851 11.5371C9.80851 11.9748 9.45365 12.3297 9.01591 12.3297H8.98437C8.54663 12.3297 8.19177 11.9748 8.19177 11.5371ZM9.00014 6.7815C8.55369 6.7815 8.19177 7.14341 8.19177 7.58986V9.14351C8.19177 9.58996 8.55369 9.95188 9.00014 9.95188C9.44659 9.95188 9.80851 9.58996 9.80851 9.14351V7.58986C9.80851 7.14341 9.44659 6.7815 9.00014 6.7815Z" fill="#040404"/>
3
+</svg>

+ 1
- 0
react/features/base/icons/svg/index.js View File

51
 export { default as IconEventNote } from './event_note.svg';
51
 export { default as IconEventNote } from './event_note.svg';
52
 export { default as IconExclamation } from './exclamation.svg';
52
 export { default as IconExclamation } from './exclamation.svg';
53
 export { default as IconExclamationSolid } from './exclamation-solid.svg';
53
 export { default as IconExclamationSolid } from './exclamation-solid.svg';
54
+export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
54
 export { default as IconExitFullScreen } from './exit-full-screen.svg';
55
 export { default as IconExitFullScreen } from './exit-full-screen.svg';
55
 export { default as IconFeedback } from './feedback.svg';
56
 export { default as IconFeedback } from './feedback.svg';
56
 export { default as IconFullScreen } from './full-screen.svg';
57
 export { default as IconFullScreen } from './full-screen.svg';

+ 0
- 64
react/features/base/premeeting/components/web/ToggleButton.js View File

1
-// @flow
2
-
3
-import React, { useCallback } from 'react';
4
-
5
-import { Icon, IconCheck } from '../../../icons';
6
-
7
-const mainClass = 'toggle-button';
8
-
9
-type Props = {
10
-
11
-    /**
12
-     * Text of the button.
13
-     */
14
-    children: React$Node,
15
-
16
-    /**
17
-     * If the button is toggled or not.
18
-     */
19
-    isToggled?: boolean,
20
-
21
-    /**
22
-     * OnClick button handler.
23
-     */
24
-    onClick: Function
25
-}
26
-
27
-/**
28
- * Button used as a toggle.
29
- *
30
- * @returns {ReactElement}
31
- */
32
-function ToggleButton({ children, isToggled, onClick }: Props) {
33
-    const className = isToggled ? `${mainClass} ${mainClass}--toggled` : mainClass;
34
-
35
-    const onKeyPressHandler = useCallback(e => {
36
-        if (onClick && (e.key === ' ')) {
37
-            e.preventDefault();
38
-            onClick();
39
-        }
40
-    }, [ onClick ]);
41
-
42
-    return (
43
-        <div
44
-            aria-checked = { isToggled }
45
-            className = { className }
46
-            id = 'toggle-button'
47
-            onClick = { onClick }
48
-            onKeyPress = { onKeyPressHandler }
49
-            role = 'switch'
50
-            tabIndex = { 0 }>
51
-            <div className = 'toggle-button-container'>
52
-                <div className = 'toggle-button-icon-container'>
53
-                    <Icon
54
-                        className = 'toggle-button-icon'
55
-                        size = { 10 }
56
-                        src = { IconCheck } />
57
-                </div>
58
-                <label htmlFor = 'toggle-button'>{children}</label>
59
-            </div>
60
-        </div>
61
-    );
62
-}
63
-
64
-export default ToggleButton;

+ 0
- 1
react/features/base/premeeting/components/web/index.js View File

3
 export { default as ActionButton } from './ActionButton';
3
 export { default as ActionButton } from './ActionButton';
4
 export { default as InputField } from './InputField';
4
 export { default as InputField } from './InputField';
5
 export { default as PreMeetingScreen } from './PreMeetingScreen';
5
 export { default as PreMeetingScreen } from './PreMeetingScreen';
6
-export { default as ToggleButton } from './ToggleButton';

+ 2
- 49
react/features/prejoin/components/Prejoin.js View File

7
 import { translate } from '../../base/i18n';
7
 import { translate } from '../../base/i18n';
8
 import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
8
 import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
9
 import { isVideoMutedByUser } from '../../base/media';
9
 import { isVideoMutedByUser } from '../../base/media';
10
-import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting';
10
+import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
11
 import { connect } from '../../base/redux';
11
 import { connect } from '../../base/redux';
12
 import { getDisplayName, updateSettings } from '../../base/settings';
12
 import { getDisplayName, updateSettings } from '../../base/settings';
13
 import { getLocalJitsiVideoTrack } from '../../base/tracks';
13
 import { getLocalJitsiVideoTrack } from '../../base/tracks';
14
 import {
14
 import {
15
     joinConference as joinConferenceAction,
15
     joinConference as joinConferenceAction,
16
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
16
     joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
17
-    setSkipPrejoin as setSkipPrejoinAction,
18
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
17
     setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
19
 } from '../actions';
18
 } from '../actions';
20
 import {
19
 import {
21
     isDeviceStatusVisible,
20
     isDeviceStatusVisible,
22
     isDisplayNameRequired,
21
     isDisplayNameRequired,
23
     isJoinByPhoneButtonVisible,
22
     isJoinByPhoneButtonVisible,
24
-    isJoinByPhoneDialogVisible,
25
-    isPrejoinSkipped
23
+    isJoinByPhoneDialogVisible
26
 } from '../functions';
24
 } from '../functions';
27
 
25
 
28
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
26
 import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
29
 
27
 
30
 type Props = {
28
 type Props = {
31
 
29
 
32
-    /**
33
-     * Flag signaling if the 'skip prejoin' button is toggled or not.
34
-     */
35
-    buttonIsToggled: boolean,
36
-
37
     /**
30
     /**
38
      * Flag signaling if the device status is visible or not.
31
      * Flag signaling if the device status is visible or not.
39
      */
32
      */
69
      */
62
      */
70
     roomName: string,
63
     roomName: string,
71
 
64
 
72
-    /**
73
-     * Sets visibility of the prejoin page for the next sessions.
74
-     */
75
-    setSkipPrejoin: Function,
76
-
77
     /**
65
     /**
78
      * Sets visibility of the 'JoinByPhoneDialog'.
66
      * Sets visibility of the 'JoinByPhoneDialog'.
79
      */
67
      */
138
         this._closeDialog = this._closeDialog.bind(this);
126
         this._closeDialog = this._closeDialog.bind(this);
139
         this._showDialog = this._showDialog.bind(this);
127
         this._showDialog = this._showDialog.bind(this);
140
         this._onJoinButtonClick = this._onJoinButtonClick.bind(this);
128
         this._onJoinButtonClick = this._onJoinButtonClick.bind(this);
141
-        this._onToggleButtonClick = this._onToggleButtonClick.bind(this);
142
         this._onDropdownClose = this._onDropdownClose.bind(this);
129
         this._onDropdownClose = this._onDropdownClose.bind(this);
143
         this._onOptionsClick = this._onOptionsClick.bind(this);
130
         this._onOptionsClick = this._onOptionsClick.bind(this);
144
         this._setName = this._setName.bind(this);
131
         this._setName = this._setName.bind(this);
183
         }
170
         }
184
     }
171
     }
185
 
172
 
186
-    _onToggleButtonClick: () => void;
187
-
188
-    /**
189
-     * Handler for the toggle button.
190
-     *
191
-     * @param {Object} e - The synthetic event.
192
-     * @returns {void}
193
-     */
194
-    _onToggleButtonClick() {
195
-        this.props.setSkipPrejoin(!this.props.buttonIsToggled);
196
-    }
197
-
198
     _onDropdownClose: () => void;
173
     _onDropdownClose: () => void;
199
 
174
 
200
     /**
175
     /**
321
         return (
296
         return (
322
             <PreMeetingScreen
297
             <PreMeetingScreen
323
                 showDeviceStatus = { deviceStatusVisible }
298
                 showDeviceStatus = { deviceStatusVisible }
324
-                skipPrejoinButton = { this._renderSkipPrejoinButton() }
325
                 title = { t('prejoin.joinMeeting') }
299
                 title = { t('prejoin.joinMeeting') }
326
                 videoMuted = { !showCameraPreview }
300
                 videoMuted = { !showCameraPreview }
327
                 videoTrack = { videoTrack }>
301
                 videoTrack = { videoTrack }>
400
             </PreMeetingScreen>
374
             </PreMeetingScreen>
401
         );
375
         );
402
     }
376
     }
403
-
404
-    /**
405
-     * Renders the 'skip prejoin' button.
406
-     *
407
-     * @returns {React$Element}
408
-     */
409
-    _renderSkipPrejoinButton() {
410
-        const { buttonIsToggled, t } = this.props;
411
-
412
-        return (
413
-            <div className = 'prejoin-checkbox-container'>
414
-                <ToggleButton
415
-                    isToggled = { buttonIsToggled }
416
-                    onClick = { this._onToggleButtonClick }>
417
-                    {t('prejoin.doNotShow')}
418
-                </ToggleButton>
419
-            </div>
420
-        );
421
-    }
422
 }
377
 }
423
 
378
 
424
 /**
379
 /**
432
     const showErrorOnJoin = isDisplayNameRequired(state) && !name;
387
     const showErrorOnJoin = isDisplayNameRequired(state) && !name;
433
 
388
 
434
     return {
389
     return {
435
-        buttonIsToggled: isPrejoinSkipped(state),
436
         name,
390
         name,
437
         deviceStatusVisible: isDeviceStatusVisible(state),
391
         deviceStatusVisible: isDeviceStatusVisible(state),
438
         roomName: getRoomName(state),
392
         roomName: getRoomName(state),
448
     joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
402
     joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
449
     joinConference: joinConferenceAction,
403
     joinConference: joinConferenceAction,
450
     setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
404
     setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
451
-    setSkipPrejoin: setSkipPrejoinAction,
452
     updateSettings
405
     updateSettings
453
 };
406
 };
454
 
407
 

+ 10
- 19
react/features/prejoin/components/preview/DeviceStatus.js View File

3
 import React from 'react';
3
 import React from 'react';
4
 
4
 
5
 import { translate } from '../../../base/i18n';
5
 import { translate } from '../../../base/i18n';
6
-import { Icon, IconCheckSolid, IconExclamation } from '../../../base/icons';
6
+import { Icon, IconCheckSolid, IconExclamationTriangle } from '../../../base/icons';
7
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
8
 import {
8
 import {
9
     getDeviceStatusType,
9
     getDeviceStatusType,
10
-    getDeviceStatusText,
11
-    getRawError
10
+    getDeviceStatusText
12
 } from '../../functions';
11
 } from '../../functions';
13
 
12
 
14
 export type Props = {
13
 export type Props = {
24
      */
23
      */
25
     deviceStatusType: string,
24
     deviceStatusType: string,
26
 
25
 
27
-    /**
28
-     * The error coming from device configuration.
29
-     */
30
-    rawError: string,
31
-
32
     /**
26
     /**
33
      * Used for translation.
27
      * Used for translation.
34
      */
28
      */
37
 
31
 
38
 const iconMap = {
32
 const iconMap = {
39
     warning: {
33
     warning: {
40
-        src: IconExclamation,
34
+        src: IconExclamationTriangle,
41
         className: 'device-icon--warning'
35
         className: 'device-icon--warning'
42
     },
36
     },
43
     ok: {
37
     ok: {
52
  *
46
  *
53
  * @returns {ReactElement}
47
  * @returns {ReactElement}
54
  */
48
  */
55
-function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props) {
49
+function DeviceStatus({ deviceStatusType, deviceStatusText, t }: Props) {
56
     const { src, className } = iconMap[deviceStatusType];
50
     const { src, className } = iconMap[deviceStatusType];
51
+    const hasError = deviceStatusType === 'warning';
52
+    const containerClassName = `device-status ${hasError ? 'device-status-error' : ''}`;
57
 
53
 
58
     return (
54
     return (
59
         <div
55
         <div
60
-            className = 'device-status'
56
+            className = { containerClassName }
61
             role = 'alert'
57
             role = 'alert'
62
             tabIndex = { -1 }>
58
             tabIndex = { -1 }>
63
             <Icon
59
             <Icon
64
                 className = { `device-icon ${className}` }
60
                 className = { `device-icon ${className}` }
65
                 size = { 16 }
61
                 size = { 16 }
66
                 src = { src } />
62
                 src = { src } />
67
-            <span
68
-                role = 'heading'>
69
-                {t(deviceStatusText)}
63
+            <span role = 'heading'>
64
+                {hasError ? t('prejoin.errorNoPermissions') : t(deviceStatusText)}
70
             </span>
65
             </span>
71
-            { rawError && <span>
72
-                { rawError }
73
-            </span> }
74
         </div>
66
         </div>
75
     );
67
     );
76
 }
68
 }
84
 function mapStateToProps(state) {
76
 function mapStateToProps(state) {
85
     return {
77
     return {
86
         deviceStatusText: getDeviceStatusText(state),
78
         deviceStatusText: getDeviceStatusText(state),
87
-        deviceStatusType: getDeviceStatusType(state),
88
-        rawError: getRawError(state)
79
+        deviceStatusType: getDeviceStatusType(state)
89
     };
80
     };
90
 }
81
 }
91
 
82
 

Loading…
Cancel
Save