Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 195 lines 6.3 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 {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}