diff --git a/NOTICE.txt b/NOTICE.txt
index 700ea5a79..be76476b0 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -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
diff --git a/app/components/sidebars/drawer_layout.js b/app/components/sidebars/drawer_layout.js
new file mode 100644
index 000000000..bb4d0ee90
--- /dev/null
+++ b/app/components/sidebars/drawer_layout.js
@@ -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 (
+
+
+ {this.props.children}
+
+
+
+
+
+ {this.props.renderNavigationView()}
+
+
+ );
+ }
+
+ _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,
+ },
+});
diff --git a/app/components/sidebars/main/main_sidebar.js b/app/components/sidebars/main/main_sidebar.js
index 4b37d9e13..1b28ba2e1 100644
--- a/app/components/sidebars/main/main_sidebar.js
+++ b/app/components/sidebars/main/main_sidebar.js
@@ -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 (