Feed creation

This commit is contained in:
Dan Abramov
2024-10-22 22:30:03 +01:00
parent 6eb288e58f
commit 85b75284ba
4 changed files with 99 additions and 37 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SetName':
updatedState = {...state, name: action.name.slice(0, 50)}
updatedState = {...state, name: action.name.slice(0, 24)}
break
case 'SetDescription':
updatedState = {...state, description: action.description}
+4 -8
View File
@@ -5,12 +5,12 @@ import {useLingui} from '@lingui/react'
import {useProfileQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {useWizardState} from '#/screens/StarterPack/Wizard/State'
import {atoms as a, useTheme} from '#/alf'
import * as TextField from '#/components/forms/TextField'
import {StarterPack} from '#/components/icons/StarterPack'
import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition'
import {Text} from '#/components/Typography'
import {useWizardState} from './State'
export function StepDetails() {
const {_} = useLingui()
@@ -41,17 +41,13 @@ export function StepDetails() {
</TextField.LabelText>
<TextField.Root>
<TextField.Input
label={_(
msg`${
currentProfile?.displayName || currentProfile?.handle
}'s feed`,
)}
label={_(`My feed`)}
value={state.name}
onChangeText={text => dispatch({type: 'SetName', name: text})}
/>
<TextField.SuffixText label={_(`${state.name?.length} out of 50`)}>
<TextField.SuffixText label={_(`${state.name?.length} out of 24`)}>
<Text style={[t.atoms.text_contrast_medium]}>
{state.name?.length ?? 0}/50
{state.name?.length ?? 0}/24
</Text>
</TextField.SuffixText>
</TextField.Root>
+9 -28
View File
@@ -4,7 +4,6 @@ import {
KeyboardAwareScrollView,
useKeyboardController,
} from 'react-native-keyboard-controller'
import {Image} from 'expo-image'
import {AppBskyGraphDefs, AtUri} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
@@ -13,20 +12,15 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {HITSLOP_10} from '#/lib/constants'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {logEvent} from '#/lib/statsig/statsig'
import {
getStarterPackOgCard,
parseStarterPackUri,
} from '#/lib/strings/starter-pack'
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
import {logger} from '#/logger'
import {isAndroid, isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useCreateFeedMutation} from '#/state/queries/feed'
import {useAllListMembersQuery} from '#/state/queries/list-members'
import {useProfileQuery} from '#/state/queries/profile'
import {
useCreateStarterPackMutation,
useEditStarterPackMutation,
useStarterPackQuery,
} from '#/state/queries/starter-packs'
@@ -127,10 +121,6 @@ function WizardInner({
const {setEnabled} = useKeyboardController()
const [state, dispatch] = useWizardState()
const {currentAccount} = useSession()
const {data: currentProfile} = useProfileQuery({
did: currentAccount?.did,
staleTime: 0,
})
const parsed = parseStarterPackUri(currentStarterPack?.uri)
React.useEffect(() => {
@@ -152,8 +142,7 @@ function WizardInner({
)
const getDefaultName = () => {
const displayName = createSanitizedDisplayName(currentProfile!, true)
return _(msg`${displayName}'s Starter Pack`).slice(0, 50)
return _(msg`Your feed name`)
}
const wizardUiStrings: Record<
@@ -169,18 +158,10 @@ function WizardInner({
const onSuccessCreate = (data: {uri: string; cid: string}) => {
const rkey = new AtUri(data.uri).rkey
logEvent('starterPack:create', {
setName: state.name != null,
setDescription: state.description != null,
profilesCount: state.profiles.length,
feedsCount: state.feeds.length,
})
Image.prefetch([getStarterPackOgCard(currentProfile!.did, rkey)])
dispatch({type: 'SetProcessing', processing: false})
navigation.replace('StarterPack', {
navigation.replace('ProfileFeed', {
name: currentAccount!.handle,
rkey,
new: true,
})
}
@@ -188,14 +169,14 @@ function WizardInner({
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.replace('StarterPack', {
navigation.replace('ProfileFeed', {
name: currentAccount!.handle,
rkey: parsed!.rkey,
})
}
}
const {mutate: createStarterPack} = useCreateStarterPackMutation({
const {mutate: createFeed} = useCreateFeedMutation({
onSuccess: onSuccessCreate,
onError: e => {
logger.error('Failed to create starter pack', {safeMessage: e})
@@ -203,6 +184,8 @@ function WizardInner({
Toast.show(_(msg`Failed to create starter pack`), 'xmark')
},
})
// TODO
const {mutate: editStarterPack} = useEditStarterPackMutation({
onSuccess: onSuccessEdit,
onError: e => {
@@ -224,11 +207,9 @@ function WizardInner({
currentListItems: currentListItems,
})
} else {
createStarterPack({
createFeed({
name: state.name?.trim() || getDefaultName(),
description: state.description?.trim(),
profiles: state.profiles,
feeds: state.feeds,
})
}
}
+85
View File
@@ -1,4 +1,9 @@
import {useCallback, useEffect, useMemo, useRef} from 'react'
import {
AppBskyFeedGetFeedGenerator,
AppBskyRichtextFacet,
BskyAgent,
} from '@atproto/api'
import {
AppBskyActorDefs,
AppBskyFeedDefs,
@@ -19,6 +24,7 @@ import {
useQueryClient,
} from '@tanstack/react-query'
import {until} from '#/lib/async/until'
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
@@ -639,3 +645,82 @@ export function precacheFeedFromGeneratorView(
const hydratedFeed = hydrateFeedGenerator(view)
precacheFeed(queryClient, hydratedFeed)
}
// --- feeds playground ---
interface UseCreateFeedMutationParams {
name: string
description?: string
}
export function useCreateFeedMutation({
onSuccess,
onError,
}: {
onSuccess: (data: {uri: string; cid: string}) => void
onError: (e: Error) => void
}) {
const agent = useAgent()
return useMutation<
{uri: string; cid: string},
Error,
UseCreateFeedMutationParams
>({
mutationFn: async ({name, description}) => {
let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
if (description) {
const rt = new RichText({text: description})
await rt.detectFacets(agent)
descriptionFacets = rt.facets
}
const res = await agent.api.com.atproto.repo.createRecord({
repo: agent.session!.did,
collection: 'app.bsky.feed.generator',
record: {
$type: 'app.bsky.feed.generator',
createdAt: new Date().toISOString(),
did: 'did:web:feeed.club',
displayName: name,
description,
descriptionFacets,
labels: {
$type: 'com.atproto.label.defs#selfLabels',
values: [{val: 'feeed-club'}],
},
'$club.feeed.generator': {
$type: 'club.feeed.generator',
actors: [],
handleSuffixes: [],
tags: [],
},
},
})
return res.data
},
onSuccess: async data => {
await whenAppViewReady(agent, data.uri, v => {
return typeof v?.data.view.uri === 'string'
})
// TODO: Invalidate query
onSuccess(data)
},
onError: async error => {
onError(error)
},
})
}
async function whenAppViewReady(
agent: BskyAgent,
uri: string,
fn: (res?: AppBskyFeedGetFeedGenerator.Response) => boolean,
) {
await until(
5, // 5 tries
1e3, // 1s delay between tries
fn,
() => agent.app.bsky.feed.getFeedGenerator({feed: uri}),
)
}