Fix missing header on Likes/Reposted By, add missing perf optimizations (#4867)

* fix liked by list

* fix lists

* tweaks to style

* change string
zio/stable
Hailey 2024-08-01 10:32:36 -07:00 committed by GitHub
parent c78e9e3147
commit f056cb646e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 131 additions and 129 deletions

View File

@ -1,38 +1,57 @@
import React, {useCallback, useMemo, useState} from 'react' import React, {useCallback, useMemo, useState} from 'react'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {CenteredView} from '../util/Views' import {msg} from '@lingui/macro'
import {List} from '../util/List' import {useLingui} from '@lingui/react'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {logger} from '#/logger'
import {LoadingScreen} from '../util/LoadingScreen'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {
ListFooter,
ListHeaderDesktop,
ListMaybePlaceholder,
} from '#/components/Lists'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {List} from '../util/List'
function renderItem({item}: {item: GetLikes.Like}) {
return <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
}
function keyExtractor(item: GetLikes.Like) {
return item.actor.did
}
export function PostLikedBy({uri}: {uri: string}) { export function PostLikedBy({uri}: {uri: string}) {
const {_} = useLingui()
const initialNumToRender = useInitialNumToRender()
const [isPTRing, setIsPTRing] = useState(false) const [isPTRing, setIsPTRing] = useState(false)
const { const {
data: resolvedUri, data: resolvedUri,
error: resolveError, error: resolveError,
isFetching: isFetchingResolvedUri, isLoading: isLoadingUri,
} = useResolveUriQuery(uri) } = useResolveUriQuery(uri)
const { const {
data, data,
isFetching, isLoading: isLoadingLikes,
isFetched,
isFetchingNextPage, isFetchingNextPage,
hasNextPage, hasNextPage,
fetchNextPage, fetchNextPage,
isError,
error, error,
refetch, refetch,
} = useLikedByQuery(resolvedUri?.uri) } = useLikedByQuery(resolvedUri?.uri)
const isError = Boolean(resolveError || error)
const likes = useMemo(() => { const likes = useMemo(() => {
if (data?.pages) { if (data?.pages) {
return data.pages.flatMap(page => page.likes) return data.pages.flatMap(page => page.likes)
} }
return []
}, [data]) }, [data])
const onRefresh = useCallback(async () => { const onRefresh = useCallback(async () => {
@ -46,64 +65,44 @@ export function PostLikedBy({uri}: {uri: string}) {
}, [refetch, setIsPTRing]) }, [refetch, setIsPTRing])
const onEndReached = useCallback(async () => { const onEndReached = useCallback(async () => {
if (isFetching || !hasNextPage || isError) return if (isFetchingNextPage || !hasNextPage || isError) return
try { try {
await fetchNextPage() await fetchNextPage()
} catch (err) { } catch (err) {
logger.error('Failed to load more likes', {message: err}) logger.error('Failed to load more likes', {message: err})
} }
}, [isFetching, hasNextPage, isError, fetchNextPage]) }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
const renderItem = useCallback(({item}: {item: GetLikes.Like}) => { if (likes.length < 1) {
return ( return (
<ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} /> <ListMaybePlaceholder
) isLoading={isLoadingUri || isLoadingLikes}
}, []) isError={isError}
if (isFetchingResolvedUri || !isFetched) {
return <LoadingScreen />
}
// error
// =
if (resolveError || isError) {
return (
<CenteredView>
<ErrorMessage
message={cleanError(resolveError || error)}
onPressTryAgain={onRefresh}
/> />
</CenteredView>
) )
} }
// loaded
// =
return ( return (
<List <List
data={likes} data={likes}
keyExtractor={item => item.actor.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`Liked By`)} />}
// FIXME(dan) ListFooterComponent={
// eslint-disable-next-line react/no-unstable-nested-components <ListFooter
ListFooterComponent={() => ( isFetchingNextPage={isFetchingNextPage}
<View style={styles.footer}> error={cleanError(error)}
{(isFetching || isFetchingNextPage) && <ActivityIndicator />} onRetry={fetchNextPage}
</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,
},
})

View File

@ -1,38 +1,57 @@
import React, {useMemo, useCallback, useState} from 'react' import React, {useCallback, useMemo, useState} 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 {msg} from '@lingui/macro'
import {List} from '../util/List' import {useLingui} from '@lingui/react'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {logger} from '#/logger'
import {LoadingScreen} from '../util/LoadingScreen'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {usePostRepostedByQuery} from '#/state/queries/post-reposted-by'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {usePostRepostedByQuery} from '#/state/queries/post-reposted-by'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {
ListFooter,
ListHeaderDesktop,
ListMaybePlaceholder,
} from '#/components/Lists'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {List} from '../util/List'
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
return item.did
}
export function PostRepostedBy({uri}: {uri: string}) { export function PostRepostedBy({uri}: {uri: string}) {
const {_} = useLingui()
const initialNumToRender = useInitialNumToRender()
const [isPTRing, setIsPTRing] = useState(false) const [isPTRing, setIsPTRing] = useState(false)
const { const {
data: resolvedUri, data: resolvedUri,
error: resolveError, error: resolveError,
isFetching: isFetchingResolvedUri, isLoading: isLoadingUri,
} = useResolveUriQuery(uri) } = useResolveUriQuery(uri)
const { const {
data, data,
isFetching, isLoading: isLoadingRepostedBy,
isFetched,
isFetchingNextPage, isFetchingNextPage,
hasNextPage, hasNextPage,
fetchNextPage, fetchNextPage,
isError,
error, error,
refetch, refetch,
} = usePostRepostedByQuery(resolvedUri?.uri) } = usePostRepostedByQuery(resolvedUri?.uri)
const isError = Boolean(resolveError || error)
const repostedBy = useMemo(() => { const repostedBy = useMemo(() => {
if (data?.pages) { if (data?.pages) {
return data.pages.flatMap(page => page.repostedBy) return data.pages.flatMap(page => page.repostedBy)
} }
return []
}, [data]) }, [data])
const onRefresh = useCallback(async () => { const onRefresh = useCallback(async () => {
@ -46,35 +65,20 @@ export function PostRepostedBy({uri}: {uri: string}) {
}, [refetch, setIsPTRing]) }, [refetch, setIsPTRing])
const onEndReached = useCallback(async () => { const onEndReached = useCallback(async () => {
if (isFetching || !hasNextPage || isError) return if (isFetchingNextPage || !hasNextPage || isError) return
try { try {
await fetchNextPage() await fetchNextPage()
} catch (err) { } catch (err) {
logger.error('Failed to load more reposts', {message: err}) logger.error('Failed to load more reposts', {message: err})
} }
}, [isFetching, hasNextPage, isError, fetchNextPage]) }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
const renderItem = useCallback( if (repostedBy.length < 1) {
({item}: {item: ActorDefs.ProfileViewBasic}) => {
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
},
[],
)
if (isFetchingResolvedUri || !isFetched) {
return <LoadingScreen />
}
// error
// =
if (resolveError || isError) {
return ( return (
<CenteredView> <ListMaybePlaceholder
<ErrorMessage isLoading={isLoadingUri || isLoadingRepostedBy}
message={cleanError(resolveError || error)} isError={isError}
onPressTryAgain={onRefresh}
/> />
</CenteredView>
) )
} }
@ -83,28 +87,24 @@ export function PostRepostedBy({uri}: {uri: string}) {
return ( return (
<List <List
data={repostedBy} data={repostedBy}
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`Reposted By`)} />}
// FIXME(dan) ListFooterComponent={
// eslint-disable-next-line react/no-unstable-nested-components <ListFooter
ListFooterComponent={() => ( isFetchingNextPage={isFetchingNextPage}
<View style={styles.footer}> error={cleanError(error)}
{(isFetching || isFetchingNextPage) && <ActivityIndicator />} onRetry={fetchNextPage}
</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,
},
})

View File

@ -1,13 +1,14 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {ViewHeader} from '../com/util/ViewHeader'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {useSetMinimalShellMode} from '#/state/shell'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {ViewHeader} from '../com/util/ViewHeader'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
export const PostLikedByScreen = ({route}: Props) => { export const PostLikedByScreen = ({route}: Props) => {
@ -23,7 +24,7 @@ export const PostLikedByScreen = ({route}: Props) => {
) )
return ( return (
<View> <View style={{flex: 1}}>
<ViewHeader title={_(msg`Liked By`)} /> <ViewHeader title={_(msg`Liked By`)} />
<PostLikedByComponent uri={uri} /> <PostLikedByComponent uri={uri} />
</View> </View>

View File

@ -1,13 +1,14 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {ViewHeader} from '../com/util/ViewHeader'
import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {useSetMinimalShellMode} from '#/state/shell'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy'
import {ViewHeader} from '../com/util/ViewHeader'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
export const PostRepostedByScreen = ({route}: Props) => { export const PostRepostedByScreen = ({route}: Props) => {
@ -23,7 +24,7 @@ export const PostRepostedByScreen = ({route}: Props) => {
) )
return ( return (
<View> <View style={{flex: 1}}>
<ViewHeader title={_(msg`Reposted By`)} /> <ViewHeader title={_(msg`Reposted By`)} />
<PostRepostedByComponent uri={uri} /> <PostRepostedByComponent uri={uri} />
</View> </View>

View File

@ -1,13 +1,14 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {ViewHeader} from '../com/util/ViewHeader'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {useSetMinimalShellMode} from '#/state/shell'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {ViewHeader} from '../com/util/ViewHeader'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeedLikedBy'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeedLikedBy'>
export const ProfileFeedLikedByScreen = ({route}: Props) => { export const ProfileFeedLikedByScreen = ({route}: Props) => {
@ -23,7 +24,7 @@ export const ProfileFeedLikedByScreen = ({route}: Props) => {
) )
return ( return (
<View> <View style={{flex: 1}}>
<ViewHeader title={_(msg`Liked By`)} /> <ViewHeader title={_(msg`Liked By`)} />
<PostLikedByComponent uri={uri} /> <PostLikedByComponent uri={uri} />
</View> </View>