Search custom feeds (#1031)

* paginate custom feeds

* basic search

* update `@atproto/api`

* use search from the API

* debounce search for 200ms
zio/stable
Ansh 2023-07-28 08:29:37 -07:00 committed by GitHub
parent 8e9b8b6b36
commit 38d78e16bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 18 deletions

View File

@ -15,6 +15,7 @@ module.exports = {
'coverage', 'coverage',
'*.lock', '*.lock',
'.husky', '.husky',
'patches',
], ],
overrides: [ overrides: [
{ {

View File

@ -24,7 +24,7 @@
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all" "e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
}, },
"dependencies": { "dependencies": {
"@atproto/api": "^0.4.2", "@atproto/api": "^0.4.3",
"@bam.tech/react-native-image-resizer": "^3.0.4", "@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2", "@braintree/sanitize-url": "^6.0.2",
"@expo/html-elements": "^0.4.2", "@expo/html-elements": "^0.4.2",

View File

@ -82,6 +82,21 @@ export class FeedsDiscoveryModel {
this._xIdle() this._xIdle()
}) })
search = async (query: string) => {
this._xLoading(false)
try {
const results =
await this.rootStore.agent.app.bsky.unspecced.getPopularFeedGenerators({
limit: DEFAULT_LIMIT,
query: query,
})
this._replaceAll(results)
} catch (e: any) {
this._xIdle(e)
}
this._xIdle()
}
clear() { clear() {
this.isLoading = false this.isLoading = false
this.isRefreshing = false this.isRefreshing = false
@ -93,9 +108,9 @@ export class FeedsDiscoveryModel {
// state transitions // state transitions
// = // =
_xLoading() { _xLoading(isRefreshing = true) {
this.isLoading = true this.isLoading = true
this.isRefreshing = true this.isRefreshing = isRefreshing
this.error = '' this.error = ''
} }

View File

@ -37,7 +37,7 @@ export const Step1 = observer(({model}: {model: CreateAccountModel}) => {
}, [setIsDefaultSelected, model]) }, [setIsDefaultSelected, model])
const fetchServiceDescription = React.useMemo( const fetchServiceDescription = React.useMemo(
() => debounce(() => model.fetchServiceDescription(), 1e3), () => debounce(() => model.fetchServiceDescription(), 1e3), // debouce for 1 second (1e3 = 1000ms)
[model], [model],
) )

View File

@ -21,6 +21,7 @@ interface Props {
onPressClearQuery: () => void onPressClearQuery: () => void
onPressCancelSearch: () => void onPressCancelSearch: () => void
onSubmitQuery: () => void onSubmitQuery: () => void
showMenu?: boolean
} }
export function HeaderWithInput({ export function HeaderWithInput({
isInputFocused, isInputFocused,
@ -30,6 +31,7 @@ export function HeaderWithInput({
onPressClearQuery, onPressClearQuery,
onPressCancelSearch, onPressCancelSearch,
onSubmitQuery, onSubmitQuery,
showMenu = true,
}: Props) { }: Props) {
const store = useStores() const store = useStores()
const theme = useTheme() const theme = useTheme()
@ -49,16 +51,18 @@ export function HeaderWithInput({
return ( return (
<View style={[pal.view, pal.border, styles.header]}> <View style={[pal.view, pal.border, styles.header]}>
<TouchableOpacity {showMenu ? (
testID="viewHeaderBackOrMenuBtn" <TouchableOpacity
onPress={onPressMenu} testID="viewHeaderBackOrMenuBtn"
hitSlop={MENU_HITSLOP} onPress={onPressMenu}
style={styles.headerMenuBtn} hitSlop={MENU_HITSLOP}
accessibilityRole="button" style={styles.headerMenuBtn}
accessibilityLabel="Menu" accessibilityRole="button"
accessibilityHint="Access navigation links and settings"> accessibilityLabel="Menu"
<FontAwesomeIcon icon="bars" size={18} color={pal.colors.textLight} /> accessibilityHint="Access navigation links and settings">
</TouchableOpacity> <FontAwesomeIcon icon="bars" size={18} color={pal.colors.textLight} />
</TouchableOpacity>
) : null}
<View <View
style={[ style={[
{backgroundColor: pal.colors.backgroundLight}, {backgroundColor: pal.colors.backgroundLight},

View File

@ -14,6 +14,8 @@ import {isDesktopWeb} from 'platform/detection'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {CustomFeedModel} from 'state/models/feeds/custom-feed' import {CustomFeedModel} from 'state/models/feeds/custom-feed'
import {HeaderWithInput} from 'view/com/search/HeaderWithInput'
import debounce from 'lodash.debounce'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'DiscoverFeeds'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'DiscoverFeeds'>
export const DiscoverFeedsScreen = withAuthRequired( export const DiscoverFeedsScreen = withAuthRequired(
@ -22,6 +24,37 @@ export const DiscoverFeedsScreen = withAuthRequired(
const pal = usePalette('default') const pal = usePalette('default')
const feeds = React.useMemo(() => new FeedsDiscoveryModel(store), [store]) const feeds = React.useMemo(() => new FeedsDiscoveryModel(store), [store])
// search stuff
const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
const [query, setQuery] = React.useState<string>('')
const debouncedSearchFeeds = React.useMemo(
() => debounce(() => feeds.search(query), 200), // debouce for 200 ms
[feeds, query],
)
const onChangeQuery = React.useCallback(
(text: string) => {
setQuery(text)
if (text.length > 1) {
debouncedSearchFeeds()
} else {
feeds.refresh()
}
},
[debouncedSearchFeeds, feeds],
)
const onPressClearQuery = React.useCallback(() => {
setQuery('')
feeds.refresh()
}, [feeds])
const onPressCancelSearch = React.useCallback(() => {
setIsInputFocused(false)
setQuery('')
feeds.refresh()
}, [feeds])
const onSubmitQuery = React.useCallback(() => {
feeds.search(query)
}, [feeds, query])
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
store.shell.setMinimalShellMode(false) store.shell.setMinimalShellMode(false)
@ -68,6 +101,16 @@ export const DiscoverFeedsScreen = withAuthRequired(
<CenteredView style={[styles.container, pal.view]}> <CenteredView style={[styles.container, pal.view]}>
<View style={[isDesktopWeb && styles.containerDesktop, pal.border]}> <View style={[isDesktopWeb && styles.containerDesktop, pal.border]}>
<ViewHeader title="Discover Feeds" showOnDesktop /> <ViewHeader title="Discover Feeds" showOnDesktop />
<HeaderWithInput
isInputFocused={isInputFocused}
query={query}
setIsInputFocused={setIsInputFocused}
onChangeQuery={onChangeQuery}
onPressClearQuery={onPressClearQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
showMenu={false}
/>
</View> </View>
<FlatList <FlatList
style={[!isDesktopWeb && s.flex1]} style={[!isDesktopWeb && s.flex1]}

View File

@ -40,10 +40,10 @@
tlds "^1.234.0" tlds "^1.234.0"
typed-emitter "^2.1.0" typed-emitter "^2.1.0"
"@atproto/api@^0.4.2": "@atproto/api@^0.4.3":
version "0.4.2" version "0.4.3"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.4.2.tgz#7790eb049f72437e7454c8ecc29a5ef4201c1ade" resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.4.3.tgz#d7e478bf7009df2adaf1ac6051eb3e3fea185c90"
integrity sha512-bwaT+kIJp6wpzlHc1Rus3yi29GKlwvYp4wOWAFmcyYT4qH2ZlE6NfElgi6QwdQD285EhiIKYTv7CAMRp/QO7DQ== integrity sha512-8LdREwmoA58YQDrLS0rohd7cHokhoiXfyYEeNtNlkdO0w/2QpkUCQ1PgPBP2kIRM9PhOEkKp7W3Sn8Te9Qq8jg==
dependencies: dependencies:
"@atproto/common-web" "*" "@atproto/common-web" "*"
"@atproto/uri" "*" "@atproto/uri" "*"