about summary refs log tree commit diff
path: root/src/components/LikesDialog.tsx
blob: 4c68596f7358b512d8a3ce83977d17d6dd5c88fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import React, {useCallback, useMemo} from 'react'
import {ActivityIndicator, FlatList, View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {atoms as a, useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'

interface LikesDialogProps {
  control: Dialog.DialogOuterProps['control']
  uri: string
}

export function LikesDialog(props: LikesDialogProps) {
  return (
    <Dialog.Outer control={props.control}>
      <Dialog.Handle />
      <LikesDialogInner {...props} />
    </Dialog.Outer>
  )
}

export function LikesDialogInner({control, uri}: LikesDialogProps) {
  const {_} = useLingui()
  const t = useTheme()

  const {
    data: resolvedUri,
    error: resolveError,
    isFetched: hasFetchedResolvedUri,
  } = useResolveUriQuery(uri)
  const {
    data,
    isFetching: isFetchingLikedBy,
    isFetched: hasFetchedLikedBy,
    isFetchingNextPage,
    hasNextPage,
    fetchNextPage,
    isError,
    error: likedByError,
  } = useLikedByQuery(resolvedUri?.uri)

  const isLoading = !hasFetchedResolvedUri || !hasFetchedLikedBy
  const likes = useMemo(() => {
    if (data?.pages) {
      return data.pages.flatMap(page => page.likes)
    }
    return []
  }, [data])

  const onEndReached = useCallback(async () => {
    if (isFetchingLikedBy || !hasNextPage || isError) return
    try {
      await fetchNextPage()
    } catch (err) {
      logger.error('Failed to load more likes', {message: err})
    }
  }, [isFetchingLikedBy, hasNextPage, isError, fetchNextPage])

  const renderItem = useCallback(
    ({item}: {item: GetLikes.Like}) => {
      return (
        <ProfileCardWithFollowBtn
          key={item.actor.did}
          profile={item.actor}
          onPress={() => control.close()}
        />
      )
    },
    [control],
  )

  return (
    <Dialog.Inner label={_(msg`Users that have liked this content or profile`)}>
      <Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_lg]}>
        <Trans>Liked by</Trans>
      </Text>

      {isLoading ? (
        <View style={{minHeight: 300}}>
          <Loader size="xl" />
        </View>
      ) : resolveError || likedByError || !data ? (
        <ErrorMessage message={cleanError(resolveError || likedByError)} />
      ) : likes.length === 0 ? (
        <View style={[t.atoms.bg_contrast_50, a.px_md, a.py_xl, a.rounded_md]}>
          <Text style={[a.text_center]}>
            <Trans>
              Nobody has liked this yet. Maybe you should be the first!
            </Trans>
          </Text>
        </View>
      ) : (
        <FlatList
          data={likes}
          keyExtractor={item => item.actor.did}
          onEndReached={onEndReached}
          renderItem={renderItem}
          initialNumToRender={15}
          ListFooterComponent={
            <ListFooterComponent isFetching={isFetchingNextPage} />
          }
        />
      )}

      <Dialog.Close />
    </Dialog.Inner>
  )
}

function ListFooterComponent({isFetching}: {isFetching: boolean}) {
  if (isFetching) {
    return (
      <View style={a.pt_lg}>
        <ActivityIndicator />
      </View>
    )
  }
  return null
}