From 204919f7280c6bac7876a1076fa370960ee18d5d Mon Sep 17 00:00:00 2001 From: Gustavo Cortez Date: Wed, 28 Aug 2024 16:58:59 -0300 Subject: [PATCH] New theme selector --- src/App.tsx | 37 +++--------------- src/context/PreferencesContext.tsx | 60 ++++++++++++++++++++++++++++++ src/screens/preferences.tsx | 46 ++++++++++++----------- src/store/app/app.actions.ts | 2 +- src/store/app/app.reducer.ts | 6 +-- src/store/app/app.types.ts | 4 +- src/store/app/index.ts | 2 +- src/styles/global.js | 7 ++++ 8 files changed, 105 insertions(+), 59 deletions(-) create mode 100644 src/context/PreferencesContext.tsx diff --git a/src/App.tsx b/src/App.tsx index ae4a97c13..05e870d1c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,47 +1,22 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {useColorScheme, ColorSchemeName, Appearance} from 'react-native'; +import React, {useEffect} from 'react'; -import {NavigationContainer} from '@react-navigation/native'; - -import {useAppDispatch, useAppSelector, RootState} from './store'; +import {useAppDispatch} from './store'; import {initializeApp} from './store/app'; import MainNavigation from './components/main-navigation'; -import CombinedDefaultTheme from './themes/light'; -import CombinedDarkTheme from './themes/dark'; -import {PaperProvider} from 'react-native-paper'; +import {PreferencesProvider} from './context/PreferencesContext'; const App = () => { const dispatch = useAppDispatch(); - const _theme = useAppSelector(({APP}: RootState) => APP.appTheme); - - const [theme, setTheme] = useState(useColorScheme()); - - const themeChangeListener = useCallback(() => { - if (!theme) { - setTheme(Appearance.getColorScheme()); - } - }, []); - - useEffect(() => { - Appearance.addChangeListener(themeChangeListener); - return () => { - Appearance.addChangeListener(themeChangeListener); - }; - }, [themeChangeListener]); useEffect(() => { dispatch(initializeApp()); }, []); return ( - - - - - + + + ); }; diff --git a/src/context/PreferencesContext.tsx b/src/context/PreferencesContext.tsx new file mode 100644 index 000000000..3ea446fc4 --- /dev/null +++ b/src/context/PreferencesContext.tsx @@ -0,0 +1,60 @@ +import React, {createContext, useState, useEffect, useContext} from 'react'; +import {useColorScheme, ColorSchemeName} from 'react-native'; +import {useAppDispatch, useAppSelector, RootState} from '../store'; +import {setColorScheme} from '../store/app/app.actions'; +import {PaperProvider} from 'react-native-paper'; +import {NavigationContainer} from '@react-navigation/native'; +import CombinedDefaultTheme from '../themes/light'; +import CombinedDarkTheme from '../themes/dark'; + +// Define the type for your context +type PreferencesContextType = { + setColorTheme: (theme: ColorSchemeName) => void; + colorTheme: ColorSchemeName; +}; + +export const PreferencesContext = createContext({ + setColorTheme: (theme: ColorSchemeName) => {}, + colorTheme: null, +}); + +export const PreferencesProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const _theme = useAppSelector(({APP}: RootState) => APP.colorScheme); + const [colorTheme, setColorTheme] = useState(_theme); + const dispatch = useAppDispatch(); + const systemColorScheme = useColorScheme(); + let appTheme; + if (!colorTheme) { + appTheme = + systemColorScheme === 'dark' ? CombinedDarkTheme : CombinedDefaultTheme; + } else { + appTheme = colorTheme === 'dark' ? CombinedDarkTheme : CombinedDefaultTheme; + } + + useEffect(() => { + if (!colorTheme) { + appTheme = + systemColorScheme === 'dark' ? CombinedDarkTheme : CombinedDefaultTheme; + } + }, [systemColorScheme]); + + const handleSetColorTheme = (theme: ColorSchemeName) => { + setColorTheme(theme); + dispatch(setColorScheme(theme)); + }; + + return ( + + + {children} + + + ); +}; + +export const usePreferences = () => useContext(PreferencesContext); diff --git a/src/screens/preferences.tsx b/src/screens/preferences.tsx index 671f20b26..db2332d78 100644 --- a/src/screens/preferences.tsx +++ b/src/screens/preferences.tsx @@ -1,35 +1,39 @@ -import React, {useState} from 'react'; +import React, {useState, useContext} from 'react'; import {View, Text, TouchableOpacity, Appearance} from 'react-native'; import {RadioButton, List} from 'react-native-paper'; import {useAppDispatch, useAppSelector, RootState} from '../store'; -import {useTheme} from 'react-native-paper'; +import {useTheme, Switch} from 'react-native-paper'; import {useColorScheme, ColorSchemeName} from 'react-native'; -import {appTheme} from '../store/app/app.actions'; +import { + PreferencesContext, + usePreferences, +} from '../context/PreferencesContext'; import {ContainerStyles, GlobalStyles, TextStyles} from '../styles'; const Preferences = ({route, navigation}) => { const dispatch = useAppDispatch(); - const {colors} = useTheme(); - const _theme = useAppSelector(({APP}: RootState) => APP.appTheme); - const [theme, setTheme] = useState(_theme); - const [checked, setChecked] = useState(theme); + const theme = useTheme(); + + const {colorTheme, setColorTheme} = usePreferences(); + console.log('[preferences.tsx:16]', colorTheme); /* TODO */ + + const [checked, setChecked] = useState(colorTheme); + console.log('[preferences.tsx:19]', checked); /* TODO */ const handleThemeChange = (newTheme: ColorSchemeName) => { + setColorTheme(newTheme); setChecked(newTheme); - dispatch(appTheme(newTheme)); - setTheme(newTheme); - Appearance.setColorScheme(newTheme); }; return ( - + Theme { style={[ GlobalStyles.itemContainer, { - backgroundColor: colors.background, - borderBottomColor: colors.surfaceVariant, + backgroundColor: theme.colors.background, + borderBottomColor: theme.colors.surfaceVariant, }, ]}> - + Light { style={[ GlobalStyles.itemContainer, { - backgroundColor: colors.background, - borderBottomColor: colors.surfaceVariant, + backgroundColor: theme.colors.background, + borderBottomColor: theme.colors.surfaceVariant, }, ]}> - + Dark { style={[ GlobalStyles.itemContainer, { - backgroundColor: colors.background, - borderBottomColor: colors.surfaceVariant, + backgroundColor: theme.colors.background, + borderBottomColor: theme.colors.surfaceVariant, }, ]}> - + System ({ type: AppActionTypes.APP_FAILED, }); -export const appTheme = (theme: ColorSchemeName): AppActionType => ({ +export const setColorScheme = (theme: ColorSchemeName): AppActionType => ({ type: AppActionTypes.APP_THEME, payload: theme, }); diff --git a/src/store/app/app.reducer.ts b/src/store/app/app.reducer.ts index 480bd7c21..fd4575523 100644 --- a/src/store/app/app.reducer.ts +++ b/src/store/app/app.reducer.ts @@ -5,12 +5,12 @@ export const AppReduxPersistBlackList: (keyof AppState)[] = ['appStatus']; export interface AppState { appStatus: AppStatus; - appTheme: ColorSchemeName; + colorScheme: ColorSchemeName; } const initialState: AppState = { appStatus: 'loading', - appTheme: null, + colorScheme: null, }; export const AppReducer = ( @@ -31,7 +31,7 @@ export const AppReducer = ( case AppActionTypes.APP_THEME: return { ...state, - appTheme: action.payload, + colorScheme: action.payload, }; default: return state; diff --git a/src/store/app/app.types.ts b/src/store/app/app.types.ts index 11297334e..99e2dc71f 100644 --- a/src/store/app/app.types.ts +++ b/src/store/app/app.types.ts @@ -15,9 +15,9 @@ interface AppFailed { type: typeof AppActionTypes.APP_FAILED; } -interface AppThemeAction { +interface AppColorScheme { type: typeof AppActionTypes.APP_THEME; payload: ColorSchemeName; } -export type AppActionType = AppSuccess | AppFailed | AppThemeAction; +export type AppActionType = AppSuccess | AppFailed | AppColorScheme; diff --git a/src/store/app/index.ts b/src/store/app/index.ts index 7640c1cc9..dd9ca8e11 100644 --- a/src/store/app/index.ts +++ b/src/store/app/index.ts @@ -1,2 +1,2 @@ -export {appSuccess, appFailed, appTheme} from './app.actions'; +export {appSuccess, appFailed, setColorScheme} from './app.actions'; export {initializeApp} from './app.effects'; diff --git a/src/styles/global.js b/src/styles/global.js index 1b47587da..4bd393e80 100644 --- a/src/styles/global.js +++ b/src/styles/global.js @@ -10,11 +10,18 @@ export const GlobalStyles = StyleSheet.create({ itemContainer: { flexDirection: 'row', alignItems: 'center', + backgroundColor: '#fff', padding: 15, borderBottomWidth: 1, + borderBottomColor: '#e0e0e0', }, itemText: { flex: 1, fontSize: 16, }, + subheader: { + padding: 10, + color: '#6e6e6e', + fontSize: 18, + }, });