about summary refs log tree commit diff
path: root/src/view/screens/Profile.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/screens/Profile.tsx')
-rw-r--r--src/view/screens/Profile.tsx115
1 files changed, 68 insertions, 47 deletions
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 734230c6c..946f6ac54 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -1,7 +1,8 @@
-import React, {useMemo} from 'react'
+import React, {useCallback, useMemo} from 'react'
 import {StyleSheet} from 'react-native'
 import {
   AppBskyActorDefs,
+  AppBskyGraphGetActorStarterPacks,
   moderateProfile,
   ModerationOpts,
   RichText as RichTextAPI,
@@ -9,7 +10,11 @@ import {
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect} from '@react-navigation/native'
-import {useQueryClient} from '@tanstack/react-query'
+import {
+  InfiniteData,
+  UseInfiniteQueryResult,
+  useQueryClient,
+} from '@tanstack/react-query'
 
 import {cleanError} from '#/lib/strings/errors'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
@@ -22,18 +27,23 @@ import {useAgent, useSession} from '#/state/session'
 import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
 import {useComposerControls} from '#/state/shell/composer'
 import {useAnalytics} from 'lib/analytics/analytics'
+import {IS_DEV, IS_TESTFLIGHT} from 'lib/app-info'
 import {useSetTitle} from 'lib/hooks/useSetTitle'
 import {ComposeIcon2} from 'lib/icons'
 import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
+import {useGate} from 'lib/statsig/statsig'
 import {combinedDisplayName} from 'lib/strings/display-names'
 import {isInvalidHandle} from 'lib/strings/handles'
 import {colors, s} from 'lib/styles'
+import {isWeb} from 'platform/detection'
 import {listenSoftReset} from 'state/events'
+import {useActorStarterPacksQuery} from 'state/queries/actor-starter-packs'
 import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
 import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
 import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
 import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
 import {ScreenHider} from '#/components/moderation/ScreenHider'
+import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
 import {ExpoScrollForwarderView} from '../../../modules/expo-scroll-forwarder'
 import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
 import {ProfileLists} from '../com/lists/ProfileLists'
@@ -69,6 +79,7 @@ export function ProfileScreen({route}: Props) {
   } = useProfileQuery({
     did: resolvedDid,
   })
+  const starterPacksQuery = useActorStarterPacksQuery({did: resolvedDid})
 
   const onPressTryAgain = React.useCallback(() => {
     if (resolveError) {
@@ -86,7 +97,7 @@ export function ProfileScreen({route}: Props) {
   }, [queryClient, profile?.viewer?.blockedBy, resolvedDid])
 
   // Most pushes will happen here, since we will have only placeholder data
-  if (isLoadingDid || isLoadingProfile) {
+  if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) {
     return (
       <CenteredView>
         <ProfileHeaderLoading />
@@ -108,6 +119,7 @@ export function ProfileScreen({route}: Props) {
     return (
       <ProfileScreenLoaded
         profile={profile}
+        starterPacksQuery={starterPacksQuery}
         moderationOpts={moderationOpts}
         isPlaceholderProfile={isPlaceholderProfile}
         hideBackButton={!!route.params.hideBackButton}
@@ -131,11 +143,16 @@ function ProfileScreenLoaded({
   isPlaceholderProfile,
   moderationOpts,
   hideBackButton,
+  starterPacksQuery,
 }: {
   profile: AppBskyActorDefs.ProfileViewDetailed
   moderationOpts: ModerationOpts
   hideBackButton: boolean
   isPlaceholderProfile: boolean
+  starterPacksQuery: UseInfiniteQueryResult<
+    InfiniteData<AppBskyGraphGetActorStarterPacks.OutputSchema, unknown>,
+    Error
+  >
 }) {
   const profile = useProfileShadow(profileUnshadowed)
   const {hasSession, currentAccount} = useSession()
@@ -153,6 +170,9 @@ function ProfileScreenLoaded({
   const [currentPage, setCurrentPage] = React.useState(0)
   const {_} = useLingui()
   const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
+  const gate = useGate()
+  const starterPacksEnabled =
+    IS_DEV || IS_TESTFLIGHT || (!isWeb && gate('starter_packs_enabled'))
 
   const [scrollViewTag, setScrollViewTag] = React.useState<number | null>(null)
 
@@ -162,6 +182,7 @@ function ProfileScreenLoaded({
   const likesSectionRef = React.useRef<SectionRef>(null)
   const feedsSectionRef = React.useRef<SectionRef>(null)
   const listsSectionRef = React.useRef<SectionRef>(null)
+  const starterPacksSectionRef = React.useRef<SectionRef>(null)
   const labelsSectionRef = React.useRef<SectionRef>(null)
 
   useSetTitle(combinedDisplayName(profile))
@@ -183,31 +204,23 @@ function ProfileScreenLoaded({
   const showMediaTab = !hasLabeler
   const showLikesTab = isMe
   const showFeedsTab = isMe || (profile.associated?.feedgens || 0) > 0
+  const showStarterPacksTab =
+    starterPacksEnabled &&
+    (isMe || !!starterPacksQuery.data?.pages?.[0].starterPacks.length)
   const showListsTab =
     hasSession && (isMe || (profile.associated?.lists || 0) > 0)
 
-  const sectionTitles = useMemo<string[]>(() => {
-    return [
-      showFiltersTab ? _(msg`Labels`) : undefined,
-      showListsTab && hasLabeler ? _(msg`Lists`) : undefined,
-      showPostsTab ? _(msg`Posts`) : undefined,
-      showRepliesTab ? _(msg`Replies`) : undefined,
-      showMediaTab ? _(msg`Media`) : undefined,
-      showLikesTab ? _(msg`Likes`) : undefined,
-      showFeedsTab ? _(msg`Feeds`) : undefined,
-      showListsTab && !hasLabeler ? _(msg`Lists`) : undefined,
-    ].filter(Boolean) as string[]
-  }, [
-    showPostsTab,
-    showRepliesTab,
-    showMediaTab,
-    showLikesTab,
-    showFeedsTab,
-    showListsTab,
-    showFiltersTab,
-    hasLabeler,
-    _,
-  ])
+  const sectionTitles = [
+    showFiltersTab ? _(msg`Labels`) : undefined,
+    showListsTab && hasLabeler ? _(msg`Lists`) : undefined,
+    showPostsTab ? _(msg`Posts`) : undefined,
+    showRepliesTab ? _(msg`Replies`) : undefined,
+    showMediaTab ? _(msg`Media`) : undefined,
+    showLikesTab ? _(msg`Likes`) : undefined,
+    showFeedsTab ? _(msg`Feeds`) : undefined,
+    showStarterPacksTab ? _(msg`Starter Packs`) : undefined,
+    showListsTab && !hasLabeler ? _(msg`Lists`) : undefined,
+  ].filter(Boolean) as string[]
 
   let nextIndex = 0
   let filtersIndex: number | null = null
@@ -216,6 +229,7 @@ function ProfileScreenLoaded({
   let mediaIndex: number | null = null
   let likesIndex: number | null = null
   let feedsIndex: number | null = null
+  let starterPacksIndex: number | null = null
   let listsIndex: number | null = null
   if (showFiltersTab) {
     filtersIndex = nextIndex++
@@ -235,11 +249,14 @@ function ProfileScreenLoaded({
   if (showFeedsTab) {
     feedsIndex = nextIndex++
   }
+  if (showStarterPacksTab) {
+    starterPacksIndex = nextIndex++
+  }
   if (showListsTab) {
     listsIndex = nextIndex++
   }
 
-  const scrollSectionToTop = React.useCallback(
+  const scrollSectionToTop = useCallback(
     (index: number) => {
       if (index === filtersIndex) {
         labelsSectionRef.current?.scrollToTop()
@@ -253,6 +270,8 @@ function ProfileScreenLoaded({
         likesSectionRef.current?.scrollToTop()
       } else if (index === feedsIndex) {
         feedsSectionRef.current?.scrollToTop()
+      } else if (index === starterPacksIndex) {
+        starterPacksSectionRef.current?.scrollToTop()
       } else if (index === listsIndex) {
         listsSectionRef.current?.scrollToTop()
       }
@@ -265,6 +284,7 @@ function ProfileScreenLoaded({
       likesIndex,
       feedsIndex,
       listsIndex,
+      starterPacksIndex,
     ],
   )
 
@@ -290,7 +310,7 @@ function ProfileScreenLoaded({
   // events
   // =
 
-  const onPressCompose = React.useCallback(() => {
+  const onPressCompose = () => {
     track('ProfileScreen:PressCompose')
     const mention =
       profile.handle === currentAccount?.handle ||
@@ -298,23 +318,20 @@ function ProfileScreenLoaded({
         ? undefined
         : profile.handle
     openComposer({mention})
-  }, [openComposer, currentAccount, track, profile])
+  }
 
-  const onPageSelected = React.useCallback((i: number) => {
+  const onPageSelected = (i: number) => {
     setCurrentPage(i)
-  }, [])
+  }
 
-  const onCurrentPageSelected = React.useCallback(
-    (index: number) => {
-      scrollSectionToTop(index)
-    },
-    [scrollSectionToTop],
-  )
+  const onCurrentPageSelected = (index: number) => {
+    scrollSectionToTop(index)
+  }
 
   // rendering
   // =
 
-  const renderHeader = React.useCallback(() => {
+  const renderHeader = () => {
     return (
       <ExpoScrollForwarderView scrollViewTag={scrollViewTag}>
         <ProfileHeader
@@ -327,16 +344,7 @@ function ProfileScreenLoaded({
         />
       </ExpoScrollForwarderView>
     )
-  }, [
-    scrollViewTag,
-    profile,
-    labelerInfo,
-    hasDescription,
-    descriptionRT,
-    moderationOpts,
-    hideBackButton,
-    showPlaceholder,
-  ])
+  }
 
   return (
     <ScreenHider
@@ -442,6 +450,19 @@ function ProfileScreenLoaded({
               />
             )
           : null}
+        {showStarterPacksTab
+          ? ({headerHeight, isFocused, scrollElRef}) => (
+              <ProfileStarterPacks
+                ref={starterPacksSectionRef}
+                isMe={isMe}
+                starterPacksQuery={starterPacksQuery}
+                scrollElRef={scrollElRef as ListRef}
+                headerOffset={headerHeight}
+                enabled={isFocused}
+                setScrollViewTag={setScrollViewTag}
+              />
+            )
+          : null}
         {showListsTab && !profile.associated?.labeler
           ? ({headerHeight, isFocused, scrollElRef}) => (
               <ProfileLists