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.tsx253
1 files changed, 125 insertions, 128 deletions
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index b30b4491b..d5a46c5c9 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -1,5 +1,5 @@
 import React, {useMemo} from 'react'
-import {StyleSheet, View} from 'react-native'
+import {StyleSheet} from 'react-native'
 import {useFocusEffect} from '@react-navigation/native'
 import {
   AppBskyActorDefs,
@@ -7,48 +7,39 @@ import {
   ModerationOpts,
   RichText as RichTextAPI,
 } from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
+import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {CenteredView} from '../com/util/Views'
 import {ListRef} from '../com/util/List'
-import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
-import {Feed} from 'view/com/posts/Feed'
+import {ScreenHider} from '#/components/moderation/ScreenHider'
 import {ProfileLists} from '../com/lists/ProfileLists'
 import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
-import {ProfileHeader, ProfileHeaderLoading} from '../com/profile/ProfileHeader'
 import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
 import {ErrorScreen} from '../com/util/error/ErrorScreen'
-import {EmptyState} from '../com/util/EmptyState'
 import {FAB} from '../com/util/fab/FAB'
 import {s, colors} from 'lib/styles'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {ComposeIcon2} from 'lib/icons'
 import {useSetTitle} from 'lib/hooks/useSetTitle'
 import {combinedDisplayName} from 'lib/strings/display-names'
-import {
-  FeedDescriptor,
-  resetProfilePostsQueries,
-} from '#/state/queries/post-feed'
+import {resetProfilePostsQueries} from '#/state/queries/post-feed'
 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
 import {useProfileQuery} from '#/state/queries/profile'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {useSession, getAgent} from '#/state/session'
 import {useModerationOpts} from '#/state/queries/preferences'
-import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info'
-import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
+import {useLabelerInfoQuery} from '#/state/queries/labeler'
 import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
 import {cleanError} from '#/lib/strings/errors'
-import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
-import {useQueryClient} from '@tanstack/react-query'
 import {useComposerControls} from '#/state/shell/composer'
 import {listenSoftReset} from '#/state/events'
-import {truncateAndInvalidate} from '#/state/queries/util'
-import {Text} from '#/view/com/util/text/Text'
-import {usePalette} from 'lib/hooks/usePalette'
-import {isNative} from '#/platform/detection'
 import {isInvalidHandle} from '#/lib/strings/handles'
 
+import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
+import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
+import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
+
 interface SectionRef {
   scrollToTop: () => void
 }
@@ -148,16 +139,24 @@ function ProfileScreenLoaded({
   const setMinimalShellMode = useSetMinimalShellMode()
   const {openComposer} = useComposerControls()
   const {screen, track} = useAnalytics()
+  const {
+    data: labelerInfo,
+    error: labelerError,
+    isLoading: isLabelerLoading,
+  } = useLabelerInfoQuery({
+    did: profile.did,
+    enabled: !!profile.associated?.labeler,
+  })
   const [currentPage, setCurrentPage] = React.useState(0)
   const {_} = useLingui()
   const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
-  const extraInfoQuery = useProfileExtraInfoQuery(profile.did)
   const postsSectionRef = React.useRef<SectionRef>(null)
   const repliesSectionRef = React.useRef<SectionRef>(null)
   const mediaSectionRef = React.useRef<SectionRef>(null)
   const likesSectionRef = React.useRef<SectionRef>(null)
   const feedsSectionRef = React.useRef<SectionRef>(null)
   const listsSectionRef = React.useRef<SectionRef>(null)
+  const labelsSectionRef = React.useRef<SectionRef>(null)
 
   useSetTitle(combinedDisplayName(profile))
 
@@ -171,44 +170,75 @@ function ProfileScreenLoaded({
   )
 
   const isMe = profile.did === currentAccount?.did
+  const hasLabeler = !!profile.associated?.labeler
+  const showFiltersTab = hasLabeler
+  const showPostsTab = true
   const showRepliesTab = hasSession
+  const showMediaTab = !hasLabeler
   const showLikesTab = isMe
-  const showFeedsTab = hasSession && (isMe || extraInfoQuery.data?.hasFeedgens)
-  const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists)
+  const showFeedsTab =
+    hasSession && (isMe || (profile.associated?.feedgens || 0) > 0)
+  const showListsTab =
+    hasSession && (isMe || (profile.associated?.lists || 0) > 0)
+
   const sectionTitles = useMemo<string[]>(() => {
     return [
-      _(msg`Posts`),
+      showFiltersTab ? _(msg`Labels`) : undefined,
+      showListsTab && hasLabeler ? _(msg`Lists`) : undefined,
+      showPostsTab ? _(msg`Posts`) : undefined,
       showRepliesTab ? _(msg`Replies`) : undefined,
-      _(msg`Media`),
+      showMediaTab ? _(msg`Media`) : undefined,
       showLikesTab ? _(msg`Likes`) : undefined,
       showFeedsTab ? _(msg`Feeds`) : undefined,
-      showListsTab ? _(msg`Lists`) : undefined,
+      showListsTab && !hasLabeler ? _(msg`Lists`) : undefined,
     ].filter(Boolean) as string[]
-  }, [showRepliesTab, showLikesTab, showFeedsTab, showListsTab, _])
+  }, [
+    showPostsTab,
+    showRepliesTab,
+    showMediaTab,
+    showLikesTab,
+    showFeedsTab,
+    showListsTab,
+    showFiltersTab,
+    hasLabeler,
+    _,
+  ])
 
   let nextIndex = 0
-  const postsIndex = nextIndex++
+  let filtersIndex: number | null = null
+  let postsIndex: number | null = null
   let repliesIndex: number | null = null
+  let mediaIndex: number | null = null
+  let likesIndex: number | null = null
+  let feedsIndex: number | null = null
+  let listsIndex: number | null = null
+  if (showFiltersTab) {
+    filtersIndex = nextIndex++
+  }
+  if (showPostsTab) {
+    postsIndex = nextIndex++
+  }
   if (showRepliesTab) {
     repliesIndex = nextIndex++
   }
-  const mediaIndex = nextIndex++
-  let likesIndex: number | null = null
+  if (showMediaTab) {
+    mediaIndex = nextIndex++
+  }
   if (showLikesTab) {
     likesIndex = nextIndex++
   }
-  let feedsIndex: number | null = null
   if (showFeedsTab) {
     feedsIndex = nextIndex++
   }
-  let listsIndex: number | null = null
   if (showListsTab) {
     listsIndex = nextIndex++
   }
 
   const scrollSectionToTop = React.useCallback(
     (index: number) => {
-      if (index === postsIndex) {
+      if (index === filtersIndex) {
+        labelsSectionRef.current?.scrollToTop()
+      } else if (index === postsIndex) {
         postsSectionRef.current?.scrollToTop()
       } else if (index === repliesIndex) {
         repliesSectionRef.current?.scrollToTop()
@@ -222,7 +252,15 @@ function ProfileScreenLoaded({
         listsSectionRef.current?.scrollToTop()
       }
     },
-    [postsIndex, repliesIndex, mediaIndex, likesIndex, feedsIndex, listsIndex],
+    [
+      filtersIndex,
+      postsIndex,
+      repliesIndex,
+      mediaIndex,
+      likesIndex,
+      feedsIndex,
+      listsIndex,
+    ],
   )
 
   useFocusEffect(
@@ -278,6 +316,7 @@ function ProfileScreenLoaded({
     return (
       <ProfileHeader
         profile={profile}
+        labeler={labelerInfo}
         descriptionRT={hasDescription ? descriptionRT : null}
         moderationOpts={moderationOpts}
         hideBackButton={hideBackButton}
@@ -286,6 +325,7 @@ function ProfileScreenLoaded({
     )
   }, [
     profile,
+    labelerInfo,
     descriptionRT,
     hasDescription,
     moderationOpts,
@@ -297,8 +337,8 @@ function ProfileScreenLoaded({
     <ScreenHider
       testID="profileView"
       style={styles.container}
-      screenDescription="profile"
-      moderation={moderation.account}>
+      screenDescription={_(msg`profile`)}
+      modui={moderation.ui('profileView')}>
       <PagerWithHeader
         testID="profilePager"
         isHeaderReady={!showPlaceholder}
@@ -306,19 +346,45 @@ function ProfileScreenLoaded({
         onPageSelected={onPageSelected}
         onCurrentPageSelected={onCurrentPageSelected}
         renderHeader={renderHeader}>
-        {({headerHeight, isFocused, scrollElRef}) => (
-          <FeedSection
-            ref={postsSectionRef}
-            feed={`author|${profile.did}|posts_and_author_threads`}
-            headerHeight={headerHeight}
-            isFocused={isFocused}
-            scrollElRef={scrollElRef as ListRef}
-            ignoreFilterFor={profile.did}
-          />
-        )}
+        {showFiltersTab
+          ? ({headerHeight, scrollElRef}) => (
+              <ProfileLabelsSection
+                ref={labelsSectionRef}
+                labelerInfo={labelerInfo}
+                labelerError={labelerError}
+                isLabelerLoading={isLabelerLoading}
+                moderationOpts={moderationOpts}
+                scrollElRef={scrollElRef as ListRef}
+                headerHeight={headerHeight}
+              />
+            )
+          : null}
+        {showListsTab && !!profile.associated?.labeler
+          ? ({headerHeight, isFocused, scrollElRef}) => (
+              <ProfileLists
+                ref={listsSectionRef}
+                did={profile.did}
+                scrollElRef={scrollElRef as ListRef}
+                headerOffset={headerHeight}
+                enabled={isFocused}
+              />
+            )
+          : null}
+        {showPostsTab
+          ? ({headerHeight, isFocused, scrollElRef}) => (
+              <ProfileFeedSection
+                ref={postsSectionRef}
+                feed={`author|${profile.did}|posts_and_author_threads`}
+                headerHeight={headerHeight}
+                isFocused={isFocused}
+                scrollElRef={scrollElRef as ListRef}
+                ignoreFilterFor={profile.did}
+              />
+            )
+          : null}
         {showRepliesTab
           ? ({headerHeight, isFocused, scrollElRef}) => (
-              <FeedSection
+              <ProfileFeedSection
                 ref={repliesSectionRef}
                 feed={`author|${profile.did}|posts_with_replies`}
                 headerHeight={headerHeight}
@@ -328,19 +394,21 @@ function ProfileScreenLoaded({
               />
             )
           : null}
-        {({headerHeight, isFocused, scrollElRef}) => (
-          <FeedSection
-            ref={mediaSectionRef}
-            feed={`author|${profile.did}|posts_with_media`}
-            headerHeight={headerHeight}
-            isFocused={isFocused}
-            scrollElRef={scrollElRef as ListRef}
-            ignoreFilterFor={profile.did}
-          />
-        )}
+        {showMediaTab
+          ? ({headerHeight, isFocused, scrollElRef}) => (
+              <ProfileFeedSection
+                ref={mediaSectionRef}
+                feed={`author|${profile.did}|posts_with_media`}
+                headerHeight={headerHeight}
+                isFocused={isFocused}
+                scrollElRef={scrollElRef as ListRef}
+                ignoreFilterFor={profile.did}
+              />
+            )
+          : null}
         {showLikesTab
           ? ({headerHeight, isFocused, scrollElRef}) => (
-              <FeedSection
+              <ProfileFeedSection
                 ref={likesSectionRef}
                 feed={`likes|${profile.did}`}
                 headerHeight={headerHeight}
@@ -361,7 +429,7 @@ function ProfileScreenLoaded({
               />
             )
           : null}
-        {showListsTab
+        {showListsTab && !profile.associated?.labeler
           ? ({headerHeight, isFocused, scrollElRef}) => (
               <ProfileLists
                 ref={listsSectionRef}
@@ -387,77 +455,6 @@ function ProfileScreenLoaded({
   )
 }
 
-interface FeedSectionProps {
-  feed: FeedDescriptor
-  headerHeight: number
-  isFocused: boolean
-  scrollElRef: ListRef
-  ignoreFilterFor?: string
-}
-const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
-  function FeedSectionImpl(
-    {feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor},
-    ref,
-  ) {
-    const {_} = useLingui()
-    const queryClient = useQueryClient()
-    const [hasNew, setHasNew] = React.useState(false)
-    const [isScrolledDown, setIsScrolledDown] = React.useState(false)
-
-    const onScrollToTop = React.useCallback(() => {
-      scrollElRef.current?.scrollToOffset({
-        animated: isNative,
-        offset: -headerHeight,
-      })
-      truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
-      setHasNew(false)
-    }, [scrollElRef, headerHeight, queryClient, feed, setHasNew])
-    React.useImperativeHandle(ref, () => ({
-      scrollToTop: onScrollToTop,
-    }))
-
-    const renderPostsEmpty = React.useCallback(() => {
-      return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
-    }, [_])
-
-    return (
-      <View>
-        <Feed
-          testID="postsFeed"
-          enabled={isFocused}
-          feed={feed}
-          scrollElRef={scrollElRef}
-          onHasNew={setHasNew}
-          onScrolledDownChange={setIsScrolledDown}
-          renderEmptyState={renderPostsEmpty}
-          headerOffset={headerHeight}
-          renderEndOfFeed={ProfileEndOfFeed}
-          ignoreFilterFor={ignoreFilterFor}
-        />
-        {(isScrolledDown || hasNew) && (
-          <LoadLatestBtn
-            onPress={onScrollToTop}
-            label={_(msg`Load new posts`)}
-            showIndicator={hasNew}
-          />
-        )}
-      </View>
-    )
-  },
-)
-
-function ProfileEndOfFeed() {
-  const pal = usePalette('default')
-
-  return (
-    <View style={[pal.border, {paddingTop: 32, borderTopWidth: 1}]}>
-      <Text style={[pal.textLight, pal.border, {textAlign: 'center'}]}>
-        <Trans>End of feed</Trans>
-      </Text>
-    </View>
-  )
-}
-
 function useRichText(text: string): [RichTextAPI, boolean] {
   const [prevText, setPrevText] = React.useState(text)
   const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text}))