From 842915db6406f7a796eab2c9036719978bf44c0d Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 27 Feb 2025 14:53:35 +0000 Subject: [PATCH] add recent chats to share menu --- src/components/Menu/index.tsx | 37 +++- src/components/Menu/index.web.tsx | 4 + .../PostControls/ShareMenu/RecentChats.tsx | 197 ++++++++++++++++++ .../PostControls/ShareMenu/ShareMenuItems.tsx | 73 +++---- 4 files changed, 274 insertions(+), 37 deletions(-) create mode 100644 src/components/PostControls/ShareMenu/RecentChats.tsx diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 76fc74dc1d..c5ccfa5ece 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -244,6 +244,38 @@ export function ItemRadio({selected}: {selected: boolean}) { ) } +/** + * NATIVE ONLY - for adding non-pressable items to the menu + * + * @platform ios, android + */ +export function ContainerItem({ + children, + style, +}: { + children: React.ReactNode + style?: StyleProp +}) { + const t = useTheme() + return ( + + {children} + + ) +} + export function LabelText({children}: {children: React.ReactNode}) { const t = useTheme() return ( @@ -272,13 +304,14 @@ export function Group({children, style}: GroupProps) { style, ]}> {flattenReactChildren(children).map((child, i) => { - return React.isValidElement(child) && child.type === Item ? ( + return React.isValidElement(child) && + (child.type === Item || child.type === ContainerItem) ? ( {i > 0 ? ( ) : null} {React.cloneElement(child, { - // @ts-ignore + // @ts-expect-error cloneElement is not aware of the types style: { borderRadius: 0, borderWidth: 0, diff --git a/src/components/Menu/index.web.tsx b/src/components/Menu/index.web.tsx index 27678bf2f2..7d6e50556c 100644 --- a/src/components/Menu/index.web.tsx +++ b/src/components/Menu/index.web.tsx @@ -390,3 +390,7 @@ export function Divider() { /> ) } + +export function ContainerItem() { + return null +} diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx new file mode 100644 index 0000000000..b3f8afd794 --- /dev/null +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -0,0 +1,197 @@ +import {ScrollView, View} from 'react-native' +import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {type NavigationProp} from '#/lib/routes/types' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {sanitizeHandle} from '#/lib/strings/handles' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useListConvosQuery} from '#/state/queries/messages/list-conversations' +import {useSession} from '#/state/session' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, tokens, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {useDialogContext} from '#/components/Dialog' +import {Text} from '#/components/Typography' +import {useSimpleVerificationState} from '#/components/verification' +import {VerificationCheck} from '#/components/verification/VerificationCheck' +import type * as bsky from '#/types/bsky' + +export function RecentChats({postUri}: {postUri: string}) { + const control = useDialogContext() + const {_} = useLingui() + const {currentAccount} = useSession() + const {data} = useListConvosQuery({status: 'accepted'}) + const convos = data?.pages[0]?.convos?.slice(0, 10) + const moderationOpts = useModerationOpts() + const navigation = useNavigation() + + const onSelectChat = (convoId: string) => { + control.close(() => { + navigation.navigate('MessagesConversation', { + conversation: convoId, + embed: postUri, + }) + }) + } + + if (!moderationOpts) return null + + return ( + + console.log(evt.nativeEvent.layout)}> + {convos && convos.length > 0 ? ( + convos.map(convo => { + const otherMember = convo.members.find( + member => member.did !== currentAccount?.did, + ) + + if (!otherMember || otherMember.handle === 'missing.invalid') + return null + + return ( + onSelectChat(convo.id)} + moderationOpts={moderationOpts} + /> + ) + }) + ) : ( + <> + + + + + + + )} + + {convos && convos.length === 0 && } + + ) +} + +const WIDTH = 80 + +function RecentChatItem({ + profile, + onPress, + moderationOpts, +}: { + profile: bsky.profile.AnyProfileView + onPress: () => void + moderationOpts: ModerationOpts +}) { + const {_} = useLingui() + const t = useTheme() + + const moderation = moderateProfile(profile, moderationOpts) + const name = sanitizeDisplayName( + profile.displayName || sanitizeHandle(profile.handle), + moderation.ui('displayName'), + ) + const verification = useSimpleVerificationState({profile}) + + return ( + + ) +} + +function ConvoSkeleton() { + const t = useTheme() + return ( + + + + + ) +} + +function NoConvos() { + const t = useTheme() + + return ( + + + + Start a conversation, and it will appear here. + + + ) +} diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx index 0a21b26d10..c5f530e8db 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx @@ -21,6 +21,7 @@ import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/Pap import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import {useDevMode} from '#/storage/hooks/dev-mode' +import {RecentChats} from './RecentChats' import {type ShareMenuItemsProps} from './ShareMenuItems.types' let ShareMenuItems = ({ @@ -84,6 +85,23 @@ let ShareMenuItems = ({ return ( <> + {hasSession && ( + + + + + sendViaChatControl.open()}> + + Send via direct message + + + + + )} + - - {hasSession && ( - sendViaChatControl.open()}> - - Send via direct message - - - - )} {devModeEnabled && ( - <> - - - - - Share post at:// URI - - - - - - Share author DID - - - - - + + + + Share post at:// URI + + + + + + Share author DID + + + + )}