Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

add profiles to search history (#4169)

* add profiles to search history

* increasing horizontal padding slightly

* tightening up styling

* fixing navigation issue

* making corrections

* Make the search history profiles a little smaller

* bug stomping

* Fix issues

* Persist taps

* Rm unnecessary

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by

Ryan Skinner
Paul Frazee
Dan Abramov
and committed by
GitHub
e7968bc8 6f158997

+195 -7
+195 -7
src/view/screens/Search/Search.tsx
··· 1 1 import React from 'react' 2 2 import { 3 3 ActivityIndicator, 4 + Image, 5 + ImageStyle, 4 6 Platform, 5 7 Pressable, 8 + StyleProp, 6 9 StyleSheet, 7 10 TextInput, 8 11 View, ··· 18 21 import {useFocusEffect, useNavigation} from '@react-navigation/native' 19 22 20 23 import {useAnalytics} from '#/lib/analytics/analytics' 24 + import {createHitslop} from '#/lib/constants' 21 25 import {HITSLOP_10} from '#/lib/constants' 22 26 import {usePalette} from '#/lib/hooks/usePalette' 23 27 import {MagnifyingGlassIcon} from '#/lib/icons' 28 + import {makeProfileLink} from '#/lib/routes/links' 24 29 import {NavigationProp} from '#/lib/routes/types' 25 30 import {augmentSearchQuery} from '#/lib/strings/helpers' 26 31 import {s} from '#/lib/styles' ··· 46 51 import {TabBar} from '#/view/com/pager/TabBar' 47 52 import {Post} from '#/view/com/post/Post' 48 53 import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' 54 + import {Link} from '#/view/com/util/Link' 49 55 import {List} from '#/view/com/util/List' 50 56 import {Text} from '#/view/com/util/text/Text' 51 57 import {CenteredView, ScrollView} from '#/view/com/util/Views' ··· 488 494 489 495 const [showAutocomplete, setShowAutocomplete] = React.useState(false) 490 496 const [searchHistory, setSearchHistory] = React.useState<string[]>([]) 497 + const [selectedProfiles, setSelectedProfiles] = React.useState< 498 + AppBskyActorDefs.ProfileViewBasic[] 499 + >([]) 491 500 492 501 useFocusEffect( 493 502 useNonReactiveCallback(() => { ··· 503 512 const history = await AsyncStorage.getItem('searchHistory') 504 513 if (history !== null) { 505 514 setSearchHistory(JSON.parse(history)) 515 + } 516 + const profiles = await AsyncStorage.getItem('selectedProfiles') 517 + if (profiles !== null) { 518 + setSelectedProfiles(JSON.parse(profiles)) 506 519 } 507 520 } catch (e: any) { 508 521 logger.error('Failed to load search history', {message: e}) ··· 562 575 [searchHistory, setSearchHistory], 563 576 ) 564 577 578 + const updateSelectedProfiles = React.useCallback( 579 + async (profile: AppBskyActorDefs.ProfileViewBasic) => { 580 + let newProfiles = [ 581 + profile, 582 + ...selectedProfiles.filter(p => p.did !== profile.did), 583 + ] 584 + 585 + if (newProfiles.length > 5) { 586 + newProfiles = newProfiles.slice(0, 5) 587 + } 588 + 589 + setSelectedProfiles(newProfiles) 590 + try { 591 + await AsyncStorage.setItem( 592 + 'selectedProfiles', 593 + JSON.stringify(newProfiles), 594 + ) 595 + } catch (e: any) { 596 + logger.error('Failed to save selected profiles', {message: e}) 597 + } 598 + }, 599 + [selectedProfiles, setSelectedProfiles], 600 + ) 601 + 565 602 const navigateToItem = React.useCallback( 566 603 (item: string) => { 567 604 scrollToTopWeb() ··· 598 635 [navigateToItem], 599 636 ) 600 637 638 + const handleProfileClick = React.useCallback( 639 + (profile: AppBskyActorDefs.ProfileViewBasic) => { 640 + // Slight delay to avoid updating during push nav animation. 641 + setTimeout(() => { 642 + updateSelectedProfiles(profile) 643 + }, 400) 644 + }, 645 + [updateSelectedProfiles], 646 + ) 647 + 601 648 const onSoftReset = React.useCallback(() => { 602 649 if (isWeb) { 603 650 // Empty params resets the URL to be /search rather than /search?q= ··· 629 676 [searchHistory], 630 677 ) 631 678 679 + const handleRemoveProfile = React.useCallback( 680 + (profileToRemove: AppBskyActorDefs.ProfileViewBasic) => { 681 + const updatedProfiles = selectedProfiles.filter( 682 + profile => profile.did !== profileToRemove.did, 683 + ) 684 + setSelectedProfiles(updatedProfiles) 685 + AsyncStorage.setItem( 686 + 'selectedProfiles', 687 + JSON.stringify(updatedProfiles), 688 + ).catch(e => { 689 + logger.error('Failed to update selected profiles', {message: e}) 690 + }) 691 + }, 692 + [selectedProfiles], 693 + ) 694 + 632 695 return ( 633 696 <View style={isWeb ? null : {flex: 1}}> 634 697 <CenteredView ··· 689 752 searchText={searchText} 690 753 onSubmit={onSubmit} 691 754 onResultPress={onAutocompleteResultPress} 755 + onProfileClick={handleProfileClick} 692 756 /> 693 757 ) : ( 694 758 <SearchHistory 695 759 searchHistory={searchHistory} 760 + selectedProfiles={selectedProfiles} 696 761 onItemClick={handleHistoryItemClick} 762 + onProfileClick={handleProfileClick} 697 763 onRemoveItemClick={handleRemoveHistoryItem} 764 + onRemoveProfileClick={handleRemoveProfile} 698 765 /> 699 766 )} 700 767 </View> ··· 814 881 searchText, 815 882 onSubmit, 816 883 onResultPress, 884 + onProfileClick, 817 885 }: { 818 886 isAutocompleteFetching: boolean 819 887 autocompleteData: AppBskyActorDefs.ProfileViewBasic[] | undefined 820 888 searchText: string 821 889 onSubmit: () => void 822 890 onResultPress: () => void 891 + onProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void 823 892 }): React.ReactNode => { 824 893 const moderationOpts = useModerationOpts() 825 894 const {_} = useLingui() ··· 850 919 key={item.did} 851 920 profile={item} 852 921 moderation={moderateProfile(item, moderationOpts)} 853 - onPress={onResultPress} 922 + onPress={() => { 923 + onProfileClick(item) 924 + onResultPress() 925 + }} 854 926 /> 855 927 ))} 856 928 <View style={{height: 200}} /> ··· 861 933 } 862 934 AutocompleteResults = React.memo(AutocompleteResults) 863 935 936 + function truncateText(text: string, maxLength: number) { 937 + if (text.length > maxLength) { 938 + return text.substring(0, maxLength) + '...' 939 + } 940 + return text 941 + } 942 + 864 943 function SearchHistory({ 865 944 searchHistory, 945 + selectedProfiles, 866 946 onItemClick, 947 + onProfileClick, 867 948 onRemoveItemClick, 949 + onRemoveProfileClick, 868 950 }: { 869 951 searchHistory: string[] 952 + selectedProfiles: AppBskyActorDefs.ProfileViewBasic[] 870 953 onItemClick: (item: string) => void 954 + onProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void 871 955 onRemoveItemClick: (item: string) => void 956 + onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void 872 957 }) { 873 - const {isTabletOrDesktop} = useWebMediaQueries() 958 + const {isTabletOrDesktop, isMobile} = useWebMediaQueries() 874 959 const pal = usePalette('default') 960 + 875 961 return ( 876 962 <CenteredView 877 963 sideBorders={isTabletOrDesktop} ··· 880 966 height: isWeb ? '100vh' : undefined, 881 967 }}> 882 968 <View style={styles.searchHistoryContainer}> 969 + {(searchHistory.length > 0 || selectedProfiles.length > 0) && ( 970 + <Text style={[pal.text, styles.searchHistoryTitle]}> 971 + <Trans>Recent Searches</Trans> 972 + </Text> 973 + )} 974 + {selectedProfiles.length > 0 && ( 975 + <View 976 + style={[ 977 + styles.selectedProfilesContainer, 978 + isMobile && styles.selectedProfilesContainerMobile, 979 + ]}> 980 + <ScrollView 981 + keyboardShouldPersistTaps="handled" 982 + horizontal={true} 983 + style={styles.profilesRow} 984 + contentContainerStyle={{ 985 + borderWidth: 0, 986 + }}> 987 + {selectedProfiles.slice(0, 5).map((profile, index) => ( 988 + <View 989 + key={index} 990 + style={[ 991 + styles.profileItem, 992 + isMobile && styles.profileItemMobile, 993 + ]}> 994 + <Link 995 + href={makeProfileLink(profile)} 996 + title={profile.handle} 997 + asAnchor 998 + anchorNoUnderline 999 + onBeforePress={() => onProfileClick(profile)} 1000 + style={styles.profilePressable}> 1001 + <Image 1002 + source={{uri: profile.avatar}} 1003 + style={styles.profileAvatar as StyleProp<ImageStyle>} 1004 + accessibilityIgnoresInvertColors 1005 + /> 1006 + <Text style={[pal.text, styles.profileName]}> 1007 + {truncateText(profile.displayName || '', 12)} 1008 + </Text> 1009 + </Link> 1010 + <Pressable 1011 + accessibilityRole="button" 1012 + accessibilityLabel="Remove profile" 1013 + accessibilityHint="Remove profile from search history" 1014 + onPress={() => onRemoveProfileClick(profile)} 1015 + hitSlop={createHitslop(6)} 1016 + style={styles.profileRemoveBtn}> 1017 + <FontAwesomeIcon 1018 + icon="xmark" 1019 + size={14} 1020 + style={pal.textLight as FontAwesomeIconStyle} 1021 + /> 1022 + </Pressable> 1023 + </View> 1024 + ))} 1025 + </ScrollView> 1026 + </View> 1027 + )} 883 1028 {searchHistory.length > 0 && ( 884 1029 <View style={styles.searchHistoryContent}> 885 - <Text style={[pal.text, styles.searchHistoryTitle]}> 886 - <Trans>Recent Searches</Trans> 887 - </Text> 888 - {searchHistory.map((historyItem, index) => ( 1030 + {searchHistory.slice(0, 5).map((historyItem, index) => ( 889 1031 <View 890 1032 key={index} 891 1033 style={[ ··· 982 1124 width: '100%', 983 1125 paddingHorizontal: 12, 984 1126 }, 1127 + selectedProfilesContainer: { 1128 + marginTop: 10, 1129 + paddingHorizontal: 12, 1130 + height: 80, 1131 + }, 1132 + selectedProfilesContainerMobile: { 1133 + height: 100, 1134 + }, 1135 + profilesRow: { 1136 + flexDirection: 'row', 1137 + flexWrap: 'nowrap', 1138 + }, 1139 + profileItem: { 1140 + alignItems: 'center', 1141 + marginRight: 15, 1142 + width: 78, 1143 + }, 1144 + profileItemMobile: { 1145 + width: 70, 1146 + }, 1147 + profilePressable: { 1148 + alignItems: 'center', 1149 + }, 1150 + profileAvatar: { 1151 + width: 60, 1152 + height: 60, 1153 + borderRadius: 45, 1154 + }, 1155 + profileName: { 1156 + fontSize: 12, 1157 + textAlign: 'center', 1158 + marginTop: 5, 1159 + }, 1160 + profileRemoveBtn: { 1161 + position: 'absolute', 1162 + top: 0, 1163 + right: 5, 1164 + backgroundColor: 'white', 1165 + borderRadius: 10, 1166 + width: 18, 1167 + height: 18, 1168 + alignItems: 'center', 1169 + justifyContent: 'center', 1170 + }, 985 1171 searchHistoryContent: { 986 - padding: 10, 1172 + paddingHorizontal: 10, 987 1173 borderRadius: 8, 988 1174 }, 989 1175 searchHistoryTitle: { 990 1176 fontWeight: 'bold', 1177 + paddingVertical: 12, 1178 + paddingHorizontal: 10, 991 1179 }, 992 1180 })