-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Theme switching: Dynamic StatusBar and scroll bars #32063
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
Changes from all commits
9e0d4d8
0c3ba38
8f830ab
569c388
ae5d0aa
a6c6602
abbd499
43a0323
e1ef2cf
221f30e
110943e
7e930a9
e948718
945953f
4ddd498
89f924e
81515d4
b2d2928
8f23391
3bd4136
4abcfd7
7ac2d01
c0e1db1
cde61a7
8040abe
edd8bf7
04a9810
e43cc64
33efc40
02f6e74
faefaf5
e335bd0
37b2215
38bcce7
df2342f
3353180
9027d7a
94c11e0
2ffb572
5dbca1d
2eba4b7
4d833de
941f4dc
497e98e
f05bb31
12099b6
0feb155
cb28261
1bbcc1d
915c38e
bf2fd72
9272b68
331b418
268f683
3ca7fc7
cde5ba8
20d54db
b343ef1
198e4ec
9bc522f
97d0081
91f299d
80f13fc
51ef33f
4c41816
f08d0a1
c0dbdc4
3b0ebeb
f7df8ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| { | ||
| "ignoredFiles": [ | ||
| "assets/images/empty-state_background-fade.png" // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 | ||
| "assets/images/empty-state_background-fade-dark.png", // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 | ||
| "assets/images/empty-state_background-fade-light.png" | ||
| ], | ||
| "aggressiveCompression": "false" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| function ColorSchemeWrapper({children}: React.PropsWithChildren) { | ||
| return children; | ||
| } | ||
|
|
||
| export default ColorSchemeWrapper; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import React from 'react'; | ||
| import {View} from 'react-native'; | ||
| import useTheme from '@styles/themes/useTheme'; | ||
| import useThemeStyles from '@styles/useThemeStyles'; | ||
|
|
||
| function ColorSchemeWrapper({children}: React.PropsWithChildren): React.ReactElement { | ||
| const theme = useTheme(); | ||
| const themeStyles = useThemeStyles(); | ||
|
|
||
| return <View style={[themeStyles.flex1, themeStyles.colorSchemeStyle(theme.colorScheme)]}>{children}</View>; | ||
| } | ||
|
|
||
| export default ColorSchemeWrapper; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import {createContext} from 'react'; | ||
|
|
||
| type CustomStatusBarContextType = { | ||
| isRootStatusBarDisabled: boolean; | ||
| disableRootStatusBar: (isDisabled: boolean) => void; | ||
| }; | ||
|
|
||
| const CustomStatusBarContext = createContext<CustomStatusBarContextType>({isRootStatusBarDisabled: false, disableRootStatusBar: () => undefined}); | ||
|
|
||
| export default CustomStatusBarContext; | ||
| export {type CustomStatusBarContextType}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import React, {useMemo, useState} from 'react'; | ||
| import CustomStatusBarContext from './CustomStatusBarContext'; | ||
|
|
||
| function CustomStatusBarContextProvider({children}: React.PropsWithChildren) { | ||
| const [isRootStatusBarDisabled, disableRootStatusBar] = useState(false); | ||
| const value = useMemo( | ||
| () => ({ | ||
| isRootStatusBarDisabled, | ||
| disableRootStatusBar, | ||
| }), | ||
| [isRootStatusBarDisabled], | ||
| ); | ||
|
|
||
| return <CustomStatusBarContext.Provider value={value}>{children}</CustomStatusBarContext.Provider>; | ||
| } | ||
|
|
||
| export default CustomStatusBarContextProvider; |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,91 @@ | ||
| import React, {useEffect} from 'react'; | ||
| import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; | ||
| import {EventListenerCallback, NavigationContainerEventMap} from '@react-navigation/native'; | ||
| import PropTypes from 'prop-types'; | ||
| import React, {useCallback, useContext, useEffect} from 'react'; | ||
| import {navigationRef} from '@libs/Navigation/Navigation'; | ||
| import StatusBar from '@libs/StatusBar'; | ||
| import useTheme from '@styles/themes/useTheme'; | ||
| import type CustomStatusBarType from './types'; | ||
| import CustomStatusBarContext from './CustomStatusBarContext'; | ||
|
|
||
| type CustomStatusBarProps = { | ||
| isNested: boolean; | ||
| }; | ||
|
|
||
| const propTypes = { | ||
|
chrispader marked this conversation as resolved.
|
||
| /** Whether the CustomStatusBar is nested within another CustomStatusBar. | ||
| * A nested CustomStatusBar will disable the "root" CustomStatusBar. */ | ||
| isNested: PropTypes.bool, | ||
| }; | ||
|
|
||
| type CustomStatusBarType = { | ||
| (props: CustomStatusBarProps): React.ReactNode; | ||
| displayName: string; | ||
| propTypes: typeof propTypes; | ||
| }; | ||
|
|
||
| // eslint-disable-next-line react/function-component-definition | ||
| const CustomStatusBar: CustomStatusBarType = () => { | ||
| const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { | ||
| const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); | ||
| const theme = useTheme(); | ||
|
|
||
| const isDisabled = !isNested && isRootStatusBarDisabled; | ||
|
|
||
| useEffect(() => { | ||
| Navigation.isNavigationReady().then(() => { | ||
| // Set the status bar colour depending on the current route. | ||
| // If we don't have any colour defined for a route, fall back to | ||
| // appBG color. | ||
| const currentRoute = navigationRef.getCurrentRoute(); | ||
| let currentScreenBackgroundColor = theme.appBG; | ||
| if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { | ||
| currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; | ||
| if (isNested) { | ||
| disableRootStatusBar(true); | ||
| } | ||
|
|
||
| return () => { | ||
| if (!isNested) { | ||
| return; | ||
| } | ||
| StatusBar.setBarStyle('light-content', true); | ||
| StatusBar.setBackgroundColor(currentScreenBackgroundColor); | ||
| }); | ||
| }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]); | ||
| disableRootStatusBar(false); | ||
| }; | ||
| }, [disableRootStatusBar, isNested]); | ||
|
|
||
| const updateStatusBarStyle = useCallback<EventListenerCallback<NavigationContainerEventMap, 'state'>>(() => { | ||
| if (isDisabled) { | ||
| return; | ||
| } | ||
|
|
||
| // Set the status bar colour depending on the current route. | ||
| // If we don't have any colour defined for a route, fall back to | ||
| // appBG color. | ||
| const currentRoute = navigationRef.getCurrentRoute(); | ||
|
|
||
| let currentScreenBackgroundColor = theme.appBG; | ||
| let statusBarStyle = theme.statusBarStyle; | ||
| if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { | ||
| const screenTheme = theme.PAGE_THEMES[currentRoute.name]; | ||
| currentScreenBackgroundColor = screenTheme.backgroundColor; | ||
| statusBarStyle = screenTheme.statusBarStyle; | ||
| } | ||
|
|
||
| StatusBar.setBackgroundColor(currentScreenBackgroundColor, true); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chrispader I notice that you removed the animated transition between status bar background colors that was present in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, sorry my mistake. Just added a PR here: #33200 |
||
| StatusBar.setBarStyle(statusBarStyle, true); | ||
| }, [isDisabled, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle]); | ||
|
|
||
| useEffect(() => { | ||
| navigationRef.addListener('state', updateStatusBarStyle); | ||
|
|
||
| return () => navigationRef.removeListener('state', updateStatusBarStyle); | ||
| }, [updateStatusBarStyle]); | ||
|
|
||
| useEffect(() => { | ||
| if (isDisabled) { | ||
| return; | ||
| } | ||
|
|
||
| StatusBar.setBarStyle(theme.statusBarStyle, true); | ||
| }, [isDisabled, theme.statusBarStyle]); | ||
|
|
||
| if (isDisabled) { | ||
| return null; | ||
| } | ||
|
|
||
| return <StatusBar />; | ||
| }; | ||
|
|
||
| CustomStatusBar.displayName = 'CustomStatusBar'; | ||
| CustomStatusBar.propTypes = propTypes; | ||
|
|
||
| export default CustomStatusBar; | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,14 @@ | ||
| import StatusBar from './types'; | ||
|
|
||
| // Only has custom web implementation | ||
| StatusBar.getBackgroundColor = () => null; | ||
| const setBackgroundColor = StatusBar.setBackgroundColor; | ||
|
|
||
| // We override this because it's not used – on Android our app display edge-to-edge. | ||
| // Also because Reanimated's interpolateColor gives Android native colors instead of hex strings, causing this to display a warning. | ||
| StatusBar.setBackgroundColor = () => null; | ||
| let statusBarColor: string | null = null; | ||
|
|
||
| StatusBar.getBackgroundColor = () => statusBarColor; | ||
|
|
||
| StatusBar.setBackgroundColor = (color, animated = false) => { | ||
| statusBarColor = color as string; | ||
| setBackgroundColor(color, animated); | ||
| }; | ||
|
|
||
| export default StatusBar; |
Uh oh!
There was an error while loading. Please reload this page.