Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at post-text-option 237 lines 6.7 kB view raw
1import {useCallback, useEffect, useMemo} from 'react' 2import {ScrollView, View} from 'react-native' 3import {AppBskyEmbedVideo, AtUri} from '@atproto/api' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useQueryClient} from '@tanstack/react-query' 7 8import {VIDEO_FEED_URI} from '#/lib/constants' 9import {makeCustomFeedLink} from '#/lib/routes/links' 10import {logEvent} from '#/lib/statsig/statsig' 11import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 12import {useTrendingSettingsApi} from '#/state/preferences/trending' 13import {usePostFeedQuery} from '#/state/queries/post-feed' 14import {RQKEY} from '#/state/queries/post-feed' 15import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' 16import {atoms as a, useGutters, useTheme} from '#/alf' 17import {Button, ButtonIcon} from '#/components/Button' 18import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 19import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 20import {Link} from '#/components/Link' 21import * as Prompt from '#/components/Prompt' 22import {Text} from '#/components/Typography' 23import { 24 CompactVideoPostCard, 25 CompactVideoPostCardPlaceholder, 26} from '#/components/VideoPostCard' 27 28const CARD_WIDTH = 108 29 30const FEED_DESC = `feedgen|${VIDEO_FEED_URI}` 31const FEED_PARAMS: { 32 feedCacheKey: 'discover' 33} = { 34 feedCacheKey: 'discover', 35} 36 37export function TrendingVideos() { 38 const t = useTheme() 39 const {_} = useLingui() 40 const gutters = useGutters([0, 'base']) 41 const {data, isLoading, error} = usePostFeedQuery(FEED_DESC, FEED_PARAMS) 42 43 // Refetch on unmount if nothing else is using this query. 44 const queryClient = useQueryClient() 45 useEffect(() => { 46 return () => { 47 const query = queryClient 48 .getQueryCache() 49 .find({queryKey: RQKEY(FEED_DESC, FEED_PARAMS)}) 50 if (query && query.getObserversCount() <= 1) { 51 query.fetch() 52 } 53 } 54 }, [queryClient]) 55 56 const {setTrendingVideoDisabled} = useTrendingSettingsApi() 57 const trendingPrompt = Prompt.usePromptControl() 58 59 const onConfirmHide = useCallback(() => { 60 setTrendingVideoDisabled(true) 61 logEvent('trendingVideos:hide', {context: 'interstitial:discover'}) 62 }, [setTrendingVideoDisabled]) 63 64 if (error) { 65 return null 66 } 67 68 return ( 69 <View 70 style={[ 71 a.pt_sm, 72 a.pb_lg, 73 a.border_t, 74 a.overflow_hidden, 75 t.atoms.border_contrast_low, 76 t.atoms.bg_contrast_25, 77 ]}> 78 <View 79 style={[ 80 gutters, 81 a.pb_sm, 82 a.flex_row, 83 a.align_center, 84 a.justify_between, 85 ]}> 86 <Text style={[a.text_sm, a.font_semi_bold, a.leading_snug]}> 87 <Trans>Trending Videos</Trans> 88 </Text> 89 <Button 90 label={_(msg`Dismiss this section`)} 91 size="tiny" 92 variant="solid" 93 color="secondary" 94 shape="square" 95 onPress={() => trendingPrompt.open()}> 96 <ButtonIcon icon={X} size="sm" /> 97 </Button> 98 </View> 99 100 <BlockDrawerGesture> 101 <ScrollView 102 horizontal 103 showsHorizontalScrollIndicator={false} 104 decelerationRate="fast" 105 snapToInterval={CARD_WIDTH + a.gap_md.gap} 106 style={[a.overflow_visible]}> 107 <View 108 style={[ 109 a.flex_row, 110 a.gap_md, 111 { 112 paddingLeft: gutters.paddingLeft, 113 paddingRight: gutters.paddingRight, 114 }, 115 ]}> 116 {isLoading ? ( 117 Array(10) 118 .fill(0) 119 .map((_, i) => ( 120 <View key={i} style={[{width: CARD_WIDTH}]}> 121 <CompactVideoPostCardPlaceholder /> 122 </View> 123 )) 124 ) : error || !data ? ( 125 <Text> 126 <Trans>Whoops! Trending videos failed to load.</Trans> 127 </Text> 128 ) : ( 129 <VideoCards data={data} /> 130 )} 131 </View> 132 </ScrollView> 133 </BlockDrawerGesture> 134 135 <Prompt.Basic 136 control={trendingPrompt} 137 title={_(msg`Hide trending videos?`)} 138 description={_(msg`You can update this later from your settings.`)} 139 confirmButtonCta={_(msg`Hide`)} 140 onConfirm={onConfirmHide} 141 /> 142 </View> 143 ) 144} 145 146function VideoCards({ 147 data, 148}: { 149 data: Exclude<ReturnType<typeof usePostFeedQuery>['data'], undefined> 150}) { 151 const items = useMemo(() => { 152 return data.pages 153 .flatMap(page => page.slices) 154 .map(slice => slice.items[0]) 155 .filter(Boolean) 156 .filter(item => AppBskyEmbedVideo.isView(item.post.embed)) 157 .slice(0, 8) 158 }, [data]) 159 160 return ( 161 <> 162 {items.map(item => ( 163 <View key={item.post.uri} style={[{width: CARD_WIDTH}]}> 164 <CompactVideoPostCard 165 post={item.post} 166 moderation={item.moderation} 167 sourceContext={{ 168 type: 'feedgen', 169 uri: VIDEO_FEED_URI, 170 sourceInterstitial: 'discover', 171 }} 172 onInteract={() => { 173 logEvent('videoCard:click', { 174 context: 'interstitial:discover', 175 }) 176 }} 177 /> 178 </View> 179 ))} 180 181 <ViewMoreCard /> 182 </> 183 ) 184} 185 186function ViewMoreCard() { 187 const t = useTheme() 188 const {_} = useLingui() 189 190 const href = useMemo(() => { 191 const urip = new AtUri(VIDEO_FEED_URI) 192 return makeCustomFeedLink(urip.host, urip.rkey, undefined, 'discover') 193 }, []) 194 195 const enableSquareButtons = useEnableSquareButtons() 196 197 return ( 198 <View style={[{width: CARD_WIDTH * 2}]}> 199 <Link 200 to={href} 201 label={_(msg`View more`)} 202 style={[ 203 a.justify_center, 204 a.align_center, 205 a.flex_1, 206 a.rounded_lg, 207 a.border, 208 t.atoms.border_contrast_low, 209 t.atoms.bg, 210 t.atoms.shadow_sm, 211 ]}> 212 {({pressed}) => ( 213 <View 214 style={[ 215 a.flex_row, 216 a.align_center, 217 a.gap_md, 218 { 219 opacity: pressed ? 0.6 : 1, 220 }, 221 ]}> 222 <Text style={[a.text_md]}> 223 <Trans>View more</Trans> 224 </Text> 225 <Button 226 color="primary" 227 size="small" 228 shape={enableSquareButtons ? 'square' : 'round'} 229 label={_(msg`View more trending videos`)}> 230 <ButtonIcon icon={ChevronRight} /> 231 </Button> 232 </View> 233 )} 234 </Link> 235 </View> 236 ) 237}