Apply new theme to all navigation components (#3021)

This commit is contained in:
Mattermost Build
2019-07-24 15:24:54 +02:00
committed by Elias Nahum
parent 8887319324
commit 1f8e853e41
6 changed files with 119 additions and 21 deletions

View File

@@ -133,7 +133,7 @@ export function resetToTeams(name, title, passProps = {}, options = {}) {
export function goToScreen(name, title, passProps = {}, options = {}) {
return (dispatch, getState) => {
const state = getState();
const componentId = EphemeralStore.getTopComponentId();
const componentId = EphemeralStore.getNavigationTopComponentId();
const theme = getTheme(state);
const defaultOptions = {
layout: {
@@ -168,7 +168,7 @@ export function goToScreen(name, title, passProps = {}, options = {}) {
export function popTopScreen() {
return () => {
const componentId = EphemeralStore.getTopComponentId();
const componentId = EphemeralStore.getNavigationTopComponentId();
Navigation.pop(componentId);
};
@@ -176,7 +176,7 @@ export function popTopScreen() {
export function popToRoot() {
return () => {
const componentId = EphemeralStore.getTopComponentId();
const componentId = EphemeralStore.getNavigationTopComponentId();
Navigation.popToRoot(componentId).catch(() => {
// RNN returns a promise rejection if there are no screens
@@ -286,7 +286,7 @@ export function showSearchModal(initialValue = '') {
export function dismissModal(options = {}) {
return () => {
const componentId = EphemeralStore.getTopComponentId();
const componentId = EphemeralStore.getNavigationTopComponentId();
Navigation.dismissModal(componentId, options);
};
@@ -304,7 +304,7 @@ export function dismissAllModals(options = {}) {
export function peek(name, passProps = {}, options = {}) {
return () => {
const componentId = EphemeralStore.getTopComponentId();
const componentId = EphemeralStore.getNavigationTopComponentId();
const defaultOptions = {
preview: {
commit: false,
@@ -359,3 +359,25 @@ export function dismissOverlay(componentId) {
});
};
}
export function applyTheme() {
return (dispatch, getState) => {
const theme = getTheme(getState());
EphemeralStore.getNavigationComponentIds().forEach((componentId) => {
Navigation.mergeOptions(componentId, {
topBar: {
backButton: {
color: theme.sidebarHeaderTextColor,
},
background: {
color: theme.sidebarHeaderBg,
},
title: {
color: theme.sidebarHeaderTextColor,
},
},
});
});
};
}

View File

@@ -87,11 +87,8 @@ const launchAppAndAuthenticateIfNeeded = async () => {
Navigation.events().registerAppLaunchedListener(() => {
init();
// Keep track of the latest componentId to appear and disappear
// Keep track of the latest componentId to appear
Navigation.events().registerComponentDidAppearListener(({componentId}) => {
EphemeralStore.addComponentIdToStack(componentId);
});
Navigation.events().registerComponentDidDisappearListener(({componentId}) => {
EphemeralStore.removeComponentIdFromStack(componentId);
EphemeralStore.addNavigationComponentId(componentId);
});
});

View File

@@ -3,12 +3,15 @@
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 {isLandscape, isTablet} from 'app/selectors/device';
import {applyTheme} from 'app/actions/navigation';
import Theme from './theme';
@@ -25,6 +28,7 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators({
savePreferences,
applyTheme,
}, dispatch),
});

View File

@@ -26,6 +26,7 @@ export default class Theme extends React.PureComponent {
static propTypes = {
actions: PropTypes.shape({
savePreferences: PropTypes.func.isRequired,
applyTheme: PropTypes.func.isRequired,
}).isRequired,
componentId: PropTypes.string,
allowedThemes: PropTypes.arrayOf(PropTypes.object),
@@ -61,7 +62,12 @@ export default class Theme extends React.PureComponent {
}
setTheme = (key) => {
const {userId, teamId, actions: {savePreferences}, allowedThemes} = this.props;
const {
userId,
teamId,
allowedThemes,
actions: {savePreferences, applyTheme},
} = this.props;
const {customTheme} = this.state;
const selectedTheme = allowedThemes.concat(customTheme).find((theme) => theme.key === key);
@@ -71,6 +77,7 @@ export default class Theme extends React.PureComponent {
name: teamId,
value: JSON.stringify(selectedTheme),
}]);
applyTheme();
}
renderAllowedThemeTiles = () => {

View File

@@ -3,15 +3,29 @@
import React from 'react';
import {shallow} from 'enzyme';
import {Navigation} from 'react-native-navigation';
import ThemeTile from './theme_tile';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import Preferences from 'mattermost-redux/constants/preferences';
import {applyTheme} from 'app/actions/navigation';
import EphemeralStore from 'app/store/ephemeral_store';
import Theme from './theme';
import ThemeTile from './theme_tile';
jest.mock('react-intl');
jest.mock('react-native-navigation', () => ({
Navigation: {
mergeOptions: jest.fn(),
},
}));
const mockStore = configureMockStore([thunk]);
const allowedThemes = [
{
type: 'Mattermost',
@@ -127,6 +141,7 @@ describe('Theme', () => {
const baseProps = {
actions: {
savePreferences: jest.fn(),
applyTheme: jest.fn(),
},
allowedThemes,
isLandscape: false,
@@ -144,4 +159,57 @@ describe('Theme', () => {
expect(wrapper.getElement()).toMatchSnapshot();
expect(wrapper.find(ThemeTile)).toHaveLength(4);
});
test('should apply new theme to all navigation components that have appeared', () => {
const componentIds = ['component-1', 'component-2', 'component-3'];
componentIds.forEach((componentId) => {
EphemeralStore.addNavigationComponentId(componentId);
});
const store = mockStore({
entities: {
preferences: {
myPreferences: {
theme: {},
},
},
teams: {
currentTeamId: 'current-team-id',
},
general: {
config: {},
},
},
});
baseProps.actions.applyTheme.mockImplementation(() => {
store.dispatch(applyTheme());
});
const wrapper = shallow(
<Theme {...baseProps}/>,
);
const theme = allowedThemes[0];
wrapper.instance().setTheme(theme.key);
expect(baseProps.actions.applyTheme).toHaveBeenCalledTimes(1);
const options = {
topBar: {
backButton: {
color: theme.sidebarHeaderTextColor,
},
background: {
color: theme.sidebarHeaderBg,
},
title: {
color: theme.sidebarHeaderTextColor,
},
},
};
expect(Navigation.mergeOptions.mock.calls).toEqual([
[componentIds[2], options],
[componentIds[1], options],
[componentIds[0], options],
]);
});
});

View File

@@ -6,19 +6,19 @@ class EphemeralStore {
this.appStarted = false;
this.appStartedFromPushNotification = false;
this.deviceToken = null;
this.componentIdStack = [];
this.navigationComponentIdStack = [];
}
getTopComponentId = () => this.componentIdStack[0];
getNavigationTopComponentId = () => this.navigationComponentIdStack[0];
getNavigationComponentIds = () => this.navigationComponentIdStack;
addComponentIdToStack = (componentId) => {
this.componentIdStack.unshift(componentId);
}
addNavigationComponentId = (componentId) => {
const index = this.navigationComponentIdStack.indexOf(componentId);
if (index > 0) {
this.navigationComponentIdStack.slice(index, 1);
}
removeComponentIdFromStack = (componentId) => {
this.componentIdStack = this.componentIdStack.filter((id) => {
return id !== componentId;
});
this.navigationComponentIdStack.unshift(componentId);
}
}