feat: minimized bottom menu

pull/4897/head jitsi-meet_4113
Bettenbuk Zoltan 5 years ago committed by Zoltan Bettenbuk
parent 0a64bf2068
commit 411bafb5a6
  1. 38
      package-lock.json
  2. 2
      package.json
  3. 17
      react/features/base/dialog/components/native/BottomSheet.js
  4. 30
      react/features/base/dialog/components/native/styles.js
  5. 1
      react/features/base/icons/svg/drag-handle.svg
  6. 1
      react/features/base/icons/svg/index.js
  7. 8
      react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js
  8. 2
      react/features/remote-video-menu/components/native/RemoteVideoMenu.js
  9. 110
      react/features/toolbox/components/native/OverflowMenu.js
  10. 5
      react/features/toolbox/components/native/styles.js

38
package-lock.json generated

@ -14830,6 +14830,39 @@
"jssha": "^2.2.0" "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": { "react-native-immersive": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz", "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": { "react-native-swipeout": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz", "resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz",

@ -72,12 +72,14 @@
"react-native-background-timer": "2.1.1", "react-native-background-timer": "2.1.1",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2", "react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2",
"react-native-callstats": "3.61.0", "react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
"react-native-immersive": "2.0.0", "react-native-immersive": "2.0.0",
"react-native-keep-awake": "4.0.0", "react-native-keep-awake": "4.0.0",
"react-native-linear-gradient": "2.5.6", "react-native-linear-gradient": "2.5.6",
"react-native-sound": "0.11.0", "react-native-sound": "0.11.0",
"react-native-svg": "9.7.1", "react-native-svg": "9.7.1",
"react-native-svg-transformer": "0.13.0", "react-native-svg-transformer": "0.13.0",
"react-native-swipe-gestures": "1.0.4",
"react-native-swipeout": "2.3.6", "react-native-swipeout": "2.3.6",
"react-native-watch-connectivity": "0.2.0", "react-native-watch-connectivity": "0.2.0",
"react-native-webrtc": "1.75.2", "react-native-webrtc": "1.75.2",

@ -29,7 +29,12 @@ type Props = {
* Handler for the cancel event, which happens when the user dismisses * Handler for the cancel event, which happens when the user dismisses
* the sheet. * 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<Props> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { _styles } = this.props; const { _styles, renderHeader } = this.props;
return ( return (
<SlidingView <SlidingView
@ -56,20 +61,20 @@ class BottomSheet extends PureComponent<Props> {
<View <View
pointerEvents = 'box-none' pointerEvents = 'box-none'
style = { styles.sheetAreaCover } /> style = { styles.sheetAreaCover } />
<View { renderHeader && renderHeader() }
<SafeAreaView
style = { [ style = { [
styles.sheetItemContainer, styles.sheetItemContainer,
_styles.sheet _styles.sheet
] }> ] }>
<SafeAreaView>
<ScrollView <ScrollView
bounces = { false } bounces = { false }
showsVerticalScrollIndicator = { false } > showsVerticalScrollIndicator = { false }
style = { styles.scrollView } >
{ this.props.children } { this.props.children }
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
</View> </View>
</View>
</SlidingView> </SlidingView>
); );
} }

@ -33,6 +33,10 @@ export const bottomSheetStyles = {
flex: 1 flex: 1
}, },
scrollView: {
paddingHorizontal: MD_ITEM_MARGIN_PADDING
},
/** /**
* Style for the container of the sheet. * Style for the container of the sheet.
*/ */
@ -44,9 +48,7 @@ export const bottomSheetStyles = {
}, },
sheetItemContainer: { sheetItemContainer: {
flex: -1, flex: -1
maxHeight: '60%',
paddingHorizontal: MD_ITEM_MARGIN_PADDING
} }
}; };
@ -135,6 +137,7 @@ export const inputDialog = {
* {@link https://material.io/guidelines/components/bottom-sheets.html}. * {@link https://material.io/guidelines/components/bottom-sheets.html}.
*/ */
ColorSchemeRegistry.register('BottomSheet', { ColorSchemeRegistry.register('BottomSheet', {
buttons: {
/** /**
* Style for the {@code Icon} element in a generic item of the menu. * Style for the {@code Icon} element in a generic item of the menu.
*/ */
@ -154,13 +157,6 @@ ColorSchemeRegistry.register('BottomSheet', {
opacity: 0.90 opacity: 0.90
}, },
/**
* Bottom sheet's base style.
*/
sheet: {
backgroundColor: schemeColor('background')
},
/** /**
* Container style for a generic item rendered in the menu. * Container style for a generic item rendered in the menu.
*/ */
@ -174,6 +170,20 @@ ColorSchemeRegistry.register('BottomSheet', {
* Additional style that is not directly used as a style object. * Additional style that is not directly used as a style object.
*/ */
underlayColor: ColorPalette.overflowMenuItemUnderlay underlayColor: ColorPalette.overflowMenuItemUnderlay
},
expandIcon: {
color: schemeColor('icon'),
fontSize: 16,
opacity: 0.7
},
/**
* Bottom sheet's base style.
*/
sheet: {
backgroundColor: schemeColor('background')
}
}); });
/** /**

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><defs><path id="a" d="M0 0h24v24H0V0z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path clip-path="url(#b)" d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>

After

Width:  |  Height:  |  Size: 311 B

@ -25,6 +25,7 @@ export { default as IconDeviceHeadphone } from './headset.svg';
export { default as IconDeviceSpeaker } from './volume.svg'; export { default as IconDeviceSpeaker } from './volume.svg';
export { default as IconDominantSpeaker } from './dominant-speaker.svg'; export { default as IconDominantSpeaker } from './dominant-speaker.svg';
export { default as IconDownload } from './download.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 IconEventNote } from './event_note.svg';
export { default as IconExitFullScreen } from './exit-full-screen.svg'; export { default as IconExitFullScreen } from './exit-full-screen.svg';
export { default as IconFeedback } from './feedback.svg'; export { default as IconFeedback } from './feedback.svg';

@ -270,8 +270,8 @@ class AudioRoutePickerDialog extends Component<Props, State> {
<View style = { styles.deviceRow } > <View style = { styles.deviceRow } >
<Icon <Icon
src = { icon } src = { icon }
style = { [ styles.deviceIcon, _bottomSheetStyles.iconStyle, selectedStyle ] } /> style = { [ styles.deviceIcon, _bottomSheetStyles.buttons.iconStyle, selectedStyle ] } />
<Text style = { [ styles.deviceText, _bottomSheetStyles.labelStyle, selectedStyle ] } > <Text style = { [ styles.deviceText, _bottomSheetStyles.buttons.labelStyle, selectedStyle ] } >
{ text } { text }
</Text> </Text>
</View> </View>
@ -292,8 +292,8 @@ class AudioRoutePickerDialog extends Component<Props, State> {
<View style = { styles.deviceRow } > <View style = { styles.deviceRow } >
<Icon <Icon
src = { deviceInfoMap.SPEAKER.icon } src = { deviceInfoMap.SPEAKER.icon }
style = { [ styles.deviceIcon, _bottomSheetStyles.iconStyle ] } /> style = { [ styles.deviceIcon, _bottomSheetStyles.buttons.iconStyle ] } />
<Text style = { [ styles.deviceText, _bottomSheetStyles.labelStyle ] } > <Text style = { [ styles.deviceText, _bottomSheetStyles.buttons.labelStyle ] } >
{ t('audioDevices.none') } { t('audioDevices.none') }
</Text> </Text>
</View> </View>

@ -80,7 +80,7 @@ class RemoteVideoMenu extends Component<Props> {
afterClick: this._onCancel, afterClick: this._onCancel,
showLabel: true, showLabel: true,
participantID: participant.id, participantID: participant.id,
styles: this.props._bottomSheetStyles styles: this.props._bottomSheetStyles.buttons
}; };
return ( return (

@ -1,10 +1,13 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { PureComponent } from 'react';
import { Platform } from 'react-native'; 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 { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog'; import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
import { IconDragHandle } from '../../../base/icons';
import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags'; import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles'; import { StyleType } from '../../../base/styles';
@ -16,10 +19,12 @@ import { RoomLockButton } from '../../../room-lock';
import { ClosedCaptionButton } from '../../../subtitles'; import { ClosedCaptionButton } from '../../../subtitles';
import { TileViewButton } from '../../../video-layout'; import { TileViewButton } from '../../../video-layout';
import AudioOnlyButton from './AudioOnlyButton';
import HelpButton from '../HelpButton'; import HelpButton from '../HelpButton';
import AudioOnlyButton from './AudioOnlyButton';
import RaiseHandButton from './RaiseHandButton'; import RaiseHandButton from './RaiseHandButton';
import ToggleCameraButton from './ToggleCameraButton'; import ToggleCameraButton from './ToggleCameraButton';
import styles from './styles';
/** /**
* The type of the React {@code Component} props of {@link OverflowMenu}. * The type of the React {@code Component} props of {@link OverflowMenu}.
@ -52,6 +57,14 @@ type Props = {
dispatch: Function 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 * The exported React {@code Component}. We need it to execute
* {@link hideDialog}. * {@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 * Implements a React {@code Component} with some extra actions in addition to
* those in the toolbar. * those in the toolbar.
*/ */
class OverflowMenu extends Component<Props> { class OverflowMenu extends PureComponent<Props, State> {
/** /**
* Initializes a new {@code OverflowMenu} instance. * Initializes a new {@code OverflowMenu} instance.
* *
@ -74,8 +87,15 @@ class OverflowMenu extends Component<Props> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = {
showMore: false
};
// Bind event handlers so they are only bound once per instance. // Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this); 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,17 +105,23 @@ class OverflowMenu extends Component<Props> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { _bottomSheetStyles } = this.props;
const { showMore } = this.state;
const buttonProps = { const buttonProps = {
afterClick: this._onCancel, afterClick: this._onCancel,
showLabel: true, showLabel: true,
styles: this.props._bottomSheetStyles styles: _bottomSheetStyles.buttons
}; };
return ( return (
<BottomSheet onCancel = { this._onCancel }> <BottomSheet
onCancel = { this._onCancel }
renderHeader = { this._renderMenuExpandToggle }>
<AudioRouteButton { ...buttonProps } /> <AudioRouteButton { ...buttonProps } />
<ToggleCameraButton { ...buttonProps } /> <ToggleCameraButton { ...buttonProps } />
<AudioOnlyButton { ...buttonProps } /> <AudioOnlyButton { ...buttonProps } />
<Collapsible collapsed = { !showMore }>
<RoomLockButton { ...buttonProps } /> <RoomLockButton { ...buttonProps } />
<ClosedCaptionButton { ...buttonProps } /> <ClosedCaptionButton { ...buttonProps } />
{ {
@ -112,10 +138,38 @@ class OverflowMenu extends Component<Props> {
<RaiseHandButton { ...buttonProps } /> <RaiseHandButton { ...buttonProps } />
<SharedDocumentButton { ...buttonProps } /> <SharedDocumentButton { ...buttonProps } />
<HelpButton { ...buttonProps } /> <HelpButton { ...buttonProps } />
</Collapsible>
</BottomSheet> </BottomSheet>
); );
} }
_renderMenuExpandToggle: () => React$Element<any>;
/**
* Function to render the menu toggle in the bottom sheet header area.
*
* @returns {React$Element}
*/
_renderMenuExpandToggle() {
return (
<GestureRecognizer
config = {{
velocityThreshold: 0.1,
directionalOffsetThreshold: 30
}}
onSwipe = { this._onSwipe }
style = { [
this.props._bottomSheetStyles.sheet,
styles.expandMenuContainer
] }>
<TouchableOpacity onPress = { this._onToggleMenu }>
{ /* $FlowFixMeProps */ }
<IconDragHandle style = { this.props._bottomSheetStyles.expandIcon } />
</TouchableOpacity>
</GestureRecognizer>
);
}
_onCancel: () => boolean; _onCancel: () => boolean;
/** /**
@ -133,6 +187,50 @@ class OverflowMenu extends Component<Props> {
return false; 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
});
}
} }
/** /**

@ -55,6 +55,11 @@ const whiteToolbarButtonIcon = {
*/ */
const styles = { const styles = {
expandMenuContainer: {
alignItems: 'center',
flexDirection: 'column'
},
/** /**
* The style of the toolbar. * The style of the toolbar.
*/ */

Loading…
Cancel
Save