my fork of the bluesky client

Logged out improvments (#5771)

* fetch all accounts in one go

* delete unused component

* add safeareaview to logged out layout

* add safe area insets to LoggedOut view

* add safe area insets to the error boundary

* sanitize displaynames/handles

* use button for X

* increase spacing

authored by samuel.fm and committed by

GitHub 74b0929e 9a91746f

+75 -129
+7 -8
src/Splash.tsx
··· 1 1 import React, {useCallback, useEffect} from 'react' 2 2 import { 3 - View, 4 - StyleSheet, 5 - Image as RNImage, 6 3 AccessibilityInfo, 4 + Image as RNImage, 5 + StyleSheet, 7 6 useColorScheme, 7 + View, 8 8 } from 'react-native' 9 - import * as SplashScreen from 'expo-splash-screen' 10 - import {Image} from 'expo-image' 11 9 import Animated, { 10 + Easing, 12 11 interpolate, 13 12 runOnJS, 14 13 useAnimatedStyle, 15 14 useSharedValue, 16 15 withTiming, 17 - Easing, 18 16 } from 'react-native-reanimated' 19 - import MaskedView from '@react-native-masked-view/masked-view' 20 17 import {useSafeAreaInsets} from 'react-native-safe-area-context' 21 18 import Svg, {Path, SvgProps} from 'react-native-svg' 19 + import {Image} from 'expo-image' 20 + import * as SplashScreen from 'expo-splash-screen' 21 + import MaskedView from '@react-native-masked-view/masked-view' 22 22 23 23 import {isAndroid} from '#/platform/detection' 24 24 import {Logotype} from '#/view/icons/Logotype' 25 - 26 25 // @ts-ignore 27 26 import splashImagePointer from '../assets/splash.png' 28 27 // @ts-ignore
+16 -6
src/components/AccountList.tsx
··· 1 1 import React, {useCallback} from 'react' 2 2 import {View} from 'react-native' 3 + import {AppBskyActorDefs} from '@atproto/api' 3 4 import {msg, Trans} from '@lingui/macro' 4 5 import {useLingui} from '@lingui/react' 5 6 6 - import {useProfileQuery} from '#/state/queries/profile' 7 + import {sanitizeDisplayName} from '#/lib/strings/display-names' 8 + import {sanitizeHandle} from '#/lib/strings/handles' 9 + import {useProfilesQuery} from '#/state/queries/profile' 7 10 import {type SessionAccount, useSession} from '#/state/session' 8 11 import {UserAvatar} from '#/view/com/util/UserAvatar' 9 12 import {atoms as a, useTheme} from '#/alf' ··· 26 29 const {currentAccount, accounts} = useSession() 27 30 const t = useTheme() 28 31 const {_} = useLingui() 32 + const {data: profiles} = useProfilesQuery({ 33 + handles: accounts.map(acc => acc.did), 34 + }) 29 35 30 36 const onPressAddAccount = useCallback(() => { 31 37 onSelectOther() ··· 43 49 {accounts.map(account => ( 44 50 <React.Fragment key={account.did}> 45 51 <AccountItem 52 + profile={profiles?.profiles.find(p => p.did === account.did)} 46 53 account={account} 47 54 onSelect={onSelectAccount} 48 55 isCurrentAccount={account.did === currentAccount?.did} ··· 84 91 } 85 92 86 93 function AccountItem({ 94 + profile, 87 95 account, 88 96 onSelect, 89 97 isCurrentAccount, 90 98 isPendingAccount, 91 99 }: { 100 + profile?: AppBskyActorDefs.ProfileViewDetailed 92 101 account: SessionAccount 93 102 onSelect: (account: SessionAccount) => void 94 103 isCurrentAccount: boolean ··· 96 105 }) { 97 106 const t = useTheme() 98 107 const {_} = useLingui() 99 - const {data: profile} = useProfileQuery({did: account.did}) 100 108 101 - const onPress = React.useCallback(() => { 109 + const onPress = useCallback(() => { 102 110 onSelect(account) 103 111 }, [account, onSelect]) 104 112 ··· 127 135 </View> 128 136 <Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}> 129 137 <Text emoji style={[a.font_bold]}> 130 - {profile?.displayName || account.handle}{' '} 131 - </Text> 138 + {sanitizeDisplayName( 139 + profile?.displayName || profile?.handle || account.handle, 140 + )} 141 + </Text>{' '} 132 142 <Text emoji style={[t.atoms.text_contrast_medium]}> 133 - {account.handle} 143 + {sanitizeHandle(account.handle)} 134 144 </Text> 135 145 </Text> 136 146 {isCurrentAccount ? (
+2 -2
src/screens/Deactivated.tsx
··· 104 104 }, [_, agent, setPending, setError, queryClient]) 105 105 106 106 return ( 107 - <View style={[a.h_full_vh, a.flex_1, t.atoms.bg]}> 107 + <View style={[a.util_screen_outer, a.flex_1, t.atoms.bg]}> 108 108 <ScrollView 109 109 style={[a.h_full, a.w_full]} 110 110 contentContainerStyle={{borderWidth: 0}}> ··· 112 112 style={[ 113 113 a.px_2xl, 114 114 { 115 - paddingTop: isWeb ? 64 : insets.top, 115 + paddingTop: isWeb ? 64 : insets.top + 16, 116 116 paddingBottom: isWeb ? 64 : insets.bottom, 117 117 }, 118 118 ]}>
+3 -3
src/state/shell/logged-out.tsx
··· 1 1 import React from 'react' 2 2 3 - import {isWeb} from 'platform/detection' 4 - import {useSession} from 'state/session' 5 - import {useActiveStarterPack} from 'state/shell/starter-pack' 3 + import {isWeb} from '#/platform/detection' 4 + import {useSession} from '#/state/session' 5 + import {useActiveStarterPack} from '#/state/shell/starter-pack' 6 6 7 7 type State = { 8 8 showLoggedOut: boolean
+32 -28
src/view/com/auth/LoggedOut.tsx
··· 1 1 import React from 'react' 2 - import {Pressable, View} from 'react-native' 3 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 2 + import {View} from 'react-native' 3 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {usePalette} from '#/lib/hooks/usePalette' 7 + import {PressableScale} from '#/lib/custom-animations/PressableScale' 8 8 import {logEvent} from '#/lib/statsig/statsig' 9 - import {s} from '#/lib/styles' 10 - import {isIOS} from '#/platform/detection' 11 9 import { 12 10 useLoggedOutView, 13 11 useLoggedOutViewControls, ··· 17 15 import {Login} from '#/screens/Login' 18 16 import {Signup} from '#/screens/Signup' 19 17 import {LandingScreen} from '#/screens/StarterPack/StarterPackLandingScreen' 18 + import {atoms as a, native, tokens, useTheme} from '#/alf' 19 + import {Button, ButtonIcon} from '#/components/Button' 20 + import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 20 21 import {SplashScreen} from './SplashScreen' 21 22 22 23 enum ScreenState { ··· 29 30 30 31 export function LoggedOut({onDismiss}: {onDismiss?: () => void}) { 31 32 const {_} = useLingui() 32 - const pal = usePalette('default') 33 + const t = useTheme() 34 + const insets = useSafeAreaInsets() 33 35 const setMinimalShellMode = useSetMinimalShellMode() 34 36 const {requestedAccountSwitchTo} = useLoggedOutView() 35 37 const [screenState, setScreenState] = React.useState<ScreenState>(() => { ··· 57 59 }, [clearRequestedAccount, onDismiss]) 58 60 59 61 return ( 60 - <View testID="noSessionView" style={[s.hContentRegion, pal.view]}> 62 + <View 63 + testID="noSessionView" 64 + style={[ 65 + a.util_screen_outer, 66 + t.atoms.bg, 67 + {paddingTop: insets.top, paddingBottom: insets.bottom}, 68 + ]}> 61 69 <ErrorBoundary> 62 70 {onDismiss && screenState === ScreenState.S_LoginOrCreateAccount ? ( 63 - <Pressable 64 - accessibilityHint={_(msg`Go back`)} 65 - accessibilityLabel={_(msg`Go back`)} 66 - accessibilityRole="button" 67 - style={{ 68 - position: 'absolute', 69 - top: isIOS ? 0 : 20, 70 - right: 20, 71 - padding: 10, 72 - zIndex: 100, 73 - backgroundColor: pal.text.color, 74 - borderRadius: 100, 75 - }} 71 + <Button 72 + label={_(msg`Go back`)} 73 + variant="solid" 74 + color="secondary_inverted" 75 + size="small" 76 + shape="round" 77 + PressableComponent={native(PressableScale)} 78 + style={[ 79 + a.absolute, 80 + { 81 + top: insets.top + tokens.space.xl, 82 + right: tokens.space.xl, 83 + zIndex: 100, 84 + }, 85 + ]} 76 86 onPress={onPressDismiss}> 77 - <FontAwesomeIcon 78 - icon="x" 79 - size={12} 80 - style={{ 81 - color: String(pal.textInverted.color), 82 - }} 83 - /> 84 - </Pressable> 87 + <ButtonIcon icon={XIcon} /> 88 + </Button> 85 89 ) : null} 86 90 87 91 {screenState === ScreenState.S_StarterPack ? (
+3 -1
src/view/com/util/ErrorBoundary.tsx
··· 1 1 import React, {Component, ErrorInfo, ReactNode} from 'react' 2 + import {StyleProp, ViewStyle} from 'react-native' 2 3 import {msg} from '@lingui/macro' 3 4 import {useLingui} from '@lingui/react' 4 5 ··· 9 10 interface Props { 10 11 children?: ReactNode 11 12 renderError?: (error: any) => ReactNode 13 + style?: StyleProp<ViewStyle> 12 14 } 13 15 14 16 interface State { ··· 37 39 } 38 40 39 41 return ( 40 - <CenteredView style={{height: '100%', flex: 1}}> 42 + <CenteredView style={[{height: '100%', flex: 1}, this.props.style]}> 41 43 <TranslatedErrorScreen details={this.state.error.toString()} /> 42 44 </CenteredView> 43 45 )
+6 -9
src/view/com/util/layouts/LoggedOutLayout.tsx
··· 1 1 import React from 'react' 2 2 import {ScrollView, StyleSheet, View} from 'react-native' 3 3 4 + import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' 5 + import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' 6 + import {usePalette} from '#/lib/hooks/usePalette' 7 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 8 import {isWeb} from '#/platform/detection' 5 - import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' 6 - import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible' 7 - import {usePalette} from 'lib/hooks/usePalette' 8 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 9 9 import {atoms as a} from '#/alf' 10 10 import {Text} from '../text/Text' 11 11 ··· 36 36 if (scrollable) { 37 37 return ( 38 38 <ScrollView 39 - style={styles.scrollview} 39 + style={a.flex_1} 40 40 keyboardShouldPersistTaps="handled" 41 41 keyboardDismissMode="none" 42 42 contentContainerStyle={[ ··· 75 75 {scrollable ? ( 76 76 <View style={[styles.scrollableContent, contentBg]}> 77 77 <ScrollView 78 - style={styles.scrollview} 78 + style={a.flex_1} 79 79 contentContainerStyle={styles.scrollViewContentContainer} 80 80 keyboardShouldPersistTaps="handled" 81 81 keyboardDismissMode="on-drag"> ··· 112 112 }, 113 113 scrollableContent: { 114 114 flex: 2, 115 - }, 116 - scrollview: { 117 - flex: 1, 118 115 }, 119 116 scrollViewContentContainer: { 120 117 flex: 1,
-69
src/view/com/util/layouts/TitleColumnLayout.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3 - import {usePalette} from 'lib/hooks/usePalette' 4 - import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' 5 - 6 - interface Props { 7 - testID?: string 8 - title: JSX.Element 9 - horizontal: boolean 10 - titleStyle?: StyleProp<ViewStyle> 11 - contentStyle?: StyleProp<ViewStyle> 12 - } 13 - 14 - export function TitleColumnLayout({ 15 - testID, 16 - title, 17 - horizontal, 18 - children, 19 - titleStyle, 20 - contentStyle, 21 - }: React.PropsWithChildren<Props>) { 22 - const pal = usePalette('default') 23 - const titleBg = useColorSchemeStyle(pal.viewLight, pal.view) 24 - const contentBg = useColorSchemeStyle(pal.view, { 25 - backgroundColor: pal.colors.background, 26 - borderColor: pal.colors.border, 27 - borderLeftWidth: 1, 28 - }) 29 - 30 - const layoutStyles = horizontal ? styles2Column : styles1Column 31 - return ( 32 - <View testID={testID} style={layoutStyles.container}> 33 - <View style={[layoutStyles.title, titleBg, titleStyle]}>{title}</View> 34 - <View style={[layoutStyles.content, contentBg, contentStyle]}> 35 - {children} 36 - </View> 37 - </View> 38 - ) 39 - } 40 - 41 - const styles2Column = StyleSheet.create({ 42 - container: { 43 - flexDirection: 'row', 44 - height: '100%', 45 - }, 46 - title: { 47 - flex: 1, 48 - paddingHorizontal: 40, 49 - paddingBottom: 80, 50 - justifyContent: 'center', 51 - }, 52 - content: { 53 - flex: 2, 54 - paddingHorizontal: 40, 55 - justifyContent: 'center', 56 - }, 57 - }) 58 - 59 - const styles1Column = StyleSheet.create({ 60 - container: {}, 61 - title: { 62 - paddingHorizontal: 40, 63 - paddingVertical: 40, 64 - }, 65 - content: { 66 - paddingHorizontal: 40, 67 - paddingVertical: 40, 68 - }, 69 - })
+2 -2
src/view/shell/createNativeStackNavigatorWithAuth.tsx
··· 23 23 24 24 import {PWI_ENABLED} from '#/lib/build-flags' 25 25 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 26 + import {isNative, isWeb} from '#/platform/detection' 26 27 import {useSession} from '#/state/session' 27 28 import {useOnboardingState} from '#/state/shell' 28 29 import { 29 30 useLoggedOutView, 30 31 useLoggedOutViewControls, 31 32 } from '#/state/shell/logged-out' 32 - import {isNative, isWeb} from 'platform/detection' 33 + import {LoggedOut} from '#/view/com/auth/LoggedOut' 33 34 import {Deactivated} from '#/screens/Deactivated' 34 35 import {Onboarding} from '#/screens/Onboarding' 35 36 import {SignupQueued} from '#/screens/SignupQueued' 36 - import {LoggedOut} from '../com/auth/LoggedOut' 37 37 import {BottomBarWeb} from './bottom-bar/BottomBarWeb' 38 38 import {DesktopLeftNav} from './desktop/LeftNav' 39 39 import {DesktopRightNav} from './desktop/RightNav'
+4 -1
src/view/shell/index.tsx
··· 2 2 import {BackHandler, StyleSheet, useWindowDimensions, View} from 'react-native' 3 3 import {Drawer} from 'react-native-drawer-layout' 4 4 import Animated from 'react-native-reanimated' 5 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 6 import * as NavigationBar from 'expo-navigation-bar' 6 7 import {StatusBar} from 'expo-status-bar' 7 8 import {useNavigation, useNavigationState} from '@react-navigation/native' ··· 41 42 const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled() 42 43 const setIsDrawerOpen = useSetDrawerOpen() 43 44 const winDim = useWindowDimensions() 45 + const insets = useSafeAreaInsets() 44 46 45 47 const renderDrawerContent = React.useCallback(() => <DrawerContent />, []) 46 48 const onOpenDrawer = React.useCallback( ··· 94 96 return ( 95 97 <> 96 98 <Animated.View style={[a.h_full]}> 97 - <ErrorBoundary> 99 + <ErrorBoundary 100 + style={{paddingTop: insets.top, paddingBottom: insets.bottom}}> 98 101 <Drawer 99 102 renderDrawerContent={renderDrawerContent} 100 103 drawerStyle={{width: Math.min(400, winDim.width * 0.8)}}