mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

at new-image-api 346 lines 10 kB view raw
1import React from 'react' 2import { 3 findNodeHandle, 4 ListRenderItemInfo, 5 StyleProp, 6 View, 7 ViewStyle, 8} from 'react-native' 9import {AppBskyGraphDefs, AppBskyGraphGetActorStarterPacks} from '@atproto/api' 10import {msg, Trans} from '@lingui/macro' 11import {useLingui} from '@lingui/react' 12import {useNavigation} from '@react-navigation/native' 13import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' 14 15import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack' 16import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 17import {useEmail} from '#/lib/hooks/useEmail' 18import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 19import {NavigationProp} from '#/lib/routes/types' 20import {parseStarterPackUri} from '#/lib/strings/starter-pack' 21import {logger} from '#/logger' 22import {List, ListRef} from '#/view/com/util/List' 23import {Text} from '#/view/com/util/text/Text' 24import {atoms as a, ios, useTheme} from '#/alf' 25import {Button, ButtonIcon, ButtonText} from '#/components/Button' 26import {useDialogControl} from '#/components/Dialog' 27import {LinearGradientBackground} from '#/components/LinearGradientBackground' 28import {Loader} from '#/components/Loader' 29import * as Prompt from '#/components/Prompt' 30import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' 31import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog' 32import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '../icons/Plus' 33 34interface SectionRef { 35 scrollToTop: () => void 36} 37 38interface ProfileFeedgensProps { 39 starterPacksQuery: UseInfiniteQueryResult< 40 InfiniteData<AppBskyGraphGetActorStarterPacks.OutputSchema, unknown>, 41 Error 42 > 43 scrollElRef: ListRef 44 headerOffset: number 45 enabled?: boolean 46 style?: StyleProp<ViewStyle> 47 testID?: string 48 setScrollViewTag: (tag: number | null) => void 49 isMe: boolean 50} 51 52function keyExtractor(item: AppBskyGraphDefs.StarterPackView) { 53 return item.uri 54} 55 56export const ProfileStarterPacks = React.forwardRef< 57 SectionRef, 58 ProfileFeedgensProps 59>(function ProfileFeedgensImpl( 60 { 61 starterPacksQuery: query, 62 scrollElRef, 63 headerOffset, 64 enabled, 65 style, 66 testID, 67 setScrollViewTag, 68 isMe, 69 }, 70 ref, 71) { 72 const t = useTheme() 73 const bottomBarOffset = useBottomBarOffset(100) 74 const [isPTRing, setIsPTRing] = React.useState(false) 75 const {data, refetch, isFetching, hasNextPage, fetchNextPage} = query 76 const {isTabletOrDesktop} = useWebMediaQueries() 77 78 const items = data?.pages.flatMap(page => page.starterPacks) 79 80 React.useImperativeHandle(ref, () => ({ 81 scrollToTop: () => {}, 82 })) 83 84 const onRefresh = React.useCallback(async () => { 85 setIsPTRing(true) 86 try { 87 await refetch() 88 } catch (err) { 89 logger.error('Failed to refresh starter packs', {message: err}) 90 } 91 setIsPTRing(false) 92 }, [refetch, setIsPTRing]) 93 94 const onEndReached = React.useCallback(async () => { 95 if (isFetching || !hasNextPage) return 96 97 try { 98 await fetchNextPage() 99 } catch (err) { 100 logger.error('Failed to load more starter packs', {message: err}) 101 } 102 }, [isFetching, hasNextPage, fetchNextPage]) 103 104 React.useEffect(() => { 105 if (enabled && scrollElRef.current) { 106 const nativeTag = findNodeHandle(scrollElRef.current) 107 setScrollViewTag(nativeTag) 108 } 109 }, [enabled, scrollElRef, setScrollViewTag]) 110 111 const renderItem = ({ 112 item, 113 index, 114 }: ListRenderItemInfo<AppBskyGraphDefs.StarterPackView>) => { 115 return ( 116 <View 117 style={[ 118 a.p_lg, 119 (isTabletOrDesktop || index !== 0) && a.border_t, 120 t.atoms.border_contrast_low, 121 ]}> 122 <StarterPackCard starterPack={item} /> 123 </View> 124 ) 125 } 126 127 return ( 128 <View testID={testID} style={style}> 129 <List 130 testID={testID ? `${testID}-flatlist` : undefined} 131 ref={scrollElRef} 132 data={items} 133 renderItem={renderItem} 134 keyExtractor={keyExtractor} 135 refreshing={isPTRing} 136 headerOffset={headerOffset} 137 progressViewOffset={ios(0)} 138 contentContainerStyle={{paddingBottom: headerOffset + bottomBarOffset}} 139 indicatorStyle={t.name === 'light' ? 'black' : 'white'} 140 removeClippedSubviews={true} 141 desktopFixedHeight 142 onEndReached={onEndReached} 143 onRefresh={onRefresh} 144 ListEmptyComponent={Empty} 145 ListFooterComponent={ 146 items?.length !== 0 && isMe ? CreateAnother : undefined 147 } 148 /> 149 </View> 150 ) 151}) 152 153function CreateAnother() { 154 const {_} = useLingui() 155 const t = useTheme() 156 const navigation = useNavigation<NavigationProp>() 157 158 return ( 159 <View 160 style={[ 161 a.pr_md, 162 a.pt_lg, 163 a.gap_lg, 164 a.border_t, 165 t.atoms.border_contrast_low, 166 ]}> 167 <Button 168 label={_(msg`Create a starter pack`)} 169 variant="solid" 170 color="secondary" 171 size="small" 172 style={[a.self_center]} 173 onPress={() => navigation.navigate('StarterPackWizard')}> 174 <ButtonText> 175 <Trans>Create another</Trans> 176 </ButtonText> 177 <ButtonIcon icon={Plus} position="right" /> 178 </Button> 179 </View> 180 ) 181} 182 183function Empty() { 184 const {_} = useLingui() 185 const t = useTheme() 186 const navigation = useNavigation<NavigationProp>() 187 const confirmDialogControl = useDialogControl() 188 const followersDialogControl = useDialogControl() 189 const errorDialogControl = useDialogControl() 190 191 const {needsEmailVerification} = useEmail() 192 const verifyEmailControl = useDialogControl() 193 194 const [isGenerating, setIsGenerating] = React.useState(false) 195 196 const {mutate: generateStarterPack} = useGenerateStarterPackMutation({ 197 onSuccess: ({uri}) => { 198 const parsed = parseStarterPackUri(uri) 199 if (parsed) { 200 navigation.push('StarterPack', { 201 name: parsed.name, 202 rkey: parsed.rkey, 203 }) 204 } 205 setIsGenerating(false) 206 }, 207 onError: e => { 208 logger.error('Failed to generate starter pack', {safeMessage: e}) 209 setIsGenerating(false) 210 if (e.name === 'NOT_ENOUGH_FOLLOWERS') { 211 followersDialogControl.open() 212 } else { 213 errorDialogControl.open() 214 } 215 }, 216 }) 217 218 const generate = () => { 219 setIsGenerating(true) 220 generateStarterPack() 221 } 222 223 return ( 224 <LinearGradientBackground 225 style={[ 226 a.px_lg, 227 a.py_lg, 228 a.justify_between, 229 a.gap_lg, 230 a.shadow_lg, 231 {marginTop: 2}, 232 ]}> 233 <View style={[a.gap_xs]}> 234 <Text 235 style={[ 236 a.font_bold, 237 a.text_lg, 238 t.atoms.text_contrast_medium, 239 {color: 'white'}, 240 ]}> 241 <Trans>You haven't created a starter pack yet!</Trans> 242 </Text> 243 <Text style={[a.text_md, {color: 'white'}]}> 244 <Trans> 245 Starter packs let you easily share your favorite feeds and people 246 with your friends. 247 </Trans> 248 </Text> 249 </View> 250 <View style={[a.flex_row, a.gap_md, {marginLeft: 'auto'}]}> 251 <Button 252 label={_(msg`Create a starter pack for me`)} 253 variant="ghost" 254 color="primary" 255 size="small" 256 disabled={isGenerating} 257 onPress={() => { 258 if (needsEmailVerification) { 259 verifyEmailControl.open() 260 } else { 261 confirmDialogControl.open() 262 } 263 }} 264 style={{backgroundColor: 'transparent'}}> 265 <ButtonText style={{color: 'white'}}> 266 <Trans>Make one for me</Trans> 267 </ButtonText> 268 {isGenerating && <Loader size="md" />} 269 </Button> 270 <Button 271 label={_(msg`Create a starter pack`)} 272 variant="ghost" 273 color="primary" 274 size="small" 275 disabled={isGenerating} 276 onPress={() => { 277 if (needsEmailVerification) { 278 verifyEmailControl.open() 279 } else { 280 navigation.navigate('StarterPackWizard') 281 } 282 }} 283 style={{ 284 backgroundColor: 'white', 285 borderColor: 'white', 286 width: 100, 287 }} 288 hoverStyle={[{backgroundColor: '#dfdfdf'}]}> 289 <ButtonText> 290 <Trans>Create</Trans> 291 </ButtonText> 292 </Button> 293 </View> 294 295 <Prompt.Outer control={confirmDialogControl}> 296 <Prompt.TitleText> 297 <Trans>Generate a starter pack</Trans> 298 </Prompt.TitleText> 299 <Prompt.DescriptionText> 300 <Trans> 301 Bluesky will choose a set of recommended accounts from people in 302 your network. 303 </Trans> 304 </Prompt.DescriptionText> 305 <Prompt.Actions> 306 <Prompt.Action 307 color="primary" 308 cta={_(msg`Choose for me`)} 309 onPress={generate} 310 /> 311 <Prompt.Action 312 color="secondary" 313 cta={_(msg`Let me choose`)} 314 onPress={() => { 315 navigation.navigate('StarterPackWizard') 316 }} 317 /> 318 </Prompt.Actions> 319 </Prompt.Outer> 320 <Prompt.Basic 321 control={followersDialogControl} 322 title={_(msg`Oops!`)} 323 description={_( 324 msg`You must be following at least seven other people to generate a starter pack.`, 325 )} 326 onConfirm={() => {}} 327 showCancel={false} 328 /> 329 <Prompt.Basic 330 control={errorDialogControl} 331 title={_(msg`Oops!`)} 332 description={_( 333 msg`An error occurred while generating your starter pack. Want to try again?`, 334 )} 335 onConfirm={generate} 336 confirmButtonCta={_(msg`Retry`)} 337 /> 338 <VerifyEmailDialog 339 reasonText={_( 340 msg`Before creating a starter pack, you must first verify your email.`, 341 )} 342 control={verifyEmailControl} 343 /> 344 </LinearGradientBackground> 345 ) 346}