Bluesky app fork with some witchin' additions 💫

Move theme controls to its own screen (#4866)

authored by samuel.fm and committed by

GitHub c78e9e31 388c157c

+204 -78
+1
assets/icons/moon_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12.097 2.53a1 1 0 0 1-.041 1.07 6 6 0 0 0 8.345 8.344 1 1 0 0 1 1.563.908c-.434 5.122-4.728 9.144-9.962 9.144-5.522 0-9.998-4.476-9.998-9.998 0-5.234 4.021-9.528 9.144-9.962a1 1 0 0 1 .949.494ZM9.424 4.424a7.998 7.998 0 1 0 10.152 10.152A8 8 0 0 1 9.424 4.424Z" clip-rule="evenodd"/></svg>
+1
assets/icons/phone_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M5 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v16a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H8Zm2 2a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
+1
bskyweb/cmd/bskyweb/server.go
··· 211 211 e.GET("/settings/threads", server.WebGeneric) 212 212 e.GET("/settings/external-embeds", server.WebGeneric) 213 213 e.GET("/settings/accessibility", server.WebGeneric) 214 + e.GET("/settings/appearance", server.WebGeneric) 214 215 e.GET("/sys/debug", server.WebGeneric) 215 216 e.GET("/sys/debug-mod", server.WebGeneric) 216 217 e.GET("/sys/log", server.WebGeneric)
+9
src/Navigation.tsx
··· 44 44 import {ModerationScreen} from '#/screens/Moderation' 45 45 import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers' 46 46 import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' 47 + import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings' 47 48 import { 48 49 StarterPackScreen, 49 50 StarterPackScreenShort, ··· 307 308 getComponent={() => AccessibilitySettingsScreen} 308 309 options={{ 309 310 title: title(msg`Accessibility Settings`), 311 + requireAuth: true, 312 + }} 313 + /> 314 + <Stack.Screen 315 + name="AppearanceSettings" 316 + getComponent={() => AppearanceSettingsScreen} 317 + options={{ 318 + title: title(msg`Appearance Settings`), 310 319 requireAuth: true, 311 320 }} 312 321 />
+1 -1
src/components/forms/ToggleButton.tsx
··· 23 23 style={[ 24 24 a.w_full, 25 25 a.flex_row, 26 - a.border, 27 26 a.rounded_sm, 28 27 a.overflow_hidden, 29 28 t.atoms.border_contrast_low, 29 + {borderWidth: 1}, 30 30 ]}> 31 31 {children} 32 32 </View>
+5
src/components/icons/Moon.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Moon_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 + path: 'M12.097 2.53a1 1 0 0 1-.041 1.07 6 6 0 0 0 8.345 8.344 1 1 0 0 1 1.563.908c-.434 5.122-4.728 9.144-9.962 9.144-5.522 0-9.998-4.476-9.998-9.998 0-5.234 4.021-9.528 9.144-9.962a1 1 0 0 1 .949.494ZM9.424 4.424a7.998 7.998 0 1 0 10.152 10.152A8 8 0 0 1 9.424 4.424Z', 5 + })
+5
src/components/icons/Phone.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Phone_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 + path: 'M5 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v16a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H8Zm2 2a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z', 5 + })
+1
src/lib/routes/types.ts
··· 38 38 PreferencesThreads: undefined 39 39 PreferencesExternalEmbeds: undefined 40 40 AccessibilitySettings: undefined 41 + AppearanceSettings: undefined 41 42 Search: {q?: string} 42 43 Hashtag: {tag: string; author?: string} 43 44 MessagesConversation: {conversation: string; embed?: string}
+1
src/routes.ts
··· 32 32 PreferencesThreads: '/settings/threads', 33 33 PreferencesExternalEmbeds: '/settings/external-embeds', 34 34 AccessibilitySettings: '/settings/accessibility', 35 + AppearanceSettings: '/settings/appearance', 35 36 SavedFeeds: '/settings/saved-feeds', 36 37 Support: '/support', 37 38 PrivacyPolicy: '/support/privacy',
+135
src/screens/Settings/AppearanceSettings.tsx
··· 1 + import React, {useCallback} from 'react' 2 + import {View} from 'react-native' 3 + import Animated, { 4 + FadeInDown, 5 + FadeOutDown, 6 + LayoutAnimationConfig, 7 + } from 'react-native-reanimated' 8 + import {msg, Trans} from '@lingui/macro' 9 + import {useLingui} from '@lingui/react' 10 + 11 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 12 + import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 13 + import {s} from '#/lib/styles' 14 + import {useSetThemePrefs, useThemePrefs} from '#/state/shell' 15 + import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' 16 + import {ScrollView} from '#/view/com/util/Views' 17 + import {atoms as a, native, useTheme} from '#/alf' 18 + import * as ToggleButton from '#/components/forms/ToggleButton' 19 + import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon' 20 + import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone' 21 + import {Text} from '#/components/Typography' 22 + 23 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'> 24 + export function AppearanceSettingsScreen({}: Props) { 25 + const {_} = useLingui() 26 + const t = useTheme() 27 + const {isTabletOrMobile} = useWebMediaQueries() 28 + 29 + const {colorMode, darkTheme} = useThemePrefs() 30 + const {setColorMode, setDarkTheme} = useSetThemePrefs() 31 + 32 + const onChangeAppearance = useCallback( 33 + (keys: string[]) => { 34 + const appearance = keys.find(key => key !== colorMode) as 35 + | 'system' 36 + | 'light' 37 + | 'dark' 38 + | undefined 39 + if (!appearance) return 40 + setColorMode(appearance) 41 + }, 42 + [setColorMode, colorMode], 43 + ) 44 + 45 + const onChangeDarkTheme = useCallback( 46 + (keys: string[]) => { 47 + const theme = keys.find(key => key !== darkTheme) as 48 + | 'dim' 49 + | 'dark' 50 + | undefined 51 + if (!theme) return 52 + setDarkTheme(theme) 53 + }, 54 + [setDarkTheme, darkTheme], 55 + ) 56 + 57 + return ( 58 + <LayoutAnimationConfig skipExiting skipEntering> 59 + <View testID="preferencesThreadsScreen" style={s.hContentRegion}> 60 + <ScrollView 61 + // @ts-ignore web only -prf 62 + dataSet={{'stable-gutters': 1}} 63 + contentContainerStyle={{paddingBottom: 75}}> 64 + <SimpleViewHeader 65 + showBackButton={isTabletOrMobile} 66 + style={[t.atoms.border_contrast_medium, a.border_b]}> 67 + <View style={a.flex_1}> 68 + <Text style={[a.text_2xl, a.font_bold]}> 69 + <Trans>Appearance</Trans> 70 + </Text> 71 + </View> 72 + </SimpleViewHeader> 73 + 74 + <View style={[a.p_xl, a.gap_lg]}> 75 + <View style={[a.flex_row, a.align_center, a.gap_md]}> 76 + <PhoneIcon style={t.atoms.text} /> 77 + <Text style={a.text_md}> 78 + <Trans>Mode</Trans> 79 + </Text> 80 + </View> 81 + <ToggleButton.Group 82 + label={_(msg`Dark mode`)} 83 + values={[colorMode]} 84 + onChange={onChangeAppearance}> 85 + <ToggleButton.Button label={_(msg`System`)} name="system"> 86 + <ToggleButton.ButtonText> 87 + <Trans>System</Trans> 88 + </ToggleButton.ButtonText> 89 + </ToggleButton.Button> 90 + <ToggleButton.Button label={_(msg`Light`)} name="light"> 91 + <ToggleButton.ButtonText> 92 + <Trans>Light</Trans> 93 + </ToggleButton.ButtonText> 94 + </ToggleButton.Button> 95 + <ToggleButton.Button label={_(msg`Dark`)} name="dark"> 96 + <ToggleButton.ButtonText> 97 + <Trans>Dark</Trans> 98 + </ToggleButton.ButtonText> 99 + </ToggleButton.Button> 100 + </ToggleButton.Group> 101 + {colorMode !== 'light' && ( 102 + <Animated.View 103 + entering={native(FadeInDown)} 104 + exiting={native(FadeOutDown)} 105 + style={[a.mt_md, a.gap_lg]}> 106 + <View style={[a.flex_row, a.align_center, a.gap_md]}> 107 + <MoonIcon style={t.atoms.text} /> 108 + <Text style={a.text_md}> 109 + <Trans>Dark theme</Trans> 110 + </Text> 111 + </View> 112 + 113 + <ToggleButton.Group 114 + label={_(msg`Dark theme`)} 115 + values={[darkTheme ?? 'dim']} 116 + onChange={onChangeDarkTheme}> 117 + <ToggleButton.Button label={_(msg`Dim`)} name="dim"> 118 + <ToggleButton.ButtonText> 119 + <Trans>Dim</Trans> 120 + </ToggleButton.ButtonText> 121 + </ToggleButton.Button> 122 + <ToggleButton.Button label={_(msg`Dark`)} name="dark"> 123 + <ToggleButton.ButtonText> 124 + <Trans>Dark</Trans> 125 + </ToggleButton.ButtonText> 126 + </ToggleButton.Button> 127 + </ToggleButton.Group> 128 + </Animated.View> 129 + )} 130 + </View> 131 + </ScrollView> 132 + </View> 133 + </LayoutAnimationConfig> 134 + ) 135 + }
+2
src/view/icons/index.tsx
··· 77 77 import {faLock} from '@fortawesome/free-solid-svg-icons/faLock' 78 78 import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass' 79 79 import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky' 80 + import {faPaintRoller} from '@fortawesome/free-solid-svg-icons/faPaintRoller' 80 81 import {faPause} from '@fortawesome/free-solid-svg-icons/faPause' 81 82 import {faPen} from '@fortawesome/free-solid-svg-icons/faPen' 82 83 import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib' ··· 178 179 faMagnifyingGlass, 179 180 faMessage, 180 181 faNoteSticky, 182 + faPaintRoller, 181 183 faPaste, 182 184 faPause, 183 185 faPen,
+7 -3
src/view/screens/AccessibilitySettings.tsx
··· 27 27 import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' 28 28 import {Text} from '#/view/com/util/text/Text' 29 29 import {ScrollView} from '#/view/com/util/Views' 30 + import {atoms as a} from '#/alf' 30 31 31 32 type Props = NativeStackScreenProps< 32 33 CommonNavigatorParams, ··· 61 62 showBackButton={isTabletOrMobile} 62 63 style={[ 63 64 pal.border, 64 - {borderBottomWidth: 1}, 65 - !isMobile && {borderLeftWidth: 1, borderRightWidth: 1}, 65 + a.border_b, 66 + !isMobile && { 67 + borderLeftWidth: StyleSheet.hairlineWidth, 68 + borderRightWidth: StyleSheet.hairlineWidth, 69 + }, 66 70 ]}> 67 - <View style={{flex: 1}}> 71 + <View style={a.flex_1}> 68 72 <Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}> 69 73 <Trans>Accessibility Settings</Trans> 70 74 </Text>
+3 -2
src/view/screens/PreferencesExternalEmbeds.tsx
··· 18 18 useSetExternalEmbedPref, 19 19 } from 'state/preferences' 20 20 import {ToggleButton} from 'view/com/util/forms/ToggleButton' 21 + import {atoms as a} from '#/alf' 21 22 import {SimpleViewHeader} from '../com/util/SimpleViewHeader' 22 23 import {Text} from '../com/util/text/Text' 23 24 import {ScrollView} from '../com/util/Views' ··· 47 48 contentContainerStyle={[pal.viewLight, {paddingBottom: 75}]}> 48 49 <SimpleViewHeader 49 50 showBackButton={isTabletOrMobile} 50 - style={[pal.border, {borderBottomWidth: 1}]}> 51 - <View style={{flex: 1}}> 51 + style={[pal.border, a.border_b]}> 52 + <View style={a.flex_1}> 52 53 <Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}> 53 54 <Trans>External Media Preferences</Trans> 54 55 </Text>
+3 -2
src/view/screens/PreferencesFollowingFeed.tsx
··· 19 19 import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' 20 20 import {Text} from '#/view/com/util/text/Text' 21 21 import {ScrollView} from '#/view/com/util/Views' 22 + import {atoms as a} from '#/alf' 22 23 23 24 function RepliesThresholdInput({ 24 25 enabled, ··· 99 100 contentContainerStyle={{paddingBottom: 75}}> 100 101 <SimpleViewHeader 101 102 showBackButton={isTabletOrMobile} 102 - style={[pal.border, {borderBottomWidth: 1}]}> 103 - <View style={{flex: 1}}> 103 + style={[pal.border, a.border_b]}> 104 + <View style={a.flex_1}> 104 105 <Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}> 105 106 <Trans>Following Feed Preferences</Trans> 106 107 </Text>
+2 -2
src/view/screens/PreferencesThreads.tsx
··· 45 45 contentContainerStyle={{paddingBottom: 75}}> 46 46 <SimpleViewHeader 47 47 showBackButton={isTabletOrMobile} 48 - style={[pal.border, {borderBottomWidth: 1}]}> 49 - <View style={{flex: 1}}> 48 + style={[pal.border, a.border_b]}> 49 + <View style={a.flex_1}> 50 50 <Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}> 51 51 <Trans>Thread Preferences</Trans> 52 52 </Text>
+27 -68
src/view/screens/Settings/index.tsx
··· 31 31 import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' 32 32 import {useProfileQuery} from '#/state/queries/profile' 33 33 import {SessionAccount, useSession, useSessionApi} from '#/state/session' 34 - import { 35 - useOnboardingDispatch, 36 - useSetMinimalShellMode, 37 - useSetThemePrefs, 38 - useThemePrefs, 39 - } from '#/state/shell' 34 + import {useOnboardingDispatch, useSetMinimalShellMode} from '#/state/shell' 40 35 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 41 36 import {useCloseAllActiveElements} from '#/state/util' 42 37 import {useAnalytics} from 'lib/analytics/analytics' ··· 52 47 import {NavigationProp} from 'lib/routes/types' 53 48 import {colors, s} from 'lib/styles' 54 49 import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' 55 - import {SelectableBtn} from 'view/com/util/forms/SelectableBtn' 56 50 import {ToggleButton} from 'view/com/util/forms/ToggleButton' 57 51 import {Link, TextLink} from 'view/com/util/Link' 58 52 import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' ··· 61 55 import {UserAvatar} from 'view/com/util/UserAvatar' 62 56 import {ScrollView} from 'view/com/util/Views' 63 57 import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' 64 - import {useTheme} from '#/alf' 65 - import {atoms as a} from '#/alf' 58 + import {atoms as a, useTheme} from '#/alf' 66 59 import {useDialogControl} from '#/components/Dialog' 67 60 import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' 68 61 import {navigate, resetToTab} from '#/Navigation' ··· 168 161 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> 169 162 export function SettingsScreen({}: Props) { 170 163 const queryClient = useQueryClient() 171 - const {colorMode, darkTheme} = useThemePrefs() 172 - const {setColorMode, setDarkTheme} = useSetThemePrefs() 173 164 const pal = usePalette('default') 174 165 const {_} = useLingui() 175 166 const setMinimalShellMode = useSetMinimalShellMode() ··· 294 285 295 286 const onPressAccessibilitySettings = React.useCallback(() => { 296 287 navigation.navigate('AccessibilitySettings') 288 + }, [navigation]) 289 + 290 + const onPressAppearanceSettings = React.useCallback(() => { 291 + navigation.navigate('AppearanceSettings') 297 292 }, [navigation]) 298 293 299 294 const onPressBirthday = React.useCallback(() => { ··· 437 432 <View style={styles.spacer20} /> 438 433 439 434 <Text type="xl-bold" style={[pal.text, styles.heading]}> 440 - <Trans>Appearance</Trans> 441 - </Text> 442 - <View> 443 - <View style={[styles.linkCard, pal.view, styles.selectableBtns]}> 444 - <SelectableBtn 445 - selected={colorMode === 'system'} 446 - label={_(msg`System`)} 447 - left 448 - onSelect={() => setColorMode('system')} 449 - accessibilityHint={_(msg`Sets color theme to system setting`)} 450 - /> 451 - <SelectableBtn 452 - selected={colorMode === 'light'} 453 - label={_(msg`Light`)} 454 - onSelect={() => setColorMode('light')} 455 - accessibilityHint={_(msg`Sets color theme to light`)} 456 - /> 457 - <SelectableBtn 458 - selected={colorMode === 'dark'} 459 - label={_(msg`Dark`)} 460 - right 461 - onSelect={() => setColorMode('dark')} 462 - accessibilityHint={_(msg`Sets color theme to dark`)} 463 - /> 464 - </View> 465 - </View> 466 - 467 - <View style={styles.spacer20} /> 468 - 469 - {colorMode !== 'light' && ( 470 - <> 471 - <Text type="xl-bold" style={[pal.text, styles.heading]}> 472 - <Trans>Dark Theme</Trans> 473 - </Text> 474 - <View> 475 - <View style={[styles.linkCard, pal.view, styles.selectableBtns]}> 476 - <SelectableBtn 477 - selected={!darkTheme || darkTheme === 'dim'} 478 - label={_(msg`Dim`)} 479 - left 480 - onSelect={() => setDarkTheme('dim')} 481 - accessibilityHint={_(msg`Sets dark theme to the dim theme`)} 482 - /> 483 - <SelectableBtn 484 - selected={darkTheme === 'dark'} 485 - label={_(msg`Dark`)} 486 - right 487 - onSelect={() => setDarkTheme('dark')} 488 - accessibilityHint={_(msg`Sets dark theme to the dark theme`)} 489 - /> 490 - </View> 491 - </View> 492 - <View style={styles.spacer20} /> 493 - </> 494 - )} 495 - 496 - <Text type="xl-bold" style={[pal.text, styles.heading]}> 497 435 <Trans>Basics</Trans> 498 436 </Text> 499 437 <TouchableOpacity ··· 517 455 </View> 518 456 <Text type="lg" style={pal.text}> 519 457 <Trans>Accessibility</Trans> 458 + </Text> 459 + </TouchableOpacity> 460 + <TouchableOpacity 461 + testID="appearanceSettingsBtn" 462 + style={[ 463 + styles.linkCard, 464 + pal.view, 465 + isSwitchingAccounts && styles.dimmed, 466 + ]} 467 + onPress={isSwitchingAccounts ? undefined : onPressAppearanceSettings} 468 + accessibilityRole="button" 469 + accessibilityLabel={_(msg`Appearance settings`)} 470 + accessibilityHint={_(msg`Opens appearance settings`)}> 471 + <View style={[styles.iconContainer, pal.btn]}> 472 + <FontAwesomeIcon 473 + icon="paint-roller" 474 + style={pal.text as FontAwesomeIconStyle} 475 + /> 476 + </View> 477 + <Text type="lg" style={pal.text}> 478 + <Trans>Appearance</Trans> 520 479 </Text> 521 480 </TouchableOpacity> 522 481 <TouchableOpacity