Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
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}