mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {
3 ActivityIndicator,
4 FlatList as RNFlatList,
5 RefreshControl,
6 StyleProp,
7 View,
8 ViewStyle,
9} from 'react-native'
10import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
11import {msg} from '@lingui/macro'
12import {useLingui} from '@lingui/react'
13
14import {usePalette} from '#/lib/hooks/usePalette'
15import {cleanError} from '#/lib/strings/errors'
16import {s} from '#/lib/styles'
17import {logger} from '#/logger'
18import {useModerationOpts} from '#/state/preferences/moderation-opts'
19import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
20import {atoms as a, useTheme} from '#/alf'
21import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList'
22import * as ListCard from '#/components/ListCard'
23import {Text} from '#/components/Typography'
24import {ErrorMessage} from '../util/error/ErrorMessage'
25import {List} from '../util/List'
26
27const LOADING = {_reactKey: '__loading__'}
28const EMPTY = {_reactKey: '__empty__'}
29const ERROR_ITEM = {_reactKey: '__error__'}
30
31export function MyLists({
32 filter,
33 inline,
34 style,
35 renderItem,
36 testID,
37}: {
38 filter: MyListsFilter
39 inline?: boolean
40 style?: StyleProp<ViewStyle>
41 renderItem?: (list: GraphDefs.ListView, index: number) => JSX.Element
42 testID?: string
43}) {
44 const pal = usePalette('default')
45 const t = useTheme()
46 const {_} = useLingui()
47 const moderationOpts = useModerationOpts()
48 const [isPTRing, setIsPTRing] = React.useState(false)
49 const {data, isFetching, isFetched, isError, error, refetch} =
50 useMyListsQuery(filter)
51 const isEmpty = !isFetching && !data?.length
52
53 const items = React.useMemo(() => {
54 let items: any[] = []
55 if (isError && isEmpty) {
56 items = items.concat([ERROR_ITEM])
57 }
58 if ((!isFetched && isFetching) || !moderationOpts) {
59 items = items.concat([LOADING])
60 } else if (isEmpty) {
61 items = items.concat([EMPTY])
62 } else {
63 items = items.concat(data)
64 }
65 return items
66 }, [isError, isEmpty, isFetched, isFetching, moderationOpts, data])
67
68 let emptyText
69 switch (filter) {
70 case 'curate':
71 emptyText = _(
72 msg`Public, sharable lists which can be used to drive feeds.`,
73 )
74 break
75 case 'mod':
76 emptyText = _(
77 msg`Public, sharable lists of users to mute or block in bulk.`,
78 )
79 break
80 default:
81 emptyText = _(msg`You have no lists.`)
82 break
83 }
84
85 // events
86 // =
87
88 const onRefresh = React.useCallback(async () => {
89 setIsPTRing(true)
90 try {
91 await refetch()
92 } catch (err) {
93 logger.error('Failed to refresh lists', {message: err})
94 }
95 setIsPTRing(false)
96 }, [refetch, setIsPTRing])
97
98 // rendering
99 // =
100
101 const renderItemInner = React.useCallback(
102 ({item, index}: {item: any; index: number}) => {
103 if (item === EMPTY) {
104 return (
105 <View style={[a.flex_1, a.align_center, a.gap_sm, a.px_xl, a.pt_xl]}>
106 <View
107 style={[
108 a.align_center,
109 a.justify_center,
110 a.rounded_full,
111 t.atoms.bg_contrast_25,
112 {
113 width: 32,
114 height: 32,
115 },
116 ]}>
117 <ListIcon size="md" fill={t.atoms.text_contrast_low.color} />
118 </View>
119 <Text
120 style={[
121 a.text_center,
122 a.flex_1,
123 a.text_sm,
124 a.leading_snug,
125 t.atoms.text_contrast_medium,
126 {
127 maxWidth: 200,
128 },
129 ]}>
130 {emptyText}
131 </Text>
132 </View>
133 )
134 } else if (item === ERROR_ITEM) {
135 return (
136 <ErrorMessage
137 message={cleanError(error)}
138 onPressTryAgain={onRefresh}
139 />
140 )
141 } else if (item === LOADING) {
142 return (
143 <View style={{padding: 20}}>
144 <ActivityIndicator />
145 </View>
146 )
147 }
148 return renderItem ? (
149 renderItem(item, index)
150 ) : (
151 <View
152 style={[
153 index !== 0 && a.border_t,
154 t.atoms.border_contrast_low,
155 a.px_lg,
156 a.py_lg,
157 ]}>
158 <ListCard.Default view={item} />
159 </View>
160 )
161 },
162 [t, renderItem, error, onRefresh, emptyText],
163 )
164
165 if (inline) {
166 return (
167 <View testID={testID} style={style}>
168 {items.length > 0 && (
169 <RNFlatList
170 testID={testID ? `${testID}-flatlist` : undefined}
171 data={items}
172 keyExtractor={item => (item.uri ? item.uri : item._reactKey)}
173 renderItem={renderItemInner}
174 refreshControl={
175 <RefreshControl
176 refreshing={isPTRing}
177 onRefresh={onRefresh}
178 tintColor={pal.colors.text}
179 titleColor={pal.colors.text}
180 />
181 }
182 contentContainerStyle={[s.contentContainer]}
183 removeClippedSubviews={true}
184 />
185 )}
186 </View>
187 )
188 } else {
189 return (
190 <View testID={testID} style={style}>
191 {items.length > 0 && (
192 <List
193 testID={testID ? `${testID}-flatlist` : undefined}
194 data={items}
195 keyExtractor={item => (item.uri ? item.uri : item._reactKey)}
196 renderItem={renderItemInner}
197 refreshing={isPTRing}
198 onRefresh={onRefresh}
199 contentContainerStyle={[s.contentContainer]}
200 removeClippedSubviews={true}
201 desktopFixedHeight
202 sideBorders={false}
203 />
204 )}
205 </View>
206 )
207 }
208}