* Prep * Pass in optional moderation to FeedCard * Compute moderation decision, filter contentList contexts, pass into card * Let's go a different route * Filter from within search queries * Use same search query for starter packs * Filter lists from profile tabs * Cleanup * Filter from profile feeds * Moderate post embeds * Memoize * Use ScreenHider on lists * Hide both list types * Fix crash on iOS in screen hider, fix lineheight * Memoize renderItem * Reuse objects to prevent re-renders
128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
import React, {useState} from 'react'
|
|
import {ListRenderItemInfo, View} from 'react-native'
|
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
|
|
import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api'
|
|
import {Trans} from '@lingui/macro'
|
|
|
|
import {useA11y} from '#/state/a11y'
|
|
import {DISCOVER_FEED_URI} from 'lib/constants'
|
|
import {
|
|
useGetPopularFeedsQuery,
|
|
usePopularFeedsSearch,
|
|
useSavedFeeds,
|
|
} from 'state/queries/feed'
|
|
import {SearchInput} from 'view/com/util/forms/SearchInput'
|
|
import {List} from 'view/com/util/List'
|
|
import {useWizardState} from '#/screens/StarterPack/Wizard/State'
|
|
import {atoms as a, useTheme} from '#/alf'
|
|
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
|
|
import {Loader} from '#/components/Loader'
|
|
import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition'
|
|
import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardListCard'
|
|
import {Text} from '#/components/Typography'
|
|
|
|
function keyExtractor(item: AppBskyFeedDefs.GeneratorView) {
|
|
return item.uri
|
|
}
|
|
|
|
export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
|
|
const t = useTheme()
|
|
const [state, dispatch] = useWizardState()
|
|
const [query, setQuery] = useState('')
|
|
const throttledQuery = useThrottledValue(query, 500)
|
|
const {screenReaderEnabled} = useA11y()
|
|
|
|
const {data: savedFeedsAndLists, isFetchedAfterMount: isFetchedSavedFeeds} =
|
|
useSavedFeeds()
|
|
const savedFeeds = savedFeedsAndLists?.feeds
|
|
.filter(f => f.type === 'feed' && f.view.uri !== DISCOVER_FEED_URI)
|
|
.map(f => f.view) as AppBskyFeedDefs.GeneratorView[]
|
|
|
|
const {
|
|
data: popularFeedsPages,
|
|
fetchNextPage,
|
|
isLoading: isLoadingPopularFeeds,
|
|
} = useGetPopularFeedsQuery({
|
|
limit: 30,
|
|
})
|
|
const popularFeeds = popularFeedsPages?.pages.flatMap(p => p.feeds) ?? []
|
|
|
|
// If we have saved feeds already loaded, display them immediately
|
|
// Then, when popular feeds have loaded we can concat them to the saved feeds
|
|
const suggestedFeeds =
|
|
savedFeeds || isFetchedSavedFeeds
|
|
? popularFeeds
|
|
? savedFeeds.concat(
|
|
popularFeeds.filter(f => !savedFeeds.some(sf => sf.uri === f.uri)),
|
|
)
|
|
: savedFeeds
|
|
: undefined
|
|
|
|
const {data: searchedFeeds, isFetching: isFetchingSearchedFeeds} =
|
|
usePopularFeedsSearch({query: throttledQuery})
|
|
|
|
const isLoading =
|
|
!isFetchedSavedFeeds || isLoadingPopularFeeds || isFetchingSearchedFeeds
|
|
|
|
const renderItem = ({
|
|
item,
|
|
}: ListRenderItemInfo<AppBskyFeedDefs.GeneratorView>) => {
|
|
return (
|
|
<WizardFeedCard
|
|
generator={item}
|
|
btnType="checkbox"
|
|
state={state}
|
|
dispatch={dispatch}
|
|
moderationOpts={moderationOpts}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<ScreenTransition style={[a.flex_1]} direction={state.transitionDirection}>
|
|
<View style={[a.border_b, t.atoms.border_contrast_medium]}>
|
|
<View style={[a.my_sm, a.px_md, {height: 40}]}>
|
|
<SearchInput
|
|
query={query}
|
|
onChangeQuery={t => setQuery(t)}
|
|
onPressCancelSearch={() => setQuery('')}
|
|
onSubmitQuery={() => {}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<List
|
|
data={query ? searchedFeeds : suggestedFeeds}
|
|
renderItem={renderItem}
|
|
keyExtractor={keyExtractor}
|
|
contentContainerStyle={{paddingTop: 6}}
|
|
onEndReached={
|
|
!query && !screenReaderEnabled ? () => fetchNextPage() : undefined
|
|
}
|
|
onEndReachedThreshold={2}
|
|
renderScrollComponent={props => <KeyboardAwareScrollView {...props} />}
|
|
keyboardShouldPersistTaps="handled"
|
|
disableFullWindowScroll={true}
|
|
sideBorders={false}
|
|
style={{flex: 1}}
|
|
ListEmptyComponent={
|
|
<View style={[a.flex_1, a.align_center, a.mt_lg, a.px_lg]}>
|
|
{isLoading ? (
|
|
<Loader size="lg" />
|
|
) : (
|
|
<Text
|
|
style={[
|
|
a.font_bold,
|
|
a.text_lg,
|
|
a.text_center,
|
|
a.mt_lg,
|
|
a.leading_snug,
|
|
]}>
|
|
<Trans>No feeds found. Try searching for something else.</Trans>
|
|
</Text>
|
|
)}
|
|
</View>
|
|
}
|
|
/>
|
|
</ScreenTransition>
|
|
)
|
|
}
|