Search custom feeds (#1031)
* paginate custom feeds * basic search * update `@atproto/api` * use search from the API * debounce search for 200mszio/stable
parent
8e9b8b6b36
commit
38d78e16bf
|
@ -15,6 +15,7 @@ module.exports = {
|
||||||
'coverage',
|
'coverage',
|
||||||
'*.lock',
|
'*.lock',
|
||||||
'.husky',
|
'.husky',
|
||||||
|
'patches',
|
||||||
],
|
],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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]}
|
||||||
|
|
|
@ -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" "*"
|
||||||
|
|
Loading…
Reference in New Issue