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

pull/9962/head jitsi-meet_6340
Avram Tudor 3 years ago committed by GitHub
parent 32ed2bccec
commit adbb5f8ead
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      css/premeeting/_connection-status.scss
  2. 23
      css/premeeting/_device-status.scss
  3. 13
      css/premeeting/_prejoin.scss
  4. 109
      css/premeeting/_premeeting-screens.scss
  5. 1
      lang/main.json
  6. 3
      react/features/base/icons/svg/exclamation-triangle.svg
  7. 1
      react/features/base/icons/svg/index.js
  8. 64
      react/features/base/premeeting/components/web/ToggleButton.js
  9. 1
      react/features/base/premeeting/components/web/index.js
  10. 51
      react/features/prejoin/components/Prejoin.js
  11. 29
      react/features/prejoin/components/preview/DeviceStatus.js

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

@ -1,16 +1,25 @@
.device {
&-status {
align-items: center;
align-self: stretch;
color: #fff;
display: flex;
font-size: 14px;
font-weight: 400;
justify-content: center;
line-height: 20px;
margin-top: 8px;
padding: 6px;
text-align: center;
&-error {
align-items: flex-start;
background-color: #F8AE1A;
border-radius: 6px;
color: #040404;
padding: 12px 16px;
text-align: left;
}
span {
margin-left: 16px;
}
}
&-icon {
@ -18,14 +27,8 @@
background-repeat: no-repeat;
display: inline-block;
height: 16px;
margin-right: 10px;
width: 16px;
&--warning {
svg path {
fill: rgba(241, 173, 51, 1);
}
}
&--ok {
svg path {
fill: #189b55;

@ -3,21 +3,16 @@
width: 100%;
}
&-checkbox-container {
margin-bottom: 16px;
width: 100%;
text-align: center;
}
&-error {
color: white;
background-color: #E04757;
border-radius: 6px;
padding: 4px;
box-sizing: border-box;
color: white;
font-size: 12px;
line-height: 16px;
margin-bottom: 16px;
margin-top: -8px;
font-size: 12px;
padding: 4px;
text-align: center;
width: 100%;
}

@ -16,6 +16,7 @@
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: 600;
line-height: 24px;
margin-bottom: 16px;
padding: 7px 16px;
@ -128,10 +129,22 @@
#new-toolbox {
bottom: 0;
margin-bottom: 16px;
position: relative;
transition: none;
.toolbox-content {
margin-bottom: 4px;
}
.toolbox-content-items {
background: transparent;
border-radius: 0;
box-shadow: none;
display: flex;
justify-content: space-evenly;
padding: 8px 0;
}
.toolbox-content,
.toolbox-content-wrapper,
.toolbox-content-items {
@ -163,17 +176,26 @@
padding: 16px;
width: 100%;
&-controls {
input.field {
font-size: 16px;
padding: 14px 16px;
}
}
.title {
font-size: 20px;
line-height: 28px;
letter-spacing: -0.012;
margin-bottom: 24px;
display: none;
}
}
.con-status {
margin: 16px;
width: calc(100% - 32px);
margin: 0;
width: 100%;
}
.device-status-error {
border-radius: 0;
margin: 0 -16px;
}
input.field {
@ -183,15 +205,9 @@
.action-btn {
font-size: 16px;
margin-bottom: 8px;
padding: 11px 16px;
}
.toolbox-content-items {
border-radius: 0;
display: flex;
justify-content: space-evenly;
padding: 8px 0;
}
}
input::placeholder {
@ -227,68 +243,3 @@
display: flex;
justify-content: center;
}
@mixin icon-container($bg, $fill) {
.toggle-button-icon-container {
background: $bg;
svg {
fill: $fill
}
}
}
.toggle-button {
border-radius: 6px;
cursor: pointer;
color: #fff;
font-size: 13px;
height: 40px;
margin: 0 auto;
transition: background 0.16s ease-out;
@include flex-centered();
svg {
fill: transparent;
}
label {
cursor: pointer;
}
&:hover {
background: rgba(255, 255, 255, 0.1);
.toggle-button-icon-container {
display: none;
}
}
&-container {
position: relative;
@include flex-centered();
}
&-icon-container {
border-radius: 50%;
left: -22px;
padding: 2px;
position: absolute;
}
&--toggled {
@include icon-container(white, #1C2025);
&:hover {
.toggle-button-icon-container {
display: block;
}
}
.toggle-button-icon-container {
display: block;
}
}
}

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

@ -0,0 +1,3 @@
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -51,6 +51,7 @@ export { default as IconEmail } from './envelope.svg';
export { default as IconEventNote } from './event_note.svg';
export { default as IconExclamation } from './exclamation.svg';
export { default as IconExclamationSolid } from './exclamation-solid.svg';
export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
export { default as IconExitFullScreen } from './exit-full-screen.svg';
export { default as IconFeedback } from './feedback.svg';
export { default as IconFullScreen } from './full-screen.svg';

@ -1,64 +0,0 @@
// @flow
import React, { useCallback } from 'react';
import { Icon, IconCheck } from '../../../icons';
const mainClass = 'toggle-button';
type Props = {
/**
* Text of the button.
*/
children: React$Node,
/**
* If the button is toggled or not.
*/
isToggled?: boolean,
/**
* OnClick button handler.
*/
onClick: Function
}
/**
* Button used as a toggle.
*
* @returns {ReactElement}
*/
function ToggleButton({ children, isToggled, onClick }: Props) {
const className = isToggled ? `${mainClass} ${mainClass}--toggled` : mainClass;
const onKeyPressHandler = useCallback(e => {
if (onClick && (e.key === ' ')) {
e.preventDefault();
onClick();
}
}, [ onClick ]);
return (
<div
aria-checked = { isToggled }
className = { className }
id = 'toggle-button'
onClick = { onClick }
onKeyPress = { onKeyPressHandler }
role = 'switch'
tabIndex = { 0 }>
<div className = 'toggle-button-container'>
<div className = 'toggle-button-icon-container'>
<Icon
className = 'toggle-button-icon'
size = { 10 }
src = { IconCheck } />
</div>
<label htmlFor = 'toggle-button'>{children}</label>
</div>
</div>
);
}
export default ToggleButton;

@ -3,4 +3,3 @@
export { default as ActionButton } from './ActionButton';
export { default as InputField } from './InputField';
export { default as PreMeetingScreen } from './PreMeetingScreen';
export { default as ToggleButton } from './ToggleButton';

@ -7,33 +7,26 @@ import { getRoomName } from '../../base/conference';
import { translate } from '../../base/i18n';
import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
import { isVideoMutedByUser } from '../../base/media';
import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting';
import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
import { connect } from '../../base/redux';
import { getDisplayName, updateSettings } from '../../base/settings';
import { getLocalJitsiVideoTrack } from '../../base/tracks';
import {
joinConference as joinConferenceAction,
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
setSkipPrejoin as setSkipPrejoinAction,
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
} from '../actions';
import {
isDeviceStatusVisible,
isDisplayNameRequired,
isJoinByPhoneButtonVisible,
isJoinByPhoneDialogVisible,
isPrejoinSkipped
isJoinByPhoneDialogVisible
} from '../functions';
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
type Props = {
/**
* Flag signaling if the 'skip prejoin' button is toggled or not.
*/
buttonIsToggled: boolean,
/**
* Flag signaling if the device status is visible or not.
*/
@ -69,11 +62,6 @@ type Props = {
*/
roomName: string,
/**
* Sets visibility of the prejoin page for the next sessions.
*/
setSkipPrejoin: Function,
/**
* Sets visibility of the 'JoinByPhoneDialog'.
*/
@ -138,7 +126,6 @@ class Prejoin extends Component<Props, State> {
this._closeDialog = this._closeDialog.bind(this);
this._showDialog = this._showDialog.bind(this);
this._onJoinButtonClick = this._onJoinButtonClick.bind(this);
this._onToggleButtonClick = this._onToggleButtonClick.bind(this);
this._onDropdownClose = this._onDropdownClose.bind(this);
this._onOptionsClick = this._onOptionsClick.bind(this);
this._setName = this._setName.bind(this);
@ -183,18 +170,6 @@ class Prejoin extends Component<Props, State> {
}
}
_onToggleButtonClick: () => void;
/**
* Handler for the toggle button.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onToggleButtonClick() {
this.props.setSkipPrejoin(!this.props.buttonIsToggled);
}
_onDropdownClose: () => void;
/**
@ -321,7 +296,6 @@ class Prejoin extends Component<Props, State> {
return (
<PreMeetingScreen
showDeviceStatus = { deviceStatusVisible }
skipPrejoinButton = { this._renderSkipPrejoinButton() }
title = { t('prejoin.joinMeeting') }
videoMuted = { !showCameraPreview }
videoTrack = { videoTrack }>
@ -400,25 +374,6 @@ class Prejoin extends Component<Props, State> {
</PreMeetingScreen>
);
}
/**
* Renders the 'skip prejoin' button.
*
* @returns {React$Element}
*/
_renderSkipPrejoinButton() {
const { buttonIsToggled, t } = this.props;
return (
<div className = 'prejoin-checkbox-container'>
<ToggleButton
isToggled = { buttonIsToggled }
onClick = { this._onToggleButtonClick }>
{t('prejoin.doNotShow')}
</ToggleButton>
</div>
);
}
}
/**
@ -432,7 +387,6 @@ function mapStateToProps(state): Object {
const showErrorOnJoin = isDisplayNameRequired(state) && !name;
return {
buttonIsToggled: isPrejoinSkipped(state),
name,
deviceStatusVisible: isDeviceStatusVisible(state),
roomName: getRoomName(state),
@ -448,7 +402,6 @@ const mapDispatchToProps = {
joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
joinConference: joinConferenceAction,
setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
setSkipPrejoin: setSkipPrejoinAction,
updateSettings
};

@ -3,12 +3,11 @@
import React from 'react';
import { translate } from '../../../base/i18n';
import { Icon, IconCheckSolid, IconExclamation } from '../../../base/icons';
import { Icon, IconCheckSolid, IconExclamationTriangle } from '../../../base/icons';
import { connect } from '../../../base/redux';
import {
getDeviceStatusType,
getDeviceStatusText,
getRawError
getDeviceStatusText
} from '../../functions';
export type Props = {
@ -24,11 +23,6 @@ export type Props = {
*/
deviceStatusType: string,
/**
* The error coming from device configuration.
*/
rawError: string,
/**
* Used for translation.
*/
@ -37,7 +31,7 @@ export type Props = {
const iconMap = {
warning: {
src: IconExclamation,
src: IconExclamationTriangle,
className: 'device-icon--warning'
},
ok: {
@ -52,25 +46,23 @@ const iconMap = {
*
* @returns {ReactElement}
*/
function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props) {
function DeviceStatus({ deviceStatusType, deviceStatusText, t }: Props) {
const { src, className } = iconMap[deviceStatusType];
const hasError = deviceStatusType === 'warning';
const containerClassName = `device-status ${hasError ? 'device-status-error' : ''}`;
return (
<div
className = 'device-status'
className = { containerClassName }
role = 'alert'
tabIndex = { -1 }>
<Icon
className = { `device-icon ${className}` }
size = { 16 }
src = { src } />
<span
role = 'heading'>
{t(deviceStatusText)}
<span role = 'heading'>
{hasError ? t('prejoin.errorNoPermissions') : t(deviceStatusText)}
</span>
{ rawError && <span>
{ rawError }
</span> }
</div>
);
}
@ -84,8 +76,7 @@ function DeviceStatus({ deviceStatusType, deviceStatusText, rawError, t }: Props
function mapStateToProps(state) {
return {
deviceStatusText: getDeviceStatusText(state),
deviceStatusType: getDeviceStatusType(state),
rawError: getRawError(state)
deviceStatusType: getDeviceStatusType(state)
};
}

Loading…
Cancel
Save