feat: add swipe handler to entire bottom sheet
This commit is contained in:
parent
7b9abd34a0
commit
9b60537e0f
|
@ -14873,11 +14873,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
|
|
@ -77,7 +77,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",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { PureComponent, type Node } from 'react';
|
import React, { PureComponent, type Node } from 'react';
|
||||||
import { SafeAreaView, ScrollView, View } from 'react-native';
|
import { PanResponder, SafeAreaView, ScrollView, View } from 'react-native';
|
||||||
|
|
||||||
import { ColorSchemeRegistry } from '../../../color-scheme';
|
import { ColorSchemeRegistry } from '../../../color-scheme';
|
||||||
import { SlidingView } from '../../../react';
|
import { SlidingView } from '../../../react';
|
||||||
|
@ -10,6 +10,16 @@ import { StyleType } from '../../../styles';
|
||||||
|
|
||||||
import { bottomSheetStyles as styles } from './styles';
|
import { bottomSheetStyles as styles } from './styles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal distance that needs to be moved by the finger to consider it a swipe.
|
||||||
|
*/
|
||||||
|
const GESTURE_DISTANCE_THRESHOLD = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimal speed needed to be achieved by the finger to consider it as a swipe.
|
||||||
|
*/
|
||||||
|
const GESTURE_SPEED_THRESHOLD = 0.2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of {@code BottomSheet}'s React {@code Component} prop types.
|
* The type of {@code BottomSheet}'s React {@code Component} prop types.
|
||||||
*/
|
*/
|
||||||
|
@ -31,6 +41,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
onCancel: ?Function,
|
onCancel: ?Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to be attached to the custom swipe event of the BottomSheet.
|
||||||
|
*/
|
||||||
|
onSwipe?: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to render a bottom sheet header element, if necessary.
|
* Function to render a bottom sheet header element, if necessary.
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +56,23 @@ type Props = {
|
||||||
* A component emulating Android's BottomSheet.
|
* A component emulating Android's BottomSheet.
|
||||||
*/
|
*/
|
||||||
class BottomSheet extends PureComponent<Props> {
|
class BottomSheet extends PureComponent<Props> {
|
||||||
|
panResponder: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new component.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.panResponder = PanResponder.create({
|
||||||
|
onStartShouldSetPanResponder: this._onShouldSetResponder.bind(this),
|
||||||
|
onMoveShouldSetPanResponder: this._onShouldSetResponder.bind(this),
|
||||||
|
onPanResponderRelease: this._onGestureEnd.bind(this)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
|
@ -66,7 +98,8 @@ class BottomSheet extends PureComponent<Props> {
|
||||||
style = { [
|
style = { [
|
||||||
styles.sheetItemContainer,
|
styles.sheetItemContainer,
|
||||||
_styles.sheet
|
_styles.sheet
|
||||||
] }>
|
] }
|
||||||
|
{ ...this.panResponder.panHandlers }>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
bounces = { false }
|
bounces = { false }
|
||||||
showsVerticalScrollIndicator = { false }
|
showsVerticalScrollIndicator = { false }
|
||||||
|
@ -78,6 +111,48 @@ class BottomSheet extends PureComponent<Props> {
|
||||||
</SlidingView>
|
</SlidingView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to handle a gesture end event.
|
||||||
|
*
|
||||||
|
* @param {Object} evt - The native gesture event.
|
||||||
|
* @param {Object} gestureState - The gesture state.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onGestureEnd(evt, gestureState) {
|
||||||
|
const verticalSwipe = Math.abs(gestureState.vy) > Math.abs(gestureState.vx)
|
||||||
|
&& Math.abs(gestureState.vy) > GESTURE_SPEED_THRESHOLD;
|
||||||
|
|
||||||
|
if (verticalSwipe) {
|
||||||
|
const direction = gestureState.vy > 0 ? 'down' : 'up';
|
||||||
|
const { onCancel, onSwipe } = this.props;
|
||||||
|
let isSwipeHandled = false;
|
||||||
|
|
||||||
|
if (onSwipe) {
|
||||||
|
isSwipeHandled = onSwipe(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === 'down' && !isSwipeHandled) {
|
||||||
|
// Swipe down is a special gesture that can be used to close the
|
||||||
|
// BottomSheet, so if the swipe is not handled by the parent
|
||||||
|
// component, we consider it as a request to close.
|
||||||
|
onCancel && onCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the pan responder should activate, false otherwise.
|
||||||
|
*
|
||||||
|
* @param {Object} evt - The native gesture event.
|
||||||
|
* @param {Object} gestureState - The gesture state.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
_onShouldSetResponder({ nativeEvent }, gestureState) {
|
||||||
|
return nativeEvent.touches.length === 1
|
||||||
|
&& Math.abs(gestureState.dx) > GESTURE_DISTANCE_THRESHOLD
|
||||||
|
&& Math.abs(gestureState.dy) > GESTURE_DISTANCE_THRESHOLD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Platform, TouchableOpacity } from 'react-native';
|
import { Platform, TouchableOpacity, View } from 'react-native';
|
||||||
import Collapsible from 'react-native-collapsible';
|
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';
|
||||||
|
@ -59,6 +58,11 @@ type Props = {
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the bottom scheet is scrolled to the top.
|
||||||
|
*/
|
||||||
|
scrolledToTop: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the 'more' button set needas to be rendered.
|
* True if the 'more' button set needas to be rendered.
|
||||||
*/
|
*/
|
||||||
|
@ -88,6 +92,7 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
scrolledToTop: true,
|
||||||
showMore: false
|
showMore: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,6 +122,7 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||||
return (
|
return (
|
||||||
<BottomSheet
|
<BottomSheet
|
||||||
onCancel = { this._onCancel }
|
onCancel = { this._onCancel }
|
||||||
|
onSwipe = { this._onSwipe }
|
||||||
renderHeader = { this._renderMenuExpandToggle }>
|
renderHeader = { this._renderMenuExpandToggle }>
|
||||||
<AudioRouteButton { ...buttonProps } />
|
<AudioRouteButton { ...buttonProps } />
|
||||||
<ToggleCameraButton { ...buttonProps } />
|
<ToggleCameraButton { ...buttonProps } />
|
||||||
|
@ -152,12 +158,7 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||||
*/
|
*/
|
||||||
_renderMenuExpandToggle() {
|
_renderMenuExpandToggle() {
|
||||||
return (
|
return (
|
||||||
<GestureRecognizer
|
<View
|
||||||
config = {{
|
|
||||||
velocityThreshold: 0.1,
|
|
||||||
directionalOffsetThreshold: 30
|
|
||||||
}}
|
|
||||||
onSwipe = { this._onSwipe }
|
|
||||||
style = { [
|
style = { [
|
||||||
this.props._bottomSheetStyles.sheet,
|
this.props._bottomSheetStyles.sheet,
|
||||||
styles.expandMenuContainer
|
styles.expandMenuContainer
|
||||||
|
@ -166,7 +167,7 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||||
{ /* $FlowFixMeProps */ }
|
{ /* $FlowFixMeProps */ }
|
||||||
<IconDragHandle style = { this.props._bottomSheetStyles.expandIcon } />
|
<IconDragHandle style = { this.props._bottomSheetStyles.expandIcon } />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</GestureRecognizer>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,34 +189,31 @@ class OverflowMenu extends PureComponent<Props, State> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSwipe: (string) => void;
|
_onSwipe: string => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to be invoked when a swipe gesture is detected on the menu.
|
* Callback to be invoked when swipe gesture is detected on the menu. Returns true
|
||||||
|
* if the swipe gesture is handled by the menu, false otherwise.
|
||||||
*
|
*
|
||||||
* @param {string} gestureName - The name of the swipe gesture.
|
* @param {string} direction - Direction of 'up' or 'down'.
|
||||||
* @returns {void}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
_onSwipe(gestureName) {
|
_onSwipe(direction) {
|
||||||
const { showMore } = this.state;
|
const { showMore } = this.state;
|
||||||
|
|
||||||
switch (gestureName) {
|
switch (direction) {
|
||||||
case swipeDirections.SWIPE_UP:
|
case 'up':
|
||||||
!showMore && this.setState({
|
!showMore && this.setState({
|
||||||
showMore: true
|
showMore: true
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
case swipeDirections.SWIPE_DOWN:
|
return !showMore;
|
||||||
if (showMore) {
|
case 'down':
|
||||||
// If the menu is expanded, we collapse it.
|
showMore && this.setState({
|
||||||
this.setState({
|
showMore: false
|
||||||
showMore: false
|
});
|
||||||
});
|
|
||||||
} else {
|
return showMore;
|
||||||
// If the menu is not expanded, we close the menu
|
|
||||||
this._onCancel();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,11 @@ const styles = {
|
||||||
flexDirection: 'column'
|
flexDirection: 'column'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sheetGestureRecognizer: {
|
||||||
|
alignItems: 'stretch',
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The style of the toolbar.
|
* The style of the toolbar.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue