about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-08-21 19:35:34 -0700
committerGitHub <noreply@github.com>2024-08-21 19:35:34 -0700
commit61f0be705d614a31331945e1c4b9361d71b81403 (patch)
tree093eae09977443d66ad917f45c6c5e1362559001
parent6616a6467ec53aa71e5f823c2d8c46dc01442703 (diff)
downloadvoidsky-61f0be705d614a31331945e1c4b9361d71b81403.tar.zst
Change size (#4957)
-rw-r--r--src/components/StarterPack/Main/ProfilesList.tsx31
-rw-r--r--src/components/StarterPack/Wizard/WizardEditListDialog.tsx3
-rw-r--r--src/components/StarterPack/Wizard/WizardListCard.tsx5
-rw-r--r--src/lib/constants.ts1
-rw-r--r--src/screens/Onboarding/StepFinished.tsx19
-rw-r--r--src/screens/Onboarding/util.ts12
-rw-r--r--src/screens/StarterPack/StarterPackScreen.tsx71
-rw-r--r--src/screens/StarterPack/Wizard/State.tsx10
-rw-r--r--src/screens/StarterPack/Wizard/index.tsx12
-rw-r--r--src/state/queries/list-members.ts41
-rw-r--r--src/state/queries/starter-packs.ts51
11 files changed, 168 insertions, 88 deletions
diff --git a/src/components/StarterPack/Main/ProfilesList.tsx b/src/components/StarterPack/Main/ProfilesList.tsx
index 3249f1b32..6174bff02 100644
--- a/src/components/StarterPack/Main/ProfilesList.tsx
+++ b/src/components/StarterPack/Main/ProfilesList.tsx
@@ -9,14 +9,15 @@ import {
 import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
 
 import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
 import {isBlockedOrBlocking} from 'lib/moderation/blocked-and-muted'
 import {isNative, isWeb} from 'platform/detection'
-import {useListMembersQuery} from 'state/queries/list-members'
+import {useAllListMembersQuery} 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 {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
 import {Default as ProfileCard} from '#/components/ProfileCard'
 
 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) {
@@ -39,17 +40,20 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
     ref,
   ) {
     const t = useTheme()
-    const [initialHeaderHeight] = React.useState(headerHeight)
-    const bottomBarOffset = useBottomBarOffset(20)
+    const bottomBarOffset = useBottomBarOffset(200)
+    const initialNumToRender = useInitialNumToRender()
     const {currentAccount} = useSession()
-    const {data, refetch, isError} = useListMembersQuery(listUri, 50)
+    const {data, refetch, isError} = useAllListMembersQuery(listUri)
 
     const [isPTRing, setIsPTRing] = React.useState(false)
 
     // 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))
-      .filter(p => !isBlockedOrBlocking(p) && !p.associated?.labeler)
+
+    const profiles = data
+      ?.filter(
+        p => !isBlockedOrBlocking(p.subject) && !p.subject.associated?.labeler,
+      )
+      .map(p => p.subject)
       .reverse()
     const isOwn = new AtUri(listUri).host === currentAccount?.did
 
@@ -99,7 +103,11 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
 
     if (!data) {
       return (
-        <View style={{marginTop: headerHeight, marginBottom: bottomBarOffset}}>
+        <View
+          style={[
+            a.h_full_vh,
+            {marginTop: headerHeight, marginBottom: bottomBarOffset},
+          ]}>
           <ListMaybePlaceholder
             isLoading={true}
             isError={isError}
@@ -118,10 +126,13 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
           ref={scrollElRef}
           headerOffset={headerHeight}
           ListFooterComponent={
-            <View style={[{height: initialHeaderHeight + bottomBarOffset}]} />
+            <ListFooter
+              style={{paddingBottom: bottomBarOffset, borderTopWidth: 0}}
+            />
           }
           showsVerticalScrollIndicator={false}
           desktopFixedHeight
+          initialNumToRender={initialNumToRender}
           refreshing={isPTRing}
           onRefresh={async () => {
             setIsPTRing(true)
diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
index cf755e1bc..870cbbb9f 100644
--- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
+++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
@@ -7,6 +7,7 @@ import {BottomSheetFlatListMethods} from '@discord/bottom-sheet'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
 import {isWeb} from 'platform/detection'
 import {useSession} from 'state/session'
 import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State'
@@ -42,6 +43,7 @@ export function WizardEditListDialog({
   const {_} = useLingui()
   const t = useTheme()
   const {currentAccount} = useSession()
+  const initialNumToRender = useInitialNumToRender()
 
   const listRef = useRef<BottomSheetFlatListMethods>(null)
 
@@ -148,6 +150,7 @@ export function WizardEditListDialog({
         webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
         keyboardDismissMode="on-drag"
         removeClippedSubviews={true}
+        initialNumToRender={initialNumToRender}
       />
     </Dialog.Outer>
   )
diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx
index 55cf0f02b..bd308fc73 100644
--- a/src/components/StarterPack/Wizard/WizardListCard.tsx
+++ b/src/components/StarterPack/Wizard/WizardListCard.tsx
@@ -12,7 +12,7 @@ import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {DISCOVER_FEED_URI} from 'lib/constants'
+import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from 'lib/constants'
 import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {sanitizeHandle} from 'lib/strings/handles'
 import {useSession} from 'state/session'
@@ -130,7 +130,8 @@ export function WizardProfileCard({
 
   const isMe = profile.did === currentAccount?.did
   const included = isMe || state.profiles.some(p => p.did === profile.did)
-  const disabled = isMe || (!included && state.profiles.length >= 49)
+  const disabled =
+    isMe || (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE - 1)
   const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar')
   const displayName = profile.displayName
     ? sanitizeDisplayName(profile.displayName)
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 20f6f2eff..ccd5f2dee 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -12,6 +12,7 @@ export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG
 export const EMBED_SERVICE = 'https://embed.bsky.app'
 export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
 export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'
+export const STARTER_PACK_MAX_SIZE = 150
 
 // HACK
 // Yes, this is exactly what it looks like. It's a hard-coded constant
diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx
index 825a0e723..379807d8f 100644
--- a/src/screens/Onboarding/StepFinished.tsx
+++ b/src/screens/Onboarding/StepFinished.tsx
@@ -23,6 +23,7 @@ import {useProgressGuideControls} from '#/state/shell/progress-guide'
 import {uploadBlob} from 'lib/api'
 import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
 import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs'
+import {getAllListMembers} from 'state/queries/list-members'
 import {
   useActiveStarterPack,
   useSetActiveStarterPack,
@@ -73,18 +74,20 @@ export function StepFinished() {
           starterPack: activeStarterPack.uri,
         })
         starterPack = spRes.data.starterPack
-
-        if (starterPack.list) {
-          const listRes = await agent.app.bsky.graph.getList({
-            list: starterPack.list.uri,
-            limit: 50,
-          })
-          listItems = listRes.data.items
-        }
       } catch (e) {
         logger.error('Failed to fetch starter pack', {safeMessage: e})
         // don't tell the user, just get them through onboarding.
       }
+      try {
+        if (starterPack?.list) {
+          listItems = await getAllListMembers(agent, starterPack.list.uri)
+        }
+      } catch (e) {
+        logger.error('Failed to fetch starter pack list items', {
+          safeMessage: e,
+        })
+        // don't tell the user, just get them through onboarding.
+      }
     }
 
     try {
diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts
index b9ecc4b98..14750f34c 100644
--- a/src/screens/Onboarding/util.ts
+++ b/src/screens/Onboarding/util.ts
@@ -4,6 +4,7 @@ import {
   BskyAgent,
 } from '@atproto/api'
 import {TID} from '@atproto/common-web'
+import chunk from 'lodash.chunk'
 
 import {until} from '#/lib/async/until'
 
@@ -29,10 +30,13 @@ export async function bulkWriteFollows(agent: BskyAgent, dids: string[]) {
     value: r,
   }))
 
-  await agent.com.atproto.repo.applyWrites({
-    repo: session.did,
-    writes: followWrites,
-  })
+  const chunks = chunk(followWrites, 50)
+  for (const chunk of chunks) {
+    await agent.com.atproto.repo.applyWrites({
+      repo: session.did,
+      writes: chunk,
+    })
+  }
   await whenFollowsIndexed(agent, session.did, res => !!res.data.follows.length)
 
   const followUris = new Map()
diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx
index 595e18527..5b267ff27 100644
--- a/src/screens/StarterPack/StarterPackScreen.tsx
+++ b/src/screens/StarterPack/StarterPackScreen.tsx
@@ -32,6 +32,7 @@ import {getStarterPackOgCard} from 'lib/strings/starter-pack'
 import {isWeb} from 'platform/detection'
 import {updateProfileShadow} from 'state/cache/profile-shadow'
 import {useModerationOpts} from 'state/preferences/moderation-opts'
+import {getAllListMembers} from 'state/queries/list-members'
 import {useResolvedStarterPackShortLink} from 'state/queries/resolve-short-link'
 import {useResolveDidQuery} from 'state/queries/resolve-uri'
 import {useShortenLink} from 'state/queries/shorten-link'
@@ -327,42 +328,52 @@ function Header({
 
     setIsProcessing(true)
 
+    let listItems: AppBskyGraphDefs.ListItemView[] = []
     try {
-      const list = await agent.app.bsky.graph.getList({
-        list: starterPack.list.uri,
-      })
-      const dids = list.data.items
-        .filter(
-          li =>
-            li.subject.did !== currentAccount?.did &&
-            !isBlockedOrBlocking(li.subject) &&
-            !isMuted(li.subject) &&
-            !li.subject.viewer?.following,
-        )
-        .map(li => li.subject.did)
-
-      const followUris = await bulkWriteFollows(agent, dids)
-
-      batchedUpdates(() => {
-        for (let did of dids) {
-          updateProfileShadow(queryClient, did, {
-            followingUri: followUris.get(did),
-          })
-        }
+      listItems = await getAllListMembers(agent, starterPack.list.uri)
+    } catch (e) {
+      setIsProcessing(false)
+      Toast.show(_(msg`An error occurred while trying to follow all`), 'xmark')
+      logger.error('Failed to get list members for starter pack', {
+        safeMessage: e,
       })
+      return
+    }
 
-      logEvent('starterPack:followAll', {
-        logContext: 'StarterPackProfilesList',
-        starterPack: starterPack.uri,
-        count: dids.length,
-      })
-      captureAction(ProgressGuideAction.Follow, dids.length)
-      Toast.show(_(msg`All accounts have been followed!`))
+    const dids = listItems
+      .filter(
+        li =>
+          li.subject.did !== currentAccount?.did &&
+          !isBlockedOrBlocking(li.subject) &&
+          !isMuted(li.subject) &&
+          !li.subject.viewer?.following,
+      )
+      .map(li => li.subject.did)
+
+    let followUris: Map<string, string>
+    try {
+      followUris = await bulkWriteFollows(agent, dids)
     } catch (e) {
-      Toast.show(_(msg`An error occurred while trying to follow all`), 'xmark')
-    } finally {
       setIsProcessing(false)
+      Toast.show(_(msg`An error occurred while trying to follow all`), 'xmark')
+      logger.error('Failed to follow all accounts', {safeMessage: e})
     }
+
+    setIsProcessing(false)
+    batchedUpdates(() => {
+      for (let did of dids) {
+        updateProfileShadow(queryClient, did, {
+          followingUri: followUris.get(did),
+        })
+      }
+    })
+    Toast.show(_(msg`All accounts have been followed!`))
+    captureAction(ProgressGuideAction.Follow, dids.length)
+    logEvent('starterPack:followAll', {
+      logContext: 'StarterPackProfilesList',
+      starterPack: starterPack.uri,
+      count: dids.length,
+    })
   }
 
   if (!AppBskyGraphStarterpack.isRecord(record)) {
diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx
index ba5bb147c..debb7e23c 100644
--- a/src/screens/StarterPack/Wizard/State.tsx
+++ b/src/screens/StarterPack/Wizard/State.tsx
@@ -7,6 +7,7 @@ import {
 import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
 import {msg} from '@lingui/macro'
 
+import {STARTER_PACK_MAX_SIZE} from 'lib/constants'
 import {useSession} from 'state/session'
 import * as Toast from '#/view/com/util/Toast'
 
@@ -73,9 +74,10 @@ function reducer(state: State, action: Action): State {
       updatedState = {...state, description: action.description}
       break
     case 'AddProfile':
-      if (state.profiles.length >= 51) {
+      if (state.profiles.length > STARTER_PACK_MAX_SIZE) {
         Toast.show(
-          msg`You may only add up to 50 profiles`.message ?? '',
+          msg`You may only add up to ${STARTER_PACK_MAX_SIZE} profiles`
+            .message ?? '',
           'info',
         )
       } else {
@@ -91,8 +93,8 @@ function reducer(state: State, action: Action): State {
       }
       break
     case 'AddFeed':
-      if (state.feeds.length >= 50) {
-        Toast.show(msg`You may only add up to 50 feeds`.message ?? '', 'info')
+      if (state.feeds.length >= 3) {
+        Toast.show(msg`You may only add up to 3 feeds`.message ?? '', 'info')
       } else {
         updatedState = {...state, feeds: [...state.feeds, action.feed]}
       }
diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx
index 8d9bb165b..40a4a510b 100644
--- a/src/screens/StarterPack/Wizard/index.tsx
+++ b/src/screens/StarterPack/Wizard/index.tsx
@@ -20,7 +20,7 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {logger} from '#/logger'
-import {HITSLOP_10} from 'lib/constants'
+import {HITSLOP_10, STARTER_PACK_MAX_SIZE} 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'
@@ -33,7 +33,7 @@ import {
 } from 'lib/strings/starter-pack'
 import {isAndroid, isNative, isWeb} from 'platform/detection'
 import {useModerationOpts} from 'state/preferences/moderation-opts'
-import {useListMembersQuery} from 'state/queries/list-members'
+import {useAllListMembersQuery} from 'state/queries/list-members'
 import {useProfileQuery} from 'state/queries/profile'
 import {
   useCreateStarterPackMutation,
@@ -78,11 +78,10 @@ export function Wizard({
   const listUri = starterPack?.list?.uri
 
   const {
-    data: profilesData,
+    data: listItems,
     isLoading: isLoadingProfiles,
     isError: isErrorProfiles,
-  } = useListMembersQuery(listUri, 50)
-  const listItems = profilesData?.pages.flatMap(p => p.items)
+  } = useAllListMembersQuery(listUri)
 
   const {
     data: profile,
@@ -428,7 +427,8 @@ function Footer({
       {items.length > minimumItems && (
         <View style={[a.absolute, {right: 14, top: 31}]}>
           <Text style={[a.font_bold]}>
-            {items.length}/{state.currentStep === 'Profiles' ? 50 : 3}
+            {items.length}/
+            {state.currentStep === 'Profiles' ? STARTER_PACK_MAX_SIZE : 3}
           </Text>
         </View>
       )}
diff --git a/src/state/queries/list-members.ts b/src/state/queries/list-members.ts
index 3131a2ec3..b02cc9910 100644
--- a/src/state/queries/list-members.ts
+++ b/src/state/queries/list-members.ts
@@ -1,9 +1,15 @@
-import {AppBskyActorDefs, AppBskyGraphGetList} from '@atproto/api'
+import {
+  AppBskyActorDefs,
+  AppBskyGraphDefs,
+  AppBskyGraphGetList,
+  BskyAgent,
+} from '@atproto/api'
 import {
   InfiniteData,
   QueryClient,
   QueryKey,
   useInfiniteQuery,
+  useQuery,
 } from '@tanstack/react-query'
 
 import {STALE} from '#/state/queries'
@@ -14,6 +20,7 @@ type RQPageParam = string | undefined
 
 const RQKEY_ROOT = 'list-members'
 export const RQKEY = (uri: string) => [RQKEY_ROOT, uri]
+export const RQKEY_ALL = (uri: string) => [RQKEY_ROOT, uri, 'all']
 
 export function useListMembersQuery(uri?: string, limit: number = PAGE_SIZE) {
   const agent = useAgent()
@@ -40,6 +47,38 @@ export function useListMembersQuery(uri?: string, limit: number = PAGE_SIZE) {
   })
 }
 
+export function useAllListMembersQuery(uri?: string) {
+  const agent = useAgent()
+  return useQuery({
+    staleTime: STALE.MINUTES.ONE,
+    queryKey: RQKEY_ALL(uri ?? ''),
+    queryFn: async () => {
+      return getAllListMembers(agent, uri!)
+    },
+    enabled: Boolean(uri),
+  })
+}
+
+export async function getAllListMembers(agent: BskyAgent, uri: string) {
+  let hasMore = true
+  let cursor: string | undefined
+  const listItems: AppBskyGraphDefs.ListItemView[] = []
+  // We want to cap this at 6 pages, just for anything weird happening with the api
+  let i = 0
+  while (hasMore && i < 6) {
+    const res = await agent.app.bsky.graph.getList({
+      list: uri,
+      limit: 50,
+      cursor,
+    })
+    listItems.push(...res.data.items)
+    hasMore = Boolean(res.data.cursor)
+    cursor = res.data.cursor
+  }
+  i++
+  return listItems
+}
+
 export async function invalidateListMembersQuery({
   queryClient,
   uri,
diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts
index 2cdb6b850..a3795d792 100644
--- a/src/state/queries/starter-packs.ts
+++ b/src/state/queries/starter-packs.ts
@@ -16,6 +16,7 @@ import {
   useQuery,
   useQueryClient,
 } from '@tanstack/react-query'
+import chunk from 'lodash.chunk'
 
 import {until} from 'lib/async/until'
 import {createStarterPackList} from 'lib/generate-starterpack'
@@ -200,36 +201,40 @@ export function useEditStarterPackMutation({
           i.subject.did !== agent.session?.did &&
           !profiles.find(p => p.did === i.subject.did && p.did),
       )
-
       if (removedItems.length !== 0) {
-        await agent.com.atproto.repo.applyWrites({
-          repo: agent.session!.did,
-          writes: removedItems.map(i => ({
-            $type: 'com.atproto.repo.applyWrites#delete',
-            collection: 'app.bsky.graph.listitem',
-            rkey: new AtUri(i.uri).rkey,
-          })),
-        })
+        const chunks = chunk(removedItems, 50)
+        for (const chunk of chunks) {
+          await agent.com.atproto.repo.applyWrites({
+            repo: agent.session!.did,
+            writes: chunk.map(i => ({
+              $type: 'com.atproto.repo.applyWrites#delete',
+              collection: 'app.bsky.graph.listitem',
+              rkey: new AtUri(i.uri).rkey,
+            })),
+          })
+        }
       }
 
       const addedProfiles = profiles.filter(
         p => !currentListItems.find(i => i.subject.did === p.did),
       )
-
       if (addedProfiles.length > 0) {
-        await agent.com.atproto.repo.applyWrites({
-          repo: agent.session!.did,
-          writes: addedProfiles.map(p => ({
-            $type: 'com.atproto.repo.applyWrites#create',
-            collection: 'app.bsky.graph.listitem',
-            value: {
-              $type: 'app.bsky.graph.listitem',
-              subject: p.did,
-              list: currentStarterPack.list?.uri,
-              createdAt: new Date().toISOString(),
-            },
-          })),
-        })
+        const chunks = chunk(addedProfiles, 50)
+        for (const chunk of chunks) {
+          await agent.com.atproto.repo.applyWrites({
+            repo: agent.session!.did,
+            writes: chunk.map(p => ({
+              $type: 'com.atproto.repo.applyWrites#create',
+              collection: 'app.bsky.graph.listitem',
+              value: {
+                $type: 'app.bsky.graph.listitem',
+                subject: p.did,
+                list: currentStarterPack.list?.uri,
+                createdAt: new Date().toISOString(),
+              },
+            })),
+          })
+        }
       }
 
       const rkey = parseStarterPackUri(currentStarterPack.uri)!.rkey