Liked by screen

This commit is contained in:
Eric Bailey
2024-03-09 19:29:46 -06:00
parent ee08469b4d
commit 7a02e9dcb5
7 changed files with 211 additions and 7 deletions
+6
View File
@@ -80,6 +80,7 @@ import {msg} from '@lingui/macro'
import {i18n, MessageDescriptor} from '@lingui/core'
import HashtagScreen from '#/screens/Hashtag'
import {logEvent} from './lib/statsig/statsig'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@@ -199,6 +200,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => ProfileFeedLikedByScreen}
options={{title: title(msg`Liked by`)}}
/>
<Stack.Screen
name="ProfileLabelerLikedBy"
getComponent={() => ProfileLabelerLikedByScreen}
options={{title: title(msg`Liked by`)}}
/>
<Stack.Screen
name="Debug"
getComponent={() => Storybook}
+7
View File
@@ -49,6 +49,9 @@ export const atoms = {
h_full: {
height: '100%',
},
h_full_vh: web({
height: '100vh',
}),
/*
* Border radius
@@ -524,6 +527,10 @@ export const atoms = {
/*
* Margin
*/
mx_auto: {
marginLeft: 'auto',
marginRight: 'auto',
},
m_2xs: {
margin: tokens.space._2xs,
},
+137
View File
@@ -0,0 +1,137 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {List} from '#/view/com/util/List'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {cleanError} from '#/lib/strings/errors'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Refresh} from './icons/ArrowRotateCounterClockwise'
import {atoms as a, useTheme} from '#/alf'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
export function LikedByList({uri}: {uri: string}) {
const t = useTheme()
const {_} = useLingui()
const [isPTRing, setIsPTRing] = React.useState(false)
const {
data: resolvedUri,
error: resolveError,
isFetching: isFetchingResolvedUri,
} = useResolveUriQuery(uri)
const {
data,
isFetching,
isFetched,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
isError,
error,
refetch,
} = useLikedByQuery(resolvedUri?.uri)
const likes = React.useMemo(() => {
if (data?.pages) {
return data.pages.flatMap(page => page.likes)
}
return []
}, [data])
const onRefresh = React.useCallback(async () => {
setIsPTRing(true)
try {
await refetch()
} catch (err) {
logger.error('Failed to refresh likes', {message: err})
}
setIsPTRing(false)
}, [refetch, setIsPTRing])
const onEndReached = React.useCallback(async () => {
if (isFetching || !hasNextPage || isError) return
try {
await fetchNextPage()
} catch (err) {
logger.error('Failed to load more likes', {message: err})
}
}, [isFetching, hasNextPage, isError, fetchNextPage])
const renderItem = React.useCallback(({item}: {item: GetLikes.Like}) => {
return (
<ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
)
}, [])
if (isFetchingResolvedUri || !isFetched) {
return (
<View style={[a.w_full, a.align_center, a.p_lg]}>
<Loader size="xl" />
</View>
)
}
if (resolveError || isError) {
return (
<View style={[a.p_lg]}>
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
<Text style={[a.text_md, a.pb_lg]}>
{cleanError(resolveError || error)}
</Text>
<View style={[a.flex_row, a.justify_end]}>
<Button
label={_(msg``)}
onPress={onRefresh}
size="small"
variant="solid"
color="primary">
<ButtonText>
<Trans>Try again</Trans>
</ButtonText>
<ButtonIcon icon={Refresh} position="right" />
</Button>
</View>
</View>
</View>
)
}
return likes.length ? (
<List
data={likes}
keyExtractor={item => item.actor.did}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
renderItem={renderItem}
initialNumToRender={15}
contentContainerStyle={{borderWidth: 0}}
// FIXME(dan)
// eslint-disable-next-line react/no-unstable-nested-components
ListFooterComponent={() => (
<View style={[a.w_full, a.align_center, a.p_lg]}>
{(isFetching || isFetchingNextPage) && <Loader size="xl" />}
</View>
)}
// @ts-ignore our .web version only -prf
desktopFixedHeight
/>
) : (
<View style={[a.p_lg]}>
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
Nobody has liked this yet. Maybe you should be the first!
</Trans>
</Text>
</View>
</View>
)
}
+1
View File
@@ -21,6 +21,7 @@ export type CommonNavigatorParams = {
PostRepostedBy: {name: string; rkey: string}
ProfileFeed: {name: string; rkey: string}
ProfileFeedLikedBy: {name: string; rkey: string}
ProfileLabelerLikedBy: {name: string}
Debug: undefined
DebugMod: undefined
Log: undefined
+1
View File
@@ -21,6 +21,7 @@ export const router = new Router({
PostRepostedBy: '/profile/:name/post/:rkey/reposted-by',
ProfileFeed: '/profile/:name/feed/:rkey',
ProfileFeedLikedBy: '/profile/:name/feed/:rkey/liked-by',
ProfileLabelerLikedBy: '/profile/:name/labeler/liked-by',
Debug: '/sys/debug',
DebugMod: '/sys/debug-mod',
Log: '/sys/log',
@@ -35,10 +35,9 @@ import {
Heart2_Stroke2_Corner0_Rounded as Heart,
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
} from '#/components/icons/Heart2'
import {LikesDialog} from '#/components/LikesDialog'
import * as Dialog from '#/components/Dialog'
import {DialogOuterProps} from '#/components/Dialog'
import * as Prompt from '#/components/Prompt'
import {Link} from '#/components/Link'
interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed
@@ -64,7 +63,6 @@ let ProfileHeaderLabeler = ({
const {currentAccount, hasSession} = useSession()
const {openModal} = useModalControls()
const {track} = useAnalytics()
const likesControl = Dialog.useDialogControl()
const cantSubscribePrompt = Prompt.usePromptControl()
const moderation = useMemo(
@@ -237,9 +235,14 @@ let ProfileHeaderLabeler = ({
</Button>
{typeof labeler.likeCount === 'number' && (
<Button
<Link
to={{
screen: 'ProfileLabelerLikedBy',
params: {
name: labeler.creator.handle || labeler.creator.did,
},
}}
size="tiny"
onPress={() => likesControl.open()}
label={_(
msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`,
)}>
@@ -257,13 +260,12 @@ let ProfileHeaderLabeler = ({
</Trans>
</Text>
)}
</Button>
</Link>
)}
</View>
</>
)}
</View>
<LikesDialog control={likesControl} uri={labeler.uri} />
<CantSubscribePrompt control={cantSubscribePrompt} />
</ProfileHeaderShell>
)
@@ -0,0 +1,50 @@
import React from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {LikedByList as PostLikedByComponent} from '#/components/LikedByList'
import {useSetMinimalShellMode} from '#/state/shell'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
export function ProfileLabelerLikedByScreen({
route,
}: NativeStackScreenProps<CommonNavigatorParams, 'ProfileLabelerLikedBy'>) {
const setMinimalShellMode = useSetMinimalShellMode()
const {name: handleOrDid} = route.params
const uri = makeRecordUri(handleOrDid, 'app.bsky.labeler.service', 'self')
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<View
style={[
a.mx_auto,
a.w_full,
a.h_full_vh,
gtMobile && [
a.border_l,
a.border_r,
t.atoms.border_contrast_low,
{
maxWidth: 600,
},
],
]}>
<ViewHeader showBorder title={_(msg`Liked By`)} />
<PostLikedByComponent uri={uri} />
</View>
)
}