diff --git a/react/features/base/toolbox/components/AbstractToolboxItem.js b/react/features/base/toolbox/components/AbstractToolboxItem.js index 3e251728a..4da613261 100644 --- a/react/features/base/toolbox/components/AbstractToolboxItem.js +++ b/react/features/base/toolbox/components/AbstractToolboxItem.js @@ -9,6 +9,11 @@ export type Styles = { */ iconStyle: Object, + /** + * Style for te item's label. + */ + labelStyle: Object, + /** * Style for the item itself. */ diff --git a/react/features/base/toolbox/components/ToolboxItem.native.js b/react/features/base/toolbox/components/ToolboxItem.native.js index ed3ccd682..1262cfe33 100644 --- a/react/features/base/toolbox/components/ToolboxItem.native.js +++ b/react/features/base/toolbox/components/ToolboxItem.native.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; -import { TouchableHighlight } from 'react-native'; +import { Text, TouchableHighlight, View } from 'react-native'; import { Icon } from '../../../base/font-icons'; @@ -26,9 +26,23 @@ export default class ToolboxItem extends AbstractToolboxItem { } /** - * Handles rendering of the actual item. + * Helper function to render the {@code Icon} part of this item. * - * TODO: currently no handling for labels is implemented. + * @private + * @returns {ReactElement} + */ + _renderIcon() { + const { styles } = this.props; + + return ( + + ); + } + + /** + * Handles rendering of the actual item. * * @protected * @returns {ReactElement} @@ -38,19 +52,38 @@ export default class ToolboxItem extends AbstractToolboxItem { accessibilityLabel, disabled, onClick, + showLabel, styles } = this.props; + let children; + + if (showLabel) { + // eslint-disable-next-line no-extra-parens + children = ( + + { this._renderIcon() } + + { this.label } + + + ); + } else { + children = this._renderIcon(); + } + + // When using a wrapper view, apply the style to it instead of + // applying it to the TouchableHighlight. + const style = showLabel ? undefined : styles && styles.style; + return ( - + { children } ); } diff --git a/react/features/filmstrip/components/styles.js b/react/features/filmstrip/components/styles.js index daa8f76ff..6d6fb68bc 100644 --- a/react/features/filmstrip/components/styles.js +++ b/react/features/filmstrip/components/styles.js @@ -55,8 +55,8 @@ export default { ...filmstrip, bottom: 0, flexDirection: 'column', - left: 0, position: 'absolute', + right: 0, top: 0 }, diff --git a/react/features/toolbox/components/native/OverflowMenu.js b/react/features/toolbox/components/native/OverflowMenu.js new file mode 100644 index 000000000..1f1d54892 --- /dev/null +++ b/react/features/toolbox/components/native/OverflowMenu.js @@ -0,0 +1,91 @@ +// @flow + +import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +import { hideDialog, BottomSheet } from '../../../base/dialog'; +import { AudioRouteButton } from '../../../mobile/audio-mode'; +import { PictureInPictureButton } from '../../../mobile/picture-in-picture'; +import { RoomLockButton } from '../../../room-lock'; + +import AudioOnlyButton from './AudioOnlyButton'; +import ToggleCameraButton from './ToggleCameraButton'; + +import { overflowMenuItemStyles } from './styles'; + +type Props = { + + /** + * Used for hiding the dialog when the selection was completed. + */ + dispatch: Function, +}; + +/** + * The exported React {@code Component}. We need a reference to the wrapped + * component in order to be able to hide it using the dialog hiding logic. + */ + +// eslint-disable-next-line prefer-const +let OverflowMenu_; + +/** + * Implements a React {@code Component} with some extra actions in addition to + * those in the toolbar. + */ +class OverflowMenu extends Component { + /** + * Initializes a new {@code OverflowMenu} instance. + * + * @inheritdoc + */ + constructor(props: Props) { + super(props); + + this._onCancel = this._onCancel.bind(this); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + + + + + + + + ); + } + + _onCancel: () => void; + + /** + * Hides the dialog. + * + * @private + * @returns {void} + */ + _onCancel() { + this.props.dispatch(hideDialog(OverflowMenu_)); + } +} + +OverflowMenu_ = connect()(OverflowMenu); + +export default OverflowMenu_; diff --git a/react/features/toolbox/components/native/OverflowMenuButton.js b/react/features/toolbox/components/native/OverflowMenuButton.js new file mode 100644 index 000000000..985ec2abb --- /dev/null +++ b/react/features/toolbox/components/native/OverflowMenuButton.js @@ -0,0 +1,39 @@ +// @flow + +import { connect } from 'react-redux'; + +import { openDialog } from '../../../base/dialog'; +import { translate } from '../../../base/i18n'; +import { AbstractButton } from '../../../base/toolbox'; +import type { AbstractButtonProps } from '../../../base/toolbox'; + +import OverflowMenu from './OverflowMenu'; + +type Props = AbstractButtonProps & { + + /** + * The redux {@code dispatch} function. + */ + dispatch: Function +} + +/** + * An implementation of a button for showing the {@code OverflowMenu}. + */ +class OverflowMenuButton extends AbstractButton { + accessibilityLabel = 'Overflow menu'; + iconName = 'icon-thumb-menu'; + label = 'toolbar.moreActions'; + + /** + * Handles clicking / pressing the button. + * + * @private + * @returns {void} + */ + _handleClick() { + this.props.dispatch(openDialog(OverflowMenu)); + } +} + +export default translate(connect()(OverflowMenuButton)); diff --git a/react/features/toolbox/components/native/ToolbarButton.js b/react/features/toolbox/components/native/ToolbarButton.js deleted file mode 100644 index ccb03c419..000000000 --- a/react/features/toolbox/components/native/ToolbarButton.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { TouchableHighlight } from 'react-native'; -import { connect } from 'react-redux'; - -import { Icon } from '../../../base/font-icons'; - -import AbstractToolbarButton from '../AbstractToolbarButton'; - -/** - * Represents a button in {@link Toolbar} on React Native. - * - * @extends AbstractToolbarButton - */ -class ToolbarButton extends AbstractToolbarButton { - /** - * {@code ToolbarButton} component's property types. - * - * @static - */ - static propTypes = { - ...AbstractToolbarButton.propTypes, - - /** - * Indicates whether this {@code ToolbarButton} is disabled. - */ - disabled: PropTypes.bool - }; - - /** - * Renders the button of this {@code ToolbarButton}. - * - * @param {Object} children - The children, if any, to be rendered inside - * the button. Presumably, contains the icon of this {@code ToolbarButton}. - * @protected - * @returns {ReactElement} The button of this {@code ToolbarButton}. - */ - _renderButton(children) { - const props = {}; - - 'accessibilityLabel' in this.props - && (props.accessibilityLabel = this.props.accessibilityLabel); - 'disabled' in this.props && (props.disabled = this.props.disabled); - 'onClick' in this.props && (props.onPress = this._onClick); - 'style' in this.props && (props.style = this.props.style); - 'underlayColor' in this.props - && (props.underlayColor = this.props.underlayColor); - - return React.createElement(TouchableHighlight, props, children); - } - - /** - * Renders the icon of this {@code ToolbarButton}. - * - * @inheritdoc - */ - _renderIcon() { - return super._renderIcon(Icon); - } -} - -export default connect()(ToolbarButton); diff --git a/react/features/toolbox/components/native/Toolbox.js b/react/features/toolbox/components/native/Toolbox.js index a6709dd4b..ed60956aa 100644 --- a/react/features/toolbox/components/native/Toolbox.js +++ b/react/features/toolbox/components/native/Toolbox.js @@ -9,52 +9,28 @@ import { isNarrowAspectRatio, makeAspectRatioAware } from '../../../base/responsive-ui'; -import { ColorPalette } from '../../../base/styles'; import { InviteButton } from '../../../invite'; -import { AudioRouteButton } from '../../../mobile/audio-mode'; -import { PictureInPictureButton } from '../../../mobile/picture-in-picture'; -import { RoomLockButton } from '../../../room-lock'; import AudioMuteButton from '../AudioMuteButton'; -import AudioOnlyButton from './AudioOnlyButton'; import HangupButton from '../HangupButton'; -import styles from './styles'; -import ToggleCameraButton from './ToggleCameraButton'; import VideoMuteButton from '../VideoMuteButton'; -/** - * Styles for the hangup button. - */ -const hangupButtonStyles = { - iconStyle: styles.whitePrimaryToolbarButtonIcon, - style: styles.hangup, - underlayColor: ColorPalette.buttonUnderlay -}; +import OverflowMenuButton from './OverflowMenuButton'; +import styles, { + hangupButtonStyles, + toolbarButtonStyles, + toolbarToggledButtonStyles +} from './styles'; /** - * Styles for buttons in the primary toolbar. + * Number of buttons in the toolbar. */ -const primaryToolbarButtonStyles = { - iconStyle: styles.primaryToolbarButtonIcon, - style: styles.primaryToolbarButton -}; +const NUM_TOOLBAR_BUTTONS = 4; /** - * Styles for buttons in the primary toolbar. + * Factor relating the hangup button and other toolbar buttons. */ -const primaryToolbarToggledButtonStyles = { - iconStyle: styles.whitePrimaryToolbarButtonIcon, - style: styles.whitePrimaryToolbarButton -}; - -/** - * Styles for buttons in the secondary toolbar. - */ -const secondaryToolbarButtonStyles = { - iconStyle: styles.secondaryToolbarButtonIcon, - style: styles.secondaryToolbarButton, - underlayColor: 'transparent' -}; +const TOOLBAR_BUTTON_SIZE_FACTOR = 0.8; /** * The type of {@link Toolbox}'s React {@code Component} props. @@ -62,20 +38,85 @@ const secondaryToolbarButtonStyles = { type Props = { /** - * The indicator which determines whether the toolbox is enabled. + * The indicator which determines whether the toolbox is visible. */ - _enabled: boolean, + _visible: boolean, /** - * Flag showing whether toolbar is visible. + * The redux {@code dispatch} function. */ - _visible: boolean + dispatch: Function +}; + +/** + * The type of {@link Toolbox}'s React {@code Component} state. + */ +type State = { + + /** + * The detected width for this component. + */ + width: number }; /** * Implements the conference toolbox on React Native. */ -class Toolbox extends Component { +class Toolbox extends Component { + state = { + width: 0 + }; + + /** + * Initializes a new {@code Toolbox} instance. + * + * @inheritdoc + */ + constructor(props: Props) { + super(props); + + this._onLayout = this._onLayout.bind(this); + } + + _onLayout: (Object) => void; + + /** + * Handles the "on layout" View's event and stores the width as state. + * + * @param {Object} event - The "on layout" event object/structure passed + * by react-native. + * @private + * @returns {void} + */ + _onLayout({ nativeEvent: { layout: { width } } }) { + this.setState({ width }); + } + + /** + * Calculates how large our toolbar buttons can be, given the available + * width. In the future we might want to have a size threshold, and once + * it's passed a completely different style could be used, akin to the web. + * + * @private + * @returns {number} + */ + _calculateToolbarButtonSize() { + const width = this.state.width; + const hangupButtonSize = styles.hangupButton.width; + + let buttonSize + = (width - hangupButtonSize + - (NUM_TOOLBAR_BUTTONS * styles.toolbarButton.margin * 2)) + / NUM_TOOLBAR_BUTTONS; + + // Make sure it's an even number. + buttonSize = 2 * Math.round(buttonSize / 2); + + // The button should be at most 80% of the hangup button's size. + return Math.min( + buttonSize, hangupButtonSize * TOOLBAR_BUTTON_SIZE_FACTOR); + } + /** * Implements React's {@link Component#render()}. * @@ -83,84 +124,54 @@ class Toolbox extends Component { * @returns {ReactElement} */ render() { - if (!this.props._enabled) { - return null; - } - const toolboxStyle = isNarrowAspectRatio(this) ? styles.toolboxNarrow : styles.toolboxWide; + const { _visible } = this.props; + const buttonStyles = { + ...toolbarButtonStyles + }; + const toggledButtonStyles = { + ...toolbarToggledButtonStyles + }; + + if (_visible && this.state.width) { + const buttonSize = this._calculateToolbarButtonSize(); + const extraStyle = { + borderRadius: buttonSize / 2, + height: buttonSize, + width: buttonSize + }; + + buttonStyles.style = [ buttonStyles.style, extraStyle ]; + toggledButtonStyles.style + = [ toggledButtonStyles.style, extraStyle ]; + } return ( - { this._renderToolbars() } + visible = { _visible } > + + + + + + + ); } - - /** - * Renders the toolbar which contains the primary buttons such as hangup, - * audio and video mute. - * - * @private - * @returns {ReactElement} - */ - _renderPrimaryToolbar() { - return ( - - - - - - ); - } - - /** - * Renders the toolbar which contains the secondary buttons such as toggle - * camera facing mode. - * - * @private - * @returns {ReactElement} - */ - _renderSecondaryToolbar() { - return ( - - - - - - - - - ); - } - - /** - * Renders the primary and the secondary toolbars. - * - * @private - * @returns {[ReactElement, ReactElement]} - */ - _renderToolbars() { - return [ - this._renderSecondaryToolbar(), - this._renderPrimaryToolbar() - ]; - } } /** @@ -179,21 +190,7 @@ function _mapStateToProps(state: Object): Object { const { enabled, visible } = state['features/toolbox']; return { - /** - * The indicator which determines whether the toolbox is enabled. - * - * @private - * @type {boolean} - */ - _enabled: enabled, - - /** - * Flag showing whether toolbox is visible. - * - * @protected - * @type {boolean} - */ - _visible: visible + _visible: enabled && visible }; } diff --git a/react/features/toolbox/components/native/index.js b/react/features/toolbox/components/native/index.js index 118cf0179..70fb4807a 100644 --- a/react/features/toolbox/components/native/index.js +++ b/react/features/toolbox/components/native/index.js @@ -1,2 +1 @@ -export { default as ToolbarButton } from './ToolbarButton'; export { default as Toolbox } from './Toolbox'; diff --git a/react/features/toolbox/components/native/styles.js b/react/features/toolbox/components/native/styles.js index d38529b77..c52f95b23 100644 --- a/react/features/toolbox/components/native/styles.js +++ b/react/features/toolbox/components/native/styles.js @@ -1,152 +1,83 @@ import { BoxModel, ColorPalette, createStyleSheet } from '../../../base/styles'; /** - * The base style for toolbars. - * - * @type {Object} + * The style of toolbar buttons. */ -const _toolbar = { - flex: 0, - position: 'absolute' -}; - -/** - * The base style of toolbar buttons (in primaryToolbar and secondaryToolbar). - * - * @type {Object} - */ -const _toolbarButton = { - flex: 0, - justifyContent: 'center', - opacity: 0.7 -}; - -/** - * The base icon style of toolbar buttons (in primaryToolbar and - * secondaryToolbar). - * - * @type {Object} - */ -const _toolbarButtonIcon = { - alignSelf: 'center' -}; - -/** - * The style of toolbar buttons in primaryToolbar. - */ -const primaryToolbarButton = { - ..._toolbarButton, +const toolbarButton = { backgroundColor: ColorPalette.white, - borderRadius: 30, + borderRadius: 20, borderWidth: 0, + flex: 0, flexDirection: 'row', - height: 60, + height: 40, + justifyContent: 'center', margin: BoxModel.margin, - width: 60 + marginBottom: BoxModel.margin / 2, + opacity: 0.7, + width: 40 }; /** - * The icon style of the toolbar buttons in primaryToolbar. - * - * @type {Object} + * The icon style of the toolbar buttons. */ -const primaryToolbarButtonIcon = { - ..._toolbarButtonIcon, +const toolbarButtonIcon = { + alignSelf: 'center', color: ColorPalette.darkGrey, - fontSize: 24 + fontSize: 22 }; /** - * The icon style of the toolbar buttons in secondaryToolbar. - * - * @type {Object} + * The Toolbox and toolbar related styles. */ -const secondaryToolbarButtonIcon = { - ..._toolbarButtonIcon, - color: ColorPalette.white, - fontSize: 18 -}; - -/** - * The (conference) Toolbox/Toolbar related styles. - */ -export default createStyleSheet({ +const styles = createStyleSheet({ /** - * The style of the toolbar button in {@link #primaryToolbar} which - * hangs the current conference up. + * The style of the toolbar button which hangs the current conference up. */ - hangup: { - ...primaryToolbarButton, - backgroundColor: ColorPalette.red + hangupButton: { + ...toolbarButton, + backgroundColor: ColorPalette.red, + borderRadius: 30, + height: 60, + width: 60 }, /** - * The icon style of toolbar buttons in {@link #primaryToolbar} which - * hangs the current conference up. + * The icon style of toolbar buttons which hangs the current conference up. */ hangupButtonIcon: { - ...primaryToolbarButtonIcon, - color: ColorPalette.white + ...toolbarButtonIcon, + color: ColorPalette.white, + fontSize: 24 }, /** - * The style of the toolbar which contains the primary buttons such as - * hangup, audio and video mute. + * The style of the toolbar. */ - primaryToolbar: { - ..._toolbar, + toolbar: { + alignItems: 'center', bottom: 0, + flex: 0, flexDirection: 'row', justifyContent: 'center', left: 0, + position: 'absolute', right: 0 }, /** - * The style of toolbar buttons in {@link #primaryToolbar}. + * The style of toolbar buttons. */ - primaryToolbarButton, + toolbarButton, /** - * The icon style of the toolbar buttons in {@link #primaryToolbar}. + * The icon style of the toolbar buttons. */ - primaryToolbarButtonIcon, + toolbarButtonIcon, /** - * The style of the toolbar which contains the secondary buttons such as - * toggle camera facing mode. - */ - secondaryToolbar: { - ..._toolbar, - bottom: 0, - flexDirection: 'column', - right: 0, - top: 0 - }, - - /** - * The style of toolbar buttons in {@link #secondaryToolbar}. - */ - secondaryToolbarButton: { - ..._toolbarButton, - backgroundColor: ColorPalette.darkGrey, - borderRadius: 20, - flexDirection: 'column', - height: 40, - margin: BoxModel.margin / 2, - width: 40 - }, - - /** - * The icon style of the toolbar buttons in {@link #secondaryToolbar}. - */ - secondaryToolbarButtonIcon, - - /** - * The style of the root/top-level {@link Container} of {@link Toolbox} - * which contains {@link Toolbar}s. This is narrow layout version which - * spans from the top of the screen to the top of the filmstrip located at - * the bottom of the screen. + * The style of the root/top-level {@link Container} of {@link Toolbox}. + * This is the narrow layout version which locates the toolbar on top of + * the filmstrip, at the bottom of the screen. */ toolboxNarrow: { flexDirection: 'column', @@ -154,11 +85,9 @@ export default createStyleSheet({ }, /** - * The style of the root/top-level {@link Container} of {@link Toolbox} - * which contains {@link Toolbar}s. This is wide layout version which spans - * from the top to the bottom of the screen and is located to the right of - * the filmstrip which is displayed as a column on the left side of the - * screen. + * The style of the root/top-level {@link Container} of {@link Toolbox}. + * This is the wide layout version which locates the toolbar at the bottom + * of the screen. */ toolboxWide: { bottom: 0, @@ -169,20 +98,89 @@ export default createStyleSheet({ }, /** - * The style of toolbar buttons in {@link #primaryToolbar} which display - * white icons. + * The style of toolbar buttons which display white icons. */ - whitePrimaryToolbarButton: { - ...primaryToolbarButton, + whiteToolbarButton: { + ...toolbarButton, backgroundColor: ColorPalette.buttonUnderlay }, /** - * The icon style of toolbar buttons in {@link #primaryToolbar} which - * display white icons. + * The icon style of toolbar buttons which display white icons. */ - whitePrimaryToolbarButtonIcon: { - ...primaryToolbarButtonIcon, + whiteToolbarButtonIcon: { + ...toolbarButtonIcon, color: ColorPalette.white } }); + +export default styles; + +/** + * Styles for the hangup button. + */ +export const hangupButtonStyles = { + iconStyle: styles.whiteToolbarButtonIcon, + style: styles.hangupButton, + underlayColor: ColorPalette.buttonUnderlay +}; + +/** + * Styles for buttons in the toolbar. + */ +export const toolbarButtonStyles = { + iconStyle: styles.toolbarButtonIcon, + style: styles.toolbarButton +}; + +/** + * Styles for toggled buttons in the toolbar. + */ +export const toolbarToggledButtonStyles = { + iconStyle: styles.whiteToolbarButtonIcon, + style: styles.whiteToolbarButton +}; + +/** + * Styles for the {@code OverflowMenu} items. + * + * These have been implemented as per the Material Design guidelines: + * {@link https://material.io/guidelines/components/bottom-sheets.html}. + */ +const overflowMenuStyles = createStyleSheet({ + /** + * Container style for a {@code ToolboxItem} rendered in the + * {@code OverflowMenu}. + */ + container: { + alignItems: 'center', + flexDirection: 'row', + height: 48 + }, + + /** + * Style for the {@code Icon} element in a {@code ToolboxItem} rendered in + * the {@code OverflowMenu}. + */ + icon: { + fontSize: 24 + }, + + /** + * Style for the label in a {@code ToolboxItem} rendered in the + * {@code OverflowMenu}. + */ + label: { + flex: 1, + fontSize: 16, + marginLeft: 32, + opacity: 0.87 + } +}); + +export const overflowMenuItemStyles = { + iconStyle: overflowMenuStyles.icon, + labelStyle: overflowMenuStyles.label, + style: overflowMenuStyles.container, + underlayColor: '#eee' +};