diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/App.native.tsx | 6 | ||||
-rw-r--r-- | src/App.web.tsx | 6 | ||||
-rw-r--r-- | src/Splash.tsx | 4 | ||||
-rw-r--r-- | src/alf/themes.ts | 85 | ||||
-rw-r--r-- | src/alf/util/useColorModeTheme.ts | 54 | ||||
-rw-r--r-- | src/lib/ThemeContext.tsx | 28 | ||||
-rw-r--r-- | src/lib/themes.ts | 13 | ||||
-rw-r--r-- | src/state/persisted/legacy.ts | 1 | ||||
-rw-r--r-- | src/state/persisted/schema.ts | 2 | ||||
-rw-r--r-- | src/state/shell/color-mode.tsx | 70 | ||||
-rw-r--r-- | src/state/shell/index.tsx | 2 | ||||
-rw-r--r-- | src/view/screens/Settings.tsx | 36 | ||||
-rw-r--r-- | src/view/screens/Storybook/index.tsx | 24 |
13 files changed, 197 insertions, 134 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index 41b78fc98..50a80d9fe 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -17,7 +17,6 @@ import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {init as initPersistedState} from '#/state/persisted' import {listenSessionDropped} from './state/events' -import {useColorMode} from 'state/shell' import {ThemeProvider} from 'lib/ThemeContext' import {s} from 'lib/styles' import {Shell} from 'view/shell' @@ -49,10 +48,9 @@ import {useLingui} from '@lingui/react' SplashScreen.preventAutoHideAsync() function InnerApp() { - const colorMode = useColorMode() const {isInitialLoad, currentAccount} = useSession() const {resumeSession} = useSessionApi() - const theme = useColorModeTheme(colorMode) + const theme = useColorModeTheme() const {_} = useLingui() // init @@ -75,7 +73,7 @@ function InnerApp() { key={currentAccount?.did}> <LoggedOutViewProvider> <UnreadNotifsProvider> - <ThemeProvider theme={colorMode}> + <ThemeProvider theme={theme}> {/* All components should be within this provider */} <RootSiblingParent> <GestureHandlerRootView style={s.h100pct}> diff --git a/src/App.web.tsx b/src/App.web.tsx index 1efa0567c..9d1bd3506 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -10,7 +10,6 @@ import 'view/icons' import {ThemeProvider as Alf} from '#/alf' import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {init as initPersistedState} from '#/state/persisted' -import {useColorMode} from 'state/shell' import {Shell} from 'view/shell/index' import {ToastContainer} from 'view/com/util/Toast.web' import {ThemeProvider} from 'lib/ThemeContext' @@ -36,8 +35,7 @@ import {Provider as PortalProvider} from '#/components/Portal' function InnerApp() { const {isInitialLoad, currentAccount} = useSession() const {resumeSession} = useSessionApi() - const colorMode = useColorMode() - const theme = useColorModeTheme(colorMode) + const theme = useColorModeTheme() // init useEffect(() => { @@ -55,7 +53,7 @@ function InnerApp() { key={currentAccount?.did}> <LoggedOutViewProvider> <UnreadNotifsProvider> - <ThemeProvider theme={colorMode}> + <ThemeProvider theme={theme}> {/* All components should be within this provider */} <RootSiblingParent> <SafeAreaProvider> diff --git a/src/Splash.tsx b/src/Splash.tsx index 99f9a100d..80d0a66e7 100644 --- a/src/Splash.tsx +++ b/src/Splash.tsx @@ -21,7 +21,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import Svg, {Path, SvgProps} from 'react-native-svg' import {isAndroid} from '#/platform/detection' -import {useColorMode} from '#/state/shell' +import {useThemePrefs} from 'state/shell' import {Logotype} from '#/view/icons/Logotype' // @ts-ignore @@ -75,7 +75,7 @@ export function Splash(props: React.PropsWithChildren<Props>) { isLayoutReady && reduceMotion !== undefined - const colorMode = useColorMode() + const {colorMode} = useThemePrefs() const colorScheme = useColorScheme() const themeName = colorMode === 'system' ? colorScheme : colorMode const isDarkMode = themeName === 'dark' diff --git a/src/alf/themes.ts b/src/alf/themes.ts index 7c6b7dab4..b36e782fd 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -71,7 +71,7 @@ export const lightPalette = { export const darkPalette: Palette = { white: tokens.color.gray_0, - black: tokens.color.gray_1000, + black: tokens.color.trueBlack, contrast_25: tokens.color.gray_975, contrast_50: tokens.color.gray_950, @@ -130,6 +130,11 @@ export const darkPalette: Palette = { negative_975: tokens.color.red_975, } as const +export const dimPalette: Palette = { + ...darkPalette, + black: tokens.color.gray_1000, +} as const + export const light = { name: 'light', palette: lightPalette, @@ -191,70 +196,6 @@ export const light = { }, } -export const dim: Theme = { - name: 'dim', - palette: darkPalette, - atoms: { - text: { - color: darkPalette.white, - }, - text_contrast_700: { - color: darkPalette.contrast_800, - }, - text_contrast_600: { - color: darkPalette.contrast_700, - }, - text_contrast_500: { - color: darkPalette.contrast_600, - }, - text_contrast_400: { - color: darkPalette.contrast_500, - }, - text_inverted: { - color: darkPalette.black, - }, - bg: { - backgroundColor: darkPalette.contrast_50, - }, - bg_contrast_25: { - backgroundColor: darkPalette.contrast_100, - }, - bg_contrast_50: { - backgroundColor: darkPalette.contrast_200, - }, - bg_contrast_100: { - backgroundColor: darkPalette.contrast_300, - }, - bg_contrast_200: { - backgroundColor: darkPalette.contrast_400, - }, - bg_contrast_300: { - backgroundColor: darkPalette.contrast_500, - }, - border: { - borderColor: darkPalette.contrast_200, - }, - border_contrast: { - borderColor: darkPalette.contrast_400, - }, - shadow_sm: { - ...atoms.shadow_sm, - shadowOpacity: 0.7, - shadowColor: tokens.color.trueBlack, - }, - shadow_md: { - ...atoms.shadow_md, - shadowOpacity: 0.7, - shadowColor: tokens.color.trueBlack, - }, - shadow_lg: { - ...atoms.shadow_lg, - shadowOpacity: 0.7, - shadowColor: tokens.color.trueBlack, - }, - }, -} - export const dark: Theme = { name: 'dark', palette: darkPalette, @@ -318,3 +259,17 @@ export const dark: Theme = { }, }, } + +export const dim: Theme = { + ...dark, + name: 'dim', + atoms: { + ...dark.atoms, + text_inverted: { + color: dimPalette.black, + }, + bg: { + backgroundColor: dimPalette.black, + }, + }, +} diff --git a/src/alf/util/useColorModeTheme.ts b/src/alf/util/useColorModeTheme.ts index 79cebc139..49e2ec8f5 100644 --- a/src/alf/util/useColorModeTheme.ts +++ b/src/alf/util/useColorModeTheme.ts @@ -1,10 +1,54 @@ +import React from 'react' import {useColorScheme} from 'react-native' -import * as persisted from '#/state/persisted' +import {useThemePrefs} from 'state/shell' +import {isWeb} from 'platform/detection' +import {ThemeName, light, dark, dim} from '#/alf/themes' +import * as SystemUI from 'expo-system-ui' -export function useColorModeTheme( - theme: persisted.Schema['colorMode'], -): 'light' | 'dark' { +export function useColorModeTheme(): ThemeName { const colorScheme = useColorScheme() - return (theme === 'system' ? colorScheme : theme) || 'light' + const {colorMode, darkTheme} = useThemePrefs() + + return React.useMemo(() => { + if ( + (colorMode === 'system' && colorScheme === 'light') || + colorMode === 'light' + ) { + updateDocument('light') + updateSystemBackground('light') + return 'light' + } else { + const themeName = darkTheme ?? 'dim' + updateDocument(themeName) + updateSystemBackground(themeName) + return themeName + } + }, [colorMode, darkTheme, colorScheme]) +} + +function updateDocument(theme: ThemeName) { + // @ts-ignore web only + if (isWeb && typeof window !== 'undefined') { + // @ts-ignore web only + const html = window.document.documentElement + // remove any other color mode classes + html.className = html.className.replace(/(theme)--\w+/g, '') + + html.classList.add(`theme--${theme}`) + } +} + +function updateSystemBackground(theme: ThemeName) { + switch (theme) { + case 'light': + SystemUI.setBackgroundColorAsync(light.atoms.bg.backgroundColor) + break + case 'dark': + SystemUI.setBackgroundColorAsync(dark.atoms.bg.backgroundColor) + break + case 'dim': + SystemUI.setBackgroundColorAsync(dim.atoms.bg.backgroundColor) + break + } } diff --git a/src/lib/ThemeContext.tsx b/src/lib/ThemeContext.tsx index 38bd199cb..63e2beeb1 100644 --- a/src/lib/ThemeContext.tsx +++ b/src/lib/ThemeContext.tsx @@ -1,11 +1,7 @@ import React, {ReactNode, createContext, useContext} from 'react' -import { - TextStyle, - useColorScheme, - ViewStyle, - ColorSchemeName, -} from 'react-native' -import {darkTheme, defaultTheme} from './themes' +import {TextStyle, ViewStyle} from 'react-native' +import {darkTheme, defaultTheme, dimTheme} from './themes' +import {ThemeName} from '#/alf/themes' export type ColorScheme = 'light' | 'dark' @@ -84,23 +80,31 @@ export interface Theme { export interface ThemeProviderProps { children?: ReactNode - theme?: 'light' | 'dark' | 'system' + theme: ThemeName } export const ThemeContext = createContext<Theme>(defaultTheme) export const useTheme = () => useContext(ThemeContext) -function getTheme(theme: ColorSchemeName) { - return theme === 'dark' ? darkTheme : defaultTheme +function getTheme(theme: ThemeName) { + switch (theme) { + case 'light': + return defaultTheme + case 'dim': + return dimTheme + case 'dark': + return darkTheme + default: + return defaultTheme + } } export const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme, children, }) => { - const colorScheme = useColorScheme() - const themeValue = getTheme(theme === 'system' ? colorScheme : theme) + const themeValue = getTheme(theme) return ( <ThemeContext.Provider value={themeValue}>{children}</ThemeContext.Provider> diff --git a/src/lib/themes.ts b/src/lib/themes.ts index 2d4515c77..9a3880b92 100644 --- a/src/lib/themes.ts +++ b/src/lib/themes.ts @@ -2,7 +2,7 @@ import {Platform} from 'react-native' import type {Theme} from './ThemeContext' import {colors} from './styles' -import {darkPalette, lightPalette} from '#/alf/themes' +import {darkPalette, lightPalette, dimPalette} from '#/alf/themes' export const defaultTheme: Theme = { colorScheme: 'light', @@ -336,3 +336,14 @@ export const darkTheme: Theme = { }, }, } + +export const dimTheme: Theme = { + ...darkTheme, + palette: { + ...darkTheme.palette, + default: { + ...darkTheme.palette.default, + background: dimPalette.black, + }, + }, +} diff --git a/src/state/persisted/legacy.ts b/src/state/persisted/legacy.ts index 6bb75ae86..767faf48f 100644 --- a/src/state/persisted/legacy.ts +++ b/src/state/persisted/legacy.ts @@ -69,6 +69,7 @@ const DEPRECATED_ROOT_STATE_STORAGE_KEY = 'root' export function transform(legacy: Partial<LegacySchema>): Schema { return { colorMode: legacy.shell?.colorMode || defaults.colorMode, + darkTheme: defaults.darkTheme, session: { accounts: legacy.session?.accounts || defaults.session.accounts, currentAccount: diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 870e14aaf..ade97ef74 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -18,6 +18,7 @@ export type PersistedAccount = z.infer<typeof accountSchema> export const schema = z.object({ colorMode: z.enum(['system', 'light', 'dark']), + darkTheme: z.enum(['dim', 'dark']).optional(), session: z.object({ accounts: z.array(accountSchema), currentAccount: accountSchema.optional(), @@ -60,6 +61,7 @@ export type Schema = z.infer<typeof schema> export const defaults: Schema = { colorMode: 'system', + darkTheme: 'dim', session: { accounts: [], currentAccount: undefined, diff --git a/src/state/shell/color-mode.tsx b/src/state/shell/color-mode.tsx index 192b88314..4f0391aa6 100644 --- a/src/state/shell/color-mode.tsx +++ b/src/state/shell/color-mode.tsx @@ -1,57 +1,65 @@ import React from 'react' -import {isWeb} from '#/platform/detection' import * as persisted from '#/state/persisted' -type StateContext = persisted.Schema['colorMode'] -type SetContext = (v: persisted.Schema['colorMode']) => void +type StateContext = { + colorMode: persisted.Schema['colorMode'] + darkTheme: persisted.Schema['darkTheme'] +} +type SetContext = { + setColorMode: (v: persisted.Schema['colorMode']) => void + setDarkTheme: (v: persisted.Schema['darkTheme']) => void +} -const stateContext = React.createContext<StateContext>('system') -const setContext = React.createContext<SetContext>( - (_: persisted.Schema['colorMode']) => {}, -) +const stateContext = React.createContext<StateContext>({ + colorMode: 'system', + darkTheme: 'dark', +}) +const setContext = React.createContext<SetContext>({} as SetContext) export function Provider({children}: React.PropsWithChildren<{}>) { - const [state, setState] = React.useState(persisted.get('colorMode')) + const [colorMode, setColorMode] = React.useState(persisted.get('colorMode')) + const [darkTheme, setDarkTheme] = React.useState(persisted.get('darkTheme')) - const setStateWrapped = React.useCallback( - (colorMode: persisted.Schema['colorMode']) => { - setState(colorMode) - persisted.write('colorMode', colorMode) - updateDocument(colorMode) + const setColorModeWrapped = React.useCallback( + (_colorMode: persisted.Schema['colorMode']) => { + setColorMode(_colorMode) + persisted.write('colorMode', _colorMode) }, - [setState], + [setColorMode], + ) + + const setDarkThemeWrapped = React.useCallback( + (_darkTheme: persisted.Schema['darkTheme']) => { + setDarkTheme(_darkTheme) + persisted.write('darkTheme', _darkTheme) + }, + [setDarkTheme], ) React.useEffect(() => { - updateDocument(persisted.get('colorMode')) // set on load return persisted.onUpdate(() => { - setState(persisted.get('colorMode')) - updateDocument(persisted.get('colorMode')) + setColorModeWrapped(persisted.get('colorMode')) + setDarkThemeWrapped(persisted.get('darkTheme')) }) - }, [setState]) + }, [setColorModeWrapped, setDarkThemeWrapped]) return ( - <stateContext.Provider value={state}> - <setContext.Provider value={setStateWrapped}> + <stateContext.Provider value={{colorMode, darkTheme}}> + <setContext.Provider + value={{ + setDarkTheme: setDarkThemeWrapped, + setColorMode: setColorModeWrapped, + }}> {children} </setContext.Provider> </stateContext.Provider> ) } -export function useColorMode() { +export function useThemePrefs() { return React.useContext(stateContext) } -export function useSetColorMode() { +export function useSetThemePrefs() { return React.useContext(setContext) } - -function updateDocument(colorMode: string) { - if (isWeb && typeof window !== 'undefined') { - const html = window.document.documentElement - // remove any other color mode classes - html.className = html.className.replace(/colorMode--\w+/g, '') - html.classList.add(`colorMode--${colorMode}`) - } -} diff --git a/src/state/shell/index.tsx b/src/state/shell/index.tsx index 53f05055c..07909c000 100644 --- a/src/state/shell/index.tsx +++ b/src/state/shell/index.tsx @@ -14,7 +14,7 @@ export { useSetDrawerSwipeDisabled, } from './drawer-swipe-disabled' export {useMinimalShellMode, useSetMinimalShellMode} from './minimal-mode' -export {useColorMode, useSetColorMode} from './color-mode' +export {useThemePrefs, useSetThemePrefs} from './color-mode' export {useOnboardingState, useOnboardingDispatch} from './onboarding' export {useComposerState, useComposerControls} from './composer' export {useTickEveryMinute} from './tick-every-minute' diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 17e4b45c5..104506576 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -40,8 +40,8 @@ import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' import {useModalControls} from '#/state/modals' import { useSetMinimalShellMode, - useColorMode, - useSetColorMode, + useThemePrefs, + useSetThemePrefs, useOnboardingDispatch, } from '#/state/shell' import { @@ -144,8 +144,8 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> export function SettingsScreen({}: Props) { const queryClient = useQueryClient() - const colorMode = useColorMode() - const setColorMode = useSetColorMode() + const {colorMode, darkTheme} = useThemePrefs() + const {setColorMode, setDarkTheme} = useSetThemePrefs() const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() @@ -483,8 +483,36 @@ export function SettingsScreen({}: Props) { /> </View> </View> + <View style={styles.spacer20} /> + {colorMode !== 'light' && ( + <> + <Text type="xl-bold" style={[pal.text, styles.heading]}> + <Trans>Dark Theme</Trans> + </Text> + <View> + <View style={[styles.linkCard, pal.view, styles.selectableBtns]}> + <SelectableBtn + selected={!darkTheme || darkTheme === 'dim'} + label={_(msg`Dim`)} + left + onSelect={() => setDarkTheme('dim')} + accessibilityHint={_(msg`Set dark theme to the dim theme`)} + /> + <SelectableBtn + selected={darkTheme === 'dark'} + label={_(msg`Dark`)} + right + onSelect={() => setDarkTheme('dark')} + accessibilityHint={_(msg`Set dark theme to the dark theme`)} + /> + </View> + </View> + <View style={styles.spacer20} /> + </> + )} + <Text type="xl-bold" style={[pal.text, styles.heading]}> <Trans>Basics</Trans> </Text> diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index d8898f20e..40929555e 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native' import {CenteredView, ScrollView} from '#/view/com/util/Views' import {atoms as a, useTheme, ThemeProvider} from '#/alf' -import {useSetColorMode} from '#/state/shell' +import {useSetThemePrefs} from '#/state/shell' import {Button} from '#/components/Button' import {Theming} from './Theming' @@ -19,7 +19,7 @@ import {Icons} from './Icons' export function Storybook() { const t = useTheme() - const setColorMode = useSetColorMode() + const {setColorMode, setDarkTheme} = useSetThemePrefs() return ( <ScrollView> @@ -38,7 +38,7 @@ export function Storybook() { variant="solid" color="secondary" size="small" - label='Set theme to "system"' + label='Set theme to "light"' onPress={() => setColorMode('light')}> Light </Button> @@ -46,8 +46,22 @@ export function Storybook() { variant="solid" color="secondary" size="small" - label='Set theme to "system"' - onPress={() => setColorMode('dark')}> + label='Set theme to "dim"' + onPress={() => { + setColorMode('dark') + setDarkTheme('dim') + }}> + Dim + </Button> + <Button + variant="solid" + color="secondary" + size="small" + label='Set theme to "dark"' + onPress={() => { + setColorMode('dark') + setDarkTheme('dark') + }}> Dark </Button> </View> |