about summary refs log tree commit diff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/Messages/Conversation/index.tsx104
-rw-r--r--src/screens/Messages/List/ChatListItem.tsx70
-rw-r--r--src/screens/Messages/List/index.tsx12
3 files changed, 129 insertions, 57 deletions
diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx
index f382647a5..05df3e23b 100644
--- a/src/screens/Messages/Conversation/index.tsx
+++ b/src/screens/Messages/Conversation/index.tsx
@@ -3,7 +3,7 @@ import {TouchableOpacity, View} from 'react-native'
 import {KeyboardProvider} from 'react-native-keyboard-controller'
 import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -12,8 +12,12 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
 import {useGate} from '#/lib/statsig/statsig'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {useCurrentConvoId} from '#/state/messages/current-convo-id'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {useProfileQuery} from '#/state/queries/profile'
 import {BACK_HITSLOP} from 'lib/constants'
+import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {isIOS, isWeb} from 'platform/detection'
 import {ConvoProvider, isConvoActive, useConvo} from 'state/messages/convo'
 import {ConvoStatus} from 'state/messages/convo/types'
@@ -27,6 +31,7 @@ import {ListMaybePlaceholder} from '#/components/Lists'
 import {Loader} from '#/components/Loader'
 import {Text} from '#/components/Typography'
 import {ClipClopGate} from '../gate'
+
 type Props = NativeStackScreenProps<
   CommonNavigatorParams,
   'MessagesConversation'
@@ -137,7 +142,7 @@ function Inner() {
 }
 
 let Header = ({
-  profile,
+  profile: initialProfile,
 }: {
   profile?: AppBskyActorDefs.ProfileViewBasic
 }): React.ReactNode => {
@@ -145,12 +150,8 @@ let Header = ({
   const {_} = useLingui()
   const {gtTablet} = useBreakpoints()
   const navigation = useNavigation<NavigationProp>()
-  const convoState = useConvo()
-
-  const isDeletedAccount = profile?.handle === 'missing.invalid'
-  const displayName = isDeletedAccount
-    ? 'Deleted Account'
-    : profile?.displayName
+  const moderationOpts = useModerationOpts()
+  const {data: profile} = useProfileQuery({did: initialProfile?.did})
 
   const onPressBack = useCallback(() => {
     if (isWeb) {
@@ -195,23 +196,12 @@ let Header = ({
       ) : (
         <View style={{width: 30}} />
       )}
-      <View style={[a.align_center, a.gap_sm, a.flex_1]}>
-        {profile ? (
-          <View style={[a.align_center]}>
-            <PreviewableUserAvatar size={32} profile={profile} />
-            <Text
-              style={[a.text_lg, a.font_bold, a.pt_sm, a.pb_2xs]}
-              numberOfLines={1}>
-              {displayName}
-            </Text>
-            {!isDeletedAccount && (
-              <Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}>
-                @{profile.handle}
-              </Text>
-            )}
-          </View>
-        ) : (
-          <>
+
+      {profile && moderationOpts ? (
+        <HeaderReady profile={profile} moderationOpts={moderationOpts} />
+      ) : (
+        <>
+          <View style={[a.align_center, a.gap_sm, a.flex_1]}>
             <View
               style={[
                 {width: 32, height: 32},
@@ -234,19 +224,69 @@ let Header = ({
                 t.atoms.bg_contrast_25,
               ]}
             />
-          </>
-        )}
+          </View>
+
+          <View style={{width: 30}} />
+        </>
+      )}
+    </View>
+  )
+}
+Header = React.memo(Header)
+
+function HeaderReady({
+  profile: profileUnshadowed,
+  moderationOpts,
+}: {
+  profile: AppBskyActorDefs.ProfileViewBasic
+  moderationOpts: ModerationOpts
+}) {
+  const t = useTheme()
+  const convoState = useConvo()
+  const profile = useProfileShadow(profileUnshadowed)
+  const moderation = React.useMemo(
+    () => moderateProfile(profile, moderationOpts),
+    [profile, moderationOpts],
+  )
+
+  const isDeletedAccount = profile?.handle === 'missing.invalid'
+  const displayName = isDeletedAccount
+    ? 'Deleted Account'
+    : sanitizeDisplayName(
+        profile.displayName || profile.handle,
+        moderation.ui('displayName'),
+      )
+
+  return (
+    <>
+      <View style={[a.align_center, a.gap_sm, a.flex_1]}>
+        <View style={[a.align_center]}>
+          <PreviewableUserAvatar
+            size={32}
+            profile={profile}
+            moderation={moderation.ui('avatar')}
+          />
+          <Text
+            style={[a.text_lg, a.font_bold, a.pt_sm, a.pb_2xs]}
+            numberOfLines={1}>
+            {displayName}
+          </Text>
+          {!isDeletedAccount && (
+            <Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}>
+              @{profile.handle}
+            </Text>
+          )}
+        </View>
       </View>
-      {isConvoActive(convoState) && profile ? (
+
+      {isConvoActive(convoState) && (
         <ConvoMenu
           convo={convoState.convo}
           profile={profile}
           currentScreen="conversation"
+          moderation={moderation}
         />
-      ) : (
-        <View style={{width: 30}} />
       )}
-    </View>
+    </>
   )
 }
-Header = React.memo(Header)
diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx
index 57a8e0348..aa47e9503 100644
--- a/src/screens/Messages/List/ChatListItem.tsx
+++ b/src/screens/Messages/List/ChatListItem.tsx
@@ -1,13 +1,21 @@
 import React from 'react'
 import {View} from 'react-native'
-import {ChatBskyConvoDefs} from '@atproto/api'
+import {
+  AppBskyActorDefs,
+  ChatBskyConvoDefs,
+  moderateProfile,
+  ModerationOpts,
+} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
 
 import {NavigationProp} from '#/lib/routes/types'
 import {isNative} from '#/platform/detection'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {useSession} from '#/state/session'
+import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {TimeElapsed} from '#/view/com/util/TimeElapsed'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
@@ -17,25 +25,53 @@ import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/
 import {useMenuControl} from '#/components/Menu'
 import {Text} from '#/components/Typography'
 
-export function ChatListItem({
+export function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) {
+  const {currentAccount} = useSession()
+  const otherUser = convo.members.find(
+    member => member.did !== currentAccount?.did,
+  )
+  const moderationOpts = useModerationOpts()
+
+  if (!otherUser || !moderationOpts) {
+    return null
+  }
+
+  return (
+    <ChatListItemReady
+      convo={convo}
+      profile={otherUser}
+      moderationOpts={moderationOpts}
+    />
+  )
+}
+
+function ChatListItemReady({
   convo,
-  index,
+  profile: profileUnshadowed,
+  moderationOpts,
 }: {
   convo: ChatBskyConvoDefs.ConvoView
-  index: number
+  profile: AppBskyActorDefs.ProfileViewBasic
+  moderationOpts: ModerationOpts
 }) {
   const t = useTheme()
   const {_} = useLingui()
   const {currentAccount} = useSession()
   const menuControl = useMenuControl()
   const {gtMobile} = useBreakpoints()
-  const otherUser = convo.members.find(
-    member => member.did !== currentAccount?.did,
+  const profile = useProfileShadow(profileUnshadowed)
+  const moderation = React.useMemo(
+    () => moderateProfile(profile, moderationOpts),
+    [profile, moderationOpts],
   )
-  const isDeletedAccount = otherUser?.handle === 'missing.invalid'
+
+  const isDeletedAccount = profile.handle === 'missing.invalid'
   const displayName = isDeletedAccount
     ? 'Deleted Account'
-    : otherUser?.displayName || otherUser?.handle
+    : sanitizeDisplayName(
+        profile.displayName || profile.handle,
+        moderation.ui('displayName'),
+      )
 
   let lastMessage = _(msg`No messages yet`)
   let lastMessageSentAt: string | null = null
@@ -73,10 +109,6 @@ export function ChatListItem({
     })
   }, [convo.id, navigation])
 
-  if (!otherUser) {
-    return null
-  }
-
   return (
     <View
       // @ts-expect-error web only
@@ -85,7 +117,7 @@ export function ChatListItem({
       onFocus={onFocus}
       onBlur={onMouseLeave}>
       <Button
-        label={otherUser.displayName || otherUser.handle}
+        label={profile.displayName || profile.handle}
         onPress={onPress}
         style={a.flex_1}
         onLongPress={isNative ? menuControl.open : undefined}>
@@ -98,10 +130,13 @@ export function ChatListItem({
               a.py_md,
               a.gap_md,
               (hovered || pressed) && t.atoms.bg_contrast_25,
-              index === 0 && [a.border_t, a.pt_lg],
               t.atoms.border_contrast_low,
             ]}>
-            <UserAvatar avatar={otherUser?.avatar} size={52} />
+            <UserAvatar
+              avatar={profile.avatar}
+              size={52}
+              moderation={moderation.ui('avatar')}
+            />
             <View style={[a.flex_1, a.flex_row, a.align_center]}>
               <View style={[a.flex_1]}>
                 <View
@@ -154,7 +189,7 @@ export function ChatListItem({
                   <Text
                     numberOfLines={1}
                     style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}>
-                    @{otherUser.handle}
+                    @{profile.handle}
                   </Text>
                 )}
                 <Text
@@ -196,7 +231,7 @@ export function ChatListItem({
               )}
               <ConvoMenu
                 convo={convo}
-                profile={otherUser}
+                profile={profile}
                 control={menuControl}
                 currentScreen="list"
                 showMarkAsRead={convo.unreadCount > 0}
@@ -204,6 +239,7 @@ export function ChatListItem({
                 triggerOpacity={
                   !gtMobile || showActions || menuControl.isOpen ? 1 : 0
                 }
+                moderation={moderation}
               />
             </View>
           </View>
diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx
index 6300a976b..2ae17a141 100644
--- a/src/screens/Messages/List/index.tsx
+++ b/src/screens/Messages/List/index.tsx
@@ -29,14 +29,8 @@ import {ChatListItem} from './ChatListItem'
 
 type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'Messages'>
 
-function renderItem({
-  item,
-  index,
-}: {
-  item: ChatBskyConvoDefs.ConvoView
-  index: number
-}) {
-  return <ChatListItem convo={item} index={index} />
+function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) {
+  return <ChatListItem convo={item} />
 }
 
 function keyExtractor(item: ChatBskyConvoDefs.ConvoView) {
@@ -232,6 +226,8 @@ function DesktopHeader({
         a.gap_lg,
         a.px_lg,
         a.py_sm,
+        a.border_b,
+        t.atoms.border_contrast_low,
       ]}>
       <Text style={[a.text_2xl, a.font_bold]}>
         <Trans>Messages</Trans>