Bluesky app fork with some witchin' additions 馃挮
at main 204 lines 6.6 kB view raw
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}