Allow channel drawer to close when showing the team list (#2290)
* Allow channel drawer to close when showing the team list * prevent closing the sidebar when jump to is active * Add proper header to drawer_layout.js
This commit is contained in:
parent
2f7705609f
commit
e6f70707cc
6 changed files with 458 additions and 19 deletions
|
|
@ -1433,7 +1433,7 @@ SOFTWARE.
|
|||
|
||||
## react-native-drawer-layout
|
||||
|
||||
This product contains 'react-native-drawer-layout' by Brent Vatne.
|
||||
This product contains a modified version of 'react-native-drawer-layout' by Brent Vatne.
|
||||
|
||||
A platform-agnostic drawer layout. Pure JavaScript implementation on iOS and native implementation on Android. Why? Because the drawer layout is a useful component regardless of the platform! And if you can use it without changing any code, that's perfect
|
||||
|
||||
|
|
|
|||
439
app/components/sidebars/drawer_layout.js
Normal file
439
app/components/sidebars/drawer_layout.js
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
/* eslint-disable */
|
||||
// Original work: https://github.com/react-native-community/react-native-drawer-layout .
|
||||
// Modified work: Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
Dimensions,
|
||||
Keyboard,
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
I18nManager,
|
||||
} from 'react-native';
|
||||
|
||||
const MIN_SWIPE_DISTANCE = 3;
|
||||
const DEVICE_WIDTH = parseFloat(Dimensions.get('window').width);
|
||||
const THRESHOLD = DEVICE_WIDTH / 2;
|
||||
const VX_MAX = 0.1;
|
||||
|
||||
const IDLE = 'Idle';
|
||||
const DRAGGING = 'Dragging';
|
||||
const SETTLING = 'Settling';
|
||||
|
||||
export type PropType = {
|
||||
children: any,
|
||||
drawerBackgroundColor?: string,
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||
drawerPosition: 'left' | 'right',
|
||||
drawerWidth: number,
|
||||
keyboardDismissMode?: 'none' | 'on-drag',
|
||||
onDrawerClose?: Function,
|
||||
onDrawerOpen?: Function,
|
||||
onDrawerSlide?: Function,
|
||||
onDrawerStateChanged?: Function,
|
||||
renderNavigationView: () => any,
|
||||
statusBarBackgroundColor?: string,
|
||||
useNativeAnimations?: boolean,
|
||||
};
|
||||
|
||||
export type StateType = {
|
||||
accessibilityViewIsModal: boolean,
|
||||
drawerShown: boolean,
|
||||
openValue: any,
|
||||
};
|
||||
|
||||
export type EventType = {
|
||||
stopPropagation: Function,
|
||||
};
|
||||
|
||||
export type PanResponderEventType = {
|
||||
dx: number,
|
||||
dy: number,
|
||||
moveX: number,
|
||||
moveY: number,
|
||||
vx: number,
|
||||
vy: number,
|
||||
};
|
||||
|
||||
export type DrawerMovementOptionType = {
|
||||
velocity?: number,
|
||||
};
|
||||
|
||||
export default class DrawerLayout extends Component {
|
||||
props: PropType;
|
||||
state: StateType;
|
||||
_lastOpenValue: number;
|
||||
_panResponder: any;
|
||||
_isClosing: boolean;
|
||||
_closingAnchorValue: number;
|
||||
canClose: boolean;
|
||||
|
||||
static defaultProps = {
|
||||
drawerWidth: 0,
|
||||
drawerPosition: 'left',
|
||||
useNativeAnimations: false,
|
||||
};
|
||||
|
||||
static positions = {
|
||||
Left: 'left',
|
||||
Right: 'right',
|
||||
};
|
||||
|
||||
constructor(props: PropType, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.canClose = true;
|
||||
this.state = {
|
||||
accessibilityViewIsModal: false,
|
||||
drawerShown: false,
|
||||
openValue: new Animated.Value(0),
|
||||
};
|
||||
}
|
||||
|
||||
getDrawerPosition() {
|
||||
const { drawerPosition } = this.props;
|
||||
const rtl = I18nManager.isRTL;
|
||||
return rtl
|
||||
? drawerPosition === 'left' ? 'right' : 'left' // invert it
|
||||
: drawerPosition;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { openValue } = this.state;
|
||||
|
||||
openValue.addListener(({ value }) => {
|
||||
const drawerShown = value > 0;
|
||||
const accessibilityViewIsModal = drawerShown;
|
||||
if (drawerShown !== this.state.drawerShown) {
|
||||
this.setState({ drawerShown, accessibilityViewIsModal });
|
||||
}
|
||||
|
||||
if (this.props.keyboardDismissMode === 'on-drag') {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
this._lastOpenValue = value;
|
||||
if (this.props.onDrawerSlide) {
|
||||
this.props.onDrawerSlide({ nativeEvent: { offset: value } });
|
||||
}
|
||||
});
|
||||
|
||||
this._panResponder = PanResponder.create({
|
||||
onMoveShouldSetPanResponder: this._shouldSetPanResponder,
|
||||
onPanResponderGrant: this._panResponderGrant,
|
||||
onPanResponderMove: this._panResponderMove,
|
||||
onPanResponderTerminationRequest: () => false,
|
||||
onPanResponderRelease: this._panResponderRelease,
|
||||
onPanResponderTerminate: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { accessibilityViewIsModal, drawerShown, openValue } = this.state;
|
||||
|
||||
const {
|
||||
drawerBackgroundColor,
|
||||
drawerWidth,
|
||||
drawerPosition,
|
||||
} = this.props;
|
||||
|
||||
/**
|
||||
* We need to use the "original" drawer position here
|
||||
* as RTL turns position left and right on its own
|
||||
**/
|
||||
const dynamicDrawerStyles = {
|
||||
backgroundColor: drawerBackgroundColor,
|
||||
width: drawerWidth,
|
||||
left: drawerPosition === 'left' ? 0 : null,
|
||||
right: drawerPosition === 'right' ? 0 : null,
|
||||
};
|
||||
|
||||
/* Drawer styles */
|
||||
let outputRange;
|
||||
|
||||
if (this.getDrawerPosition() === 'left') {
|
||||
outputRange = [-drawerWidth, 0];
|
||||
} else {
|
||||
outputRange = [drawerWidth, 0];
|
||||
}
|
||||
|
||||
const drawerTranslateX = openValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange,
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const animatedDrawerStyles = {
|
||||
transform: [{ translateX: drawerTranslateX }],
|
||||
};
|
||||
|
||||
/* Overlay styles */
|
||||
const overlayOpacity = openValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 0.7],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const animatedOverlayStyles = { opacity: overlayOpacity };
|
||||
const pointerEvents = drawerShown ? 'auto' : 'none';
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{ flex: 1, backgroundColor: 'transparent' }}
|
||||
{...this._panResponder.panHandlers}
|
||||
>
|
||||
<Animated.View style={styles.main}>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
<TouchableWithoutFeedback
|
||||
pointerEvents={pointerEvents}
|
||||
onPress={this._onOverlayClick}
|
||||
>
|
||||
<Animated.View
|
||||
pointerEvents={pointerEvents}
|
||||
style={[styles.overlay, animatedOverlayStyles]}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
<Animated.View
|
||||
accessibilityViewIsModal={accessibilityViewIsModal}
|
||||
style={[
|
||||
styles.drawer,
|
||||
dynamicDrawerStyles,
|
||||
animatedDrawerStyles,
|
||||
]}
|
||||
>
|
||||
{this.props.renderNavigationView()}
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_onOverlayClick = (e: EventType) => {
|
||||
e.stopPropagation();
|
||||
if (!this._isLockedClosed() && !this._isLockedOpen()) {
|
||||
this.closeDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
_emitStateChanged = (newState: string) => {
|
||||
if (this.props.onDrawerStateChanged) {
|
||||
this.props.onDrawerStateChanged(newState);
|
||||
}
|
||||
};
|
||||
|
||||
openDrawer = (options: DrawerMovementOptionType = {}) => {
|
||||
this._emitStateChanged(SETTLING);
|
||||
Animated.spring(this.state.openValue, {
|
||||
toValue: 1,
|
||||
bounciness: 0,
|
||||
restSpeedThreshold: 0.1,
|
||||
useNativeDriver: this.props.useNativeAnimations,
|
||||
...options,
|
||||
}).start(() => {
|
||||
if (this.props.onDrawerOpen) {
|
||||
this.props.onDrawerOpen();
|
||||
}
|
||||
this._emitStateChanged(IDLE);
|
||||
});
|
||||
};
|
||||
|
||||
closeDrawer = (options: DrawerMovementOptionType = {}) => {
|
||||
this._emitStateChanged(SETTLING);
|
||||
Animated.spring(this.state.openValue, {
|
||||
toValue: 0,
|
||||
bounciness: 0,
|
||||
restSpeedThreshold: 1,
|
||||
useNativeDriver: this.props.useNativeAnimations,
|
||||
...options,
|
||||
}).start(() => {
|
||||
if (this.props.onDrawerClose) {
|
||||
this.props.onDrawerClose();
|
||||
}
|
||||
this._emitStateChanged(IDLE);
|
||||
});
|
||||
};
|
||||
|
||||
_handleDrawerOpen = () => {
|
||||
if (this.props.onDrawerOpen) {
|
||||
this.props.onDrawerOpen();
|
||||
}
|
||||
};
|
||||
|
||||
_handleDrawerClose = () => {
|
||||
if (this.props.onDrawerClose) {
|
||||
this.props.onDrawerClose();
|
||||
}
|
||||
};
|
||||
|
||||
_shouldSetPanResponder = (
|
||||
e: EventType,
|
||||
{ moveX, dx, dy }: PanResponderEventType,
|
||||
) => {
|
||||
if (!dx || !dy || Math.abs(dx) < MIN_SWIPE_DISTANCE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._isLockedClosed() || this._isLockedOpen() || !this.canClose) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.getDrawerPosition() === 'left') {
|
||||
const overlayArea = DEVICE_WIDTH -
|
||||
(DEVICE_WIDTH - this.props.drawerWidth);
|
||||
|
||||
if (this._lastOpenValue === 1) {
|
||||
if (
|
||||
(dx < 0 && Math.abs(dx) > Math.abs(dy) * 3) ||
|
||||
moveX > overlayArea
|
||||
) {
|
||||
this._isClosing = true;
|
||||
this._closingAnchorValue = this._getOpenValueForX(moveX);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (moveX <= 35 && dx > 0) {
|
||||
this._isClosing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const overlayArea = DEVICE_WIDTH - this.props.drawerWidth;
|
||||
|
||||
if (this._lastOpenValue === 1) {
|
||||
if (
|
||||
(dx > 0 && Math.abs(dx) > Math.abs(dy) * 3) ||
|
||||
moveX < overlayArea
|
||||
) {
|
||||
this._isClosing = true;
|
||||
this._closingAnchorValue = this._getOpenValueForX(moveX);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (moveX >= DEVICE_WIDTH - 35 && dx < 0) {
|
||||
this._isClosing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_panResponderGrant = () => {
|
||||
this._emitStateChanged(DRAGGING);
|
||||
};
|
||||
|
||||
_panResponderMove = (e: EventType, { moveX }: PanResponderEventType) => {
|
||||
let openValue = this._getOpenValueForX(moveX);
|
||||
|
||||
if (this._isClosing) {
|
||||
openValue = 1 - (this._closingAnchorValue - openValue);
|
||||
}
|
||||
|
||||
if (openValue > 1) {
|
||||
openValue = 1;
|
||||
} else if (openValue < 0) {
|
||||
openValue = 0;
|
||||
}
|
||||
|
||||
this.state.openValue.setValue(openValue);
|
||||
};
|
||||
|
||||
_panResponderRelease = (
|
||||
e: EventType,
|
||||
{ moveX, vx }: PanResponderEventType,
|
||||
) => {
|
||||
const previouslyOpen = this._isClosing;
|
||||
const isWithinVelocityThreshold = vx < VX_MAX && vx > -VX_MAX;
|
||||
|
||||
if (this.getDrawerPosition() === 'left') {
|
||||
if (
|
||||
(vx > 0 && moveX > THRESHOLD) ||
|
||||
vx >= VX_MAX ||
|
||||
(isWithinVelocityThreshold &&
|
||||
previouslyOpen &&
|
||||
moveX > THRESHOLD)
|
||||
) {
|
||||
this.openDrawer({ velocity: vx });
|
||||
} else if (
|
||||
(vx < 0 && moveX < THRESHOLD) ||
|
||||
vx < -VX_MAX ||
|
||||
(isWithinVelocityThreshold && !previouslyOpen)
|
||||
) {
|
||||
this.closeDrawer({ velocity: vx });
|
||||
} else if (previouslyOpen) {
|
||||
this.openDrawer();
|
||||
} else {
|
||||
this.closeDrawer();
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(vx < 0 && moveX < THRESHOLD) ||
|
||||
vx <= -VX_MAX ||
|
||||
(isWithinVelocityThreshold &&
|
||||
previouslyOpen &&
|
||||
moveX < THRESHOLD)
|
||||
) {
|
||||
this.openDrawer({ velocity: (-1) * vx });
|
||||
} else if (
|
||||
(vx > 0 && moveX > THRESHOLD) ||
|
||||
vx > VX_MAX ||
|
||||
(isWithinVelocityThreshold && !previouslyOpen)
|
||||
) {
|
||||
this.closeDrawer({ velocity: (-1) * vx });
|
||||
} else if (previouslyOpen) {
|
||||
this.openDrawer();
|
||||
} else {
|
||||
this.closeDrawer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_isLockedClosed = () => {
|
||||
return this.props.drawerLockMode === 'locked-closed' &&
|
||||
!this.state.drawerShown;
|
||||
};
|
||||
|
||||
_isLockedOpen = () => {
|
||||
return this.props.drawerLockMode === 'locked-open' &&
|
||||
this.state.drawerShown;
|
||||
};
|
||||
|
||||
_getOpenValueForX(x: number): number {
|
||||
const { drawerWidth } = this.props;
|
||||
|
||||
if (this.getDrawerPosition() === 'left') {
|
||||
return x / drawerWidth;
|
||||
}
|
||||
|
||||
// position === 'right'
|
||||
return (DEVICE_WIDTH - x) / drawerWidth;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
drawer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex: 1001,
|
||||
},
|
||||
main: {
|
||||
flex: 1,
|
||||
zIndex: 0,
|
||||
},
|
||||
overlay: {
|
||||
backgroundColor: '#000',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
},
|
||||
});
|
||||
|
|
@ -10,12 +10,12 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import DrawerLayout from 'react-native-drawer-layout';
|
||||
|
||||
import {General, WebsocketEvents} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import DrawerLayout from 'app/components/sidebars/drawer_layout';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
|
|
@ -50,8 +50,6 @@ export default class ChannelSidebar extends Component {
|
|||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
swiperIndex = 1;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
|
@ -60,9 +58,9 @@ export default class ChannelSidebar extends Component {
|
|||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
|
||||
this.swiperIndex = 1;
|
||||
this.state = {
|
||||
show: false,
|
||||
lockMode: 'unlocked',
|
||||
openDrawerOffset,
|
||||
drawerOpened: false,
|
||||
};
|
||||
|
|
@ -102,7 +100,7 @@ export default class ChannelSidebar extends Component {
|
|||
|
||||
return nextProps.currentTeamId !== currentTeamId ||
|
||||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
|
||||
nextProps.teamsCount !== teamsCount || this.state.lockMode !== nextState.lockMode;
|
||||
nextProps.teamsCount !== teamsCount;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
@ -257,10 +255,13 @@ export default class ChannelSidebar extends Component {
|
|||
|
||||
onPageSelected = (index) => {
|
||||
this.swiperIndex = index;
|
||||
if (this.swiperIndex === 0) {
|
||||
this.setState({lockMode: 'locked-open'});
|
||||
} else {
|
||||
this.setState({lockMode: 'unlocked'});
|
||||
|
||||
if (this.refs.drawer) {
|
||||
if (this.swiperIndex === 0) {
|
||||
this.refs.drawer.canClose = false;
|
||||
} else {
|
||||
this.refs.drawer.canClose = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -272,10 +273,16 @@ export default class ChannelSidebar extends Component {
|
|||
if (isLandscape || isTablet) {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
if (this.refs.drawer) {
|
||||
this.refs.drawer.canClose = true;
|
||||
}
|
||||
this.setState({openDrawerOffset});
|
||||
};
|
||||
|
||||
onSearchStart = () => {
|
||||
if (this.refs.drawer) {
|
||||
this.refs.drawer.canClose = false;
|
||||
}
|
||||
this.setState({openDrawerOffset: 0});
|
||||
};
|
||||
|
||||
|
|
@ -372,11 +379,10 @@ export default class ChannelSidebar extends Component {
|
|||
|
||||
render() {
|
||||
const {children, deviceWidth} = this.props;
|
||||
const {lockMode, openDrawerOffset} = this.state;
|
||||
const {openDrawerOffset} = this.state;
|
||||
|
||||
return (
|
||||
<DrawerLayout
|
||||
drawerLockMode={lockMode}
|
||||
ref='drawer'
|
||||
renderNavigationView={this.renderNavigationView}
|
||||
onDrawerClose={this.handleDrawerClose}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import {
|
|||
ScrollView,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import DrawerLayout from 'react-native-drawer-layout';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import DrawerLayout from 'app/components/sidebars/drawer_layout';
|
||||
import UserStatus from 'app/components/user_status';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
|
||||
|
|
|
|||
5
package-lock.json
generated
5
package-lock.json
generated
|
|
@ -12721,11 +12721,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-2.1.0.tgz",
|
||||
"integrity": "sha512-BFCBXwz8xuLvHLVFVeQM+RhaY8yZ38PEWt9WSbq5VIoZ/VssP6uu51XxOfdwaMALOrAHIojK0SiYnd155upZAg=="
|
||||
},
|
||||
"react-native-drawer-layout": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-2.0.0.tgz",
|
||||
"integrity": "sha512-zYmzv+4QKDwxmAj7pF1yr8VUb+Hp7oBlwMjRZsvv56vPwO89I+kPTI8G78uwSNIf5b4e/iOChE4vpB5J2XsJFA=="
|
||||
},
|
||||
"react-native-drawer-layout-polyfill": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-drawer-layout-polyfill/-/react-native-drawer-layout-polyfill-1.3.2.tgz",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
"react-native-device-info": "github:enahum/react-native-device-info#f28e4ef36cd2e3d30f36ed96f2341591fc3f038f",
|
||||
"react-native-doc-viewer": "2.7.8",
|
||||
"react-native-document-picker": "2.1.0",
|
||||
"react-native-drawer-layout": "2.0.0",
|
||||
"react-native-exception-handler": "2.9.0",
|
||||
"react-native-image-gallery": "github:enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f",
|
||||
"react-native-image-picker": "0.27.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue