Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
7d538de
Implement ThemeProvider with only one animated color (WIP)
Mar 28, 2023
92b9835
Wrap app with ThemeProvider
Mar 28, 2023
7aa60de
use animated color for app background
Mar 28, 2023
288160e
add change listener to useIsLightMode
Mar 28, 2023
81f2f94
Removing shouldKeyReportActionsByID
chiragsalian Mar 1, 2023
fb3b662
Merge branch 'main' into @chrispader/theme-switching
Mar 28, 2023
564e5b0
fix: wrong imports
Mar 28, 2023
774856d
move hooks to actual function body
Mar 28, 2023
866344a
fix: missing React import
Mar 28, 2023
63e8323
add flex 1
Mar 28, 2023
1d9c20c
fix: move ThemeProvider into ComposeProviders
Mar 30, 2023
55b04f7
Merge branch 'main' into @chrispader/theme-switching
Apr 19, 2023
5c2ad33
subscribe to system color theme and add ONYXKEY
Apr 19, 2023
fc0c32d
update ThemeProvider
Apr 19, 2023
6122a37
chore: update reanimated
Apr 19, 2023
1a20110
add missing useEffect dependency
Apr 19, 2023
9d8aafd
remove color animation from NavigationRoot
Apr 19, 2023
bf67d21
move reanimated babel plugin to last position
Apr 19, 2023
8adeef7
Add theme switching button to Expensify main component
Apr 19, 2023
698ae62
Add color animation to ReportScreen
Apr 19, 2023
5f0c824
update reanimated again
Apr 19, 2023
26d12a1
downgrade rea to 3.0.0
Apr 19, 2023
a4d11b0
move animation test to Expensify component
Apr 19, 2023
71d6744
update REA
Apr 20, 2023
1b7a1a9
add patch for reanimated bug (https://github.com/react-navigation/rea…
Apr 20, 2023
f340e6f
add random view with animated color
Apr 20, 2023
798f57a
downgrade cocoapods version
Apr 20, 2023
fb722f2
use ComposeProviders for ThemeProvider
Apr 20, 2023
d294ea7
extract theme color interpolation into hook
Apr 20, 2023
cd481d5
use default dark colors as ThemeContext default value
Apr 20, 2023
7b33416
update demonstration view
Apr 20, 2023
96416d2
handle the case when there is no theme set yet
Apr 20, 2023
1ce89f7
simplify demo
Apr 20, 2023
f555698
move light color values into an external object
Apr 20, 2023
4ea846f
Merge branch 'main' into @chrispader/theme-switching
May 2, 2023
e80d068
improve when theme animation is triggered
May 2, 2023
68e554c
update "useIsLightMode" hook into "useColorPreference"
May 2, 2023
4c268e0
add StylesProvider, useStyles and useTheme hook
May 2, 2023
b493fc1
fix eslint errors
May 2, 2023
6b504ed
reset NavigationRoot
May 2, 2023
62e96ce
Merge branch 'main' into @chrispader/theme-switching
May 3, 2023
4275e90
add color theme preferences
May 3, 2023
4e32f73
move demo to ReportScreen
May 3, 2023
72d5925
update pods
May 3, 2023
92b0a96
Merge branch 'main' into @chrispader/theme-switching
May 15, 2023
ca8bc6e
Merge branch 'main' into @chrispader/theme-switching
May 16, 2023
6c655b1
fix: linting
May 16, 2023
3c23fd2
Merge branch 'main' into @chrispader/theme-switching
May 22, 2023
df0f050
rename certain files
May 22, 2023
90dbc19
re-write documentation so it fits the new file structure
May 22, 2023
b3bd015
fix import
May 22, 2023
6360b9a
Merge branch 'main' into @chrispader/theme-switching
May 23, 2023
72e2e06
rename useColorPreference hook to useThemePreference
May 23, 2023
a09f3f9
Merge branch 'main' into @chrispader/theme-switching
May 26, 2023
9779bda
rename styles file back
May 26, 2023
ffa2087
fix: demo
May 26, 2023
48bac2b
Merge branch 'main' into @chrispader/theme-switching
Jun 22, 2023
7d85d8c
rename ThemeStylesProvider and remove animations
Jun 22, 2023
5c99ef1
Merge branch 'main' into @chrispader/theme-switching
Jun 27, 2023
99fa85e
remove extra line
Jun 27, 2023
0295709
add DEFAULT_THEME to useThemePreference hook
Jun 27, 2023
e4e9016
remove REA ThemeProvider
Jun 27, 2023
12d5417
add back line
Jun 27, 2023
1d8659d
add back color value
Jun 27, 2023
5384efb
use constant color theme values
Jun 27, 2023
f640466
remove changes to preferences
Jun 27, 2023
decf617
remove ColorThemePage
Jun 27, 2023
e83d9d0
Merge branch 'main' into @chrispader/theme-switching
Jul 4, 2023
d0c7471
update ThemeStylesContext name
Jul 4, 2023
a48328a
fix: consistent naming
Jul 4, 2023
a2fe0a6
fix: ThemeStylesProvider name
Jul 4, 2023
a1cb1b5
Update src/ONYXKEYS.js
chrispader Jul 5, 2023
1e01bcf
Merge branch 'main' into @chrispader/theme-switching
chrispader Jul 5, 2023
656bdf9
fix: theme name consistency and comments
chrispader Jul 5, 2023
5274bf7
Merge branch '@chrispader/theme-switching' of github.com:margelo/expe…
chrispader Jul 5, 2023
7db4a93
remove mock light theme
Jul 5, 2023
e638d8e
add comment
Jul 5, 2023
14b2b6d
fix: wrong variable
Jul 5, 2023
8358a20
update context variable names
Jul 5, 2023
9b6d3a8
fix: prettier
Jul 5, 2023
97def5a
Merge branch 'main' into @chrispader/theme-switching
Jul 7, 2023
5661be1
fix: legacy REA code
chrispader Jul 10, 2023
c342542
update comment
chrispader Jul 10, 2023
b91894a
use value from CONST
chrispader Jul 10, 2023
99eef40
update conditionals
chrispader Jul 10, 2023
1aee573
move default theme to `CONST.THEME`
chrispader Jul 10, 2023
de29201
Merge branch 'main' into @chrispader/theme-switching
Jul 11, 2023
c3da152
remove reanimated patch file
Jul 11, 2023
c1d67ce
add comment about reanimated plugin
chrispader Jul 11, 2023
b3f939b
Merge branch 'main' into @chrispader/theme-switching
Jul 12, 2023
d4ac193
Merge branch 'main' into @chrispader/theme-switching
chrispader Jul 13, 2023
abd5b2c
add prop types comments
chrispader Jul 13, 2023
51ab4d4
add PreferredThemeProvider
chrispader Jul 13, 2023
74ec10f
remove Onyx.connect and add comment
chrispader Jul 13, 2023
fa306fc
Merge branch 'main' into @chrispader/theme-switching
chrispader Jul 14, 2023
73935f0
remove unused import
chrispader Jul 14, 2023
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
4 changes: 2 additions & 2 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ const webpack = {
const metro = {
presets: [require('metro-react-native-babel-preset')],
plugins: [
'react-native-reanimated/plugin',

// This is needed due to a react-native bug: https://github.com/facebook/react-native/issues/29084#issuecomment-1030732709
// It is included in metro-react-native-babel-preset but needs to be before plugin-proposal-class-properties or FlatList will break
'@babel/plugin-transform-flow-strip-types',

['@babel/plugin-proposal-class-properties', {loose: true}],
['@babel/plugin-proposal-private-methods', {loose: true}],
['@babel/plugin-proposal-private-property-in-object', {loose: true}],
// The reanimated babel plugin needs to be last, as stated here: https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation
'react-native-reanimated/plugin',
Comment thread
chrispader marked this conversation as resolved.
],
};

Expand Down
6 changes: 5 additions & 1 deletion contributingGuides/STYLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

## Where to Define Styles

All styles must be defined in the `/styles` directory and `styles.js` contains the final export after gathering all appropriate styles. Unlike some React Native applications we are not using `StyleSheet.create()` and instead store styles as plain JS objects. There are also many helper styles available for direct use in components.
Styles can either be theme-related or not. "Theme-related" means that a style contains some sort of color attributes (backgroundColor, color, borderColor). "Non-theme-related" styles may not contain no color attributes.

All non-theme-related styles must be defined in the `/styles` directory and `styles.js` contains the final export after gathering all appropriate styles. Unlike some React Native applications we are not using `StyleSheet.create()` and instead store styles as plain JS objects. There are also many helper styles available for direct use in components.
Comment thread
chrispader marked this conversation as resolved.

All styles that contain theme colors have to be defined in the `ThemeStylesProvider` component, as those need to be dynamically created and animated.

These helper styles are loosely based on the [Bootstrap system of CSS utility helper classes](https://getbootstrap.com/docs/5.0/utilities/spacing/) and are typically incremented by units of `4`.

Expand Down
4 changes: 4 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import SafeArea from './components/SafeArea';
import * as Environment from './libs/Environment/Environment';
import {WindowDimensionsProvider} from './components/withWindowDimensions';
import {KeyboardStateProvider} from './components/withKeyboardState';
import ThemeProvider from './styles/themes/ThemeProvider';
import ThemeStylesProvider from './styles/ThemeStylesProvider';
import {CurrentReportIDContextProvider} from './components/withCurrentReportID';
import * as Session from './libs/actions/Session';

Expand Down Expand Up @@ -50,6 +52,8 @@ function App() {
KeyboardStateProvider,
CurrentReportIDContextProvider,
PickerStateProvider,
ThemeProvider,
ThemeStylesProvider,
]}
>
<CustomStatusBar />
Expand Down
16 changes: 15 additions & 1 deletion src/components/OnyxProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext
const [withBlockedFromConcierge, BlockedFromConciergeProvider] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE);
const [withBetas, BetasProvider, BetasContext] = createOnyxContext(ONYXKEYS.BETAS);
const [withReportCommentDrafts, ReportCommentDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT);
const [withPreferredTheme, PreferredThemeProvider, PreferredThemeContext] = createOnyxContext(ONYXKEYS.PREFERRED_THEME);

const propTypes = {
/** Rendered child component */
Expand All @@ -30,6 +31,7 @@ function OnyxProvider(props) {
BlockedFromConciergeProvider,
BetasProvider,
ReportCommentDraftsProvider,
PreferredThemeProvider,
]}
>
{props.children}
Expand All @@ -42,4 +44,16 @@ OnyxProvider.propTypes = propTypes;

export default OnyxProvider;

export {withNetwork, withPersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, withBetas, NetworkContext, BetasContext, withReportCommentDrafts};
export {
withNetwork,
withPersonalDetails,
withReportActionsDrafts,
withCurrentDate,
withBlockedFromConcierge,
withBetas,
NetworkContext,
BetasContext,
withReportCommentDrafts,
withPreferredTheme,
PreferredThemeContext,
};
6 changes: 6 additions & 0 deletions src/styles/ThemeStylesContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import styles from './styles';

const ThemeStylesContext = React.createContext(styles);

export default ThemeStylesContext;
37 changes: 37 additions & 0 deletions src/styles/ThemeStylesProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable react/jsx-props-no-spreading */
import React, {useMemo} from 'react';
import PropTypes from 'prop-types';
import useTheme from './themes/useTheme';
import StylesContext from './ThemeStylesContext';
import defaultStyles from './styles';

const propTypes = {
/** Rendered child component */
children: PropTypes.node.isRequired,
Comment thread
chrispader marked this conversation as resolved.
};

function ThemeStylesProvider(props) {
const theme = useTheme();

const appContentStyle = useMemo(
() => ({
...defaultStyles.appContent,
backgroundColor: theme.appBG,
}),
[theme.appBG],
);

const styles = useMemo(
() => ({
...defaultStyles,
appContent: appContentStyle,
}),
[appContentStyle],
);

return <StylesContext.Provider value={styles}>{props.children}</StylesContext.Provider>;
}
ThemeStylesProvider.propTypes = propTypes;
ThemeStylesProvider.displayName = 'ThemeStylesProvider';

export default ThemeStylesProvider;
6 changes: 6 additions & 0 deletions src/styles/themes/ThemeContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import defaultColors from './default';

const ThemeContext = React.createContext(defaultColors);

export default ThemeContext;
27 changes: 27 additions & 0 deletions src/styles/themes/ThemeProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable react/jsx-props-no-spreading */
import React, {useMemo} from 'react';
import PropTypes from 'prop-types';
import ThemeContext from './ThemeContext';
import useThemePreference from './useThemePreference';
import CONST from '../../CONST';

// Going to eventually import the light theme here too
import darkTheme from './default';

const propTypes = {
/** Rendered child component */
children: PropTypes.node.isRequired,
Comment thread
chrispader marked this conversation as resolved.
};

function ThemeProvider(props) {
const themePreference = useThemePreference();

const theme = useMemo(() => (themePreference === CONST.THEME.LIGHT ? /* TODO: replace with light theme */ darkTheme : darkTheme), [themePreference]);

return <ThemeContext.Provider value={theme}>{props.children}</ThemeContext.Provider>;
}

ThemeProvider.propTypes = propTypes;
ThemeProvider.displayName = 'ThemeProvider';

export default ThemeProvider;
14 changes: 14 additions & 0 deletions src/styles/themes/useTheme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useContext} from 'react';
import ThemeContext from './ThemeContext';

function useTheme() {
const theme = useContext(ThemeContext);

if (!theme) {
throw new Error('StylesContext was null! Are you sure that you wrapped the component under a <ThemeProvider>?');
}

return theme;
}

export default useTheme;
28 changes: 28 additions & 0 deletions src/styles/themes/useThemePreference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useState, useEffect, useContext} from 'react';
import {Appearance} from 'react-native';
import CONST from '../../CONST';
import {PreferredThemeContext} from '../../components/OnyxProvider';

function useThemePreference() {
const [themePreference, setThemePreference] = useState(CONST.THEME.DEFAULT);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a default value for themePreference as a state variable and then updating it in useEffect caused an unnecessary (and then cancelled) network request here #34669

const [systemTheme, setSystemTheme] = useState();
const preferredThemeContext = 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;
}, []);

useEffect(() => {
const theme = preferredThemeContext || 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);
else setThemePreference(theme);
}, [preferredThemeContext, systemTheme]);

return themePreference;
}

export default useThemePreference;
14 changes: 14 additions & 0 deletions src/styles/useThemeStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useContext} from 'react';
import ThemeStylesContext from './ThemeStylesContext';

function useThemeStyles() {
const themeStyles = useContext(ThemeStylesContext);

if (!themeStyles) {
throw new Error('StylesContext was null! Are you sure that you wrapped the component under a <ThemeStylesProvider>?');
}

return themeStyles;
}

export default useThemeStyles;