about summary refs log tree commit diff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/Profile/Header/ProfileHeaderStandard.tsx14
-rw-r--r--src/screens/Profile/Header/Shell.tsx2
-rw-r--r--src/screens/Profile/KnownFollowers.tsx134
3 files changed, 149 insertions, 1 deletions
diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
index f4b8d7705..f8a87a68e 100644
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -30,6 +30,10 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {MessageProfileButton} from '#/components/dms/MessageProfileButton'
 import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
 import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
+import {
+  KnownFollowers,
+  shouldShowKnownFollowers,
+} from '#/components/KnownFollowers'
 import * as Prompt from '#/components/Prompt'
 import {RichText} from '#/components/RichText'
 import {ProfileHeaderDisplayName} from './DisplayName'
@@ -268,6 +272,16 @@ let ProfileHeaderStandard = ({
                 />
               </View>
             ) : undefined}
+
+            {!isMe &&
+              shouldShowKnownFollowers(profile.viewer?.knownFollowers) && (
+                <View style={[a.flex_row, a.align_center, a.gap_sm, a.pt_md]}>
+                  <KnownFollowers
+                    profile={profile}
+                    moderationOpts={moderationOpts}
+                  />
+                </View>
+              )}
           </>
         )}
       </View>
diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx
index 553b38a3b..82cba1704 100644
--- a/src/screens/Profile/Header/Shell.tsx
+++ b/src/screens/Profile/Header/Shell.tsx
@@ -83,7 +83,7 @@ let ProfileHeaderShell = ({
 
       {!isPlaceholderProfile && (
         <View
-          style={[a.px_lg, a.pb_sm]}
+          style={[a.px_lg, a.py_xs]}
           pointerEvents={isIOS ? 'auto' : 'box-none'}>
           {isMe ? (
             <LabelsOnMe details={{did: profile.did}} labels={profile.labels} />
diff --git a/src/screens/Profile/KnownFollowers.tsx b/src/screens/Profile/KnownFollowers.tsx
new file mode 100644
index 000000000..5cb45a11e
--- /dev/null
+++ b/src/screens/Profile/KnownFollowers.tsx
@@ -0,0 +1,134 @@
+import React from 'react'
+import {View} from 'react-native'
+import {AppBskyActorDefs} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useFocusEffect} from '@react-navigation/native'
+
+import {cleanError} from '#/lib/strings/errors'
+import {logger} from '#/logger'
+import {useProfileKnownFollowersQuery} from '#/state/queries/known-followers'
+import {useResolveDidQuery} from '#/state/queries/resolve-uri'
+import {useSetMinimalShellMode} from '#/state/shell'
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
+import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
+import {List} from '#/view/com/util/List'
+import {ViewHeader} from '#/view/com/util/ViewHeader'
+import {
+  ListFooter,
+  ListHeaderDesktop,
+  ListMaybePlaceholder,
+} from '#/components/Lists'
+
+function renderItem({item}: {item: AppBskyActorDefs.ProfileViewBasic}) {
+  return <ProfileCardWithFollowBtn key={item.did} profile={item} />
+}
+
+function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
+  return item.did
+}
+
+type Props = NativeStackScreenProps<
+  CommonNavigatorParams,
+  'ProfileKnownFollowers'
+>
+export const ProfileKnownFollowersScreen = ({route}: Props) => {
+  const {_} = useLingui()
+  const setMinimalShellMode = useSetMinimalShellMode()
+  const initialNumToRender = useInitialNumToRender()
+
+  const {name} = route.params
+
+  const [isPTRing, setIsPTRing] = React.useState(false)
+  const {
+    data: resolvedDid,
+    isLoading: isDidLoading,
+    error: resolveError,
+  } = useResolveDidQuery(route.params.name)
+  const {
+    data,
+    isLoading: isFollowersLoading,
+    isFetchingNextPage,
+    hasNextPage,
+    fetchNextPage,
+    error,
+    refetch,
+  } = useProfileKnownFollowersQuery(resolvedDid)
+
+  const onRefresh = React.useCallback(async () => {
+    setIsPTRing(true)
+    try {
+      await refetch()
+    } catch (err) {
+      logger.error('Failed to refresh followers', {message: err})
+    }
+    setIsPTRing(false)
+  }, [refetch, setIsPTRing])
+
+  const onEndReached = React.useCallback(async () => {
+    if (isFetchingNextPage || !hasNextPage || !!error) return
+    try {
+      await fetchNextPage()
+    } catch (err) {
+      logger.error('Failed to load more followers', {message: err})
+    }
+  }, [isFetchingNextPage, hasNextPage, error, fetchNextPage])
+
+  const followers = React.useMemo(() => {
+    if (data?.pages) {
+      return data.pages.flatMap(page => page.followers)
+    }
+    return []
+  }, [data])
+
+  const isError = Boolean(resolveError || error)
+
+  useFocusEffect(
+    React.useCallback(() => {
+      setMinimalShellMode(false)
+    }, [setMinimalShellMode]),
+  )
+
+  if (followers.length < 1) {
+    return (
+      <ListMaybePlaceholder
+        isLoading={isDidLoading || isFollowersLoading}
+        isError={isError}
+        emptyType="results"
+        emptyMessage={_(msg`You don't follow any users who follow @${name}.`)}
+        errorMessage={cleanError(resolveError || error)}
+        onRetry={isError ? refetch : undefined}
+      />
+    )
+  }
+
+  return (
+    <View style={{flex: 1}}>
+      <ViewHeader title={_(msg`Followers you know`)} />
+      <List
+        data={followers}
+        renderItem={renderItem}
+        keyExtractor={keyExtractor}
+        refreshing={isPTRing}
+        onRefresh={onRefresh}
+        onEndReached={onEndReached}
+        onEndReachedThreshold={4}
+        ListHeaderComponent={
+          <ListHeaderDesktop title={_(msg`Followers you know`)} />
+        }
+        ListFooterComponent={
+          <ListFooter
+            isFetchingNextPage={isFetchingNextPage}
+            error={cleanError(error)}
+            onRetry={fetchNextPage}
+          />
+        }
+        // @ts-ignore our .web version only -prf
+        desktopFixedHeight
+        initialNumToRender={initialNumToRender}
+        windowSize={11}
+      />
+    </View>
+  )
+}