my fork of the bluesky client
1import {useCallback, useMemo} from 'react'
2import {ActivityIndicator, FlatList, View} from 'react-native'
3import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {cleanError} from '#/lib/strings/errors'
8import {logger} from '#/logger'
9import {useLikedByQuery} from '#/state/queries/post-liked-by'
10import {useResolveUriQuery} from '#/state/queries/resolve-uri'
11import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
12import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
13import {atoms as a, useTheme} from '#/alf'
14import * as Dialog from '#/components/Dialog'
15import {Loader} from '#/components/Loader'
16import {Text} from '#/components/Typography'
17
18interface LikesDialogProps {
19 control: Dialog.DialogOuterProps['control']
20 uri: string
21}
22
23export function LikesDialog(props: LikesDialogProps) {
24 return (
25 <Dialog.Outer control={props.control}>
26 <Dialog.Handle />
27 <LikesDialogInner {...props} />
28 </Dialog.Outer>
29 )
30}
31
32export function LikesDialogInner({control, uri}: LikesDialogProps) {
33 const {_} = useLingui()
34 const t = useTheme()
35
36 const {
37 data: resolvedUri,
38 error: resolveError,
39 isFetched: hasFetchedResolvedUri,
40 } = useResolveUriQuery(uri)
41 const {
42 data,
43 isFetching: isFetchingLikedBy,
44 isFetched: hasFetchedLikedBy,
45 isFetchingNextPage,
46 hasNextPage,
47 fetchNextPage,
48 isError,
49 error: likedByError,
50 } = useLikedByQuery(resolvedUri?.uri)
51
52 const isLoading = !hasFetchedResolvedUri || !hasFetchedLikedBy
53 const likes = useMemo(() => {
54 if (data?.pages) {
55 return data.pages.flatMap(page => page.likes)
56 }
57 return []
58 }, [data])
59
60 const onEndReached = useCallback(async () => {
61 if (isFetchingLikedBy || !hasNextPage || isError) return
62 try {
63 await fetchNextPage()
64 } catch (err) {
65 logger.error('Failed to load more likes', {message: err})
66 }
67 }, [isFetchingLikedBy, hasNextPage, isError, fetchNextPage])
68
69 const renderItem = useCallback(
70 ({item}: {item: GetLikes.Like}) => {
71 return (
72 <ProfileCardWithFollowBtn
73 key={item.actor.did}
74 profile={item.actor}
75 onPress={() => control.close()}
76 />
77 )
78 },
79 [control],
80 )
81
82 return (
83 <Dialog.Inner label={_(msg`Users that have liked this content or profile`)}>
84 <Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_lg]}>
85 <Trans>Liked by</Trans>
86 </Text>
87
88 {isLoading ? (
89 <View style={{minHeight: 300}}>
90 <Loader size="xl" />
91 </View>
92 ) : resolveError || likedByError || !data ? (
93 <ErrorMessage message={cleanError(resolveError || likedByError)} />
94 ) : likes.length === 0 ? (
95 <View style={[t.atoms.bg_contrast_50, a.px_md, a.py_xl, a.rounded_md]}>
96 <Text style={[a.text_center]}>
97 <Trans>
98 Nobody has liked this yet. Maybe you should be the first!
99 </Trans>
100 </Text>
101 </View>
102 ) : (
103 <FlatList
104 data={likes}
105 keyExtractor={item => item.actor.did}
106 onEndReached={onEndReached}
107 renderItem={renderItem}
108 initialNumToRender={15}
109 ListFooterComponent={
110 <ListFooterComponent isFetching={isFetchingNextPage} />
111 }
112 />
113 )}
114
115 <Dialog.Close />
116 </Dialog.Inner>
117 )
118}
119
120function ListFooterComponent({isFetching}: {isFetching: boolean}) {
121 if (isFetching) {
122 return (
123 <View style={a.pt_lg}>
124 <ActivityIndicator />
125 </View>
126 )
127 }
128 return null
129}