MM-18530 - Update Main Sidebar on iOS (#3622)

* initial working code

* minimum changes for main sidebar working

* add settings sidebar back in

* fix visual bug with sidebar showing underneath

* fix bug for android

* cleanups

* minor cleanups and update snapshot

* add basic test

* fix style errors

* review changes
This commit is contained in:
Nick Brady 2019-12-04 10:19:50 -08:00 committed by Elias Nahum
parent 4c4e1907cb
commit a0c5ad5c2b
4 changed files with 108 additions and 35 deletions

View file

@ -196,14 +196,16 @@ export default class DrawerLayout extends Component {
left: drawerPosition === 'left' ? 0 : null,
right: drawerPosition === 'right' ? 0 : null,
};
/* Drawer styles */
let outputRange;
// ios main sidebar sits mostly under the main screen, with slight move
const translateDistance = Platform.OS === 'ios' && drawerPosition === 'left' ?
Math.floor(drawerWidth * 0.2) : drawerWidth
if (this.getDrawerPosition() === 'left') {
outputRange = [-drawerWidth, 0];
outputRange = [-translateDistance, 0];
} else {
outputRange = [drawerWidth, 0];
outputRange = [translateDistance, 0];
}
const drawerTranslateX = this.openValue.interpolate({
@ -215,30 +217,18 @@ export default class DrawerLayout extends Component {
transform: [{ translateX: drawerTranslateX }],
};
/* Overlay styles */
const overlayOpacity = this.openValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.5],
extrapolate: 'clamp',
});
const animatedOverlayStyles = { opacity: overlayOpacity };
const pointerEvents = drawerShown ? 'auto' : 'none';
// 0 - ios main drawer
// 1 - main | tablet
// 2 - overlay
// 3 - android main drawer | settings drawer
const drawerZIndex = drawerPosition === 'left' && Platform.OS === 'ios' ? 0 : 3
return (
<React.Fragment>
<TouchableWithoutFeedback
pointerEvents={pointerEvents}
onPress={this._onOverlayClick}
>
<Animated.View
pointerEvents={pointerEvents}
style={[styles.overlay, animatedOverlayStyles]}
/>
</TouchableWithoutFeedback>
<Animated.View
accessibilityViewIsModal={accessibilityViewIsModal}
style={[
styles.drawer,
StyleFactory.drawer(drawerZIndex),
dynamicDrawerStyles,
animatedDrawerStyles,
]}
@ -271,20 +261,61 @@ export default class DrawerLayout extends Component {
};
render() {
const {isTablet} = this.props;
const {drawerPosition, drawerWidth, isTablet} = this.props;
const panHandlers = isTablet ? emptyObject : this._panResponder.panHandlers;
const containerStyles = [styles.container];
if (isTablet) {
containerStyles.push(styles.tabletContainer);
}
const mainStyles = [styles.main]
if (drawerPosition === 'left' && Platform.OS === 'ios') {
/* Drawer styles */
let outputRange;
if (this.getDrawerPosition() === 'left') {
outputRange = [0, drawerWidth];
} else {
outputRange = [drawerWidth, 0];
}
const drawerTranslateX = this.openValue.interpolate({
inputRange: [0, 1],
outputRange,
extrapolate: 'clamp',
});
const animatedDrawerStyles = {
transform: [{ translateX: drawerTranslateX }],
};
mainStyles.push(animatedDrawerStyles)
}
/* Overlay styles */
const overlayOpacity = this.openValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.5],
extrapolate: 'clamp',
});
const animatedOverlayStyles = { opacity: overlayOpacity };
const pointerEvents = this.state.drawerShown ? 'auto' : 'none';
return (
<View
style={containerStyles}
{...panHandlers}
>
{this.renderDrawerForTablet()}
<Animated.View style={styles.main}>
<Animated.View style={mainStyles}>
<TouchableWithoutFeedback
pointerEvents={pointerEvents}
onPress={this._onOverlayClick}
>
<Animated.View
pointerEvents={pointerEvents}
style={[styles.overlay, animatedOverlayStyles]}
/>
</TouchableWithoutFeedback>
{this.props.children}
</Animated.View>
{this.renderDrawer()}
@ -508,6 +539,17 @@ export default class DrawerLayout extends Component {
}
}
class StyleFactory {
static drawer(zIndex) {
return ({
position: 'absolute',
top: 0,
bottom: 0,
zIndex,
});
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
@ -516,27 +558,22 @@ const styles = StyleSheet.create({
tabletContainer: {
flexDirection: 'row',
},
drawer: {
position: 'absolute',
top: 0,
bottom: 0,
zIndex: 1001,
},
tablet: {
height: '100%',
zIndex: 0,
zIndex: 1,
},
main: {
flex: 1,
zIndex: 0,
zIndex: 1,
position: 'relative', // so overlay pins to this View
},
overlay: {
backgroundColor: '#000',
position: 'absolute',
top: 0,
left: -350,
left: 0,
bottom: 0,
right: 0,
zIndex: 1000,
zIndex: 2,
},
});

View file

@ -2,6 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Platform} from 'react-native';
import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
@ -82,4 +83,26 @@ describe('MainSidebar', () => {
wrapper.setProps({theme: newTheme});
expect(instance.render).toHaveBeenCalledTimes(1);
});
test('should render main sidebar below PostList for iOS', () => {
Platform.OS = 'ios';
const wrapper = shallow(
<MainSidebar {...baseProps}/>
);
const drawer = wrapper.dive().childAt(1);
const drawerStyle = drawer.props().style.reduce((acc, obj) => ({...acc, ...obj}));
expect(drawerStyle).toHaveProperty('zIndex', 0);
});
test('should render main sidebar above PostList for android', () => {
Platform.OS = 'android';
const wrapper = shallow(
<MainSidebar {...baseProps}/>
);
const drawer = wrapper.dive().childAt(1);
const drawerStyle = drawer.props().style.reduce((acc, obj) => ({...acc, ...obj}));
expect(drawerStyle).toHaveProperty('zIndex', 3);
});
});

View file

@ -20,7 +20,7 @@ import SafeAreaView from 'app/components/safe_area_view';
import SettingsSidebar from 'app/components/sidebars/settings';
import {preventDoubleTap} from 'app/utils/tap';
import {setNavigatorStyles} from 'app/utils/theme';
import {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
import PushNotifications from 'app/push_notifications';
import EphemeralStore from 'app/store/ephemeral_store';
import tracker from 'app/utils/time_tracker';
@ -301,6 +301,7 @@ export default class ChannelBase extends PureComponent {
);
}
const baseStyle = getStyleFromTheme(theme);
return (
<MainSidebar
ref={this.channelSidebarRef}
@ -311,7 +312,9 @@ export default class ChannelBase extends PureComponent {
ref={this.settingsSidebarRef}
blurPostTextBox={this.blurPostTextBox}
>
{drawerContent}
<View style={baseStyle.backdrop}>
{drawerContent}
</View>
</SettingsSidebar>
<InteractiveDialogController
theme={theme}
@ -327,6 +330,15 @@ export default class ChannelBase extends PureComponent {
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
backdrop: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
};
});
export const style = StyleSheet.create({
flex: {
flex: 1,

View file

@ -98,6 +98,7 @@ jest.doMock('react-native', () => {
}, ReactNative);
});
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
jest.mock('../node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter');
jest.mock('react-native-device-info', () => {