Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Lazy load ViewShot (#5517)

* lazy one spot

* lazy signup

* fix type

* tweak type, fix missing viewshot type

* only import type oops

authored by

Hailey and committed by
GitHub
389e6f15 dd1944e9

+97 -67
+11 -6
src/components/StarterPack/QrCode.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import QRCode from 'react-native-qrcode-styled' 4 - import ViewShot from 'react-native-view-shot' 4 + import type ViewShot from 'react-native-view-shot' 5 5 import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 6 6 import {Trans} from '@lingui/macro' 7 7 8 - import {isWeb} from 'platform/detection' 9 - import {Logo} from 'view/icons/Logo' 10 - import {Logotype} from 'view/icons/Logotype' 8 + import {isWeb} from '#/platform/detection' 9 + import {Logo} from '#/view/icons/Logo' 10 + import {Logotype} from '#/view/icons/Logotype' 11 11 import {useTheme} from '#/alf' 12 12 import {atoms as a} from '#/alf' 13 13 import {LinearGradientBackground} from '#/components/LinearGradientBackground' 14 14 import {Text} from '#/components/Typography' 15 + 16 + const LazyViewShot = React.lazy( 17 + // @ts-expect-error dynamic import 18 + () => import('react-native-view-shot/src/index'), 19 + ) 15 20 16 21 interface Props { 17 22 starterPack: AppBskyGraphDefs.StarterPackView ··· 29 34 } 30 35 31 36 return ( 32 - <ViewShot ref={ref}> 37 + <LazyViewShot ref={ref}> 33 38 <LinearGradientBackground 34 39 style={[ 35 40 {width: 300, minHeight: 390}, ··· 79 84 </Text> 80 85 </View> 81 86 </LinearGradientBackground> 82 - </ViewShot> 87 + </LazyViewShot> 83 88 ) 84 89 }) 85 90
+49 -41
src/components/StarterPack/QrCodeDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import ViewShot from 'react-native-view-shot' 3 + import type ViewShot from 'react-native-view-shot' 4 4 import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' 5 5 import {createAssetAsync} from 'expo-media-library' 6 6 import * as Sharing from 'expo-sharing' ··· 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 10 11 + import {logEvent} from '#/lib/statsig/statsig' 11 12 import {logger} from '#/logger' 12 - import {logEvent} from 'lib/statsig/statsig' 13 - import {isNative, isWeb} from 'platform/detection' 13 + import {isNative, isWeb} from '#/platform/detection' 14 14 import * as Toast from '#/view/com/util/Toast' 15 15 import {atoms as a} from '#/alf' 16 16 import {Button, ButtonText} from '#/components/Button' ··· 153 153 <Dialog.ScrollableInner 154 154 label={_(msg`Create a QR code for a starter pack`)}> 155 155 <View style={[a.flex_1, a.align_center, a.gap_5xl]}> 156 - {!link ? ( 157 - <View style={[a.align_center, a.p_xl]}> 158 - <Loader size="xl" /> 159 - </View> 160 - ) : ( 161 - <> 162 - <QrCode starterPack={starterPack} link={link} ref={ref} /> 163 - {isProcessing ? ( 164 - <View> 165 - <Loader size="xl" /> 166 - </View> 167 - ) : ( 168 - <View 169 - style={[a.w_full, a.gap_md, isWeb && [a.flex_row_reverse]]}> 170 - <Button 171 - label={_(msg`Copy QR code`)} 172 - variant="solid" 173 - color="secondary" 174 - size="small" 175 - onPress={isWeb ? onCopyPress : onSharePress}> 176 - <ButtonText> 177 - {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 178 - </ButtonText> 179 - </Button> 180 - <Button 181 - label={_(msg`Save QR code`)} 182 - variant="solid" 183 - color="secondary" 184 - size="small" 185 - onPress={onSavePress}> 186 - <ButtonText> 187 - <Trans>Save</Trans> 188 - </ButtonText> 189 - </Button> 190 - </View> 191 - )} 192 - </> 193 - )} 156 + <React.Suspense fallback={<Loading />}> 157 + {!link ? ( 158 + <Loading /> 159 + ) : ( 160 + <> 161 + <QrCode starterPack={starterPack} link={link} ref={ref} /> 162 + {isProcessing ? ( 163 + <View> 164 + <Loader size="xl" /> 165 + </View> 166 + ) : ( 167 + <View 168 + style={[a.w_full, a.gap_md, isWeb && [a.flex_row_reverse]]}> 169 + <Button 170 + label={_(msg`Copy QR code`)} 171 + variant="solid" 172 + color="secondary" 173 + size="small" 174 + onPress={isWeb ? onCopyPress : onSharePress}> 175 + <ButtonText> 176 + {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 177 + </ButtonText> 178 + </Button> 179 + <Button 180 + label={_(msg`Save QR code`)} 181 + variant="solid" 182 + color="secondary" 183 + size="small" 184 + onPress={onSavePress}> 185 + <ButtonText> 186 + <Trans>Save</Trans> 187 + </ButtonText> 188 + </Button> 189 + </View> 190 + )} 191 + </> 192 + )} 193 + </React.Suspense> 194 194 </View> 195 195 </Dialog.ScrollableInner> 196 196 </Dialog.Outer> 197 197 ) 198 198 } 199 + 200 + function Loading() { 201 + return ( 202 + <View style={[a.align_center, a.p_xl]}> 203 + <Loader size="xl" /> 204 + </View> 205 + ) 206 + }
+15 -7
src/screens/Onboarding/StepProfile/PlaceholderCanvas.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import ViewShot from 'react-native-view-shot' 3 + import type ViewShot from 'react-native-view-shot' 4 4 5 5 import {useAvatar} from '#/screens/Onboarding/StepProfile/index' 6 6 import {atoms as a} from '#/alf' 7 7 8 + const LazyViewShot = React.lazy( 9 + // @ts-expect-error dynamic import 10 + () => import('react-native-view-shot/src/index'), 11 + ) 12 + 8 13 const SIZE_MULTIPLIER = 5 9 14 10 15 export interface PlaceholderCanvasRef { 11 - capture: () => Promise<string> 16 + capture: () => Promise<string | undefined> 12 17 } 13 18 14 19 // This component is supposed to be invisible to the user. We only need this for ViewShot to have something to ··· 16 21 export const PlaceholderCanvas = React.forwardRef<PlaceholderCanvasRef, {}>( 17 22 function PlaceholderCanvas({}, ref) { 18 23 const {avatar} = useAvatar() 19 - const viewshotRef = React.useRef() 24 + const viewshotRef = React.useRef<ViewShot>(null) 20 25 const Icon = avatar.placeholder.component 21 26 22 27 const styles = React.useMemo( ··· 32 37 ) 33 38 34 39 React.useImperativeHandle(ref, () => ({ 35 - // @ts-ignore this library doesn't have types 36 - capture: viewshotRef.current.capture, 40 + capture: async () => { 41 + if (viewshotRef.current?.capture) { 42 + return await viewshotRef.current.capture() 43 + } 44 + }, 37 45 })) 38 46 39 47 return ( 40 48 <View style={styles.container}> 41 - <ViewShot 49 + <LazyViewShot 42 50 // @ts-ignore this library doesn't have types 43 51 ref={viewshotRef} 44 52 options={{ ··· 60 68 style={{color: 'white'}} 61 69 /> 62 70 </View> 63 - </ViewShot> 71 + </LazyViewShot> 64 72 </View> 65 73 ) 66 74 },
+10 -6
src/screens/Onboarding/StepProfile/index.tsx
··· 10 10 import {useLingui} from '@lingui/react' 11 11 12 12 import {useAnalytics} from '#/lib/analytics/analytics' 13 + import {usePhotoLibraryPermission} from '#/lib/hooks/usePermissions' 14 + import {compressIfNeeded} from '#/lib/media/manip' 15 + import {openCropper} from '#/lib/media/picker' 16 + import {getDataUriSize} from '#/lib/media/util' 17 + import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' 13 18 import {logEvent, useGate} from '#/lib/statsig/statsig' 14 - import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions' 15 - import {compressIfNeeded} from 'lib/media/manip' 16 - import {openCropper} from 'lib/media/picker' 17 - import {getDataUriSize} from 'lib/media/util' 18 - import {useRequestNotificationsPermission} from 'lib/notifications/notifications' 19 - import {isNative, isWeb} from 'platform/detection' 19 + import {isNative, isWeb} from '#/platform/detection' 20 20 import { 21 21 DescriptionText, 22 22 OnboardingControls, ··· 132 132 133 133 const onContinue = React.useCallback(async () => { 134 134 let imageUri = avatar?.image?.path 135 + 136 + // In the event that view-shot didn't load in time and the user pressed continue, this will just be undefined 137 + // and the default avatar will be used. We don't want to block getting through create if this fails for some 138 + // reason 135 139 if (!imageUri || avatar.useCreatedAvatar) { 136 140 imageUri = await canvasRef.current?.capture() 137 141 }
+8 -6
src/screens/Onboarding/state.ts
··· 51 51 | { 52 52 type: 'setProfileStepResults' 53 53 isCreatedAvatar: boolean 54 - image?: OnboardingState['profileStepResults']['image'] 55 - imageUri: string 54 + image: OnboardingState['profileStepResults']['image'] | undefined 55 + imageUri: string | undefined 56 56 imageMime: string 57 - creatorState?: { 58 - emoji: Emoji 59 - backgroundColor: AvatarColor 60 - } 57 + creatorState: 58 + | { 59 + emoji: Emoji 60 + backgroundColor: AvatarColor 61 + } 62 + | undefined 61 63 } 62 64 63 65 export type ApiResponseMap = {
+4 -1
src/screens/Signup/StepInfo/index.tsx
··· 6 6 import type tldts from 'tldts' 7 7 8 8 import {logEvent} from '#/lib/statsig/statsig' 9 + import {isEmailMaybeInvalid} from '#/lib/strings/email' 9 10 import {logger} from '#/logger' 10 - import {isEmailMaybeInvalid} from 'lib/strings/email' 11 11 import {ScreenTransition} from '#/screens/Login/ScreenTransition' 12 12 import {is13, is18, useSignupContext} from '#/screens/Signup/state' 13 13 import {Policies} from '#/screens/Signup/StepInfo/Policies' ··· 59 59 import('tldts/dist/index.cjs.min.js').then(tldts => { 60 60 tldtsRef.current = tldts 61 61 }) 62 + // This will get used in the avatar creator a few steps later, so lets preload it now 63 + // @ts-expect-error - valid path 64 + import('react-native-view-shot/src/index') 62 65 }, []) 63 66 64 67 const onNextPress = () => {