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