ProfileFollows and ProfileFollowers cleanup (#3219)
* cleanup PostThread rm some more unnecessary code cleanup some more pieces fix `isLoading` logic few fixes organize refactor `PostThread` allow chaining of `postThreadQuery` Update `Hashtag` screen with the component changes Make some changes to the List components adjust height and padding of bottom loader to account for bottom bar * rm unnecessary chaining logic * maxReplies logic * adjust error logic * use `<` instead of `<=` * add back warning comment * remove unused prop * adjust order * implement list improvements for followers/follows * update prop name * small adjustments fix flex add window size adjust isLoading * remove log * don't show retry for no results * don't show error if `isLoading`zio/stable
parent
addd66b37f
commit
b9474a5d55
|
@ -1,39 +1,66 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
|
||||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||||
import {CenteredView} from '../util/Views'
|
|
||||||
import {LoadingScreen} from '../util/LoadingScreen'
|
|
||||||
import {List} from '../util/List'
|
import {List} from '../util/List'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
|
||||||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||||
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
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}) {
|
export function ProfileFollowers({name}: {name: string}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const initialNumToRender = useInitialNumToRender()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||||
const {
|
const {
|
||||||
data: resolvedDid,
|
data: resolvedDid,
|
||||||
|
isLoading: isDidLoading,
|
||||||
error: resolveError,
|
error: resolveError,
|
||||||
isFetching: isFetchingDid,
|
|
||||||
} = useResolveDidQuery(name)
|
} = useResolveDidQuery(name)
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
isLoading: isFollowersLoading,
|
||||||
isFetching,
|
isFetching,
|
||||||
isFetched,
|
|
||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
isError,
|
|
||||||
error,
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useProfileFollowersQuery(resolvedDid)
|
} = 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(() => {
|
const followers = React.useMemo(() => {
|
||||||
if (data?.pages) {
|
if (data?.pages) {
|
||||||
return data.pages.flatMap(page => page.followers)
|
return data.pages.flatMap(page => page.followers)
|
||||||
}
|
}
|
||||||
|
return []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const onRefresh = React.useCallback(async () => {
|
const onRefresh = React.useCallback(async () => {
|
||||||
|
@ -47,7 +74,7 @@ export function ProfileFollowers({name}: {name: string}) {
|
||||||
}, [refetch, setIsPTRing])
|
}, [refetch, setIsPTRing])
|
||||||
|
|
||||||
const onEndReached = async () => {
|
const onEndReached = async () => {
|
||||||
if (isFetching || !hasNextPage || isError) return
|
if (isFetching || !hasNextPage || !!error) return
|
||||||
try {
|
try {
|
||||||
await fetchNextPage()
|
await fetchNextPage()
|
||||||
} catch (err) {
|
} 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 (
|
return (
|
||||||
<CenteredView>
|
<View style={{flex: 1}}>
|
||||||
<ErrorMessage
|
<ListMaybePlaceholder
|
||||||
message={cleanError(resolveError || error)}
|
isLoading={isDidLoading || isFollowersLoading}
|
||||||
onPressTryAgain={onRefresh}
|
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}
|
||||||
/>
|
/>
|
||||||
</CenteredView>
|
{followers.length > 0 && (
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loaded
|
|
||||||
// =
|
|
||||||
return (
|
|
||||||
<List
|
<List
|
||||||
data={followers}
|
data={followers}
|
||||||
keyExtractor={item => item.did}
|
renderItem={renderItem}
|
||||||
|
keyExtractor={keyExtractor}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
renderItem={renderItem}
|
onEndReachedThreshold={4}
|
||||||
initialNumToRender={15}
|
ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />}
|
||||||
// FIXME(dan)
|
ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
ListFooterComponent={() => (
|
|
||||||
<View style={styles.footer}>
|
|
||||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
// @ts-ignore our .web version only -prf
|
// @ts-ignore our .web version only -prf
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
initialNumToRender={initialNumToRender}
|
||||||
|
windowSize={11}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
footer: {
|
|
||||||
height: 200,
|
|
||||||
paddingTop: 20,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,39 +1,65 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
|
||||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||||
import {CenteredView} from '../util/Views'
|
|
||||||
import {LoadingScreen} from '../util/LoadingScreen'
|
|
||||||
import {List} from '../util/List'
|
import {List} from '../util/List'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
|
||||||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||||
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
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}) {
|
export function ProfileFollows({name}: {name: string}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const initialNumToRender = useInitialNumToRender()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||||
const {
|
const {
|
||||||
data: resolvedDid,
|
data: resolvedDid,
|
||||||
|
isLoading: isDidLoading,
|
||||||
error: resolveError,
|
error: resolveError,
|
||||||
isFetching: isFetchingDid,
|
|
||||||
} = useResolveDidQuery(name)
|
} = useResolveDidQuery(name)
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
isLoading: isFollowsLoading,
|
||||||
isFetching,
|
isFetching,
|
||||||
isFetched,
|
|
||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
isError,
|
|
||||||
error,
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useProfileFollowsQuery(resolvedDid)
|
} = 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(() => {
|
const follows = React.useMemo(() => {
|
||||||
if (data?.pages) {
|
if (data?.pages) {
|
||||||
return data.pages.flatMap(page => page.follows)
|
return data.pages.flatMap(page => page.follows)
|
||||||
}
|
}
|
||||||
|
return []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const onRefresh = React.useCallback(async () => {
|
const onRefresh = React.useCallback(async () => {
|
||||||
|
@ -47,7 +73,7 @@ export function ProfileFollows({name}: {name: string}) {
|
||||||
}, [refetch, setIsPTRing])
|
}, [refetch, setIsPTRing])
|
||||||
|
|
||||||
const onEndReached = async () => {
|
const onEndReached = async () => {
|
||||||
if (isFetching || !hasNextPage || isError) return
|
if (isFetching || !hasNextPage || !!error) return
|
||||||
try {
|
try {
|
||||||
await fetchNextPage()
|
await fetchNextPage()
|
||||||
} catch (err) {
|
} 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 (
|
return (
|
||||||
<CenteredView>
|
<>
|
||||||
<ErrorMessage
|
<ListMaybePlaceholder
|
||||||
message={cleanError(resolveError || error)}
|
isLoading={isDidLoading || isFollowsLoading}
|
||||||
onPressTryAgain={onRefresh}
|
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}
|
||||||
/>
|
/>
|
||||||
</CenteredView>
|
{follows.length > 0 && (
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loaded
|
|
||||||
// =
|
|
||||||
return (
|
|
||||||
<List
|
<List
|
||||||
data={follows}
|
data={follows}
|
||||||
keyExtractor={item => item.did}
|
renderItem={renderItem}
|
||||||
|
keyExtractor={keyExtractor}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
renderItem={renderItem}
|
onEndReachedThreshold={4}
|
||||||
initialNumToRender={15}
|
ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />}
|
||||||
// FIXME(dan)
|
ListFooterComponent={<ListFooter isFetching={isFetchingNextPage} />}
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
ListFooterComponent={() => (
|
|
||||||
<View style={styles.footer}>
|
|
||||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
// @ts-ignore our .web version only -prf
|
// @ts-ignore our .web version only -prf
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
initialNumToRender={initialNumToRender}
|
||||||
|
windowSize={11}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
footer: {
|
|
||||||
height: 200,
|
|
||||||
paddingTop: 20,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const ProfileFollowersScreen = ({route}: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={{flex: 1}}>
|
||||||
<ViewHeader title={_(msg`Followers`)} />
|
<ViewHeader title={_(msg`Followers`)} />
|
||||||
<ProfileFollowersComponent name={name} />
|
<ProfileFollowersComponent name={name} />
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const ProfileFollowsScreen = ({route}: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={{flex: 1}}>
|
||||||
<ViewHeader title={_(msg`Following`)} />
|
<ViewHeader title={_(msg`Following`)} />
|
||||||
<ProfileFollowsComponent name={name} />
|
<ProfileFollowsComponent name={name} />
|
||||||
</View>
|
</View>
|
||||||
|
|
Loading…
Reference in New Issue