From 411bafb5a62c96ae1bc176d4580458fcb6ebe6c0 Mon Sep 17 00:00:00 2001 From: Bettenbuk Zoltan Date: Mon, 25 Nov 2019 13:01:54 +0100 Subject: [PATCH] feat: minimized bottom menu --- package-lock.json | 38 +++++ package.json | 2 + .../dialog/components/native/BottomSheet.js | 27 ++-- .../base/dialog/components/native/styles.js | 76 ++++++---- react/features/base/icons/svg/drag-handle.svg | 1 + react/features/base/icons/svg/index.js | 1 + .../components/AudioRoutePickerDialog.js | 8 +- .../components/native/RemoteVideoMenu.js | 2 +- .../toolbox/components/native/OverflowMenu.js | 142 +++++++++++++++--- .../toolbox/components/native/styles.js | 5 + 10 files changed, 231 insertions(+), 71 deletions(-) create mode 100644 react/features/base/icons/svg/drag-handle.svg diff --git a/package-lock.json b/package-lock.json index f3e9f46d6a..e582847b13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14830,6 +14830,39 @@ "jssha": "^2.2.0" } }, + "react-native-collapsible": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-native-collapsible/-/react-native-collapsible-1.5.1.tgz", + "integrity": "sha512-uQQ2s6l+7+L/pzJroisWsDsyVYVF5bQ+jGbLATNioRh/03SpEL8pcQEVKqVWswcNNR0B9GENixHaLzmuZIwpQg==", + "requires": { + "prop-types": "^15.6.2" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + } + } + }, "react-native-immersive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz", @@ -14871,6 +14904,11 @@ } } }, + "react-native-swipe-gestures": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.4.tgz", + "integrity": "sha512-C/vz0KPHNyqHk3uF4Cz/jzd/0N8z34ZgsjAZUh/RsXPH2FtJJf3Fw73pQDWJSoCMtvVadlztb8xQ+/aEQrll7w==" + }, "react-native-swipeout": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz", diff --git a/package.json b/package.json index 6d00b14eb7..b0501446d6 100644 --- a/package.json +++ b/package.json @@ -72,12 +72,14 @@ "react-native-background-timer": "2.1.1", "react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2", "react-native-callstats": "3.61.0", + "react-native-collapsible": "1.5.1", "react-native-immersive": "2.0.0", "react-native-keep-awake": "4.0.0", "react-native-linear-gradient": "2.5.6", "react-native-sound": "0.11.0", "react-native-svg": "9.7.1", "react-native-svg-transformer": "0.13.0", + "react-native-swipe-gestures": "1.0.4", "react-native-swipeout": "2.3.6", "react-native-watch-connectivity": "0.2.0", "react-native-webrtc": "1.75.2", diff --git a/react/features/base/dialog/components/native/BottomSheet.js b/react/features/base/dialog/components/native/BottomSheet.js index 9a2b0b3043..6c4c292593 100644 --- a/react/features/base/dialog/components/native/BottomSheet.js +++ b/react/features/base/dialog/components/native/BottomSheet.js @@ -29,7 +29,12 @@ type Props = { * Handler for the cancel event, which happens when the user dismisses * the sheet. */ - onCancel: ?Function + onCancel: ?Function, + + /** + * Function to render a bottom sheet header element, if necessary. + */ + renderHeader: ?Function }; /** @@ -43,7 +48,7 @@ class BottomSheet extends PureComponent { * @returns {ReactElement} */ render() { - const { _styles } = this.props; + const { _styles, renderHeader } = this.props; return ( { - - - - { this.props.children } - - - + + { this.props.children } + + ); diff --git a/react/features/base/dialog/components/native/styles.js b/react/features/base/dialog/components/native/styles.js index 66804d943d..d04d9c8482 100644 --- a/react/features/base/dialog/components/native/styles.js +++ b/react/features/base/dialog/components/native/styles.js @@ -33,6 +33,10 @@ export const bottomSheetStyles = { flex: 1 }, + scrollView: { + paddingHorizontal: MD_ITEM_MARGIN_PADDING + }, + /** * Style for the container of the sheet. */ @@ -44,9 +48,7 @@ export const bottomSheetStyles = { }, sheetItemContainer: { - flex: -1, - maxHeight: '60%', - paddingHorizontal: MD_ITEM_MARGIN_PADDING + flex: -1 } }; @@ -135,23 +137,45 @@ export const inputDialog = { * {@link https://material.io/guidelines/components/bottom-sheets.html}. */ ColorSchemeRegistry.register('BottomSheet', { - /** - * Style for the {@code Icon} element in a generic item of the menu. - */ - iconStyle: { - color: schemeColor('icon'), - fontSize: 24 + buttons: { + /** + * Style for the {@code Icon} element in a generic item of the menu. + */ + iconStyle: { + color: schemeColor('icon'), + fontSize: 24 + }, + + /** + * Style for the label in a generic item rendered in the menu. + */ + labelStyle: { + color: schemeColor('text'), + flexShrink: 1, + fontSize: MD_FONT_SIZE, + marginLeft: 32, + opacity: 0.90 + }, + + /** + * Container style for a generic item rendered in the menu. + */ + style: { + alignItems: 'center', + flexDirection: 'row', + height: MD_ITEM_HEIGHT + }, + + /** + * Additional style that is not directly used as a style object. + */ + underlayColor: ColorPalette.overflowMenuItemUnderlay }, - /** - * Style for the label in a generic item rendered in the menu. - */ - labelStyle: { - color: schemeColor('text'), - flexShrink: 1, - fontSize: MD_FONT_SIZE, - marginLeft: 32, - opacity: 0.90 + expandIcon: { + color: schemeColor('icon'), + fontSize: 16, + opacity: 0.7 }, /** @@ -159,21 +183,7 @@ ColorSchemeRegistry.register('BottomSheet', { */ sheet: { backgroundColor: schemeColor('background') - }, - - /** - * Container style for a generic item rendered in the menu. - */ - style: { - alignItems: 'center', - flexDirection: 'row', - height: MD_ITEM_HEIGHT - }, - - /** - * Additional style that is not directly used as a style object. - */ - underlayColor: ColorPalette.overflowMenuItemUnderlay + } }); /** diff --git a/react/features/base/icons/svg/drag-handle.svg b/react/features/base/icons/svg/drag-handle.svg new file mode 100644 index 0000000000..9140fa7734 --- /dev/null +++ b/react/features/base/icons/svg/drag-handle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/react/features/base/icons/svg/index.js b/react/features/base/icons/svg/index.js index 0d3a8c8b47..a8030883a9 100644 --- a/react/features/base/icons/svg/index.js +++ b/react/features/base/icons/svg/index.js @@ -25,6 +25,7 @@ export { default as IconDeviceHeadphone } from './headset.svg'; export { default as IconDeviceSpeaker } from './volume.svg'; export { default as IconDominantSpeaker } from './dominant-speaker.svg'; export { default as IconDownload } from './download.svg'; +export { default as IconDragHandle } from './drag-handle.svg'; export { default as IconEventNote } from './event_note.svg'; export { default as IconExitFullScreen } from './exit-full-screen.svg'; export { default as IconFeedback } from './feedback.svg'; diff --git a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js index 406e0f001d..89c39994a2 100644 --- a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js +++ b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js @@ -270,8 +270,8 @@ class AudioRoutePickerDialog extends Component { - + style = { [ styles.deviceIcon, _bottomSheetStyles.buttons.iconStyle, selectedStyle ] } /> + { text } @@ -292,8 +292,8 @@ class AudioRoutePickerDialog extends Component { - + style = { [ styles.deviceIcon, _bottomSheetStyles.buttons.iconStyle ] } /> + { t('audioDevices.none') } diff --git a/react/features/remote-video-menu/components/native/RemoteVideoMenu.js b/react/features/remote-video-menu/components/native/RemoteVideoMenu.js index 3a5c051ec7..2b1b2afa03 100644 --- a/react/features/remote-video-menu/components/native/RemoteVideoMenu.js +++ b/react/features/remote-video-menu/components/native/RemoteVideoMenu.js @@ -80,7 +80,7 @@ class RemoteVideoMenu extends Component { afterClick: this._onCancel, showLabel: true, participantID: participant.id, - styles: this.props._bottomSheetStyles + styles: this.props._bottomSheetStyles.buttons }; return ( diff --git a/react/features/toolbox/components/native/OverflowMenu.js b/react/features/toolbox/components/native/OverflowMenu.js index 6483039a54..4f8f7c08fb 100644 --- a/react/features/toolbox/components/native/OverflowMenu.js +++ b/react/features/toolbox/components/native/OverflowMenu.js @@ -1,10 +1,13 @@ // @flow -import React, { Component } from 'react'; -import { Platform } from 'react-native'; +import React, { PureComponent } from 'react'; +import { Platform, TouchableOpacity } from 'react-native'; +import Collapsible from 'react-native-collapsible'; +import GestureRecognizer, { swipeDirections } from 'react-native-swipe-gestures'; import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog'; +import { IconDragHandle } from '../../../base/icons'; import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags'; import { connect } from '../../../base/redux'; import { StyleType } from '../../../base/styles'; @@ -16,10 +19,12 @@ import { RoomLockButton } from '../../../room-lock'; import { ClosedCaptionButton } from '../../../subtitles'; import { TileViewButton } from '../../../video-layout'; -import AudioOnlyButton from './AudioOnlyButton'; import HelpButton from '../HelpButton'; + +import AudioOnlyButton from './AudioOnlyButton'; import RaiseHandButton from './RaiseHandButton'; import ToggleCameraButton from './ToggleCameraButton'; +import styles from './styles'; /** * The type of the React {@code Component} props of {@link OverflowMenu}. @@ -52,6 +57,14 @@ type Props = { dispatch: Function }; +type State = { + + /** + * True if the 'more' button set needas to be rendered. + */ + showMore: boolean +} + /** * The exported React {@code Component}. We need it to execute * {@link hideDialog}. @@ -65,7 +78,7 @@ let OverflowMenu_; // eslint-disable-line prefer-const * Implements a React {@code Component} with some extra actions in addition to * those in the toolbar. */ -class OverflowMenu extends Component { +class OverflowMenu extends PureComponent { /** * Initializes a new {@code OverflowMenu} instance. * @@ -74,8 +87,15 @@ class OverflowMenu extends Component { constructor(props: Props) { super(props); + this.state = { + showMore: false + }; + // Bind event handlers so they are only bound once per instance. this._onCancel = this._onCancel.bind(this); + this._onSwipe = this._onSwipe.bind(this); + this._onToggleMenu = this._onToggleMenu.bind(this); + this._renderMenuExpandToggle = this._renderMenuExpandToggle.bind(this); } /** @@ -85,37 +105,71 @@ class OverflowMenu extends Component { * @returns {ReactElement} */ render() { + const { _bottomSheetStyles } = this.props; + const { showMore } = this.state; + const buttonProps = { afterClick: this._onCancel, showLabel: true, - styles: this.props._bottomSheetStyles + styles: _bottomSheetStyles.buttons }; return ( - + - - - { - this.props._recordingEnabled - && - } - - - - { - this.props._chatEnabled - && - } - - - + + + + { + this.props._recordingEnabled + && + } + + + + { + this.props._chatEnabled + && + } + + + + ); } + _renderMenuExpandToggle: () => React$Element; + + /** + * Function to render the menu toggle in the bottom sheet header area. + * + * @returns {React$Element} + */ + _renderMenuExpandToggle() { + return ( + + + { /* $FlowFixMeProps */ } + + + + ); + } + _onCancel: () => boolean; /** @@ -133,6 +187,50 @@ class OverflowMenu extends Component { return false; } + + _onSwipe: (string) => void; + + /** + * Callback to be invoked when a swipe gesture is detected on the menu. + * + * @param {string} gestureName - The name of the swipe gesture. + * @returns {void} + */ + _onSwipe(gestureName) { + const { showMore } = this.state; + + switch (gestureName) { + case swipeDirections.SWIPE_UP: + !showMore && this.setState({ + showMore: true + }); + break; + case swipeDirections.SWIPE_DOWN: + if (showMore) { + // If the menu is expanded, we collapse it. + this.setState({ + showMore: false + }); + } else { + // If the menu is not expanded, we close the menu + this._onCancel(); + } + break; + } + } + + _onToggleMenu: () => void; + + /** + * Callback to be invoked when the expand menu button is pressed. + * + * @returns {void} + */ + _onToggleMenu() { + this.setState({ + showMore: !this.state.showMore + }); + } } /** diff --git a/react/features/toolbox/components/native/styles.js b/react/features/toolbox/components/native/styles.js index 6b8e4813a4..a53c959f92 100644 --- a/react/features/toolbox/components/native/styles.js +++ b/react/features/toolbox/components/native/styles.js @@ -55,6 +55,11 @@ const whiteToolbarButtonIcon = { */ const styles = { + expandMenuContainer: { + alignItems: 'center', + flexDirection: 'column' + }, + /** * The style of the toolbar. */