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