diff --git a/assets/icons/qrCode_stroke2_corner0_rounded.svg b/assets/icons/qrCode_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..b17db39533 --- /dev/null +++ b/assets/icons/qrCode_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx new file mode 100644 index 0000000000..7318267442 --- /dev/null +++ b/src/components/StarterPack/ProfileStarterPacks.tsx @@ -0,0 +1,193 @@ +import React from 'react' +import { + findNodeHandle, + ListRenderItemInfo, + StyleProp, + View, + ViewStyle, +} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' + +import {cleanError} from '#/lib/strings/errors' +import {useTheme} from '#/lib/ThemeContext' +import {logger} from '#/logger' +import {isNative} from '#/platform/detection' +import {hydrateFeedGenerator} from '#/state/queries/feed' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' +import {usePalette} from 'lib/hooks/usePalette' +import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import {ErrorMessage} from 'view/com/util/error/ErrorMessage' +import {List, ListRef} from 'view/com/util/List' +import {LoadMoreRetryBtn} from 'view/com/util/LoadMoreRetryBtn' +import {Text} from 'view/com/util/text/Text' +import {StarterPackCard} from '#/components/StarterPack/StarterPackCard' + +const LOADING = {_reactKey: '__loading__'} +const EMPTY = {_reactKey: '__empty__'} +const ERROR_ITEM = {_reactKey: '__error__'} +const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} + +interface SectionRef { + scrollToTop: () => void +} + +interface ProfileFeedgensProps { + did: string + scrollElRef: ListRef + headerOffset: number + enabled?: boolean + style?: StyleProp + testID?: string + setScrollViewTag: (tag: number | null) => void +} + +export const ProfileStarterPacks = React.forwardRef< + SectionRef, + ProfileFeedgensProps +>(function ProfileFeedgensImpl( + {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, + ref, +) { + const pal = usePalette('default') + const {_} = useLingui() + const theme = useTheme() + const [isPTRing, setIsPTRing] = React.useState(false) + const opts = React.useMemo(() => ({enabled}), [enabled]) + const { + data, + isFetching, + isFetched, + hasNextPage, + fetchNextPage, + isError, + error, + refetch, + } = useProfileFeedgensQuery(did, opts) + const isEmpty = !isFetching && !data?.pages[0]?.feeds.length + const {data: preferences} = usePreferencesQuery() + + const items = React.useMemo(() => { + let items: any[] = [] + if (isError && isEmpty) { + items = items.concat([ERROR_ITEM]) + } + if (!isFetched && isFetching) { + items = items.concat([LOADING]) + } else if (isEmpty) { + items = items.concat([EMPTY]) + } else if (data?.pages) { + for (const page of data?.pages) { + items = items.concat(page.feeds.map(feed => hydrateFeedGenerator(feed))) + } + } + if (isError && !isEmpty) { + items = items.concat([LOAD_MORE_ERROR_ITEM]) + } + return items + }, [isError, isEmpty, isFetched, isFetching, data]) + + const queryClient = useQueryClient() + + const onScrollToTop = React.useCallback(() => { + scrollElRef.current?.scrollToOffset({ + animated: isNative, + offset: -headerOffset, + }) + queryClient.invalidateQueries({queryKey: RQKEY(did)}) + }, [scrollElRef, queryClient, headerOffset, did]) + + React.useImperativeHandle(ref, () => ({ + scrollToTop: onScrollToTop, + })) + + const onRefresh = React.useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh starter packs', {message: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) + + const onEndReached = React.useCallback(async () => { + if (isFetching || !hasNextPage || isError) return + + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more starter packs', {message: err}) + } + }, [isFetching, hasNextPage, isError, fetchNextPage]) + + const onPressRetryLoadMore = React.useCallback(() => { + fetchNextPage() + }, [fetchNextPage]) + + const renderItem = React.useCallback( + ({item, index}: ListRenderItemInfo) => { + if (item === EMPTY) { + return ( + + + You have no feeds. + + + ) + } else if (item === ERROR_ITEM) { + return ( + + ) + } else if (item === LOAD_MORE_ERROR_ITEM) { + return ( + + ) + } else if (item === LOADING) { + return + } + if (preferences) { + return + } + return null + }, + [error, refetch, onPressRetryLoadMore, pal, preferences, _], + ) + + React.useEffect(() => { + if (enabled && scrollElRef.current) { + const nativeTag = findNodeHandle(scrollElRef.current) + setScrollViewTag(nativeTag) + } + }, [enabled, scrollElRef, setScrollViewTag]) + + return ( + + item._reactKey || item.uri} + renderItem={renderItem} + refreshing={isPTRing} + onRefresh={onRefresh} + headerOffset={headerOffset} + contentContainerStyle={isNative && {paddingBottom: headerOffset + 100}} + indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} + removeClippedSubviews={true} + // @ts-ignore our .web version only -prf + desktopFixedHeight + onEndReached={onEndReached} + /> + + ) +}) diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx new file mode 100644 index 0000000000..52cae2040a --- /dev/null +++ b/src/components/StarterPack/StarterPackCard.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import {View} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {StarterPackIcon} from '#/components/icons/StarterPackIcon' +import {Link} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function StarterPackCard({hideTopBorder}: {hideTopBorder?: boolean}) { + const t = useTheme() + + return ( + + + + + + Science + + Starter pack by @bossett.social + + + + 380 users have joined! + + + + + ) +} diff --git a/src/components/icons/QrCode.tsx b/src/components/icons/QrCode.tsx new file mode 100644 index 0000000000..e841071f70 --- /dev/null +++ b/src/components/icons/QrCode.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const QrCode_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm6 0H5v4h4V5ZM3 15a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4Zm6 0H5v4h4v-4ZM13 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V5Zm6 0h-4v4h4V5ZM14 13a1 1 0 0 1 1 1v1h1a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1Zm3 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Zm0 4a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-1v1a1 1 0 1 1-2 0v-2Z', +}) diff --git a/src/screens/StarterPack/Main/index.tsx b/src/screens/StarterPack/Main/index.tsx index 1bb4fa11f4..d4bf8b8274 100644 --- a/src/screens/StarterPack/Main/index.tsx +++ b/src/screens/StarterPack/Main/index.tsx @@ -8,11 +8,15 @@ import {CommonNavigatorParams} from 'lib/routes/types' import {isWeb} from 'platform/detection' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' +import {EventStopper} from 'view/com/util/EventStopper' import {CenteredView} from 'view/com/util/Views' import {FeedsList} from '#/screens/StarterPack/Main/FeedsList' import {ProfilesList} from '#/screens/StarterPack/Main/ProfilesList' import {atoms as a} from '#/alf' import {Button, ButtonText} from '#/components/Button' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' +import {QrCode_Stroke2_Corner0_Rounded as QrCode} from '#/components/icons/QrCode' +import * as Menu from '#/components/Menu' /** * TEMPORARY CONTENT, DO NOT TRANSLATE @@ -96,7 +100,7 @@ export function StarterPackScreen({}: NativeStackScreenProps< // const {id} = route.params return ( - + ) @@ -148,16 +152,48 @@ function Header({isOwn}: {isOwn: boolean}) { creator={undefined} avatarType="starter-pack"> - + + + + {({props}) => { + return ( + + ) + }} + + + + {}}> + + Share link + + + + {}}> + + Create QR code + + + + + + + {isOwn && (