diff options
-rw-r--r-- | src/components/dms/MessageProfileButton.tsx | 39 | ||||
-rw-r--r-- | src/screens/Profile/Header/ProfileHeaderLabeler.tsx | 6 | ||||
-rw-r--r-- | src/screens/Profile/Header/ProfileHeaderStandard.tsx | 56 | ||||
-rw-r--r-- | src/state/queries/messages/get-convo-for-members.ts | 32 | ||||
-rw-r--r-- | src/view/com/profile/ProfileMenu.tsx | 34 |
5 files changed, 125 insertions, 42 deletions
diff --git a/src/components/dms/MessageProfileButton.tsx b/src/components/dms/MessageProfileButton.tsx new file mode 100644 index 000000000..6f227de65 --- /dev/null +++ b/src/components/dms/MessageProfileButton.tsx @@ -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> + ) +} diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 459bd0d95..e79b345cd 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -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, ]}> diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index f3f2a370d..66141e782 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -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} diff --git a/src/state/queries/messages/get-convo-for-members.ts b/src/state/queries/messages/get-convo-for-members.ts index 083146b83..9187c1607 100644 --- a/src/state/queries/messages/get-convo-for-members.ts +++ b/src/state/queries/messages/get-convo-for-members.ts @@ -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, + }) +} diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index cb0b1d97c..e3357ec1e 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -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, ]}> |