New empty state for feeds

This commit is contained in:
Dan Abramov
2024-10-22 21:06:46 +01:00
parent 090ac041c3
commit 371cabfabc
2 changed files with 189 additions and 13 deletions
+188 -13
View File
@@ -7,24 +7,33 @@ import {
ViewStyle,
} from 'react-native'
import {msg} from '@lingui/macro'
import {Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack'
import {NavigationProp} from '#/lib/routes/types'
import {cleanError} from '#/lib/strings/errors'
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens'
import {EmptyState} from '#/view/com/util/EmptyState'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {Text} from '#/view/com/util/text/Text'
import {atoms as a, ios, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import * as FeedCard from '#/components/FeedCard'
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {LinearGradientBackground} from '#/components/LinearGradientBackground'
import * as Prompt from '#/components/Prompt'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
const LOADING = {_reactKey: '__loading__'}
const EMPTY = {_reactKey: '__empty__'}
const ERROR_ITEM = {_reactKey: '__error__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
@@ -40,13 +49,23 @@ interface ProfileFeedgensProps {
style?: StyleProp<ViewStyle>
testID?: string
setScrollViewTag: (tag: number | null) => void
isMe: boolean
}
export const ProfileFeedgens = React.forwardRef<
SectionRef,
ProfileFeedgensProps
>(function ProfileFeedgensImpl(
{did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag},
{
did,
scrollElRef,
headerOffset,
enabled,
style,
testID,
setScrollViewTag,
isMe,
},
ref,
) {
const {_} = useLingui()
@@ -74,7 +93,7 @@ export const ProfileFeedgens = React.forwardRef<
if (!isFetched && isFetching) {
items = items.concat([LOADING])
} else if (isEmpty) {
items = items.concat([EMPTY])
// -- handled separately --
} else if (data?.pages) {
for (const page of data?.pages) {
items = items.concat(page.feeds)
@@ -131,15 +150,7 @@ export const ProfileFeedgens = React.forwardRef<
const renderItem = React.useCallback(
({item, index}: ListRenderItemInfo<any>) => {
if (item === EMPTY) {
return (
<EmptyState
icon="hashtag"
message={_(msg`You have no feeds.`)}
testID="listsEmpty"
/>
)
} else if (item === ERROR_ITEM) {
if (item === ERROR_ITEM) {
return (
<ErrorMessage message={cleanError(error)} onPressTryAgain={refetch} />
)
@@ -198,7 +209,171 @@ export const ProfileFeedgens = React.forwardRef<
// @ts-ignore our .web version only -prf
desktopFixedHeight
onEndReached={onEndReached}
ListEmptyComponent={Empty}
ListFooterComponent={
items?.length !== 0 && isMe ? CreateAnother : undefined
}
/>
</View>
)
})
function CreateAnother() {
const {_} = useLingui()
const t = useTheme()
const navigation = useNavigation<NavigationProp>()
return (
<View
style={[
a.pr_md,
a.pt_lg,
a.gap_lg,
a.border_t,
t.atoms.border_contrast_low,
]}>
<Button
label={_(msg`Create a starter pack`)}
variant="solid"
color="secondary"
size="small"
style={[a.self_center]}
onPress={() => navigation.navigate('StarterPackWizard')}>
<ButtonText>
<Trans>Create another</Trans>
</ButtonText>
<ButtonIcon icon={Plus} position="right" />
</Button>
</View>
)
}
function Empty() {
const {_} = useLingui()
const t = useTheme()
const navigation = useNavigation<NavigationProp>()
const confirmDialogControl = useDialogControl()
const followersDialogControl = useDialogControl()
const errorDialogControl = useDialogControl()
const [isGenerating, setIsGenerating] = React.useState(false)
const {mutate: generateStarterPack} = useGenerateStarterPackMutation({
onSuccess: ({uri}) => {
const parsed = parseStarterPackUri(uri)
if (parsed) {
navigation.push('StarterPack', {
name: parsed.name,
rkey: parsed.rkey,
})
}
setIsGenerating(false)
},
onError: e => {
logger.error('Failed to generate starter pack', {safeMessage: e})
setIsGenerating(false)
if (e.name === 'NOT_ENOUGH_FOLLOWERS') {
followersDialogControl.open()
} else {
errorDialogControl.open()
}
},
})
const generate = () => {
setIsGenerating(true)
generateStarterPack()
}
return (
<LinearGradientBackground
style={[
a.px_lg,
a.py_lg,
a.justify_between,
a.gap_lg,
a.shadow_lg,
{marginTop: 2},
]}>
<View style={[a.gap_xs]}>
<Text
style={[
a.font_bold,
a.text_lg,
t.atoms.text_contrast_medium,
{color: 'white'},
]}>
<Trans>You haven't created a feed yet!</Trans>
</Text>
<Text style={[a.text_md, {color: 'white'}]}>
<Trans>
Create a feed to curate your own and others people's posts.
</Trans>
</Text>
</View>
<View style={[a.flex_row, a.gap_md, {marginLeft: 'auto'}]}>
<Button
label={_(msg`Create a starter pack`)}
variant="ghost"
color="primary"
size="small"
disabled={isGenerating}
onPress={() => navigation.navigate('StarterPackWizard')}
style={{
backgroundColor: 'white',
borderColor: 'white',
width: 100,
}}
hoverStyle={[{backgroundColor: '#dfdfdf'}]}>
<ButtonText>
<Trans>Create</Trans>
</ButtonText>
</Button>
</View>
<Prompt.Outer control={confirmDialogControl}>
<Prompt.TitleText>
<Trans>Generate a starter pack</Trans>
</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>
Bluesky will choose a set of recommended accounts from people in
your network.
</Trans>
</Prompt.DescriptionText>
<Prompt.Actions>
<Prompt.Action
color="primary"
cta={_(msg`Choose for me`)}
onPress={generate}
/>
<Prompt.Action
color="secondary"
cta={_(msg`Let me choose`)}
onPress={() => {
navigation.navigate('StarterPackWizard')
}}
/>
</Prompt.Actions>
</Prompt.Outer>
<Prompt.Basic
control={followersDialogControl}
title={_(msg`Oops!`)}
description={_(
msg`You must be following at least seven other people to generate a starter pack.`,
)}
onConfirm={() => {}}
showCancel={false}
/>
<Prompt.Basic
control={errorDialogControl}
title={_(msg`Oops!`)}
description={_(
msg`An error occurred while generating your starter pack. Want to try again?`,
)}
onConfirm={generate}
confirmButtonCta={_(msg`Retry`)}
/>
</LinearGradientBackground>
)
}
+1
View File
@@ -453,6 +453,7 @@ function ProfileScreenLoaded({
? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileFeedgens
ref={feedsSectionRef}
isMe={isMe}
did={profile.did}
scrollElRef={scrollElRef as ListRef}
headerOffset={headerHeight}