Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme switching: Going Live! (Add preferences menu entry for ThemePage) #21669

Merged
merged 16 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,10 @@ const CONST = {
DEFAULT: 'default',
},
THEME: {
DEFAULT: 'dark',
LIGHT: 'light',
DEFAULT: 'system',
FALLBACK: 'dark',
DARK: 'dark',
LIGHT: 'light',
SYSTEM: 'system',
},
COLOR_SCHEME: {
Expand Down
14 changes: 4 additions & 10 deletions src/hooks/useThemePreference.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import {useContext, useEffect, useState} from 'react';
import {Appearance, ColorSchemeName} from 'react-native';
import {useColorScheme} from 'react-native';
import {PreferredThemeContext} from '@components/OnyxProvider';
import {ThemePreferenceWithoutSystem} from '@styles/theme/types';
import CONST from '@src/CONST';

function useThemePreference() {
const [themePreference, setThemePreference] = useState<ThemePreferenceWithoutSystem>(CONST.THEME.DEFAULT);
const [systemTheme, setSystemTheme] = useState<ColorSchemeName>();
const [themePreference, setThemePreference] = useState<ThemePreferenceWithoutSystem>(CONST.THEME.FALLBACK);
const preferredThemeFromStorage = useContext(PreferredThemeContext);

useEffect(() => {
// This is used for getting the system theme, that can be set in the OS's theme settings. This will always return either "light" or "dark" and will update automatically if the OS theme changes.
const systemThemeSubscription = Appearance.addChangeListener(({colorScheme}) => setSystemTheme(colorScheme));
return () => systemThemeSubscription.remove();
}, []);
const systemTheme = useColorScheme();

useEffect(() => {
const theme = preferredThemeFromStorage ?? CONST.THEME.DEFAULT;

// If the user chooses to use the device theme settings, we need to set the theme preference to the system theme
if (theme === CONST.THEME.SYSTEM) {
setThemePreference(systemTheme ?? CONST.THEME.DEFAULT);
setThemePreference(systemTheme ?? CONST.THEME.FALLBACK);
} else {
setThemePreference(theme);
}
Expand Down
3 changes: 1 addition & 2 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.SETTINGS.PREFERENCES.ROOT]: () => require('../../../pages/settings/Preferences/PreferencesPage').default as React.ComponentType,
[SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: () => require('../../../pages/settings/Preferences/PriorityModePage').default as React.ComponentType,
[SCREENS.SETTINGS.PREFERENCES.LANGUAGE]: () => require('../../../pages/settings/Preferences/LanguagePage').default as React.ComponentType,
// Will be uncommented as part of https://github.com/Expensify/App/issues/21670
// [SCREENS.SETTINGS.PREFERENCES.THEME]: () => require('../../../pages/settings/Preferences/ThemePage').default as React.ComponentType,
[SCREENS.SETTINGS.PREFERENCES.THEME]: () => require('../../../pages/settings/Preferences/ThemePage').default as React.ComponentType,
[SCREENS.SETTINGS.CLOSE]: () => require('../../../pages/settings/Security/CloseAccountPage').default as React.ComponentType,
[SCREENS.SETTINGS.SECURITY]: () => require('../../../pages/settings/Security/SecuritySettingsPage').default as React.ComponentType,
[SCREENS.SETTINGS.ABOUT]: () => require('../../../pages/settings/AboutPage/AboutPage').default as React.ComponentType,
Expand Down
13 changes: 13 additions & 0 deletions src/pages/settings/Preferences/PreferencesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const propTypes = {
/** The chat priority mode */
priorityMode: PropTypes.string,

/** The app's color theme */
preferredTheme: PropTypes.string,

/** The details about the user that is signed in */
user: PropTypes.shape({
/** Whether or not the user is subscribed to news updates */
Expand All @@ -33,6 +36,7 @@ const propTypes = {

const defaultProps = {
priorityMode: CONST.PRIORITY_MODE.DEFAULT,
preferredTheme: CONST.DEFAULT_THEME,
user: {},
};

Expand Down Expand Up @@ -80,6 +84,12 @@ function PreferencesPage(props) {
description={translate('languagePage.language')}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_LANGUAGE)}
/>
<MenuItemWithTopDescription
shouldShowRightIcon
title={translate(`themePage.themes.${props.preferredTheme || CONST.THEME.DEFAULT}.label`)}
description={translate('themePage.theme')}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_THEME)}
/>
{/* Enable additional test features in non-production environments */}
{!isProduction && (
<View style={[styles.ml5, styles.mr8, styles.mt6]}>
Expand All @@ -102,4 +112,7 @@ export default withOnyx({
user: {
key: ONYXKEYS.USER,
},
preferredTheme: {
key: ONYXKEYS.PREFERRED_THEME,
},
})(PreferencesPage);
27 changes: 11 additions & 16 deletions src/pages/settings/Preferences/ThemePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

const propTypes = {
...withLocalizePropTypes,

/** The theme of the app */
preferredTheme: PropTypes.string,
};
Expand All @@ -28,9 +25,10 @@ const defaultProps = {

function ThemePage(props) {
const styles = useThemeStyles();
const localesToThemes = _.map(_.values(_.omit(CONST.THEME, 'DEFAULT')), (theme) => ({
const {translate} = useLocalize();
const localesToThemes = _.map(_.values(_.omit(CONST.THEME, 'DEFAULT', 'FALLBACK')), (theme) => ({
value: theme,
text: props.translate(`themePage.themes.${theme}.label`),
text: translate(`themePage.themes.${theme}.label`),
keyForList: theme,
isSelected: (props.preferredTheme || CONST.THEME.DEFAULT) === theme,
}));
Expand All @@ -41,13 +39,13 @@ function ThemePage(props) {
testID={ThemePage.displayName}
>
<HeaderWithBackButton
title={props.translate('themePage.theme')}
title={translate('themePage.theme')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PREFERENCES)}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>

<Text style={[styles.mh5, styles.mv4]}>{props.translate('themePage.chooseThemeBelowOrSync')}</Text>
<Text style={[styles.mh5, styles.mv4]}>{translate('themePage.chooseThemeBelowOrSync')}</Text>

<SelectionList
sections={[{data: localesToThemes}]}
Expand All @@ -62,11 +60,8 @@ ThemePage.displayName = 'ThemePage';
ThemePage.propTypes = propTypes;
ThemePage.defaultProps = defaultProps;

export default compose(
withLocalize,
withOnyx({
preferredTheme: {
key: ONYXKEYS.PREFERRED_THEME,
},
}),
)(ThemePage);
export default withOnyx({
preferredTheme: {
key: ONYXKEYS.PREFERRED_THEME,
},
})(ThemePage);
2 changes: 1 addition & 1 deletion src/styles/theme/illustrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Illustrations = {
[CONST.THEME.DARK]: darkIllustrations,
} satisfies Record<ThemePreferenceWithoutSystem, IllustrationsType>;

const DefaultIllustrations = Illustrations[CONST.THEME.DEFAULT];
const DefaultIllustrations = Illustrations[CONST.THEME.FALLBACK];

export default Illustrations;
export {DefaultIllustrations};
2 changes: 1 addition & 1 deletion src/styles/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const themes = {
[CONST.THEME.DARK]: darkTheme,
} satisfies Record<ThemePreferenceWithoutSystem, ThemeColors>;

const defaultTheme = themes[CONST.THEME.DEFAULT];
const defaultTheme = themes[CONST.THEME.FALLBACK];

export default themes;
export {defaultTheme};
2 changes: 1 addition & 1 deletion src/styles/theme/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {type ColorScheme, type StatusBarStyle} from '..';
type Color = string;

type ThemePreference = (typeof CONST.THEME)[keyof typeof CONST.THEME];
type ThemePreferenceWithoutSystem = Exclude<ThemePreference, 'system'>;
type ThemePreferenceWithoutSystem = Exclude<ThemePreference, typeof CONST.THEME.SYSTEM>;

type ThemeColors = {
// Figma keys
Expand Down
Loading