diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 27f43afd3a..97c3340d3f 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -2,6 +2,7 @@ import React, {useImperativeHandle} from 'react'
import {View, Dimensions} from 'react-native'
import BottomSheet, {
BottomSheetBackdrop,
+ BottomSheetFlatList,
BottomSheetScrollView,
BottomSheetTextInput,
BottomSheetView,
@@ -23,6 +24,7 @@ export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
// @ts-ignore
export const Input = createInput(BottomSheetTextInput)
+export const FlatList = BottomSheetFlatList
export function Outer({
children,
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 5bb352900c..3639d3e6b3 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -16,6 +16,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
export {Input} from '#/components/forms/TextField'
+export {FlatList} from 'react-native'
const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/components/LikesDialog.tsx b/src/components/LikesDialog.tsx
new file mode 100644
index 0000000000..283ebc51c0
--- /dev/null
+++ b/src/components/LikesDialog.tsx
@@ -0,0 +1,140 @@
+import React, {useMemo, useCallback} from 'react'
+import {ActivityIndicator, View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
+import {BottomSheetFlatList} from '@gorhom/bottom-sheet'
+
+import {useResolveUriQuery} from '#/state/queries/resolve-uri'
+import {useLikedByQuery} from '#/state/queries/post-liked-by'
+import {cleanError} from '#/lib/strings/errors'
+import {logger} from '#/logger'
+
+import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+import * as Dialog from '#/components/Dialog'
+import {Button} from '#/components/Button'
+import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
+import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
+
+interface LikesDialogProps {
+ control: Dialog.DialogOuterProps['control']
+ uri: string
+}
+
+export function LikesDialog(props: LikesDialogProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export function LikesDialogInner({control, uri}: LikesDialogProps) {
+ const {_} = useLingui()
+ const t = useTheme()
+ const {gtMobile} = useBreakpoints()
+
+ const {
+ data: resolvedUri,
+ error: resolveError,
+ isFetching: isFetchingResolvedUri,
+ } = useResolveUriQuery(uri)
+ const {
+ data,
+ isFetching,
+ isFetched,
+ isFetchingNextPage,
+ hasNextPage,
+ fetchNextPage,
+ isError,
+ error,
+ } = useLikedByQuery(resolvedUri?.uri)
+ const likes = useMemo(() => {
+ if (data?.pages) {
+ return data.pages.flatMap(page => page.likes)
+ }
+ }, [data])
+
+ const onEndReached = 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 = useCallback(
+ ({item}: {item: GetLikes.Like}) => {
+ return (
+ control.close()}
+ />
+ )
+ },
+ [control],
+ )
+
+ return (
+
+
+ Liked by
+
+ {isFetchingResolvedUri || !isFetched ? (
+
+ ) : resolveError || isError ? (
+
+ ) : likes?.length === 0 ? (
+
+
+
+ Nobody has liked this yet. Maybe you should be the first!
+
+
+
+ ) : (
+ item.actor.did}
+ onEndReached={onEndReached}
+ renderItem={renderItem}
+ initialNumToRender={15}
+ ListFooterComponent={
+
+ }
+ />
+ )}
+
+
+
+
+ )
+}
+
+function ListFooterComponent({isFetching}: {isFetching: boolean}) {
+ if (isFetching) {
+ return (
+
+
+
+ )
+ }
+ return null
+}
diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx
index 497ab6a4a7..e2a5d59f0b 100644
--- a/src/components/moderation/LabelsOnMeDialog.tsx
+++ b/src/components/moderation/LabelsOnMeDialog.tsx
@@ -32,7 +32,6 @@ export interface LabelsOnMeDialogProps {
}
export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) {
- const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {subject, labels} = props
@@ -120,6 +119,7 @@ function Label({
control: Dialog.DialogOuterProps['control']
}) {
const t = useTheme()
+ const {_} = useLingui()
const {labeler, strings} = useLabelInfo(label)
return (
-
) : undefined}
-
+
{typeof labeler.likeCount === 'number' && (
-
-
- Liked by {labeler.likeCount}{' '}
- {pluralize(labeler.likeCount, 'user')}
-
-
+ likesControl.open()}
+ label={_(
+ msg`Liked by ${labeler.likeCount} ${pluralize(
+ labeler.likeCount,
+ 'user',
+ )}`,
+ )}>
+
+
+ Liked by {labeler.likeCount}{' '}
+ {pluralize(labeler.likeCount, 'user')}
+
+
+
)}
>
)}
+
)
}
diff --git a/src/state/queries/post-liked-by.ts b/src/state/queries/post-liked-by.ts
index 2cde07f280..a0498ada44 100644
--- a/src/state/queries/post-liked-by.ts
+++ b/src/state/queries/post-liked-by.ts
@@ -12,9 +12,9 @@ const PAGE_SIZE = 30
type RQPageParam = string | undefined
// TODO refactor invalidate on mutate?
-export const RQKEY = (resolvedUri: string) => ['post-liked-by', resolvedUri]
+export const RQKEY = (resolvedUri: string) => ['liked-by', resolvedUri]
-export function usePostLikedByQuery(resolvedUri: string | undefined) {
+export function useLikedByQuery(resolvedUri: string | undefined) {
return useInfiniteQuery<
AppBskyFeedGetLikes.OutputSchema,
Error,
diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx
index 55463dc137..0760ed7ff3 100644
--- a/src/view/com/post-thread/PostLikedBy.tsx
+++ b/src/view/com/post-thread/PostLikedBy.tsx
@@ -8,7 +8,7 @@ import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {logger} from '#/logger'
import {LoadingScreen} from '../util/LoadingScreen'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
-import {usePostLikedByQuery} from '#/state/queries/post-liked-by'
+import {useLikedByQuery} from '#/state/queries/post-liked-by'
import {cleanError} from '#/lib/strings/errors'
export function PostLikedBy({uri}: {uri: string}) {
@@ -28,7 +28,7 @@ export function PostLikedBy({uri}: {uri: string}) {
isError,
error,
refetch,
- } = usePostLikedByQuery(resolvedUri?.uri)
+ } = useLikedByQuery(resolvedUri?.uri)
const likes = useMemo(() => {
if (data?.pages) {
return data.pages.flatMap(page => page.likes)
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
index 66f762fabf..81de4568ff 100644
--- a/src/view/com/profile/ProfileCard.tsx
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -31,6 +31,7 @@ export function ProfileCard({
noBorder,
followers,
renderButton,
+ onPress,
style,
}: {
testID?: string
@@ -42,6 +43,7 @@ export function ProfileCard({
renderButton?: (
profile: Shadow,
) => React.ReactNode
+ onPress?: () => void
style?: StyleProp
}) {
const pal = usePalette('default')
@@ -68,6 +70,7 @@ export function ProfileCard({
]}
href={makeProfileLink(profile)}
title={profile.handle}
+ onBeforePress={onPress}
asAnchor
anchorNoUnderline>
@@ -233,11 +236,13 @@ export function ProfileCardWithFollowBtn({
noBg,
noBorder,
followers,
+ onPress,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
noBg?: boolean
noBorder?: boolean
followers?: AppBskyActorDefs.ProfileView[] | undefined
+ onPress?: () => void
}) {
const {currentAccount} = useSession()
const isMe = profile.did === currentAccount?.did
@@ -253,6 +258,7 @@ export function ProfileCardWithFollowBtn({
? undefined
: profileShadow =>
}
+ onPress={onPress}
/>
)
}
diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx
index d52d3c0e61..e4e02e4d76 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -49,6 +49,7 @@ interface Props extends ComponentProps {
anchorNoUnderline?: boolean
navigationAction?: 'push' | 'replace' | 'navigate'
onPointerEnter?: () => void
+ onBeforePress?: () => void
}
export const Link = memo(function Link({
@@ -62,6 +63,7 @@ export const Link = memo(function Link({
accessible,
anchorNoUnderline,
navigationAction,
+ onBeforePress,
...props
}: Props) {
const {closeModal} = useModalControls()
@@ -71,6 +73,7 @@ export const Link = memo(function Link({
const onPress = React.useCallback(
(e?: Event) => {
+ onBeforePress?.()
if (typeof href === 'string') {
return onPressInner(
closeModal,
@@ -82,7 +85,7 @@ export const Link = memo(function Link({
)
}
},
- [closeModal, navigation, navigationAction, href, openLink],
+ [closeModal, navigation, navigationAction, href, openLink, onBeforePress],
)
if (noFeedback) {