From 719154b337f9f0257553cf5c8174b22a50f984e7 Mon Sep 17 00:00:00 2001 From: "xan.lol" Date: Wed, 4 Mar 2026 02:04:39 -0800 Subject: [PATCH] feat: new color theme selector (and fixes) fix: evergarden primary color + hue shift for positive/negative colors, and pink/yellow colors! --- src/alf/index.tsx | 42 +++-- src/alf/themes.ts | 4 +- src/screens/Settings/AppearanceSettings.tsx | 193 +++++++++++++++----- 3 files changed, 180 insertions(+), 59 deletions(-) diff --git a/src/alf/index.tsx b/src/alf/index.tsx index 71f06ac5c..5a57a6003 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -92,6 +92,15 @@ export function shiftPalette(palette: Palette, hueShift: number): Palette { const keys = Object.keys(newPalette) as Array keys.forEach(key => { + if ( + key.startsWith('positive_') || + key.startsWith('negative_') || + key === 'like' || + key === 'pink' || + key === 'yellow' + ) { + return + } newPalette[key] = changeHue(newPalette[key], hueShift) }) @@ -201,11 +210,13 @@ export function ThemeProvider({ [setFontFamily], ) - const value = React.useMemo( - () => ({ - themes: hueShifter(currentScheme, hue), + const value = React.useMemo(() => { + const shiftedThemes = hueShifter(currentScheme, hue) + + return { + themes: shiftedThemes, themeName: themeName, - theme: hueShifter(currentScheme, hue)[themeName], + theme: shiftedThemes[themeName], fonts: { scale: fontScale, scaleMultiplier: fontScaleMultiplier, @@ -214,18 +225,17 @@ export function ThemeProvider({ setFontFamily: setFontFamilyAndPersist, }, flags: {}, - }), - [ - currentScheme, - hue, - themeName, - fontScale, - fontScaleMultiplier, - fontFamily, - setFontScaleAndPersist, - setFontFamilyAndPersist, - ], - ) + } + }, [ + currentScheme, + hue, + themeName, + fontScale, + fontScaleMultiplier, + fontFamily, + setFontScaleAndPersist, + setFontFamilyAndPersist, + ]) return {children} } diff --git a/src/alf/themes.ts b/src/alf/themes.ts index 93af0b4a8..8cfd9f63a 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -1270,7 +1270,7 @@ export const EVERGARDEN_PALETTE: Palette = { primary_200: `hsl(72, 42%, 86%)`, primary_300: `hsl(74, 54%, 84%)`, primary_400: `hsl(83, 46%, 83%)`, - primary_500: `hsl(90, 46.2%, 79.6%)`, + primary_500: `hsl(150, 24%, 64%)`, primary_600: `hsl(149, 44%, 72%)`, primary_700: `hsl(167, 35%, 60%)`, primary_800: `hsl(187, 42%, 36%)`, @@ -1336,7 +1336,7 @@ export const EVERGARDEN_SUBDUED_PALETTE: Palette = { primary_200: `hsl(72, 42%, 86%)`, primary_300: `hsl(74, 54%, 84%)`, primary_400: `hsl(83, 46%, 83%)`, - primary_500: `hsl(90, 46.2%, 79.6%)`, + primary_500: `hsl(150, 24%, 64%)`, primary_600: `hsl(149, 44%, 72%)`, primary_700: `hsl(167, 35%, 60%)`, primary_800: `hsl(187, 42%, 36%)`, diff --git a/src/screens/Settings/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx index f3a576ce4..70089b707 100644 --- a/src/screens/Settings/AppearanceSettings.tsx +++ b/src/screens/Settings/AppearanceSettings.tsx @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {View} from 'react-native' +import {Pressable, View} from 'react-native' import Animated, { FadeInUp, FadeOutUp, @@ -26,12 +26,27 @@ import {useKawaiiMode, useSetKawaiiMode} from '#/state/preferences/kawaii' import {useSetThemePrefs, useThemePrefs} from '#/state/shell' import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem' import {type Alf, atoms as a, native, useAlf, useTheme} from '#/alf' +import { + BLACKSKY_PALETTE, + BLUESKY_PALETTE, + CATPPUCIN_PALETTE, + DEER_PALETTE, + DEFAULT_PALETTE, + EVERGARDEN_PALETTE, + KITTY_PALETTE, + REDDWARF_PALETTE, + ZEPPELIN_PALETTE, +} from '#/alf/themes' import * as SegmentedControl from '#/components/forms/SegmentedControl' import {Slider} from '#/components/forms/Slider' import * as Toggle from '#/components/forms/Toggle' import {Circle_And_Square_Stroke1_Corner0_Rounded_Filled as SquareIcon} from '#/components/icons/CircleAndSquare' import {ColorPalette_Stroke2_Corner0_Rounded as ColorPaletteIcon} from '#/components/icons/ColorPalette' import {type Props as SVGIconProps} from '#/components/icons/common' +import { + Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled, + Heart2_Stroke2_Corner0_Rounded as HeartIconOutline, +} from '#/components/icons/Heart2' import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon' import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone' import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' @@ -43,6 +58,24 @@ import {IS_INTERNAL, IS_NATIVE} from '#/env' import * as SettingsList from './components/SettingsList' type Props = NativeStackScreenProps + +type ColorSchemeName = + | 'witchsky' + | 'bluesky' + | 'blacksky' + | 'deer' + | 'zeppelin' + | 'kitty' + | 'reddwarf' + | 'catppuccin' + | 'evergarden' + +type ColorSchemeOption = { + name: ColorSchemeName + label: string + primary: string +} + export function AppearanceSettingsScreen({}: Props) { const {_} = useLingui() const {fonts} = useAlf() @@ -69,18 +102,7 @@ export function AppearanceSettingsScreen({}: Props) { ) const onChangeScheme = useCallback( - ( - value: - | 'witchsky' - | 'bluesky' - | 'blacksky' - | 'deer' - | 'zeppelin' - | 'kitty' - | 'reddwarf' - | 'catppuccin' - | 'evergarden', - ) => { + (value: ColorSchemeName) => { setColorScheme(value) }, [setColorScheme], @@ -107,16 +129,52 @@ export function AppearanceSettingsScreen({}: Props) { [fonts], ) - const colorSchemes = [ - {name: 'witchsky', label: _(msg`Witchsky`)}, - {name: 'bluesky', label: _(msg`Bluesky`)}, - {name: 'blacksky', label: _(msg`Blacksky`)}, - {name: 'deer', label: _(msg`Deer`)}, - {name: 'zeppelin', label: _(msg`Zeppelin`)}, - {name: 'kitty', label: _(msg`Kitty`)}, - {name: 'reddwarf', label: _(msg`Red Dwarf`)}, - {name: 'catppuccin', label: _(msg`Catppuccin`)}, - {name: 'evergarden', label: _(msg`Evergarden`)}, + const colorSchemes: ColorSchemeOption[] = [ + { + name: 'witchsky', + label: _(msg`Witchsky`), + primary: DEFAULT_PALETTE.primary_500, + }, + { + name: 'bluesky', + label: _(msg`Bluesky`), + primary: BLUESKY_PALETTE.primary_500, + }, + { + name: 'blacksky', + label: _(msg`Blacksky`), + primary: BLACKSKY_PALETTE.primary_500, + }, + { + name: 'deer', + label: _(msg`Deer`), + primary: DEER_PALETTE.primary_500, + }, + { + name: 'zeppelin', + label: _(msg`Zeppelin`), + primary: ZEPPELIN_PALETTE.primary_500, + }, + { + name: 'kitty', + label: _(msg`Kitty`), + primary: KITTY_PALETTE.primary_500, + }, + { + name: 'reddwarf', + label: _(msg`Red Dwarf`), + primary: REDDWARF_PALETTE.primary_500, + }, + { + name: 'catppuccin', + label: _(msg`Catppuccin`), + primary: CATPPUCIN_PALETTE.primary_500, + }, + { + name: 'evergarden', + label: _(msg`Evergarden`), + primary: EVERGARDEN_PALETTE.primary_500, + }, ] return ( @@ -186,24 +244,11 @@ export function AppearanceSettingsScreen({}: Props) { Choose which color scheme to use: - - onChangeScheme(value as typeof colorScheme) - }> - - {colorSchemes.map(({name, label}) => ( - - - - {label} - - - ))} - - + Hue shift the colors: @@ -325,6 +370,72 @@ export function AppearanceSettingsScreen({}: Props) { ) } +function ColorSchemeGrid({ + schemes, + selectedScheme, + onSchemeChange, +}: { + schemes: ColorSchemeOption[] + selectedScheme: ColorSchemeName + onSchemeChange: (scheme: ColorSchemeName) => void +}) { + const t = useTheme() + return ( + + {schemes.map(({name, label, primary}) => { + const isSelected = selectedScheme === name + const HeartIcon = isSelected ? HeartIconFilled : HeartIconOutline + return ( + onSchemeChange(name)} + style={[ + a.flex_1, + a.rounded_md, + a.overflow_hidden, + {minWidth: '30%'}, + a.border, + { + borderColor: isSelected + ? primary + : t.atoms.border_contrast_low.borderColor, + borderWidth: isSelected ? 2 : 1, + }, + ]}> + + + + + {label} + + + + + + ) + })} + + ) +} + export function AppearanceToggleButtonGroup({ title, description, -- 2.51.2