forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {View} from 'react-native'
2import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated'
3import {type AppBskyActorDefs} from '@atproto/api'
4import {Trans} from '@lingui/react/macro'
5
6import {PressableScale} from '#/lib/custom-animations/PressableScale'
7import {sanitizeDisplayName} from '#/lib/strings/display-names'
8import {sanitizeHandle} from '#/lib/strings/handles'
9import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
10import {UserAvatar} from '#/view/com/util/UserAvatar'
11import {atoms as a, platform, useTheme} from '#/alf'
12import {PdsBadge} from '#/components/PdsBadge'
13import {Text} from '#/components/Typography'
14import {useSimpleVerificationState} from '#/components/verification'
15import {VerificationCheck} from '#/components/verification/VerificationCheck'
16
17export function Autocomplete({
18 prefix,
19 onSelect,
20}: {
21 prefix: string
22 onSelect: (item: string) => void
23}) {
24 const t = useTheme()
25
26 const isActive = !!prefix
27 const {data: suggestions, isFetching} = useActorAutocompleteQuery(
28 prefix,
29 true,
30 )
31
32 if (!isActive) return null
33
34 return (
35 <Animated.View
36 entering={FadeInDown.duration(200)}
37 exiting={FadeOut.duration(100)}
38 style={[
39 t.atoms.bg,
40 a.mt_sm,
41 a.border,
42 a.rounded_sm,
43 t.atoms.border_contrast_high,
44 {marginLeft: -62},
45 ]}>
46 {suggestions?.length ? (
47 suggestions.slice(0, 5).map((item, index, arr) => {
48 return (
49 <AutocompleteProfileCard
50 key={item.did}
51 profile={item}
52 itemIndex={index}
53 totalItems={arr.length}
54 onPress={() => {
55 onSelect(item.handle)
56 }}
57 />
58 )
59 })
60 ) : (
61 <Text style={[a.text_md, a.px_sm, a.py_md]}>
62 {isFetching ? <Trans>Loading...</Trans> : <Trans>No result</Trans>}
63 </Text>
64 )}
65 </Animated.View>
66 )
67}
68
69function AutocompleteProfileCard({
70 profile,
71 itemIndex,
72 totalItems,
73 onPress,
74}: {
75 profile: AppBskyActorDefs.ProfileViewBasic
76 itemIndex: number
77 totalItems: number
78 onPress: () => void
79}) {
80 const t = useTheme()
81 const state = useSimpleVerificationState({profile})
82 const displayName = sanitizeDisplayName(
83 profile.displayName || sanitizeHandle(profile.handle),
84 )
85 return (
86 <View
87 style={[
88 itemIndex !== totalItems - 1 && a.border_b,
89 t.atoms.border_contrast_high,
90 a.px_sm,
91 a.py_md,
92 ]}
93 key={profile.did}>
94 <PressableScale
95 testID="autocompleteButton"
96 style={[a.flex_row, a.gap_lg, a.justify_between, a.align_center]}
97 onPress={onPress}
98 accessibilityLabel={`Select ${profile.handle}`}
99 accessibilityHint="">
100 <View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_1]}>
101 <UserAvatar
102 avatar={profile.avatar ?? null}
103 size={24}
104 type={profile.associated?.labeler ? 'labeler' : 'user'}
105 />
106 <View
107 style={[
108 a.flex_row,
109 a.align_center,
110 a.gap_xs,
111 platform({ios: a.flex_1}),
112 ]}>
113 <Text
114 style={[a.text_md, a.font_semi_bold, a.leading_snug]}
115 emoji
116 numberOfLines={1}>
117 {displayName}
118 </Text>
119 <View style={[{marginTop: platform({android: -2})}]}>
120 <PdsBadge did={profile.did} size="sm" />
121 </View>
122 {state.isVerified && (
123 <View
124 style={[
125 {
126 marginTop: platform({android: -2}),
127 },
128 ]}>
129 <VerificationCheck
130 width={12}
131 verifier={state.role === 'verifier'}
132 />
133 </View>
134 )}
135 </View>
136 </View>
137 <Text
138 style={[t.atoms.text_contrast_medium, a.text_right, a.leading_snug]}
139 numberOfLines={1}>
140 {sanitizeHandle(profile.handle, '@')}
141 </Text>
142 </PressableScale>
143 </View>
144 )
145}