add profile button

This commit is contained in:
Samuel Newman
2024-05-18 20:57:01 +01:00
parent 5343910570
commit 7496540d07
3 changed files with 104 additions and 39 deletions
@@ -9,12 +9,15 @@ import {
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig' import {useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isIOS, isWeb} from '#/platform/detection' import {isIOS, isWeb} from '#/platform/detection'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useMaybeConvoForUser} from '#/state/queries/messages/get-convo-for-members'
import { import {
useProfileBlockMutationQueue, useProfileBlockMutationQueue,
useProfileFollowMutationQueue, useProfileFollowMutationQueue,
@@ -29,6 +32,7 @@ import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
@@ -71,6 +75,9 @@ let ProfileHeaderStandard = ({
const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
const unblockPromptControl = Prompt.usePromptControl() const unblockPromptControl = Prompt.usePromptControl()
const requireAuth = useRequireAuth() const requireAuth = useRequireAuth()
const navigation = useNavigation<NavigationProp>()
const {data: convoId} = useMaybeConvoForUser(profile.did)
const onPressEditProfile = React.useCallback(() => { const onPressEditProfile = React.useCallback(() => {
track('ProfileHeader:EditProfileButtonClicked') track('ProfileHeader:EditProfileButtonClicked')
@@ -141,6 +148,11 @@ let ProfileHeaderStandard = ({
} }
}, [_, queueUnblock, track]) }, [_, queueUnblock, track])
const onPressDm = React.useCallback(() => {
if (!convoId) return
navigation.navigate('MessagesConversation', {conversation: convoId})
}, [navigation, convoId])
const isMe = React.useMemo( const isMe = React.useMemo(
() => currentAccount?.did === profile.did, () => currentAccount?.did === profile.did,
[currentAccount, profile], [currentAccount, profile],
@@ -156,7 +168,14 @@ let ProfileHeaderStandard = ({
style={[a.px_lg, a.pt_md, a.pb_sm]} style={[a.px_lg, a.pt_md, a.pb_sm]}
pointerEvents={isIOS ? 'auto' : 'box-none'}> pointerEvents={isIOS ? 'auto' : 'box-none'}>
<View <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'}> pointerEvents={isIOS ? 'auto' : 'box-none'}>
{isMe ? ( {isMe ? (
<Button <Button
@@ -166,7 +185,7 @@ let ProfileHeaderStandard = ({
variant="solid" variant="solid"
onPress={onPressEditProfile} onPress={onPressEditProfile}
label={_(msg`Edit profile`)} label={_(msg`Edit profile`)}
style={a.rounded_full}> style={[a.rounded_full, a.py_sm]}>
<ButtonText> <ButtonText>
<Trans>Edit Profile</Trans> <Trans>Edit Profile</Trans>
</ButtonText> </ButtonText>
@@ -181,7 +200,7 @@ let ProfileHeaderStandard = ({
label={_(msg`Unblock`)} label={_(msg`Unblock`)}
disabled={!hasSession} disabled={!hasSession}
onPress={() => unblockPromptControl.open()} onPress={() => unblockPromptControl.open()}
style={a.rounded_full}> style={[a.rounded_full, a.py_sm]}>
<ButtonText> <ButtonText>
<Trans context="action">Unblock</Trans> <Trans context="action">Unblock</Trans>
</ButtonText> </ButtonText>
@@ -190,14 +209,31 @@ let ProfileHeaderStandard = ({
) : !profile.viewer?.blockedBy ? ( ) : !profile.viewer?.blockedBy ? (
<> <>
{hasSession && ( {hasSession && (
<>
{convoId && (
<Button
testID="dmBtn"
size="small"
color="secondary"
variant="solid"
shape="round"
onPress={onPressDm}
label={_(msg`Message ${profile.handle}`)}
style={{width: 36, height: 36}}>
<Envelope style={t.atoms.text} size="md" />
</Button>
)}
<Button <Button
testID="suggestedFollowsBtn" testID="suggestedFollowsBtn"
size="small" size="small"
color={showSuggestedFollows ? 'primary' : 'secondary'} color={showSuggestedFollows ? 'primary' : 'secondary'}
variant="solid" variant="solid"
shape="round" shape="round"
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)} onPress={() =>
label={_(msg`Show follows similar to ${profile.handle}`)}> setShowSuggestedFollows(!showSuggestedFollows)
}
label={_(msg`Show follows similar to ${profile.handle}`)}
style={{width: 36, height: 36}}>
<FontAwesomeIcon <FontAwesomeIcon
icon="user-plus" icon="user-plus"
style={ style={
@@ -208,6 +244,7 @@ let ProfileHeaderStandard = ({
size={14} size={14}
/> />
</Button> </Button>
</>
)} )}
<Button <Button
@@ -223,7 +260,7 @@ let ProfileHeaderStandard = ({
onPress={ onPress={
profile.viewer?.following ? onPressUnfollow : onPressFollow profile.viewer?.following ? onPressUnfollow : onPressFollow
} }
style={[a.rounded_full, a.gap_xs]}> style={[a.rounded_full, a.gap_xs, a.py_sm]}>
<ButtonIcon <ButtonIcon
position="left" position="left"
icon={profile.viewer?.following ? Check : Plus} icon={profile.viewer?.following ? Check : Plus}
@@ -1,11 +1,14 @@
import {ChatBskyConvoGetConvoForMembers} from '@atproto/api' 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 {logger} from '#/logger'
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {RQKEY as CONVO_KEY} from './conversation' import {RQKEY as CONVO_KEY} from './conversation'
const RQKEY_ROOT = 'convo-for-user'
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
export function useGetConvoForMembers({ export function useGetConvoForMembers({
onSuccess, onSuccess,
onError, onError,
@@ -35,3 +38,28 @@ 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
}
},
})
}
+17 -17
View File
@@ -1,41 +1,42 @@
import React, {memo} from 'react' import React, {memo} from 'react'
import {TouchableOpacity} from 'react-native' import {TouchableOpacity} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import * as Toast from 'view/com/util/Toast'
import {EventStopper} from 'view/com/util/EventStopper' import {logger} from '#/logger'
import {useSession} from 'state/session' import {useAnalytics} from 'lib/analytics/analytics'
import * as Menu from '#/components/Menu'
import {useTheme} from '#/alf'
import {usePalette} from 'lib/hooks/usePalette'
import {HITSLOP_10} from 'lib/constants' import {HITSLOP_10} from 'lib/constants'
import {usePalette} from 'lib/hooks/usePalette'
import {makeProfileLink} from 'lib/routes/links'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {makeProfileLink} from 'lib/routes/links' import {Shadow} from 'state/cache/types'
import {useAnalytics} from 'lib/analytics/analytics'
import {useModalControls} from 'state/modals' import {useModalControls} from 'state/modals'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import { import {
RQKEY as profileQueryKey, RQKEY as profileQueryKey,
useProfileBlockMutationQueue, useProfileBlockMutationQueue,
useProfileFollowMutationQueue, useProfileFollowMutationQueue,
useProfileMuteMutationQueue, useProfileMuteMutationQueue,
} from 'state/queries/profile' } 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 {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 {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle'
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2'
import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck' import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck'
import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX' 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 {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {logger} from '#/logger' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
import {Shadow} from 'state/cache/types' import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
let ProfileMenu = ({ let ProfileMenu = ({
profile, profile,
@@ -192,9 +193,8 @@ let ProfileMenu = ({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingVertical: 10, padding: 8,
borderRadius: 50, borderRadius: 50,
paddingHorizontal: 16,
}, },
pal.btn, pal.btn,
]}> ]}>