Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 153 lines 5.0 kB view raw
1import React from 'react' 2import {Dimensions, ScrollView, View} from 'react-native' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Plural} from '@lingui/react/macro' 6 7import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 8import {type FeedPostSlice} from '#/state/queries/post-feed' 9import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' 10import {atoms as a, useTheme} from '#/alf' 11import {Button, ButtonIcon} from '#/components/Button' 12import { 13 ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft, 14 ChevronRight_Stroke2_Corner0_Rounded as ChevronRight, 15} from '#/components/icons/Chevron' 16import {Text} from '#/components/Typography' 17import {PostFeedItem} from './PostFeedItem' 18 19const CARD_WIDTH = 320 20const CARD_INTERVAL = CARD_WIDTH + a.gap_md.gap 21 22export function PostFeedItemCarousel({items}: {items: FeedPostSlice[]}) { 23 const t = useTheme() 24 const {_} = useLingui() 25 const ref = React.useRef<ScrollView>(null) 26 const [scrollX, setScrollX] = React.useState(0) 27 28 const enableSquareButtons = useEnableSquareButtons() 29 30 const scrollTo = React.useCallback( 31 (item: number) => { 32 setScrollX(item) 33 34 ref.current?.scrollTo({ 35 x: item * CARD_INTERVAL, 36 y: 0, 37 animated: true, 38 }) 39 }, 40 [ref], 41 ) 42 43 const scrollLeft = React.useCallback(() => { 44 const newPos = scrollX > 0 ? scrollX - 1 : items.length - 1 45 scrollTo(newPos) 46 }, [scrollTo, scrollX, items.length]) 47 48 const scrollRight = React.useCallback(() => { 49 const newPos = scrollX < items.length - 1 ? scrollX + 1 : 0 50 scrollTo(newPos) 51 }, [scrollTo, scrollX, items.length]) 52 53 return ( 54 <View 55 style={[a.border_t, t.atoms.border_contrast_low, t.atoms.bg_contrast_25]}> 56 <View 57 style={[ 58 a.py_lg, 59 a.px_md, 60 a.pb_xs, 61 a.flex_row, 62 a.align_center, 63 a.justify_between, 64 ]}> 65 <Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}> 66 {items.length}{' '} 67 <Plural value={items.length} one="repost" other="reposts" /> 68 </Text> 69 <View style={[a.gap_md, a.flex_row, a.align_end]}> 70 <Button 71 label={_(msg`Scroll carousel left`)} 72 size="tiny" 73 variant="ghost" 74 color="secondary" 75 shape={enableSquareButtons ? 'square' : 'round'} 76 onPress={() => scrollLeft()}> 77 <ButtonIcon icon={ChevronLeft} /> 78 </Button> 79 <Button 80 label={_(msg`Scroll carousel right`)} 81 size="tiny" 82 variant="ghost" 83 color="secondary" 84 shape={enableSquareButtons ? 'square' : 'round'} 85 onPress={() => scrollRight()}> 86 <ButtonIcon icon={ChevronRight} /> 87 </Button> 88 </View> 89 </View> 90 <BlockDrawerGesture> 91 <View> 92 <ScrollView 93 horizontal 94 snapToInterval={CARD_INTERVAL} 95 decelerationRate="fast" 96 /* TODO: figure out how to not get this to break on the last item 97 onScroll={e => { 98 setScrollX(Math.floor(e.nativeEvent.contentOffset.x / CARD_INTERVAL)) 99 }} 100*/ 101 ref={ref}> 102 <View 103 style={[ 104 a.px_md, 105 a.pt_sm, 106 a.pb_lg, 107 a.flex_row, 108 a.gap_md, 109 a.align_start, 110 ]}> 111 {items.map(slice => { 112 const item = slice.items[0] 113 114 return ( 115 <View 116 style={[ 117 { 118 maxHeight: Dimensions.get('window').height * 0.65, 119 width: CARD_WIDTH, 120 }, 121 a.rounded_md, 122 a.border, 123 t.atoms.bg, 124 t.atoms.border_contrast_low, 125 a.flex_shrink_0, 126 a.overflow_hidden, 127 ]} 128 key={item._reactKey}> 129 <PostFeedItem 130 post={item.post} 131 record={item.record} 132 reason={slice.reason} 133 feedContext={slice.feedContext} 134 moderation={item.moderation} 135 parentAuthor={item.parentAuthor} 136 isParentBlocked={item.isParentBlocked} 137 isParentNotFound={item.isParentNotFound} 138 hideTopBorder={true} 139 isCarouselItem={true} 140 rootPost={slice.items[0].post} 141 showReplyTo={false} 142 reqId={undefined} 143 /> 144 </View> 145 ) 146 })} 147 </View> 148 </ScrollView> 149 </View> 150 </BlockDrawerGesture> 151 </View> 152 ) 153}