forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {Pressable, ScrollView, View} from 'react-native'
2import {moderateProfile, type ModerationOpts} from '@atproto/api'
3import {Trans, useLingui} from '@lingui/react/macro'
4
5import {createHitslop, HITSLOP_10} from '#/lib/constants'
6import {makeProfileLink} from '#/lib/routes/links'
7import {sanitizeDisplayName} from '#/lib/strings/display-names'
8import {sanitizeHandle} from '#/lib/strings/handles'
9import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
10import {useModerationOpts} from '#/state/preferences/moderation-opts'
11import {UserAvatar} from '#/view/com/util/UserAvatar'
12import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
13import {atoms as a} from '#/alf'
14import {Button, ButtonIcon} from '#/components/Button'
15import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
16import * as Layout from '#/components/Layout'
17import {Link} from '#/components/Link'
18import {Text} from '#/components/Typography'
19import {useSimpleVerificationState} from '#/components/verification'
20import {VerificationCheck} from '#/components/verification/VerificationCheck'
21import {useAnalytics} from '#/analytics'
22import type * as bsky from '#/types/bsky'
23
24export function SearchHistory({
25 searchHistory,
26 selectedProfiles,
27 onItemClick,
28 onProfileClick,
29 onRemoveItemClick,
30 onRemoveProfileClick,
31}: {
32 searchHistory: string[]
33 selectedProfiles: bsky.profile.AnyProfileView[]
34 onItemClick: (item: string) => void
35 onProfileClick: (profile: bsky.profile.AnyProfileView) => void
36 onRemoveItemClick: (item: string) => void
37 onRemoveProfileClick: (profile: bsky.profile.AnyProfileView) => void
38}) {
39 const ax = useAnalytics()
40 const {t: l} = useLingui()
41 const moderationOpts = useModerationOpts()
42
43 const enableSquareButtons = useEnableSquareButtons()
44
45 return (
46 <Layout.Content
47 keyboardDismissMode="interactive"
48 keyboardShouldPersistTaps="handled">
49 <View style={[a.w_full, a.gap_md]}>
50 {(searchHistory.length > 0 || selectedProfiles.length > 0) && (
51 <View style={[a.px_lg, a.pt_sm]}>
52 <Text style={[a.text_md, a.font_semi_bold]}>
53 <Trans>Recent searches</Trans>
54 </Text>
55 </View>
56 )}
57
58 {selectedProfiles.length > 0 && (
59 <View>
60 <BlockDrawerGesture>
61 <ScrollView
62 horizontal
63 keyboardShouldPersistTaps="handled"
64 showsHorizontalScrollIndicator={false}
65 contentContainerStyle={[
66 a.px_lg,
67 a.flex_row,
68 a.flex_nowrap,
69 a.gap_xl,
70 ]}>
71 {moderationOpts &&
72 selectedProfiles.map((profile, index) => (
73 <RecentProfileItem
74 key={profile.did}
75 profile={profile}
76 moderationOpts={moderationOpts}
77 onPress={() => {
78 ax.metric('search:recent:press', {
79 profileDid: profile.did,
80 position: index,
81 })
82 onProfileClick(profile)
83 }}
84 onRemove={() => onRemoveProfileClick(profile)}
85 />
86 ))}
87 </ScrollView>
88 </BlockDrawerGesture>
89 </View>
90 )}
91
92 {searchHistory.length > 0 && (
93 <View style={[a.px_lg, a.pt_sm]}>
94 {searchHistory.slice(0, 5).map((historyItem, index) => (
95 <View key={index} style={[a.flex_row, a.align_center]}>
96 <Pressable
97 accessibilityRole="button"
98 onPress={() => {
99 ax.metric('search:query', {
100 source: 'history',
101 })
102 onItemClick(historyItem)
103 }}
104 hitSlop={HITSLOP_10}
105 style={[a.flex_1, a.py_sm]}>
106 <Text style={[a.text_md]}>{historyItem}</Text>
107 </Pressable>
108 <Button
109 label={l`Remove ${historyItem}`}
110 onPress={() => onRemoveItemClick(historyItem)}
111 size="small"
112 variant="ghost"
113 color="secondary"
114 shape={enableSquareButtons ? 'square' : 'round'}>
115 <ButtonIcon icon={XIcon} />
116 </Button>
117 </View>
118 ))}
119 </View>
120 )}
121 </View>
122 </Layout.Content>
123 )
124}
125
126function RecentProfileItem({
127 profile,
128 moderationOpts,
129 onPress,
130 onRemove,
131}: {
132 profile: bsky.profile.AnyProfileView
133 moderationOpts: ModerationOpts
134 onPress: () => void
135 onRemove: () => void
136}) {
137 const {t: l} = useLingui()
138 const width = 80
139
140 const moderation = moderateProfile(profile, moderationOpts)
141 const name = sanitizeDisplayName(
142 profile.displayName || sanitizeHandle(profile.handle),
143 moderation.ui('displayName'),
144 )
145 const verification = useSimpleVerificationState({profile})
146
147 const enableSquareButtons = useEnableSquareButtons()
148
149 return (
150 <View style={[a.relative]}>
151 <Link
152 to={makeProfileLink(profile)}
153 label={profile.handle}
154 onPress={onPress}
155 style={[
156 a.flex_col,
157 a.align_center,
158 a.gap_xs,
159 {
160 width,
161 },
162 ]}>
163 <UserAvatar
164 avatar={profile.avatar}
165 type={profile.associated?.labeler ? 'labeler' : 'user'}
166 size={width - 8}
167 moderation={moderation.ui('avatar')}
168 />
169 <View style={[a.flex_row, a.align_center, a.justify_center, a.w_full]}>
170 <Text emoji style={[a.text_xs, a.leading_snug]} numberOfLines={1}>
171 {name}
172 </Text>
173 {verification.showBadge && (
174 <View style={[a.pl_2xs]}>
175 <VerificationCheck
176 width={10}
177 verifier={verification.role === 'verifier'}
178 />
179 </View>
180 )}
181 </View>
182 </Link>
183 <Button
184 label={l`Remove profile`}
185 hitSlop={createHitslop(6)}
186 size="tiny"
187 variant="outline"
188 color="secondary"
189 shape={enableSquareButtons ? 'square' : 'round'}
190 onPress={onRemove}
191 style={[
192 a.absolute,
193 {
194 top: 0,
195 right: 0,
196 height: 18,
197 width: 18,
198 },
199 ]}>
200 <ButtonIcon icon={XIcon} />
201 </Button>
202 </View>
203 )
204}