diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index e0fc7ef54b..fc688961b4 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -30,7 +30,7 @@ import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' -import {RichText} from '#/components/RichText' +import {RichText, RichTextProps} from '#/components/RichText' import {Text} from '#/components/Typography' type Props = @@ -89,7 +89,8 @@ export function Link({ } else { precacheList(queryClient, view) } - }}> + }} + style={[a.flex_col]}> {children} ) @@ -100,11 +101,7 @@ export function Outer({children}: {children: React.ReactNode}) { } export function Header({children}: {children: React.ReactNode}) { - return ( - - {children} - - ) + return {children} } export type AvatarProps = {src: string | undefined; size?: number} @@ -198,7 +195,10 @@ export function TitleAndBylinePlaceholder({creator}: {creator?: boolean}) { ) } -export function Description({description}: {description?: string}) { +export function Description({ + description, + ...rest +}: {description?: string} & Partial) { const rt = React.useMemo(() => { if (!description) return const rt = new RichTextApi({text: description || ''}) @@ -206,7 +206,29 @@ export function Description({description}: {description?: string}) { return rt }, [description]) if (!rt) return null - return + return +} + +export function DescriptionPlaceholder() { + const t = useTheme() + return ( + + + + + + ) } export function Likes({count}: {count: number}) { diff --git a/src/components/FeedSuggestedFollows/index.tsx b/src/components/FeedSuggestedFollows/index.tsx index a32369e87b..84dca7f2fa 100644 --- a/src/components/FeedSuggestedFollows/index.tsx +++ b/src/components/FeedSuggestedFollows/index.tsx @@ -156,25 +156,26 @@ export function FeedSuggestedFollowsCards() { {content} - - + + + ) } diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 9ba44eabe4..7511775978 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -17,6 +17,19 @@ import {Text, TextProps} from '#/components/Typography' const WORD_WRAP = {wordWrap: 1} +export type RichTextProps = TextStyleProp & + Pick & { + value: RichTextAPI | string + testID?: string + numberOfLines?: number + disableLinks?: boolean + enableTags?: boolean + authorHandle?: string + onLinkPress?: LinkProps['onPress'] + interactiveStyle?: TextStyle + emojiMultiplier?: number + } + export function RichText({ testID, value, @@ -29,18 +42,7 @@ export function RichText({ onLinkPress, interactiveStyle, emojiMultiplier = 1.85, -}: TextStyleProp & - Pick & { - value: RichTextAPI | string - testID?: string - numberOfLines?: number - disableLinks?: boolean - enableTags?: boolean - authorHandle?: string - onLinkPress?: LinkProps['onPress'] - interactiveStyle?: TextStyle - emojiMultiplier?: number - }) { +}: RichTextProps) { const richText = React.useMemo( () => value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), diff --git a/src/components/feedInterstitials/SuggestedFeeds.tsx b/src/components/feedInterstitials/SuggestedFeeds.tsx new file mode 100644 index 0000000000..b5ac5f63e3 --- /dev/null +++ b/src/components/feedInterstitials/SuggestedFeeds.tsx @@ -0,0 +1,175 @@ +import React from 'react' +import {View} from 'react-native' +import {ScrollView} from 'react-native-gesture-handler' +import {AppBskyFeedDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {NavigationProp} from '#/lib/routes/types' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useGetPopularFeedsQuery} from '#/state/queries/feed' +import {atoms as a, useBreakpoints, useTheme, ViewStyleProp} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as FeedCard from '#/components/FeedCard' +import {ArrowRight_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' +import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Refresh} from '#/components/icons/ArrowRotateCounterClockwise' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {Text} from '#/components/Typography' + +function CardOuter({ + children, + style, +}: {children: React.ReactNode | React.ReactNode[]} & ViewStyleProp) { + const t = useTheme() + const {gtMobile} = useBreakpoints() + return ( + + {children} + + ) +} + +export function SuggestedFollowCardSkeleton() { + const t = useTheme() + return ( + + + + + + + + + ) +} + +export function ErrorState({retry}: {retry: () => void}) { + const t = useTheme() + return ( + <> + + + Whoops, something went wrong :( + + + + + + + ) +} + +export function SuggestedFeedsCards() { + const t = useTheme() + const {_} = useLingui() + const { + data, + isLoading: isLoadingFeeds, + error, + refetch, + } = useGetPopularFeedsQuery({limit: 3}) + const moderationOpts = useModerationOpts() + const navigation = useNavigation() + const {gtMobile} = useBreakpoints() + const isLoading = isLoadingFeeds || !moderationOpts + + const feeds = React.useMemo(() => { + const items: AppBskyFeedDefs.GeneratorView[] = [] + + if (!data) return items + + for (const page of data.pages) { + for (const feed of page.feeds) { + items.push(feed) + } + } + + return items + }, [data]) + + const content = isLoading ? ( + Array(3) + .fill(0) + .map((_, i) => ) + ) : error || !feeds ? ( + + ) : ( + <> + {feeds.map(feed => ( + + + + + + + + + + + + + ))} + + ) + + return gtMobile ? ( + + {content} + + ) : ( + + + {content} + + + + + ) +} diff --git a/src/screens/Home/EmptyTimeline.tsx b/src/screens/Home/EmptyTimeline.tsx index 4c70193a72..e965cba110 100644 --- a/src/screens/Home/EmptyTimeline.tsx +++ b/src/screens/Home/EmptyTimeline.tsx @@ -5,19 +5,23 @@ import {useLingui} from '@lingui/react' import {emitSoftReset} from '#/state/events' import {CenteredView} from '#/view/com/util/Views' -import {atoms as a} from '#/alf' +import {atoms as a, useBreakpoints} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Divider} from '#/components/Divider' +import {SuggestedFeedsCards} from '#/components/feedInterstitials/SuggestedFeeds' import {FeedSuggestedFollowsCards} from '#/components/FeedSuggestedFollows' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Refresh} from '#/components/icons/ArrowRotateCounterClockwise' import {Text} from '#/components/Typography' export function EmptyTimeline() { const {_} = useLingui() + const {gtMobile} = useBreakpoints() + + const sectionPadding = gtMobile ? a.pt_5xl : a.pt_3xl return ( - - + + Your Following feed is empty :( @@ -60,6 +64,16 @@ export function EmptyTimeline() { + + + + You can also explore new feeds + + + + + + ) }