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 ( - )} )} + ) } 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) {