about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Lists.tsx4
-rw-r--r--src/components/dialogs/GifSelect.tsx1
-rw-r--r--src/components/dms/NewChat.tsx37
-rw-r--r--src/components/dms/util.ts18
-rw-r--r--src/screens/Messages/List/ChatListItem.tsx18
5 files changed, 61 insertions, 17 deletions
diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx
index 721e877be..8cbe2810e 100644
--- a/src/components/Lists.tsx
+++ b/src/components/Lists.tsx
@@ -136,6 +136,7 @@ let ListMaybePlaceholder = ({
   onGoBack,
   hideBackButton,
   sideBorders,
+  topBorder = true,
 }: {
   isLoading: boolean
   noEmpty?: boolean
@@ -149,6 +150,7 @@ let ListMaybePlaceholder = ({
   onGoBack?: () => void
   hideBackButton?: boolean
   sideBorders?: boolean
+  topBorder?: boolean
 }): React.ReactNode => {
   const t = useTheme()
   const {_} = useLingui()
@@ -165,7 +167,7 @@ let ListMaybePlaceholder = ({
           {paddingTop: 175, paddingBottom: 110},
         ]}
         sideBorders={sideBorders ?? gtMobile}
-        topBorder={!gtTablet}>
+        topBorder={topBorder && !gtTablet}>
         <View style={[a.w_full, a.align_center, {top: 100}]}>
           <Loader size="xl" />
         </View>
diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx
index 57389ba2b..4a3ce42aa 100644
--- a/src/components/dialogs/GifSelect.tsx
+++ b/src/components/dialogs/GifSelect.tsx
@@ -197,6 +197,7 @@ function GifList({
                 onGoBack={onGoBack}
                 emptyType="results"
                 sideBorders={false}
+                topBorder={false}
                 errorTitle={_(msg`Failed to load GIFs`)}
                 errorMessage={_(msg`There was an issue connecting to Tenor.`)}
                 emptyMessage={
diff --git a/src/components/dms/NewChat.tsx b/src/components/dms/NewChat.tsx
index 5dde8628c..3975c0c5d 100644
--- a/src/components/dms/NewChat.tsx
+++ b/src/components/dms/NewChat.tsx
@@ -10,6 +10,7 @@ import {sanitizeHandle} from '#/lib/strings/handles'
 import {isWeb} from '#/platform/detection'
 import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
+import {useSession} from '#/state/session'
 import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete'
 import {FAB} from '#/view/com/util/fab/FAB'
 import * as Toast from '#/view/com/util/Toast'
@@ -23,6 +24,7 @@ import {Button} from '../Button'
 import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '../icons/Envelope'
 import {ListMaybePlaceholder} from '../Lists'
 import {Text} from '../Typography'
+import {canBeMessaged} from './util'
 
 export function NewChat({
   control,
@@ -82,6 +84,7 @@ function SearchablePeopleList({
   const moderationOpts = useModerationOpts()
   const control = Dialog.useDialogContext()
   const listRef = useRef<BottomSheetFlatListMethods>(null)
+  const {currentAccount} = useSession()
 
   const [searchText, setSearchText] = useState('')
 
@@ -95,12 +98,17 @@ function SearchablePeopleList({
   const renderItem = useCallback(
     ({item: profile}: {item: AppBskyActorDefs.ProfileView}) => {
       if (!moderationOpts) return null
+
       const moderation = moderateProfile(profile, moderationOpts)
+
+      const disabled = !canBeMessaged(profile)
+      const handle = sanitizeHandle(profile.handle, '@')
+
       return (
         <Button
           label={profile.displayName || sanitizeHandle(profile.handle)}
-          onPress={() => onCreateChat(profile.did)}>
-          {({hovered, pressed}) => (
+          onPress={() => !disabled && onCreateChat(profile.did)}>
+          {({hovered, pressed, focused}) => (
             <View
               style={[
                 a.flex_1,
@@ -110,7 +118,9 @@ function SearchablePeopleList({
                 a.align_center,
                 a.flex_row,
                 a.rounded_sm,
-                pressed
+                disabled
+                  ? {opacity: 0.5}
+                  : pressed || focused
                   ? t.atoms.bg_contrast_25
                   : hovered
                   ? t.atoms.bg_contrast_50
@@ -131,8 +141,12 @@ function SearchablePeopleList({
                     moderation.ui('displayName'),
                   )}
                 </Text>
-                <Text style={t.atoms.text_contrast_high} numberOfLines={1}>
-                  {sanitizeHandle(profile.handle, '@')}
+                <Text style={t.atoms.text_contrast_high} numberOfLines={2}>
+                  {disabled ? (
+                    <Trans>{handle} can't be messaged</Trans>
+                  ) : (
+                    handle
+                  )}
                 </Text>
               </View>
             </View>
@@ -166,7 +180,6 @@ function SearchablePeopleList({
             t.atoms.bg,
           ]}
         />
-        <Dialog.Close />
         <Text
           style={[
             a.text_2xl,
@@ -201,14 +214,23 @@ function SearchablePeopleList({
             autoFocus
           />
         </TextField.Root>
+        <Dialog.Close />
       </View>
     )
   }, [t.atoms.bg, _, control, searchText])
 
+  const dataWithoutSelf = useMemo(() => {
+    return (
+      actorAutocompleteData?.filter(
+        profile => profile.did !== currentAccount?.did,
+      ) ?? []
+    )
+  }, [actorAutocompleteData, currentAccount?.did])
+
   return (
     <Dialog.InnerFlatList
       ref={listRef}
-      data={actorAutocompleteData}
+      data={dataWithoutSelf}
       renderItem={renderItem}
       ListHeaderComponent={
         <>
@@ -235,6 +257,7 @@ function SearchablePeopleList({
                 hideBackButton={true}
                 emptyType="results"
                 sideBorders={false}
+                topBorder={false}
                 emptyMessage={
                   isError
                     ? _(msg`No search results found for "${searchText}".`)
diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts
new file mode 100644
index 000000000..5952b9acf
--- /dev/null
+++ b/src/components/dms/util.ts
@@ -0,0 +1,18 @@
+import {AppBskyActorDefs} from '@atproto/api'
+
+export function canBeMessaged(profile: AppBskyActorDefs.ProfileView) {
+  switch (profile.associated?.chat?.allowIncoming) {
+    case 'none':
+      return false
+    case 'all':
+      return true
+    // if unset, treat as following
+    case 'following':
+    case undefined:
+      return Boolean(profile.viewer?.followedBy)
+    // any other values are invalid according to the lexicon, so
+    // let's treat as false to be safe
+    default:
+      return false
+  }
+}
diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx
index aa47e9503..0a0f8c575 100644
--- a/src/screens/Messages/List/ChatListItem.tsx
+++ b/src/screens/Messages/List/ChatListItem.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, {useCallback, useState} from 'react'
 import {View} from 'react-native'
 import {
   AppBskyActorDefs,
@@ -88,22 +88,22 @@ function ChatListItemReady({
   }
 
   const navigation = useNavigation<NavigationProp>()
-  const [showActions, setShowActions] = React.useState(false)
+  const [showActions, setShowActions] = useState(false)
 
-  const onMouseEnter = React.useCallback(() => {
+  const onMouseEnter = useCallback(() => {
     setShowActions(true)
   }, [])
 
-  const onMouseLeave = React.useCallback(() => {
+  const onMouseLeave = useCallback(() => {
     setShowActions(false)
   }, [])
 
-  const onFocus = React.useCallback<React.FocusEventHandler>(e => {
+  const onFocus = useCallback<React.FocusEventHandler>(e => {
     if (e.nativeEvent.relatedTarget == null) return
     setShowActions(true)
   }, [])
 
-  const onPress = React.useCallback(() => {
+  const onPress = useCallback(() => {
     navigation.push('MessagesConversation', {
       conversation: convo.id,
     })
@@ -119,9 +119,9 @@ function ChatListItemReady({
       <Button
         label={profile.displayName || profile.handle}
         onPress={onPress}
-        style={a.flex_1}
+        style={[a.flex_1]}
         onLongPress={isNative ? menuControl.open : undefined}>
-        {({hovered, pressed}) => (
+        {({hovered, pressed, focused}) => (
           <View
             style={[
               a.flex_row,
@@ -129,7 +129,7 @@ function ChatListItemReady({
               a.px_lg,
               a.py_md,
               a.gap_md,
-              (hovered || pressed) && t.atoms.bg_contrast_25,
+              (hovered || pressed || focused) && t.atoms.bg_contrast_25,
               t.atoms.border_contrast_low,
             ]}>
             <UserAvatar