about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/view/com/profile/ProfileFollowers.tsx124
-rw-r--r--src/view/com/profile/ProfileFollows.tsx123
-rw-r--r--src/view/screens/ProfileFollowers.tsx2
-rw-r--r--src/view/screens/ProfileFollows.tsx2
4 files changed, 133 insertions, 118 deletions
diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx
index 411ae6c17..b11a33f27 100644
--- a/src/view/com/profile/ProfileFollowers.tsx
+++ b/src/view/com/profile/ProfileFollowers.tsx
@@ -1,39 +1,66 @@
 import React from 'react'
-import {ActivityIndicator, StyleSheet, View} from 'react-native'
 import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
-import {CenteredView} from '../util/Views'
-import {LoadingScreen} from '../util/LoadingScreen'
 import {List} from '../util/List'
-import {ErrorMessage} from '../util/error/ErrorMessage'
 import {ProfileCardWithFollowBtn} from './ProfileCard'
 import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
 import {logger} from '#/logger'
 import {cleanError} from '#/lib/strings/errors'
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {
+  ListFooter,
+  ListHeaderDesktop,
+  ListMaybePlaceholder,
+} from '#/components/Lists'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useSession} from 'state/session'
+import {View} from 'react-native'
+
+function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
+  return <ProfileCardWithFollowBtn key={item.did} profile={item} />
+}
+
+function keyExtractor(item: ActorDefs.ProfileViewBasic) {
+  return item.did
+}
 
 export function ProfileFollowers({name}: {name: string}) {
+  const {_} = useLingui()
+  const initialNumToRender = useInitialNumToRender()
+  const {currentAccount} = useSession()
+
   const [isPTRing, setIsPTRing] = React.useState(false)
   const {
     data: resolvedDid,
+    isLoading: isDidLoading,
     error: resolveError,
-    isFetching: isFetchingDid,
   } = useResolveDidQuery(name)
   const {
     data,
+    isLoading: isFollowersLoading,
     isFetching,
-    isFetched,
     isFetchingNextPage,
     hasNextPage,
     fetchNextPage,
-    isError,
     error,
     refetch,
   } = useProfileFollowersQuery(resolvedDid)
 
+  const isError = React.useMemo(
+    () => !!resolveError || !!error,
+    [resolveError, error],
+  )
+
+  const isMe = React.useMemo(() => {
+    return resolvedDid === currentAccount?.did
+  }, [resolvedDid, currentAccount?.did])
+
   const followers = React.useMemo(() => {
     if (data?.pages) {
       return data.pages.flatMap(page => page.followers)
     }
+    return []
   }, [data])
 
   const onRefresh = React.useCallback(async () => {
@@ -47,7 +74,7 @@ export function ProfileFollowers({name}: {name: string}) {
   }, [refetch, setIsPTRing])
 
   const onEndReached = async () => {
-    if (isFetching || !hasNextPage || isError) return
+    if (isFetching || !hasNextPage || !!error) return
     try {
       await fetchNextPage()
     } catch (err) {
@@ -55,57 +82,38 @@ export function ProfileFollowers({name}: {name: string}) {
     }
   }
 
-  const renderItem = React.useCallback(
-    ({item}: {item: ActorDefs.ProfileViewBasic}) => (
-      <ProfileCardWithFollowBtn key={item.did} profile={item} />
-    ),
-    [],
-  )
-
-  if (isFetchingDid || !isFetched) {
-    return <LoadingScreen />
-  }
-
-  // error
-  // =
-  if (resolveError || isError) {
-    return (
-      <CenteredView>
-        <ErrorMessage
-          message={cleanError(resolveError || error)}
-          onPressTryAgain={onRefresh}
-        />
-      </CenteredView>
-    )
-  }
-
-  // loaded
-  // =
   return (
-    <List
-      data={followers}
-      keyExtractor={item => item.did}
-      refreshing={isPTRing}
-      onRefresh={onRefresh}
-      onEndReached={onEndReached}
-      renderItem={renderItem}
-      initialNumToRender={15}
-      // FIXME(dan)
-      // eslint-disable-next-line react/no-unstable-nested-components
-      ListFooterComponent={() => (
-        <View style={styles.footer}>
-          {(isFetching || isFetchingNextPage) && <ActivityIndicator />}
-        </View>
+    <View style={{flex: 1}}>
+      <ListMaybePlaceholder
+        isLoading={isDidLoading || isFollowersLoading}
+        isEmpty={followers.length < 1}
+        isError={isError}
+        emptyType="results"
+        emptyMessage={
+          isMe
+            ? _(msg`You do not have any followers.`)
+            : _(msg`This user doesn't have any followers.`)
+        }
+        errorMessage={cleanError(resolveError || error)}
+        onRetry={isError ? refetch : undefined}
+      />
+      {followers.length > 0 && (
+        <List
+          data={followers}
+          renderItem={renderItem}
+          keyExtractor={keyExtractor}
+          refreshing={isPTRing}
+          onRefresh={onRefresh}
+          onEndReached={onEndReached}
+          onEndReachedThreshold={4}
+          ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />}
+          ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
+          // @ts-ignore our .web version only -prf
+          desktopFixedHeight
+          initialNumToRender={initialNumToRender}
+          windowSize={11}
+        />
       )}
-      // @ts-ignore our .web version only -prf
-      desktopFixedHeight
-    />
+    </View>
   )
 }
-
-const styles = StyleSheet.create({
-  footer: {
-    height: 200,
-    paddingTop: 20,
-  },
-})
diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx
index bd4af1081..d99e2b840 100644
--- a/src/view/com/profile/ProfileFollows.tsx
+++ b/src/view/com/profile/ProfileFollows.tsx
@@ -1,39 +1,65 @@
 import React from 'react'
-import {ActivityIndicator, StyleSheet, View} from 'react-native'
 import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
-import {CenteredView} from '../util/Views'
-import {LoadingScreen} from '../util/LoadingScreen'
 import {List} from '../util/List'
-import {ErrorMessage} from '../util/error/ErrorMessage'
 import {ProfileCardWithFollowBtn} from './ProfileCard'
 import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
 import {logger} from '#/logger'
 import {cleanError} from '#/lib/strings/errors'
+import {
+  ListFooter,
+  ListHeaderDesktop,
+  ListMaybePlaceholder,
+} from '#/components/Lists'
+import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {useSession} from 'state/session'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
+  return <ProfileCardWithFollowBtn key={item.did} profile={item} />
+}
+
+function keyExtractor(item: ActorDefs.ProfileViewBasic) {
+  return item.did
+}
 
 export function ProfileFollows({name}: {name: string}) {
+  const {_} = useLingui()
+  const initialNumToRender = useInitialNumToRender()
+  const {currentAccount} = useSession()
+
   const [isPTRing, setIsPTRing] = React.useState(false)
   const {
     data: resolvedDid,
+    isLoading: isDidLoading,
     error: resolveError,
-    isFetching: isFetchingDid,
   } = useResolveDidQuery(name)
   const {
     data,
+    isLoading: isFollowsLoading,
     isFetching,
-    isFetched,
     isFetchingNextPage,
     hasNextPage,
     fetchNextPage,
-    isError,
     error,
     refetch,
   } = useProfileFollowsQuery(resolvedDid)
 
+  const isError = React.useMemo(
+    () => !!resolveError || !!error,
+    [resolveError, error],
+  )
+
+  const isMe = React.useMemo(() => {
+    return resolvedDid === currentAccount?.did
+  }, [resolvedDid, currentAccount?.did])
+
   const follows = React.useMemo(() => {
     if (data?.pages) {
       return data.pages.flatMap(page => page.follows)
     }
+    return []
   }, [data])
 
   const onRefresh = React.useCallback(async () => {
@@ -47,7 +73,7 @@ export function ProfileFollows({name}: {name: string}) {
   }, [refetch, setIsPTRing])
 
   const onEndReached = async () => {
-    if (isFetching || !hasNextPage || isError) return
+    if (isFetching || !hasNextPage || !!error) return
     try {
       await fetchNextPage()
     } catch (err) {
@@ -55,57 +81,38 @@ export function ProfileFollows({name}: {name: string}) {
     }
   }
 
-  const renderItem = React.useCallback(
-    ({item}: {item: ActorDefs.ProfileViewBasic}) => (
-      <ProfileCardWithFollowBtn key={item.did} profile={item} />
-    ),
-    [],
-  )
-
-  if (isFetchingDid || !isFetched) {
-    return <LoadingScreen />
-  }
-
-  // error
-  // =
-  if (resolveError || isError) {
-    return (
-      <CenteredView>
-        <ErrorMessage
-          message={cleanError(resolveError || error)}
-          onPressTryAgain={onRefresh}
-        />
-      </CenteredView>
-    )
-  }
-
-  // loaded
-  // =
   return (
-    <List
-      data={follows}
-      keyExtractor={item => item.did}
-      refreshing={isPTRing}
-      onRefresh={onRefresh}
-      onEndReached={onEndReached}
-      renderItem={renderItem}
-      initialNumToRender={15}
-      // FIXME(dan)
-      // eslint-disable-next-line react/no-unstable-nested-components
-      ListFooterComponent={() => (
-        <View style={styles.footer}>
-          {(isFetching || isFetchingNextPage) && <ActivityIndicator />}
-        </View>
+    <>
+      <ListMaybePlaceholder
+        isLoading={isDidLoading || isFollowsLoading}
+        isEmpty={follows.length < 1}
+        isError={isError}
+        emptyType="results"
+        emptyMessage={
+          isMe
+            ? _(msg`You are not following anyone.`)
+            : _(msg`This user isn't following anyone.`)
+        }
+        errorMessage={cleanError(resolveError || error)}
+        onRetry={isError ? refetch : undefined}
+      />
+      {follows.length > 0 && (
+        <List
+          data={follows}
+          renderItem={renderItem}
+          keyExtractor={keyExtractor}
+          refreshing={isPTRing}
+          onRefresh={onRefresh}
+          onEndReached={onEndReached}
+          onEndReachedThreshold={4}
+          ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />}
+          ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
+          // @ts-ignore our .web version only -prf
+          desktopFixedHeight
+          initialNumToRender={initialNumToRender}
+          windowSize={11}
+        />
       )}
-      // @ts-ignore our .web version only -prf
-      desktopFixedHeight
-    />
+    </>
   )
 }
-
-const styles = StyleSheet.create({
-  footer: {
-    height: 200,
-    paddingTop: 20,
-  },
-})
diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx
index 2cad08cb5..6f8ecc2e8 100644
--- a/src/view/screens/ProfileFollowers.tsx
+++ b/src/view/screens/ProfileFollowers.tsx
@@ -21,7 +21,7 @@ export const ProfileFollowersScreen = ({route}: Props) => {
   )
 
   return (
-    <View>
+    <View style={{flex: 1}}>
       <ViewHeader title={_(msg`Followers`)} />
       <ProfileFollowersComponent name={name} />
     </View>
diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx
index 80502b98b..bdab20153 100644
--- a/src/view/screens/ProfileFollows.tsx
+++ b/src/view/screens/ProfileFollows.tsx
@@ -21,7 +21,7 @@ export const ProfileFollowsScreen = ({route}: Props) => {
   )
 
   return (
-    <View>
+    <View style={{flex: 1}}>
       <ViewHeader title={_(msg`Following`)} />
       <ProfileFollowsComponent name={name} />
     </View>