MM-17424 Setting to enable/disable fixed sidebar (#3060)

This commit is contained in:
Elias Nahum 2019-08-02 13:50:31 -04:00 committed by GitHub
parent 94b48813f9
commit d230bdac1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 679 additions and 19 deletions

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChannelSidebar should match, full snapshot 1`] = `
exports[`MainSidebar should match, full snapshot 1`] = `
<DrawerLayout
drawerPosition="left"
drawerWidth={-30}

View file

@ -11,6 +11,7 @@ import {
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import AsyncStorage from '@react-native-community/async-storage';
import {General, WebsocketEvents} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
@ -79,9 +80,11 @@ export default class ChannelSidebar extends Component {
this.mounted = true;
this.props.actions.getTeams();
this.handleDimensions();
this.handlePermanentSidebar();
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.on('renderDrawer', this.handleShowDrawerContent);
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
EventEmitter.on(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, this.handlePermanentSidebar);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
Dimensions.addEventListener('change', this.handleDimensions);
}
@ -103,7 +106,7 @@ export default class ChannelSidebar extends Component {
shouldComponentUpdate(nextProps, nextState) {
const {currentTeamId, deviceWidth, isLandscape, teamsCount} = this.props;
const {openDrawerOffset, isSplitView, show, searching} = this.state;
const {openDrawerOffset, isSplitView, permanentSidebar, show, searching} = this.state;
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== show || nextState.searching !== searching) {
return true;
@ -112,7 +115,8 @@ export default class ChannelSidebar extends Component {
return nextProps.currentTeamId !== currentTeamId ||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
nextProps.teamsCount !== teamsCount ||
nextState.isSplitView !== isSplitView;
nextState.isSplitView !== isSplitView ||
nextState.permanentSidebar !== permanentSidebar;
}
componentWillUnmount() {
@ -120,6 +124,7 @@ export default class ChannelSidebar extends Component {
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
EventEmitter.off('renderDrawer', this.handleShowDrawerContent);
EventEmitter.off(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, this.handlePermanentSidebar);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
Dimensions.addEventListener('change', this.handleDimensions);
}
@ -142,6 +147,13 @@ export default class ChannelSidebar extends Component {
}
};
handlePermanentSidebar = async () => {
if (DeviceTypes.IS_TABLET && this.mounted) {
const enabled = await AsyncStorage.getItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS);
this.setState({permanentSidebar: enabled === 'true'});
}
};
handleShowDrawerContent = () => {
requestAnimationFrame(() => this.setState({show: true}));
};
@ -396,7 +408,7 @@ export default class ChannelSidebar extends Component {
render() {
const {children, deviceWidth} = this.props;
const {openDrawerOffset} = this.state;
const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView;
const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView && this.state.permanentSidebar;
const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset);
return (

View file

@ -6,11 +6,13 @@ import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
import ChannelSidebar from './main_sidebar';
import {DeviceTypes} from 'app/constants';
import MainSidebar from './main_sidebar';
jest.mock('react-intl');
describe('ChannelSidebar', () => {
describe('MainSidebar', () => {
const baseProps = {
actions: {
getTeams: jest.fn(),
@ -30,9 +32,30 @@ describe('ChannelSidebar', () => {
test('should match, full snapshot', () => {
const wrapper = shallow(
<ChannelSidebar {...baseProps}/>
<MainSidebar {...baseProps}/>
);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should not set the permanentSidebar state if not Tablet', () => {
const wrapper = shallow(
<MainSidebar {...baseProps}/>
);
wrapper.instance().handlePermanentSidebar();
expect(wrapper.state('permanentSidebar')).toBeUndefined();
});
test('should set the permanentSidebar state if Tablet', async () => {
const wrapper = shallow(
<MainSidebar {...baseProps}/>
);
DeviceTypes.IS_TABLET = true;
await wrapper.instance().handlePermanentSidebar();
expect(wrapper.state('permanentSidebar')).toBeDefined();
});
});

View file

@ -20,4 +20,5 @@ export default {
IS_IPHONE_X: DeviceInfo.getModel().includes('iPhone X'),
IS_TABLET: DeviceInfo.isTablet(),
VIDEOS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Videos`,
PERMANENT_SIDEBAR_SETTINGS: '@PERMANENT_SIDEBAR_SETTINGS',
};

13
app/init/device.js Normal file
View file

@ -0,0 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AsyncStorage from '@react-native-community/async-storage';
import {DeviceTypes} from 'app/constants';
if (DeviceTypes.IS_TABLET) {
AsyncStorage.getItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS).then((value) => {
if (!value) {
AsyncStorage.setItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, 'true');
}
});
}

View file

@ -12,6 +12,7 @@ import {setDeepLinkURL} from 'app/actions/views/root';
import initialState from 'app/initial_state';
import {getAppCredentials} from 'app/init/credentials';
import emmProvider from 'app/init/emm_provider';
import 'app/init/device';
import 'app/init/fetch';
import globalEventHandler from 'app/init/global_event_handler';
import {registerScreens} from 'app/screens';

View file

@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChannelNavBar should match, full snapshot 1`] = `
<View
style={
Array [
Object {
"backgroundColor": "#1153ab",
"flexDirection": "row",
"justifyContent": "flex-start",
"width": "100%",
"zIndex": 10,
},
Object {
"paddingHorizontal": 0,
},
Object {
"height": 44,
},
]
}
>
<Connect(ChannelDrawerButton)
openDrawer={[MockFunction]}
visible={true}
/>
<Connect(ChannelTitle)
canHaveSubtitle={true}
onPress={[MockFunction]}
/>
<Connect(ChannelSearchButton)
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
/>
<Connect(SettingDrawerButton)
openDrawer={[MockFunction]}
/>
</View>
`;

View file

@ -4,6 +4,9 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Dimensions, Platform, View} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {DeviceTypes, ViewTypes} from 'app/constants';
import mattermostManaged from 'app/mattermost_managed';
@ -38,12 +41,15 @@ export default class ChannelNavBar extends PureComponent {
componentDidMount() {
this.mounted = true;
this.handleDimensions();
this.handlePermanentSidebar();
Dimensions.addEventListener('change', this.handleDimensions);
EventEmitter.on(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, this.handlePermanentSidebar);
}
componentWillUnmount() {
this.mounted = false;
Dimensions.removeEventListener('change', this.handleDimensions);
EventEmitter.off(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, this.handlePermanentSidebar);
}
handleDimensions = () => {
@ -55,6 +61,14 @@ export default class ChannelNavBar extends PureComponent {
}
};
handlePermanentSidebar = () => {
if (DeviceTypes.IS_TABLET && this.mounted) {
AsyncStorage.getItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS).then((enabled) => {
this.setState({permanentSidebar: enabled === 'true'});
});
}
};
render() {
const {isLandscape, onPress, theme} = this.props;
const {openChannelDrawer, openSettingsDrawer} = this.props;
@ -87,7 +101,7 @@ export default class ChannelNavBar extends PureComponent {
}
let drawerButtonVisible = false;
if (!DeviceTypes.IS_TABLET || this.state.isSplitView) {
if (!DeviceTypes.IS_TABLET || this.state.isSplitView || !this.state.permanentSidebar) {
drawerButtonVisible = true;
}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
import {DeviceTypes} from 'app/constants';
import ChannelNavBar from './channel_nav_bar';
jest.mock('react-intl');
describe('ChannelNavBar', () => {
const baseProps = {
isLandscape: false,
openChannelDrawer: jest.fn(),
openSettingsDrawer: jest.fn(),
onPress: jest.fn(),
theme: Preferences.THEMES.default,
};
test('should match, full snapshot', () => {
const wrapper = shallow(
<ChannelNavBar {...baseProps}/>
);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should not set the permanentSidebar state if not Tablet', () => {
const wrapper = shallow(
<ChannelNavBar {...baseProps}/>
);
wrapper.instance().handlePermanentSidebar();
expect(wrapper.state('permanentSidebar')).toBeUndefined();
});
test('should set the permanentSidebar state if Tablet', async () => {
const wrapper = shallow(
<ChannelNavBar {...baseProps}/>
);
DeviceTypes.IS_TABLET = true;
await wrapper.instance().handlePermanentSidebar();
expect(wrapper.state('permanentSidebar')).toBeDefined();
});
});

View file

@ -28,7 +28,7 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('ChannelMembers', () => wrapper(require('app/screens/channel_members').default), () => require('app/screens/channel_members').default);
Navigation.registerComponent('ChannelPeek', () => wrapper(require('app/screens/channel_peek').default), () => require('app/screens/channel_peek').default);
Navigation.registerComponent('ClientUpgrade', () => wrapper(require('app/screens/client_upgrade').default), () => require('app/screens/client_upgrade').default);
Navigation.registerComponent('ClockDisplay', () => wrapper(require('app/screens/clock_display').default), () => require('app/screens/clock_display').default);
Navigation.registerComponent('ClockDisplaySettings', () => wrapper(require('app/screens/settings/clock_display').default), () => require('app/screens/settings/clock_display').default);
Navigation.registerComponent('Code', () => wrapper(require('app/screens/code').default), () => require('app/screens/code').default);
Navigation.registerComponent('CreateChannel', () => wrapper(require('app/screens/create_channel').default), () => require('app/screens/create_channel').default);
Navigation.registerComponent('DisplaySettings', () => wrapper(require('app/screens/settings/display_settings').default), () => require('app/screens/settings/display_settings').default);
@ -64,16 +64,17 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('SelectorScreen', () => wrapper(require('app/screens/selector_screen').default), () => require('app/screens/selector_screen').default);
Navigation.registerComponent('SelectServer', () => wrapper(SelectServer), () => SelectServer);
Navigation.registerComponent('SelectTeam', () => wrapper(require('app/screens/select_team').default), () => require('app/screens/select_team').default);
Navigation.registerComponent('SelectTimezone', () => wrapper(require('app/screens/timezone/select_timezone').default), () => require('app/screens/timezone/select_timezone').default);
Navigation.registerComponent('SelectTimezone', () => wrapper(require('app/screens/settings/timezone/select_timezone').default), () => require('app/screens/settings/timezone/select_timezone').default);
Navigation.registerComponent('Settings', () => wrapper(require('app/screens/settings/general').default), () => require('app/screens/settings/general').default);
Navigation.registerComponent('SidebarSettings', () => wrapper(require('app/screens//settings/sidebar').default), () => require('app/screens/settings/sidebar').default);
Navigation.registerComponent('SSO', () => wrapper(require('app/screens/sso').default), () => require('app/screens/sso').default);
Navigation.registerComponent('Table', () => wrapper(require('app/screens/table').default), () => require('app/screens/table').default);
Navigation.registerComponent('TableImage', () => wrapper(require('app/screens/table_image').default), () => require('app/screens/table_image').default);
Navigation.registerComponent('TermsOfService', () => wrapper(require('app/screens/terms_of_service').default), () => require('app/screens/terms_of_service').default);
Navigation.registerComponent('TextPreview', () => wrapper(require('app/screens/text_preview').default), () => require('app/screens/text_preview').default);
Navigation.registerComponent('ThemeSettings', () => wrapper(require('app/screens/theme').default), () => require('app/screens/theme').default);
Navigation.registerComponent('ThemeSettings', () => wrapper(require('app/screens/settings/theme').default), () => require('app/screens/settings/theme').default);
Navigation.registerComponent('Thread', () => wrapper(require('app/screens/thread').default), () => require('app/screens/thread').default);
Navigation.registerComponent('TimezoneSettings', () => wrapper(require('app/screens/timezone').default), () => require('app/screens/timezone').default);
Navigation.registerComponent('TimezoneSettings', () => wrapper(require('app/screens/settings/timezone').default), () => require('app/screens/settings/timezone').default);
Navigation.registerComponent('ErrorTeamsList', () => wrapper(require('app/screens/error_teams_list').default), () => require('app/screens/error_teams_list').default);
Navigation.registerComponent('UserProfile', () => wrapper(require('app/screens/user_profile').default), () => require('app/screens/user_profile').default);
}
}

View file

@ -19,7 +19,7 @@ import ClockDisplayBase from './clock_display_base';
export default class ClockDisplay extends ClockDisplayBase {
static propTypes = {
showModal: PropTypes.bool.isRequired,
militaryTime: PropTypes.bool.isRequired,
militaryTime: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
};

View file

@ -76,3 +76,118 @@ exports[`DisplaySettings should match snapshot 1`] = `
</View>
</View>
`;
exports[`DisplaySettings should match snapshot on Tablet devices 1`] = `
<View
style={
Object {
"backgroundColor": "#ffffff",
"flex": 1,
}
}
>
<Connect(StatusBar) />
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.06)",
"flex": 1,
"paddingTop": 35,
}
}
>
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.1)",
"height": 1,
}
}
/>
<SettingsItem
defaultMessage="Sidebar"
i18nId="mobile.display_settings.sidebar"
iconName="columns"
iconType="fontawesome"
isDestructor={false}
onPress={[Function]}
separator={true}
showArrow={false}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
/>
<SettingsItem
defaultMessage="Clock Display"
i18nId="mobile.advanced_settings.clockDisplay"
iconName="ios-time"
iconType="ion"
isDestructor={false}
onPress={[Function]}
separator={false}
showArrow={false}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
/>
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.1)",
"height": 1,
}
}
/>
</View>
</View>
`;

View file

@ -9,13 +9,13 @@ import {
View,
} from 'react-native';
import SettingsItem from 'app/screens/settings/settings_item';
import {DeviceTypes} from 'app/constants';
import StatusBar from 'app/components/status_bar';
import ClockDisplay from 'app/screens/settings/clock_display';
import SettingsItem from 'app/screens/settings/settings_item';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ClockDisplay from 'app/screens/clock_display';
export default class DisplaySettings extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
@ -53,6 +53,15 @@ export default class DisplaySettings extends PureComponent {
this.setState({showClockDisplaySettings: true});
});
goToSidebarSettings = preventDoubleTap(() => {
const {actions, theme} = this.props;
const {intl} = this.context;
const screen = 'SidebarSettings';
const title = intl.formatMessage({id: 'mobile.display_settings.sidebar', defaultMessage: 'Sidebar'});
actions.goToScreen(screen, title, {theme});
});
goToTimezoneSettings = preventDoubleTap(() => {
const {actions} = this.props;
const {intl} = this.context;
@ -104,11 +113,28 @@ export default class DisplaySettings extends PureComponent {
);
}
let sidebar;
if (DeviceTypes.IS_TABLET) {
sidebar = (
<SettingsItem
defaultMessage='Sidebar'
i18nId='mobile.display_settings.sidebar'
iconName='columns'
iconType='fontawesome'
onPress={this.goToSidebarSettings}
separator={true}
showArrow={false}
theme={theme}
/>
);
}
return (
<View style={style.container}>
<StatusBar/>
<View style={style.wrapper}>
<View style={style.divider}/>
{sidebar}
{enableTheme && (
<SettingsItem
defaultMessage='Theme'

View file

@ -3,9 +3,12 @@
import React from 'react';
import {shallow} from 'enzyme';
import SettingsItem from 'app/screens/settings/settings_item';
import Preferences from 'mattermost-redux/constants/preferences';
import {DeviceTypes} from 'app/constants';
import SettingsItem from 'app/screens/settings/settings_item';
import DisplaySettings from './display_settings';
jest.mock('react-intl');
@ -33,4 +36,19 @@ describe('DisplaySettings', () => {
wrapper.setProps({enableTimezone: true});
expect(wrapper.find(SettingsItem).length).toBe(3);
});
test('should match snapshot on Tablet devices', () => {
DeviceTypes.IS_TABLET = true;
const wrapper = shallow(
<DisplaySettings {...baseProps}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
expect(wrapper.find(SettingsItem).length).toBe(2);
wrapper.setProps({enableTheme: true});
expect(wrapper.find(SettingsItem).length).toBe(3);
wrapper.setProps({enableTimezone: true});
expect(wrapper.find(SettingsItem).length).toBe(4);
});
});

View file

@ -0,0 +1,119 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SidebarSettings should match, full snapshot 1`] = `null`;
exports[`SidebarSettings should match, full snapshot 2`] = `
<View
style={
Object {
"backgroundColor": "#ffffff",
"flex": 1,
}
}
>
<Connect(StatusBar) />
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.06)",
"flex": 1,
"paddingTop": 35,
}
}
>
<section
disableHeader={true}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
>
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.1)",
"height": 1,
}
}
/>
<sectionItem
action={[Function]}
actionType="toggle"
description={
<FormattedText
defaultMessage="Keep the sidebar open permanently"
id="mobile.sidebar_settings.permanent_description"
/>
}
label={
<FormattedText
defaultMessage="Permanent Sidebar"
id="mobile.sidebar_settings.permanent"
/>
}
selected={false}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
/>
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.1)",
"height": 1,
}
}
/>
</section>
</View>
</View>
`;

View file

@ -0,0 +1,119 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
View,
Platform,
} from 'react-native';
import {intlShape} from 'react-intl';
import AsyncStorage from '@react-native-community/async-storage';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {DeviceTypes} from 'app/constants';
import FormattedText from 'app/components/formatted_text';
import StatusBar from 'app/components/status_bar';
import Section from 'app/screens/settings/section';
import SectionItem from 'app/screens/settings/section_item';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class SidebarSettings extends PureComponent {
static propTypes = {
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape,
};
constructor(props) {
super(props);
this.loadSetting();
}
loadSetting = async () => {
const value = await AsyncStorage.getItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS);
const enabled = Boolean(value === 'true');
this.setState({enabled});
};
saveSetting = (enabled) => {
AsyncStorage.setItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, enabled.toString());
this.setState({enabled}, () => EventEmitter.emit(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS));
};
render() {
if (!this.state) {
return null;
}
const {
theme,
} = this.props;
const {enabled} = this.state;
const style = getStyleSheet(theme);
return (
<View style={style.container}>
<StatusBar/>
<View style={style.wrapper}>
<Section
disableHeader={true}
theme={theme}
>
<View style={style.divider}/>
<SectionItem
label={(
<FormattedText
id='mobile.sidebar_settings.permanent'
defaultMessage='Permanent Sidebar'
/>
)}
description={(
<FormattedText
id='mobile.sidebar_settings.permanent_description'
defaultMessage='Keep the sidebar open permanently'
/>
)}
action={this.saveSetting}
actionType='toggle'
selected={enabled}
theme={theme}
/>
<View style={style.divider}/>
</Section>
</View>
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
wrapper: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
flex: 1,
...Platform.select({
ios: {
paddingTop: 35,
},
}),
},
divider: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 1,
},
separator: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 1,
marginLeft: 15,
},
};
});

View file

@ -0,0 +1,79 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
import {DeviceTypes} from 'app/constants';
import MainSidebar from 'app/components/sidebars/main/main_sidebar';
import SidebarSettings from './index';
jest.mock('react-intl');
jest.mock('app/mattermost_managed', () => ({
isRunningInSplitView: jest.fn().mockResolvedValue(false),
}));
describe('SidebarSettings', () => {
const baseProps = {
theme: Preferences.THEMES.default,
};
test('should match, full snapshot', async () => {
const wrapper = shallow(
<SidebarSettings {...baseProps}/>
);
expect(wrapper.getElement()).toMatchSnapshot();
await wrapper.instance().loadSetting();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should set the Permanent Sidebar value to false', async () => {
const wrapper = shallow(
<SidebarSettings {...baseProps}/>
);
await wrapper.instance().loadSetting();
expect(wrapper.state('enabled')).toBe(false);
});
test('should set the Permanent Sidebar value to true and update the sidebar', async () => {
DeviceTypes.IS_TABLET = true;
const wrapper = shallow(
<SidebarSettings {...baseProps}/>
);
const mainProps = {
actions: {
getTeams: jest.fn(),
logChannelSwitch: jest.fn(),
makeDirectChannel: jest.fn(),
setChannelDisplayName: jest.fn(),
setChannelLoading: jest.fn(),
},
blurPostTextBox: jest.fn(),
currentTeamId: 'current-team-id',
currentUserId: 'current-user-id',
deviceWidth: 10,
isLandscape: false,
teamsCount: 2,
theme: Preferences.THEMES.default,
};
const mainSidebar = shallow(
<MainSidebar {...mainProps}/>
);
await wrapper.instance().loadSetting();
expect(wrapper.state('enabled')).toBe(false);
await wrapper.instance().saveSetting(true);
expect(wrapper.state('enabled')).toBe(true);
expect(mainSidebar.state('permanentSidebar')).toBe(true);
});
});

View file

@ -207,6 +207,7 @@
"mobile.create_channel.public": "New Public Channel",
"mobile.create_post.read_only": "This channel is read-only",
"mobile.custom_list.no_results": "No Results",
"mobile.display_settings.sidebar": "Sidebar",
"mobile.display_settings.theme": "Theme",
"mobile.document_preview.failed_description": "An error occurred while opening the document. Please make sure you have a {fileType} viewer installed and try again.\n",
"mobile.document_preview.failed_title": "Open Document failed",
@ -435,11 +436,12 @@
"mobile.share_extension.error_message": "An error has occurred while using the share extension.",
"mobile.share_extension.error_title": "Extension Error",
"mobile.share_extension.team": "Team",
"mobile.sidebar_settings.permanent": "Permanent Sidebar",
"mobile.sidebar_settings.permanent_description": "Keep the sidebar open permanently",
"mobile.suggestion.members": "Members",
"mobile.terms_of_service.alert_cancel": "Cancel",
"mobile.terms_of_service.alert_ok": "OK",
"mobile.terms_of_service.alert_retry": "Try Again",
"mobile.terms_of_service.get_terms_error_description": "Make sure you have an internet connection or {refresh}. If this issue persists, contact your System Administrator.",
"mobile.terms_of_service.terms_rejected": "You must agree to the terms of service before accessing {siteName}. Please contact your System Administrator for more details.",
"mobile.timezone_settings.automatically": "Set automatically",
"mobile.timezone_settings.manual": "Change timezone",