forked from Ivasoft/mattermost-mobile
Theme selection screen (#2320)
* Theme selection screen (initial commit) PR Review changes Add custom theme option for users who are already using a custom theme Fix for limited allowed themes Selected item case fix Memoized available themes request, other optimizations updated snapshots * Custom theme option changes - title. spacing, save option on state for life-cycle of screen
This commit is contained in:
committed by
Elias Nahum
parent
bc387ed74e
commit
7c711caf2d
@@ -62,6 +62,7 @@ export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('TableImage', () => wrapWithContextProvider(require('app/screens/table_image').default), store, Provider);
|
||||
Navigation.registerComponent('TermsOfService', () => wrapWithContextProvider(require('app/screens/terms_of_service').default), store, Provider);
|
||||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(require('app/screens/text_preview').default), store, Provider);
|
||||
Navigation.registerComponent('ThemeSettings', () => wrapWithContextProvider(require('app/screens/theme').default), store, Provider);
|
||||
Navigation.registerComponent('Thread', () => wrapWithContextProvider(require('app/screens/thread').default), store, Provider);
|
||||
Navigation.registerComponent('TimezoneSettings', () => wrapWithContextProvider(require('app/screens/timezone').default), store, Provider);
|
||||
Navigation.registerComponent('ErrorTeamsList', () => wrapWithContextProvider(require('app/screens/error_teams_list').default), store, Provider);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DisplaySettings should match snapshot 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="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>
|
||||
`;
|
||||
@@ -20,6 +20,7 @@ export default class DisplaySettings extends PureComponent {
|
||||
static propTypes = {
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
enableTheme: PropTypes.bool.isRequired,
|
||||
enableTimezone: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
@@ -72,12 +73,30 @@ export default class DisplaySettings extends PureComponent {
|
||||
});
|
||||
});
|
||||
|
||||
goToThemeSettings = preventDoubleTap(() => {
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
navigator.push({
|
||||
screen: 'ThemeSettings',
|
||||
title: intl.formatMessage({id: 'mobile.display_settings.theme', defaultMessage: 'Theme'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
closeClockDisplaySettings = () => {
|
||||
this.setState({showClockDisplaySettings: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme, enableTimezone} = this.props;
|
||||
const {theme, enableTimezone, enableTheme} = this.props;
|
||||
const {showClockDisplaySettings} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
@@ -114,6 +133,18 @@ export default class DisplaySettings extends PureComponent {
|
||||
<StatusBar/>
|
||||
<View style={style.wrapper}>
|
||||
<View style={style.divider}/>
|
||||
{enableTheme && (
|
||||
<SettingsItem
|
||||
defaultMessage='Theme'
|
||||
i18nId='mobile.display_settings.theme'
|
||||
iconName='ios-color-palette'
|
||||
iconType='ion'
|
||||
onPress={this.goToThemeSettings}
|
||||
separator={true}
|
||||
showArrow={false}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
<SettingsItem
|
||||
defaultMessage='Clock Display'
|
||||
i18nId='mobile.advanced_settings.clockDisplay'
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import SettingsItem from 'app/screens/settings/settings_item';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import DisplaySettings from './display_settings';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
describe('DisplaySettings', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.default,
|
||||
enableTheme: false,
|
||||
enableTimezone: false,
|
||||
navigator: {push: () => {}}, // eslint-disable-line no-empty-function
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<DisplaySettings {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.find(SettingsItem).length).toBe(1);
|
||||
wrapper.setProps({enableTheme: true});
|
||||
expect(wrapper.find(SettingsItem).length).toBe(2);
|
||||
wrapper.setProps({enableTimezone: true});
|
||||
expect(wrapper.find(SettingsItem).length).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -6,13 +6,17 @@ import {connect} from 'react-redux';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {isTimezoneEnabled} from 'app/utils/timezone';
|
||||
import {isThemeSwitchingEnabled} from 'app/utils/theme';
|
||||
|
||||
import DisplaySettings from './display_settings';
|
||||
import {getAllowedThemes} from 'app/selectors/theme';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const enableTheme = isThemeSwitchingEnabled(state) && getAllowedThemes(state).length > 1;
|
||||
|
||||
return {
|
||||
enableTheme,
|
||||
enableTimezone,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
|
||||
69
app/screens/theme/__snapshots__/theme.test.js.snap
Normal file
69
app/screens/theme/__snapshots__/theme.test.js.snap
Normal file
@@ -0,0 +1,69 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Theme should match snapshot 1`] = `
|
||||
<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",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FlatList
|
||||
disableVirtualization={false}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={1}
|
||||
onEndReachedThreshold={2}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
/>
|
||||
</section>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
28
app/screens/theme/index.js
Normal file
28
app/screens/theme/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {getAllowedThemes, getCustomTheme} from 'app/selectors/theme';
|
||||
|
||||
import Theme from './theme';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
userId: getCurrentUserId(state),
|
||||
teamId: getCurrentTeamId(state),
|
||||
theme: getTheme(state),
|
||||
allowedThemes: getAllowedThemes(state),
|
||||
customTheme: getCustomTheme(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreators({
|
||||
savePreferences,
|
||||
}, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Theme);
|
||||
134
app/screens/theme/theme.js
Normal file
134
app/screens/theme/theme.js
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View, FlatList} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import Section from 'app/screens/settings/section';
|
||||
import SectionItem from 'app/screens/settings/section_item';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
export default class Theme extends React.PureComponent {
|
||||
static propTypes = {
|
||||
teamId: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
savePreferences: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
allowedThemes: PropTypes.arrayOf(PropTypes.object),
|
||||
customTheme: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
customTheme: null,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
if (!state.customTheme && props.customTheme) {
|
||||
return {
|
||||
customTheme: props.customTheme,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setTheme = (key) => {
|
||||
const {userId, teamId, actions: {savePreferences}, allowedThemes} = this.props;
|
||||
const {customTheme} = this.state;
|
||||
const selectedTheme = allowedThemes.concat(customTheme).find((theme) => theme.key === key);
|
||||
|
||||
savePreferences(userId, [{
|
||||
user_id: userId,
|
||||
category: Preferences.CATEGORY_THEME,
|
||||
name: teamId,
|
||||
value: JSON.stringify(selectedTheme),
|
||||
}]);
|
||||
}
|
||||
|
||||
renderThemeRow = ({item, title}) => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SectionItem
|
||||
label={(
|
||||
<FormattedText
|
||||
id={`user.settings.display.${item.type}`}
|
||||
defaultMessage={title || item.type}
|
||||
/>
|
||||
)}
|
||||
action={this.setTheme}
|
||||
actionType='select'
|
||||
actionValue={item.key}
|
||||
selected={item.type.toLowerCase() === theme.type.toLowerCase()}
|
||||
theme={theme}
|
||||
/>
|
||||
<View style={style.divider}/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
keyExtractor = (item) => item.key;
|
||||
|
||||
render() {
|
||||
const {theme, allowedThemes} = this.props;
|
||||
const {customTheme} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<View style={style.wrapper}>
|
||||
<Section
|
||||
disableHeader={true}
|
||||
theme={theme}
|
||||
>
|
||||
<FlatList
|
||||
data={allowedThemes}
|
||||
renderItem={this.renderThemeRow}
|
||||
keyExtractor={this.keyExtractor}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{customTheme &&
|
||||
<Section
|
||||
disableHeader={true}
|
||||
theme={theme}
|
||||
>
|
||||
{this.renderThemeRow({item: customTheme, title: 'Custom'})}
|
||||
</Section>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
150
app/screens/theme/theme.test.js
Normal file
150
app/screens/theme/theme.test.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FlatList} from 'react-native';
|
||||
import {shallow} from 'enzyme';
|
||||
import Section from 'app/screens/settings/section';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import Theme from './theme';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
describe('Theme', () => {
|
||||
const baseProps = {
|
||||
teamId: 'test-team',
|
||||
theme: Preferences.THEMES.default,
|
||||
userId: 'test-user',
|
||||
actions: {
|
||||
savePreferences: jest.fn(),
|
||||
},
|
||||
allowedThemes,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<Theme {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.find(Section).first().exists()).toEqual(true);
|
||||
expect(wrapper.find(FlatList).first().exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
const allowedThemes = [
|
||||
{
|
||||
default: {
|
||||
type: 'Mattermost',
|
||||
sidebarBg: '#145dbf',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#4578bf',
|
||||
sidebarTextActiveBorder: '#579eff',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#1153ab',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#06d6a0',
|
||||
awayIndicator: '#ffbc42',
|
||||
dndIndicator: '#f74343',
|
||||
mentionBg: '#ffffff',
|
||||
mentionColor: '#145dbf',
|
||||
centerChannelBg: '#ffffff',
|
||||
centerChannelColor: '#3d3c40',
|
||||
newMessageSeparator: '#ff8800',
|
||||
linkColor: '#2389d7',
|
||||
buttonBg: '#166de0',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#fd5960',
|
||||
mentionHighlightBg: '#ffe577',
|
||||
mentionHighlightLink: '#166de0',
|
||||
codeTheme: 'github',
|
||||
},
|
||||
},
|
||||
{
|
||||
organization: {
|
||||
type: 'Organization',
|
||||
sidebarBg: '#2071a7',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#136197',
|
||||
sidebarTextActiveBorder: '#7ab0d6',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#2f81b7',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#7dbe00',
|
||||
awayIndicator: '#dcbd4e',
|
||||
dndIndicator: '#ff6a6a',
|
||||
mentionBg: '#fbfbfb',
|
||||
mentionColor: '#2071f7',
|
||||
centerChannelBg: '#f2f4f8',
|
||||
centerChannelColor: '#333333',
|
||||
newMessageSeparator: '#ff8800',
|
||||
linkColor: '#2f81b7',
|
||||
buttonBg: '#1dacfc',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#a94442',
|
||||
mentionHighlightBg: '#f3e197',
|
||||
mentionHighlightLink: '#2f81b7',
|
||||
codeTheme: 'github',
|
||||
},
|
||||
},
|
||||
{
|
||||
mattermostDark: {
|
||||
type: 'Mattermost Dark',
|
||||
sidebarBg: '#1b2c3e',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#4a5664',
|
||||
sidebarTextActiveBorder: '#66b9a7',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#1b2c3e',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#65dcc8',
|
||||
awayIndicator: '#c1b966',
|
||||
dndIndicator: '#e81023',
|
||||
mentionBg: '#b74a4a',
|
||||
mentionColor: '#ffffff',
|
||||
centerChannelBg: '#2f3e4e',
|
||||
centerChannelColor: '#dddddd',
|
||||
newMessageSeparator: '#5de5da',
|
||||
linkColor: '#a4ffeb',
|
||||
buttonBg: '#4cbba4',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#ff6461',
|
||||
mentionHighlightBg: '#984063',
|
||||
mentionHighlightLink: '#a4ffeb',
|
||||
codeTheme: 'solarized-dark',
|
||||
},
|
||||
},
|
||||
{
|
||||
windows10: {
|
||||
type: 'Windows Dark',
|
||||
sidebarBg: '#171717',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#302e30',
|
||||
sidebarTextActiveBorder: '#196caf',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#1f1f1f',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#399fff',
|
||||
awayIndicator: '#c1b966',
|
||||
dndIndicator: '#e81023',
|
||||
mentionBg: '#0177e7',
|
||||
mentionColor: '#ffffff',
|
||||
centerChannelBg: '#1f1f1f',
|
||||
centerChannelColor: '#dddddd',
|
||||
newMessageSeparator: '#cc992d',
|
||||
linkColor: '#0d93ff',
|
||||
buttonBg: '#0177e7',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#ff6461',
|
||||
mentionHighlightBg: '#784098',
|
||||
mentionHighlightLink: '#a4ffeb',
|
||||
codeTheme: 'monokai',
|
||||
},
|
||||
},
|
||||
];
|
||||
39
app/selectors/theme.js
Normal file
39
app/selectors/theme.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
export const getAllowedThemes = createSelector(
|
||||
getConfig,
|
||||
getTheme,
|
||||
(config) => {
|
||||
const allThemes = Object.keys(Preferences.THEMES).map((key) => ({
|
||||
...Preferences.THEMES[key],
|
||||
key,
|
||||
}));
|
||||
let acceptableThemes = allThemes;
|
||||
const allowedThemeKeys = (config.AllowedThemes || '').split(',').filter(String);
|
||||
if (allowedThemeKeys.length) {
|
||||
acceptableThemes = allThemes.filter((theme) => allowedThemeKeys.includes(theme.key));
|
||||
}
|
||||
return acceptableThemes;
|
||||
}
|
||||
);
|
||||
|
||||
export const getCustomTheme = createSelector(
|
||||
getConfig,
|
||||
getTheme,
|
||||
(config, activeTheme) => {
|
||||
if (config.AllowCustomThemes === 'true' && activeTheme.type === 'custom') {
|
||||
return {
|
||||
...activeTheme,
|
||||
key: 'custom',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
@@ -27,3 +27,8 @@ export function setNavigatorStyles(navigator, theme) {
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
});
|
||||
}
|
||||
|
||||
export function isThemeSwitchingEnabled(state) {
|
||||
const {config} = state.entities.general;
|
||||
return config.EnableThemeSelection === 'true';
|
||||
}
|
||||
|
||||
@@ -202,6 +202,8 @@
|
||||
"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.theme": "Theme",
|
||||
"mobile.display_settings.custom_theme": "Custom 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",
|
||||
"mobile.downloader.android_complete": "Download complete",
|
||||
|
||||
Reference in New Issue
Block a user