add recent chats to share menu
This commit is contained in:
@@ -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<ViewStyle>
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.rounded_md,
|
||||||
|
a.border,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
{paddingVertical: 10},
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function LabelText({children}: {children: React.ReactNode}) {
|
export function LabelText({children}: {children: React.ReactNode}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
return (
|
return (
|
||||||
@@ -272,13 +304,14 @@ export function Group({children, style}: GroupProps) {
|
|||||||
style,
|
style,
|
||||||
]}>
|
]}>
|
||||||
{flattenReactChildren(children).map((child, i) => {
|
{flattenReactChildren(children).map((child, i) => {
|
||||||
return React.isValidElement(child) && child.type === Item ? (
|
return React.isValidElement(child) &&
|
||||||
|
(child.type === Item || child.type === ContainerItem) ? (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
{i > 0 ? (
|
{i > 0 ? (
|
||||||
<View style={[a.border_b, t.atoms.border_contrast_low]} />
|
<View style={[a.border_b, t.atoms.border_contrast_low]} />
|
||||||
) : null}
|
) : null}
|
||||||
{React.cloneElement(child, {
|
{React.cloneElement(child, {
|
||||||
// @ts-ignore
|
// @ts-expect-error cloneElement is not aware of the types
|
||||||
style: {
|
style: {
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
|
|||||||
@@ -390,3 +390,7 @@ export function Divider() {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ContainerItem() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<NavigationProp>()
|
||||||
|
|
||||||
|
const onSelectChat = (convoId: string) => {
|
||||||
|
control.close(() => {
|
||||||
|
navigation.navigate('MessagesConversation', {
|
||||||
|
conversation: convoId,
|
||||||
|
embed: postUri,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moderationOpts) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[a.relative, a.flex_1, {marginHorizontal: tokens.space.md * -1}]}>
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
style={[a.flex_1, a.pt_2xs, {minHeight: 98}]}
|
||||||
|
contentContainerStyle={[a.gap_sm, a.px_md]}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
onLayout={evt => 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 (
|
||||||
|
<RecentChatItem
|
||||||
|
key={convo.id}
|
||||||
|
profile={otherMember}
|
||||||
|
onPress={() => onSelectChat(convo.id)}
|
||||||
|
moderationOpts={moderationOpts}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ConvoSkeleton />
|
||||||
|
<ConvoSkeleton />
|
||||||
|
<ConvoSkeleton />
|
||||||
|
<ConvoSkeleton />
|
||||||
|
<ConvoSkeleton />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
{convos && convos.length === 0 && <NoConvos />}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Button
|
||||||
|
onPress={onPress}
|
||||||
|
label={_(msg`Send post to ${name}`)}
|
||||||
|
style={[
|
||||||
|
a.flex_col,
|
||||||
|
{width: WIDTH},
|
||||||
|
a.gap_sm,
|
||||||
|
a.justify_start,
|
||||||
|
a.align_center,
|
||||||
|
]}>
|
||||||
|
<UserAvatar
|
||||||
|
avatar={profile.avatar}
|
||||||
|
size={WIDTH - 8}
|
||||||
|
type={profile.associated?.labeler ? 'labeler' : 'user'}
|
||||||
|
moderation={moderation.ui('avatar')}
|
||||||
|
/>
|
||||||
|
<View style={[a.flex_row, a.align_center, a.justify_center, a.w_full]}>
|
||||||
|
<Text
|
||||||
|
emoji
|
||||||
|
style={[a.text_xs, a.leading_snug, t.atoms.text_contrast_medium]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
{verification.showBadge && (
|
||||||
|
<View style={[a.pl_2xs]}>
|
||||||
|
<VerificationCheck
|
||||||
|
width={10}
|
||||||
|
verifier={verification.role === 'verifier'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConvoSkeleton() {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_col,
|
||||||
|
{width: WIDTH, height: WIDTH + 15},
|
||||||
|
a.gap_xs,
|
||||||
|
a.justify_start,
|
||||||
|
a.align_center,
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
t.atoms.bg_contrast_50,
|
||||||
|
{width: WIDTH - 8, height: WIDTH - 8},
|
||||||
|
a.rounded_full,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
t.atoms.bg_contrast_50,
|
||||||
|
{width: WIDTH - 8, height: 10},
|
||||||
|
a.rounded_xs,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoConvos() {
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
a.px_2xl,
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[a.absolute, a.inset_0, t.atoms.bg_contrast_25, {opacity: 0.5}]}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_sm,
|
||||||
|
t.atoms.text_contrast_high,
|
||||||
|
a.text_center,
|
||||||
|
a.font_bold,
|
||||||
|
]}>
|
||||||
|
Start a conversation, and it will appear here.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/Pap
|
|||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {useDevMode} from '#/storage/hooks/dev-mode'
|
import {useDevMode} from '#/storage/hooks/dev-mode'
|
||||||
|
import {RecentChats} from './RecentChats'
|
||||||
import {type ShareMenuItemsProps} from './ShareMenuItems.types'
|
import {type ShareMenuItemsProps} from './ShareMenuItems.types'
|
||||||
|
|
||||||
let ShareMenuItems = ({
|
let ShareMenuItems = ({
|
||||||
@@ -84,6 +85,23 @@ let ShareMenuItems = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Menu.Outer>
|
<Menu.Outer>
|
||||||
|
{hasSession && (
|
||||||
|
<Menu.Group>
|
||||||
|
<Menu.ContainerItem>
|
||||||
|
<RecentChats postUri={postUri} />
|
||||||
|
</Menu.ContainerItem>
|
||||||
|
<Menu.Item
|
||||||
|
testID="postDropdownSendViaDMBtn"
|
||||||
|
label={_(msg`Send via direct message`)}
|
||||||
|
onPress={() => sendViaChatControl.open()}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Send via direct message</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Send} position="right" />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
testID="postDropdownShareBtn"
|
testID="postDropdownShareBtn"
|
||||||
@@ -116,23 +134,9 @@ let ShareMenuItems = ({
|
|||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
<Menu.ItemIcon icon={ChainLinkIcon} position="right" />
|
<Menu.ItemIcon icon={ChainLinkIcon} position="right" />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
{hasSession && (
|
|
||||||
<Menu.Item
|
|
||||||
testID="postDropdownSendViaDMBtn"
|
|
||||||
label={_(msg`Send via direct message`)}
|
|
||||||
onPress={() => sendViaChatControl.open()}>
|
|
||||||
<Menu.ItemText>
|
|
||||||
<Trans>Send via direct message</Trans>
|
|
||||||
</Menu.ItemText>
|
|
||||||
<Menu.ItemIcon icon={Send} position="right" />
|
|
||||||
</Menu.Item>
|
|
||||||
)}
|
|
||||||
</Menu.Group>
|
</Menu.Group>
|
||||||
|
|
||||||
{devModeEnabled && (
|
{devModeEnabled && (
|
||||||
<>
|
|
||||||
<Menu.Divider />
|
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
testID="postAtUriShareBtn"
|
testID="postAtUriShareBtn"
|
||||||
@@ -153,7 +157,6 @@ let ShareMenuItems = ({
|
|||||||
<Menu.ItemIcon icon={ClipboardIcon} position="right" />
|
<Menu.ItemIcon icon={ClipboardIcon} position="right" />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Group>
|
</Menu.Group>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Menu.Outer>
|
</Menu.Outer>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user