Add starter pack embeds to posts (#4699)

* starter pack embeds

* revert test code

* Types

* add `BaseLink`

* precache on click

* rm log

* add a comment

* loading state

* top margin

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
Hailey 2024-07-03 18:15:08 -07:00 committed by GitHub
parent a3d4fb652b
commit aa7117edb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 246 additions and 41 deletions

View file

@ -1,5 +1,10 @@
import React from 'react'
import {GestureResponderEvent} from 'react-native'
import {
GestureResponderEvent,
Pressable,
StyleProp,
ViewStyle,
} from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {StackActions, useLinkProps} from '@react-navigation/native'
@ -323,3 +328,45 @@ export function InlineLinkText({
</Text>
)
}
/**
* A Pressable that uses useLink to handle navigation. It is unstyled, so can be used in cases where the Button styles
* in Link are not desired.
* @param displayText
* @param style
* @param children
* @param rest
* @constructor
*/
export function BaseLink({
displayText,
onPress: onPressOuter,
style,
children,
...rest
}: {
style?: StyleProp<ViewStyle>
children: React.ReactNode
to: string
action: 'push' | 'replace' | 'navigate'
onPress?: () => false | void
shareOnLongPress?: boolean
label: string
displayText?: string
}) {
const {onPress, ...btnProps} = useLink({
displayText: displayText ?? rest.to,
...rest,
})
return (
<Pressable
style={style}
onPress={e => {
onPressOuter?.()
onPress(e)
}}
{...btnProps}>
{children}
</Pressable>
)
}

View file

@ -11,10 +11,12 @@ import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
import {isBlockedOrBlocking} from 'lib/moderation/blocked-and-muted'
import {isNative, isWeb} from 'platform/detection'
import {useListMembersQuery} from 'state/queries/list-members'
import {useSession} from 'state/session'
import {List, ListRef} from 'view/com/util/List'
import {SectionRef} from '#/screens/Profile/Sections/types'
import {atoms as a, useTheme} from '#/alf'
import {ListMaybePlaceholder} from '#/components/Lists'
import {Default as ProfileCard} from '#/components/ProfileCard'
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) {
@ -33,18 +35,17 @@ interface ProfilesListProps {
export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
function ProfilesListImpl(
{listUri, listMembersQuery, moderationOpts, headerHeight, scrollElRef},
{listUri, moderationOpts, headerHeight, scrollElRef},
ref,
) {
const t = useTheme()
const [initialHeaderHeight] = React.useState(headerHeight)
const bottomBarOffset = useBottomBarOffset(20)
const {currentAccount} = useSession()
const {data, refetch, isError} = useListMembersQuery(listUri, 50)
const [isPTRing, setIsPTRing] = React.useState(false)
const {data, refetch} = listMembersQuery
// The server returns these sorted by descending creation date, so we want to invert
const profiles = data?.pages
.flatMap(p => p.items.map(i => i.subject))
@ -96,7 +97,19 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
)
}
if (listMembersQuery)
if (!data) {
return (
<View style={{marginTop: headerHeight, marginBottom: bottomBarOffset}}>
<ListMaybePlaceholder
isLoading={true}
isError={isError}
onRetry={refetch}
/>
</View>
)
}
if (data)
return (
<List
data={getSortedProfiles()}

View file

@ -1,15 +1,20 @@
import React from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {AppBskyGraphStarterpack, AtUri} from '@atproto/api'
import {StarterPackViewBasic} from '@atproto/api/dist/client/types/app/bsky/graph/defs'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {sanitizeHandle} from 'lib/strings/handles'
import {getStarterPackOgCard} from 'lib/strings/starter-pack'
import {precacheResolvedUri} from 'state/queries/resolve-uri'
import {precacheStarterPack} from 'state/queries/starter-packs'
import {useSession} from 'state/session'
import {atoms as a, useTheme} from '#/alf'
import {StarterPack} from '#/components/icons/StarterPack'
import {Link as InternalLink, LinkProps} from '#/components/Link'
import {BaseLink} from '#/components/Link'
import {Text} from '#/components/Typography'
export function Default({starterPack}: {starterPack?: StarterPackViewBasic}) {
@ -88,10 +93,13 @@ export function Card({
export function Link({
starterPack,
children,
...rest
}: {
starterPack: StarterPackViewBasic
} & Omit<LinkProps, 'to'>) {
onPress?: () => void
children: React.ReactNode
}) {
const {_} = useLingui()
const queryClient = useQueryClient()
const {record} = starterPack
const {rkey, handleOrDid} = React.useMemo(() => {
const rkey = new AtUri(starterPack.uri).rkey
@ -104,14 +112,46 @@ export function Link({
}
return (
<InternalLink
label={record.name}
{...rest}
to={{
screen: 'StarterPack',
params: {name: handleOrDid, rkey},
<BaseLink
action="push"
to={`/starter-pack/${handleOrDid}/${rkey}`}
label={_(msg`Navigate to ${record.name}`)}
onPress={() => {
precacheResolvedUri(
queryClient,
starterPack.creator.handle,
starterPack.creator.did,
)
precacheStarterPack(queryClient, starterPack)
}}>
{children}
</InternalLink>
</BaseLink>
)
}
export function Embed({starterPack}: {starterPack: StarterPackViewBasic}) {
const t = useTheme()
const imageUri = getStarterPackOgCard(starterPack)
return (
<View
style={[
a.mt_xs,
a.border,
a.rounded_sm,
a.overflow_hidden,
t.atoms.border_contrast_low,
]}>
<Link starterPack={starterPack}>
<Image
source={imageUri}
style={[a.w_full, {aspectRatio: 1.91}]}
accessibilityIgnoresInvertColors={true}
/>
<View style={[a.px_sm, a.py_md]}>
<Card starterPack={starterPack} />
</View>
</Link>
</View>
)
}