about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/components/dms/MessageProfileButton.tsx39
-rw-r--r--src/screens/Profile/Header/ProfileHeaderLabeler.tsx6
-rw-r--r--src/screens/Profile/Header/ProfileHeaderStandard.tsx56
-rw-r--r--src/state/queries/messages/get-convo-for-members.ts32
-rw-r--r--src/view/com/profile/ProfileMenu.tsx34
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,
                 ]}>