Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 493 lines 15 kB view raw
1import {useCallback} from 'react' 2import {Pressable, View} from 'react-native' 3import Animated, { 4 FadeInUp, 5 FadeOutUp, 6 LayoutAnimationConfig, 7 LinearTransition, 8} from 'react-native-reanimated' 9import {msg} from '@lingui/core/macro' 10import {useLingui} from '@lingui/react' 11import {Trans} from '@lingui/react/macro' 12 13import { 14 type CommonNavigatorParams, 15 type NativeStackScreenProps, 16} from '#/lib/routes/types' 17import { 18 useEnableSquareAvatars, 19 useSetEnableSquareAvatars, 20} from '#/state/preferences/enable-square-avatars' 21import { 22 useEnableSquareButtons, 23 useSetEnableSquareButtons, 24} from '#/state/preferences/enable-square-buttons' 25import {useKawaiiMode, useSetKawaiiMode} from '#/state/preferences/kawaii' 26import {useSetThemePrefs, useThemePrefs} from '#/state/shell' 27import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem' 28import {type Alf, atoms as a, native, useAlf, useTheme} from '#/alf' 29import { 30 BLACKSKY_PALETTE, 31 BLUESKY_PALETTE, 32 CATPPUCIN_PALETTE, 33 DEER_PALETTE, 34 DEFAULT_PALETTE, 35 EVERGARDEN_PALETTE, 36 KITTY_PALETTE, 37 REDDWARF_PALETTE, 38 ZEPPELIN_PALETTE, 39} from '#/alf/themes' 40import * as SegmentedControl from '#/components/forms/SegmentedControl' 41import {Slider} from '#/components/forms/Slider' 42import * as Toggle from '#/components/forms/Toggle' 43import {Circle_And_Square_Stroke1_Corner0_Rounded_Filled as SquareIcon} from '#/components/icons/CircleAndSquare' 44import {ColorPalette_Stroke2_Corner0_Rounded as ColorPaletteIcon} from '#/components/icons/ColorPalette' 45import {type Props as SVGIconProps} from '#/components/icons/common' 46import { 47 Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled, 48 Heart2_Stroke2_Corner0_Rounded as HeartIconOutline, 49} from '#/components/icons/Heart2' 50import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon' 51import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone' 52import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 53import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize' 54import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase' 55import * as Layout from '#/components/Layout' 56import {Text} from '#/components/Typography' 57import {IS_INTERNAL, IS_NATIVE} from '#/env' 58import * as SettingsList from './components/SettingsList' 59 60type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'> 61 62type ColorSchemeName = 63 | 'witchsky' 64 | 'bluesky' 65 | 'blacksky' 66 | 'deer' 67 | 'zeppelin' 68 | 'kitty' 69 | 'reddwarf' 70 | 'catppuccin' 71 | 'evergarden' 72 73type ColorSchemeOption = { 74 name: ColorSchemeName 75 label: string 76 primary: string 77} 78 79export function AppearanceSettingsScreen({}: Props) { 80 const {_} = useLingui() 81 const {fonts} = useAlf() 82 const t = useTheme() 83 84 const {colorMode, colorScheme, darkTheme, hue} = useThemePrefs() 85 const {setColorMode, setColorScheme, setDarkTheme, setHue} = 86 useSetThemePrefs() 87 88 const kawaiiMode = useKawaiiMode() 89 const setKawaiiMode = useSetKawaiiMode() 90 91 const enableSquareAvatars = useEnableSquareAvatars() 92 const setEnableSquareAvatars = useSetEnableSquareAvatars() 93 94 const enableSquareButtons = useEnableSquareButtons() 95 const setEnableSquareButtons = useSetEnableSquareButtons() 96 97 const onChangeAppearance = useCallback( 98 (value: 'light' | 'system' | 'dark') => { 99 setColorMode(value) 100 }, 101 [setColorMode], 102 ) 103 104 const onChangeScheme = useCallback( 105 (value: ColorSchemeName) => { 106 setColorScheme(value) 107 }, 108 [setColorScheme], 109 ) 110 111 const onChangeDarkTheme = useCallback( 112 (value: 'dim' | 'dark') => { 113 setDarkTheme(value) 114 }, 115 [setDarkTheme], 116 ) 117 118 const onChangeFontFamily = useCallback( 119 (value: 'system' | 'theme') => { 120 fonts.setFontFamily(value) 121 }, 122 [fonts], 123 ) 124 125 const onChangeFontScale = useCallback( 126 (value: Alf['fonts']['scale']) => { 127 fonts.setFontScale(value) 128 }, 129 [fonts], 130 ) 131 132 const colorSchemes: ColorSchemeOption[] = [ 133 { 134 name: 'witchsky', 135 label: _(msg`Witchsky`), 136 primary: DEFAULT_PALETTE.primary_500, 137 }, 138 { 139 name: 'bluesky', 140 label: _(msg`Bluesky`), 141 primary: BLUESKY_PALETTE.primary_500, 142 }, 143 { 144 name: 'blacksky', 145 label: _(msg`Blacksky`), 146 primary: BLACKSKY_PALETTE.primary_500, 147 }, 148 { 149 name: 'deer', 150 label: _(msg`Deer`), 151 primary: DEER_PALETTE.primary_500, 152 }, 153 { 154 name: 'zeppelin', 155 label: _(msg`Zeppelin`), 156 primary: ZEPPELIN_PALETTE.primary_500, 157 }, 158 { 159 name: 'kitty', 160 label: _(msg`Kitty`), 161 primary: KITTY_PALETTE.primary_500, 162 }, 163 { 164 name: 'reddwarf', 165 label: _(msg`Red Dwarf`), 166 primary: REDDWARF_PALETTE.primary_500, 167 }, 168 { 169 name: 'catppuccin', 170 label: _(msg`Catppuccin`), 171 primary: CATPPUCIN_PALETTE.primary_500, 172 }, 173 { 174 name: 'evergarden', 175 label: _(msg`Evergarden`), 176 primary: EVERGARDEN_PALETTE.primary_500, 177 }, 178 ] 179 180 return ( 181 <LayoutAnimationConfig skipExiting skipEntering> 182 <Layout.Screen testID="preferencesThreadsScreen"> 183 <Layout.Header.Outer> 184 <Layout.Header.BackButton /> 185 <Layout.Header.Content> 186 <Layout.Header.TitleText> 187 <Trans>Appearance</Trans> 188 </Layout.Header.TitleText> 189 </Layout.Header.Content> 190 <Layout.Header.Slot /> 191 </Layout.Header.Outer> 192 <Layout.Content> 193 <SettingsList.Container> 194 <AppearanceToggleButtonGroup 195 title={_(msg`Color mode`)} 196 icon={PhoneIcon} 197 items={[ 198 { 199 label: _(msg`System`), 200 name: 'system', 201 }, 202 { 203 label: _(msg`Light`), 204 name: 'light', 205 }, 206 { 207 label: _(msg`Dark`), 208 name: 'dark', 209 }, 210 ]} 211 value={colorMode} 212 onChange={onChangeAppearance} 213 /> 214 215 {colorMode !== 'light' && ( 216 <Animated.View 217 entering={native(FadeInUp)} 218 exiting={native(FadeOutUp)}> 219 <AppearanceToggleButtonGroup 220 title={_(msg`Dark theme`)} 221 icon={MoonIcon} 222 items={[ 223 { 224 label: _(msg`Dim`), 225 name: 'dim', 226 }, 227 { 228 label: _(msg`Dark`), 229 name: 'dark', 230 }, 231 ]} 232 value={darkTheme ?? 'dim'} 233 onChange={onChangeDarkTheme} 234 /> 235 </Animated.View> 236 )} 237 238 <SettingsList.Group> 239 <SettingsList.ItemIcon icon={ColorPaletteIcon} /> 240 <SettingsList.ItemText> 241 <Trans>Color Theme</Trans> 242 </SettingsList.ItemText> 243 <View style={[a.w_full, a.gap_md]}> 244 <Text style={[a.flex_1, t.atoms.text_contrast_medium]}> 245 <Trans>Choose which color scheme to use:</Trans> 246 </Text> 247 <ColorSchemeGrid 248 schemes={colorSchemes} 249 selectedScheme={colorScheme} 250 onSchemeChange={onChangeScheme} 251 /> 252 <Text style={[a.flex_1, t.atoms.text_contrast_medium]}> 253 <Trans>Hue shift the colors:</Trans> 254 </Text> 255 <Slider 256 value={hue} 257 onValueChange={setHue} 258 minimumValue={0} 259 maximumValue={360} 260 step={1} 261 debounceFull={true} 262 /> 263 </View> 264 </SettingsList.Group> 265 266 <Animated.View layout={native(LinearTransition)}> 267 <SettingsList.Divider /> 268 269 <AppearanceToggleButtonGroup 270 title={_(msg`Font`)} 271 description={_( 272 msg`For the best experience, we recommend using the theme font.`, 273 )} 274 icon={Aa} 275 items={[ 276 { 277 label: _(msg`System`), 278 name: 'system', 279 }, 280 { 281 label: _(msg`Theme`), 282 name: 'theme', 283 }, 284 ]} 285 value={fonts.family} 286 onChange={onChangeFontFamily} 287 /> 288 289 <AppearanceToggleButtonGroup 290 title={_(msg`Font size`)} 291 icon={TextSize} 292 items={[ 293 { 294 label: _(msg`Smaller`), 295 name: '-1', 296 }, 297 { 298 label: _(msg`Default`), 299 name: '0', 300 }, 301 { 302 label: _(msg`Larger`), 303 name: '1', 304 }, 305 ]} 306 value={fonts.scale} 307 onChange={onChangeFontScale} 308 /> 309 310 <SettingsList.Divider /> 311 312 <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 313 <SettingsList.ItemIcon icon={SparkleIcon} /> 314 <SettingsList.ItemText> 315 <Trans>Logo</Trans> 316 </SettingsList.ItemText> 317 <Toggle.Item 318 name="kawaii_mode" 319 label={_(msg`Enable kawaii logo`)} 320 value={kawaiiMode} 321 onChange={value => setKawaiiMode(value)} 322 style={[a.w_full]}> 323 <Toggle.LabelText style={[a.flex_1]}> 324 <Trans>Enable kawaii logo</Trans> 325 </Toggle.LabelText> 326 <Toggle.Platform /> 327 </Toggle.Item> 328 </SettingsList.Group> 329 330 <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 331 <SettingsList.ItemIcon icon={SquareIcon} /> 332 <SettingsList.ItemText> 333 <Trans>Shapes</Trans> 334 </SettingsList.ItemText> 335 <Toggle.Item 336 name="enable_square_avatars" 337 label={_(msg`Enable square avatars`)} 338 value={enableSquareAvatars} 339 onChange={value => setEnableSquareAvatars(value)} 340 style={[a.w_full]}> 341 <Toggle.LabelText style={[a.flex_1]}> 342 <Trans>Enable square avatars</Trans> 343 </Toggle.LabelText> 344 <Toggle.Platform /> 345 </Toggle.Item> 346 347 <Toggle.Item 348 name="enable_square_buttons" 349 label={_(msg`Enable square buttons`)} 350 value={enableSquareButtons} 351 onChange={value => setEnableSquareButtons(value)} 352 style={[a.w_full]}> 353 <Toggle.LabelText style={[a.flex_1]}> 354 <Trans>Enable square buttons</Trans> 355 </Toggle.LabelText> 356 <Toggle.Platform /> 357 </Toggle.Item> 358 </SettingsList.Group> 359 {IS_NATIVE && IS_INTERNAL && ( 360 <> 361 <SettingsList.Divider /> 362 <AppIconSettingsListItem /> 363 </> 364 )} 365 </Animated.View> 366 </SettingsList.Container> 367 </Layout.Content> 368 </Layout.Screen> 369 </LayoutAnimationConfig> 370 ) 371} 372 373function ColorSchemeGrid({ 374 schemes, 375 selectedScheme, 376 onSchemeChange, 377}: { 378 schemes: ColorSchemeOption[] 379 selectedScheme: ColorSchemeName 380 onSchemeChange: (scheme: ColorSchemeName) => void 381}) { 382 const t = useTheme() 383 return ( 384 <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}> 385 {schemes.map(({name, label, primary}) => { 386 const isSelected = selectedScheme === name 387 const HeartIcon = isSelected ? HeartIconFilled : HeartIconOutline 388 return ( 389 <Pressable 390 accessibilityRole="button" 391 key={name} 392 onPress={() => onSchemeChange(name)} 393 style={[ 394 a.flex_1, 395 a.rounded_md, 396 a.overflow_hidden, 397 {minWidth: '30%'}, 398 a.border, 399 { 400 borderColor: isSelected 401 ? primary 402 : t.atoms.border_contrast_low.borderColor, 403 borderWidth: isSelected ? 2 : 1, 404 }, 405 ]}> 406 <View 407 style={[ 408 a.p_sm, 409 a.gap_xs, 410 {backgroundColor: t.atoms.bg.backgroundColor}, 411 ]}> 412 <View 413 style={[ 414 a.w_full, 415 a.rounded_xs, 416 {backgroundColor: primary, height: 24}, 417 ]} 418 /> 419 <View 420 style={[ 421 a.flex_row, 422 a.align_center, 423 a.justify_center, 424 a.gap_xs, 425 ]}> 426 <Text style={[a.text_sm, a.font_bold, t.atoms.text]}> 427 {label} 428 </Text> 429 <HeartIcon size="xs" style={[{color: primary}]} /> 430 </View> 431 </View> 432 </Pressable> 433 ) 434 })} 435 </View> 436 ) 437} 438 439export function AppearanceToggleButtonGroup<T extends string>({ 440 title, 441 description, 442 icon: Icon, 443 items, 444 value, 445 onChange, 446}: { 447 title: string 448 description?: string 449 icon: React.ComponentType<SVGIconProps> 450 items: { 451 label: string 452 name: T 453 }[] 454 value: T 455 onChange: (value: T) => void 456}) { 457 const t = useTheme() 458 return ( 459 <> 460 <SettingsList.Group contentContainerStyle={[a.gap_sm]} iconInset={false}> 461 <SettingsList.ItemIcon icon={Icon} /> 462 <SettingsList.ItemText>{title}</SettingsList.ItemText> 463 {description && ( 464 <Text 465 style={[ 466 a.text_sm, 467 a.leading_snug, 468 t.atoms.text_contrast_medium, 469 a.w_full, 470 ]}> 471 {description} 472 </Text> 473 )} 474 <SegmentedControl.Root 475 type="radio" 476 label={title} 477 value={value} 478 onChange={onChange}> 479 {items.map(item => ( 480 <SegmentedControl.Item 481 key={item.name} 482 label={item.label} 483 value={item.name}> 484 <SegmentedControl.ItemText> 485 {item.label} 486 </SegmentedControl.ItemText> 487 </SegmentedControl.Item> 488 ))} 489 </SegmentedControl.Root> 490 </SettingsList.Group> 491 </> 492 ) 493}