[🐴] DM button on profile (#4097)

* add profile button

* separate out button to component

* normalise subscribe to labeller button size

* infinite staletime

* use Link rather than Button and change icon

* adjust icon position
zio/stable
Samuel Newman 2024-05-20 17:18:56 +01:00 committed by GitHub
parent 2414559b80
commit 24f8794d4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 125 additions and 42 deletions

View File

@ -0,0 +1,39 @@
import React from 'react'
import {AppBskyActorDefs} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMaybeConvoForUser} from '#/state/queries/messages/get-convo-for-members'
import {atoms as a, useTheme} from '#/alf'
import {Message_Stroke2_Corner0_Rounded as Message} from '../icons/Message'
import {Link} from '../Link'
export function MessageProfileButton({
profile,
}: {
profile: AppBskyActorDefs.ProfileView
}) {
const {_} = useLingui()
const t = useTheme()
const {data: convoId} = useMaybeConvoForUser(profile.did)
if (!convoId) return null
return (
<Link
testID="dmBtn"
size="small"
color="secondary"
variant="solid"
shape="round"
label={_(msg`Message ${profile.handle}`)}
to={`/messages/${convoId}`}
style={[a.justify_center, {width: 36, height: 36}]}>
<Message
style={[t.atoms.text, {marginLeft: 1, marginBottom: 1}]}
size="md"
/>
</Link>
)
}

View File

@ -128,7 +128,7 @@ let ProfileHeaderLabeler = ({
const onPressSubscribe = React.useCallback(
() =>
requireAuth(async () => {
requireAuth(async (): Promise<void> => {
if (!canSubscribe) {
cantSubscribePrompt.open()
return
@ -197,7 +197,6 @@ let ProfileHeaderLabeler = ({
<View
style={[
{
paddingVertical: 12,
backgroundColor:
isSubscribed || !canSubscribe
? state.hovered || state.pressed
@ -207,7 +206,8 @@ let ProfileHeaderLabeler = ({
? tokens.color.temp_purple_dark
: tokens.color.temp_purple,
},
a.px_lg,
a.py_sm,
a.px_md,
a.rounded_sm,
a.gap_sm,
]}>

View File

@ -28,6 +28,7 @@ import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {MessageProfileButton} from '#/components/dms/MessageProfileButton'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import * as Prompt from '#/components/Prompt'
@ -156,7 +157,14 @@ let ProfileHeaderStandard = ({
style={[a.px_lg, a.pt_md, a.pb_sm]}
pointerEvents={isIOS ? 'auto' : 'box-none'}>
<View
style={[a.flex_row, a.justify_end, a.gap_sm, a.pb_sm]}
style={[
{paddingLeft: 90},
a.flex_row,
a.justify_end,
a.gap_sm,
a.pb_sm,
a.flex_wrap,
]}
pointerEvents={isIOS ? 'auto' : 'box-none'}>
{isMe ? (
<Button
@ -166,7 +174,7 @@ let ProfileHeaderStandard = ({
variant="solid"
onPress={onPressEditProfile}
label={_(msg`Edit profile`)}
style={a.rounded_full}>
style={[a.rounded_full, a.py_sm]}>
<ButtonText>
<Trans>Edit Profile</Trans>
</ButtonText>
@ -181,7 +189,7 @@ let ProfileHeaderStandard = ({
label={_(msg`Unblock`)}
disabled={!hasSession}
onPress={() => unblockPromptControl.open()}
style={a.rounded_full}>
style={[a.rounded_full, a.py_sm]}>
<ButtonText>
<Trans context="action">Unblock</Trans>
</ButtonText>
@ -190,24 +198,30 @@ let ProfileHeaderStandard = ({
) : !profile.viewer?.blockedBy ? (
<>
{hasSession && (
<Button
testID="suggestedFollowsBtn"
size="small"
color={showSuggestedFollows ? 'primary' : 'secondary'}
variant="solid"
shape="round"
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
label={_(msg`Show follows similar to ${profile.handle}`)}>
<FontAwesomeIcon
icon="user-plus"
style={
showSuggestedFollows
? {color: t.palette.white}
: t.atoms.text
<>
<MessageProfileButton profile={profile} />
<Button
testID="suggestedFollowsBtn"
size="small"
color={showSuggestedFollows ? 'primary' : 'secondary'}
variant="solid"
shape="round"
onPress={() =>
setShowSuggestedFollows(!showSuggestedFollows)
}
size={14}
/>
</Button>
label={_(msg`Show follows similar to ${profile.handle}`)}
style={{width: 36, height: 36}}>
<FontAwesomeIcon
icon="user-plus"
style={
showSuggestedFollows
? {color: t.palette.white}
: t.atoms.text
}
size={14}
/>
</Button>
</>
)}
<Button
@ -223,7 +237,7 @@ let ProfileHeaderStandard = ({
onPress={
profile.viewer?.following ? onPressUnfollow : onPressFollow
}
style={[a.rounded_full, a.gap_xs]}>
style={[a.rounded_full, a.gap_xs, a.py_sm]}>
<ButtonIcon
position="left"
icon={profile.viewer?.following ? Check : Plus}

View File

@ -1,11 +1,15 @@
import {ChatBskyConvoGetConvoForMembers} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
import {useAgent} from '#/state/session'
import {STALE} from '..'
import {RQKEY as CONVO_KEY} from './conversation'
const RQKEY_ROOT = 'convo-for-user'
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
export function useGetConvoForMembers({
onSuccess,
onError,
@ -35,3 +39,29 @@ export function useGetConvoForMembers({
},
})
}
/**
* Gets the conversation ID for a given DID. Returns null if it's not possible to message them.
*/
export function useMaybeConvoForUser(did: string) {
const {getAgent} = useAgent()
return useQuery({
queryKey: RQKEY(did),
queryFn: async () => {
const convo = await getAgent()
.api.chat.bsky.convo.getConvoForMembers(
{members: [did]},
{headers: DM_SERVICE_HEADERS},
)
.catch(() => ({success: null}))
if (convo.success) {
return convo.data.convo.id
} else {
return null
}
},
staleTime: STALE.INFINITY,
})
}

View File

@ -1,41 +1,42 @@
import React, {memo} from 'react'
import {TouchableOpacity} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useQueryClient} from '@tanstack/react-query'
import * as Toast from 'view/com/util/Toast'
import {EventStopper} from 'view/com/util/EventStopper'
import {useSession} from 'state/session'
import * as Menu from '#/components/Menu'
import {useTheme} from '#/alf'
import {usePalette} from 'lib/hooks/usePalette'
import {logger} from '#/logger'
import {useAnalytics} from 'lib/analytics/analytics'
import {HITSLOP_10} from 'lib/constants'
import {usePalette} from 'lib/hooks/usePalette'
import {makeProfileLink} from 'lib/routes/links'
import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers'
import {makeProfileLink} from 'lib/routes/links'
import {useAnalytics} from 'lib/analytics/analytics'
import {Shadow} from 'state/cache/types'
import {useModalControls} from 'state/modals'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {
RQKEY as profileQueryKey,
useProfileBlockMutationQueue,
useProfileFollowMutationQueue,
useProfileMuteMutationQueue,
} from 'state/queries/profile'
import {useSession} from 'state/session'
import {EventStopper} from 'view/com/util/EventStopper'
import * as Toast from 'view/com/util/Toast'
import {useTheme} from '#/alf'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle'
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2'
import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck'
import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX'
import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {logger} from '#/logger'
import {Shadow} from 'state/cache/types'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
let ProfileMenu = ({
profile,
@ -192,9 +193,8 @@ let ProfileMenu = ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 10,
padding: 8,
borderRadius: 50,
paddingHorizontal: 16,
},
pal.btn,
]}>