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 remove_new_user_progress_guide 273 lines 7.7 kB view raw
1import React, { 2 useCallback, 3 useImperativeHandle, 4 useMemo, 5 useRef, 6 useState, 7} from 'react' 8import {TextInput, View} from 'react-native' 9import {BottomSheetFlatListMethods} from '@discord/bottom-sheet' 10import {msg, Trans} from '@lingui/macro' 11import {useLingui} from '@lingui/react' 12 13import {cleanError} from '#/lib/strings/errors' 14import {isWeb} from '#/platform/detection' 15import { 16 Gif, 17 useFeaturedGifsQuery, 18 useGifSearchQuery, 19} from '#/state/queries/tenor' 20import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' 21import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 22import {atoms as a, useBreakpoints, useTheme} from '#/alf' 23import * as Dialog from '#/components/Dialog' 24import * as TextField from '#/components/forms/TextField' 25import {useThrottledValue} from '#/components/hooks/useThrottledValue' 26import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' 27import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' 28import {Button, ButtonIcon, ButtonText} from '../Button' 29import {ListFooter, ListMaybePlaceholder} from '../Lists' 30import {GifPreview} from './GifSelect.shared' 31 32export function GifSelectDialog({ 33 controlRef, 34 onClose, 35 onSelectGif: onSelectGifProp, 36}: { 37 controlRef: React.RefObject<{open: () => void}> 38 onClose: () => void 39 onSelectGif: (gif: Gif) => void 40}) { 41 const control = Dialog.useDialogControl() 42 43 useImperativeHandle(controlRef, () => ({ 44 open: () => control.open(), 45 })) 46 47 const onSelectGif = useCallback( 48 (gif: Gif) => { 49 control.close(() => onSelectGifProp(gif)) 50 }, 51 [control, onSelectGifProp], 52 ) 53 54 const renderErrorBoundary = useCallback( 55 (error: any) => <DialogError details={String(error)} />, 56 [], 57 ) 58 59 return ( 60 <Dialog.Outer 61 control={control} 62 nativeOptions={{sheet: {snapPoints: ['100%']}}} 63 onClose={onClose}> 64 <Dialog.Handle /> 65 <ErrorBoundary renderError={renderErrorBoundary}> 66 <GifList control={control} onSelectGif={onSelectGif} /> 67 </ErrorBoundary> 68 </Dialog.Outer> 69 ) 70} 71 72function GifList({ 73 control, 74 onSelectGif, 75}: { 76 control: Dialog.DialogControlProps 77 onSelectGif: (gif: Gif) => void 78}) { 79 const {_} = useLingui() 80 const t = useTheme() 81 const {gtMobile} = useBreakpoints() 82 const textInputRef = useRef<TextInput>(null) 83 const listRef = useRef<BottomSheetFlatListMethods>(null) 84 const [undeferredSearch, setSearch] = useState('') 85 const search = useThrottledValue(undeferredSearch, 500) 86 87 const isSearching = search.length > 0 88 89 const trendingQuery = useFeaturedGifsQuery() 90 const searchQuery = useGifSearchQuery(search) 91 92 const { 93 data, 94 fetchNextPage, 95 isFetchingNextPage, 96 hasNextPage, 97 error, 98 isLoading, 99 isError, 100 refetch, 101 } = isSearching ? searchQuery : trendingQuery 102 103 const flattenedData = useMemo(() => { 104 return data?.pages.flatMap(page => page.results) || [] 105 }, [data]) 106 107 const renderItem = useCallback( 108 ({item}: {item: Gif}) => { 109 return <GifPreview gif={item} onSelectGif={onSelectGif} /> 110 }, 111 [onSelectGif], 112 ) 113 114 const onEndReached = React.useCallback(() => { 115 if (isFetchingNextPage || !hasNextPage || error) return 116 fetchNextPage() 117 }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) 118 119 const hasData = flattenedData.length > 0 120 121 const onGoBack = useCallback(() => { 122 if (isSearching) { 123 // clear the input and reset the state 124 textInputRef.current?.clear() 125 setSearch('') 126 } else { 127 control.close() 128 } 129 }, [control, isSearching]) 130 131 const listHeader = useMemo(() => { 132 return ( 133 <View 134 style={[ 135 a.relative, 136 a.mb_lg, 137 a.flex_row, 138 a.align_center, 139 !gtMobile && isWeb && a.gap_md, 140 ]}> 141 {/* cover top corners */} 142 <View 143 style={[ 144 a.absolute, 145 a.inset_0, 146 { 147 borderBottomLeftRadius: 8, 148 borderBottomRightRadius: 8, 149 }, 150 t.atoms.bg, 151 ]} 152 /> 153 154 {!gtMobile && isWeb && ( 155 <Button 156 size="small" 157 variant="ghost" 158 color="secondary" 159 shape="round" 160 onPress={() => control.close()} 161 label={_(msg`Close GIF dialog`)}> 162 <ButtonIcon icon={Arrow} size="md" /> 163 </Button> 164 )} 165 166 <TextField.Root> 167 <TextField.Icon icon={Search} /> 168 <TextField.Input 169 label={_(msg`Search GIFs`)} 170 placeholder={_(msg`Search Tenor`)} 171 onChangeText={text => { 172 setSearch(text) 173 listRef.current?.scrollToOffset({offset: 0, animated: false}) 174 }} 175 returnKeyType="search" 176 clearButtonMode="while-editing" 177 inputRef={textInputRef} 178 maxLength={50} 179 onKeyPress={({nativeEvent}) => { 180 if (nativeEvent.key === 'Escape') { 181 control.close() 182 } 183 }} 184 /> 185 </TextField.Root> 186 </View> 187 ) 188 }, [gtMobile, t.atoms.bg, _, control]) 189 190 return ( 191 <> 192 {gtMobile && <Dialog.Close />} 193 <Dialog.InnerFlatList 194 ref={listRef} 195 key={gtMobile ? '3 cols' : '2 cols'} 196 data={flattenedData} 197 renderItem={renderItem} 198 numColumns={gtMobile ? 3 : 2} 199 columnWrapperStyle={a.gap_sm} 200 ListHeaderComponent={ 201 <> 202 {listHeader} 203 {!hasData && ( 204 <ListMaybePlaceholder 205 isLoading={isLoading} 206 isError={isError} 207 onRetry={refetch} 208 onGoBack={onGoBack} 209 emptyType="results" 210 sideBorders={false} 211 topBorder={false} 212 errorTitle={_(msg`Failed to load GIFs`)} 213 errorMessage={_(msg`There was an issue connecting to Tenor.`)} 214 emptyMessage={ 215 isSearching 216 ? _(msg`No search results found for "${search}".`) 217 : _( 218 msg`No featured GIFs found. There may be an issue with Tenor.`, 219 ) 220 } 221 /> 222 )} 223 </> 224 } 225 stickyHeaderIndices={[0]} 226 onEndReached={onEndReached} 227 onEndReachedThreshold={4} 228 keyExtractor={(item: Gif) => item.id} 229 // @ts-expect-error web only 230 style={isWeb && {minHeight: '100vh'}} 231 keyboardDismissMode="on-drag" 232 ListFooterComponent={ 233 hasData ? ( 234 <ListFooter 235 isFetchingNextPage={isFetchingNextPage} 236 error={cleanError(error)} 237 onRetry={fetchNextPage} 238 style={{borderTopWidth: 0}} 239 /> 240 ) : null 241 } 242 /> 243 </> 244 ) 245} 246 247function DialogError({details}: {details?: string}) { 248 const {_} = useLingui() 249 const control = Dialog.useDialogContext() 250 251 return ( 252 <Dialog.ScrollableInner style={a.gap_md} label={_(msg`An error occured`)}> 253 <Dialog.Close /> 254 <ErrorScreen 255 title={_(msg`Oh no!`)} 256 message={_( 257 msg`There was an unexpected issue in the application. Please let us know if this happened to you!`, 258 )} 259 details={details} 260 /> 261 <Button 262 label={_(msg`Close dialog`)} 263 onPress={() => control.close()} 264 color="primary" 265 size="medium" 266 variant="solid"> 267 <ButtonText> 268 <Trans>Close</Trans> 269 </ButtonText> 270 </Button> 271 </Dialog.ScrollableInner> 272 ) 273}