about summary refs log tree commit diff
path: root/src/screens/Messages/List
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/Messages/List')
-rw-r--r--src/screens/Messages/List/ChatListItem.tsx378
-rw-r--r--src/screens/Messages/List/index.tsx345
2 files changed, 0 insertions, 723 deletions
diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx
deleted file mode 100644
index 11c071082..000000000
--- a/src/screens/Messages/List/ChatListItem.tsx
+++ /dev/null
@@ -1,378 +0,0 @@
-import React, {useCallback, useState} from 'react'
-import {GestureResponderEvent, View} from 'react-native'
-import {
-  AppBskyActorDefs,
-  AppBskyEmbedRecord,
-  ChatBskyConvoDefs,
-  moderateProfile,
-  ModerationOpts,
-} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {useHaptics} from '#/lib/haptics'
-import {decrementBadgeCount} from '#/lib/notifications/notifications'
-import {logEvent} from '#/lib/statsig/statsig'
-import {sanitizeDisplayName} from '#/lib/strings/display-names'
-import {
-  postUriToRelativePath,
-  toBskyAppUrl,
-  toShortUrl,
-} from '#/lib/strings/url-helpers'
-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 {TimeElapsed} from '#/view/com/util/TimeElapsed'
-import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
-import * as tokens from '#/alf/tokens'
-import {ConvoMenu} from '#/components/dms/ConvoMenu'
-import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2'
-import {Link} from '#/components/Link'
-import {useMenuControl} from '#/components/Menu'
-import {PostAlerts} from '#/components/moderation/PostAlerts'
-import {Text} from '#/components/Typography'
-
-export let ChatListItem = ({
-  convo,
-}: {
-  convo: ChatBskyConvoDefs.ConvoView
-}): React.ReactNode => {
-  const {currentAccount} = useSession()
-  const moderationOpts = useModerationOpts()
-
-  const otherUser = convo.members.find(
-    member => member.did !== currentAccount?.did,
-  )
-
-  if (!otherUser || !moderationOpts) {
-    return null
-  }
-
-  return (
-    <ChatListItemReady
-      convo={convo}
-      profile={otherUser}
-      moderationOpts={moderationOpts}
-    />
-  )
-}
-
-ChatListItem = React.memo(ChatListItem)
-
-function ChatListItemReady({
-  convo,
-  profile: profileUnshadowed,
-  moderationOpts,
-}: {
-  convo: ChatBskyConvoDefs.ConvoView
-  profile: AppBskyActorDefs.ProfileViewBasic
-  moderationOpts: ModerationOpts
-}) {
-  const t = useTheme()
-  const {_} = useLingui()
-  const {currentAccount} = useSession()
-  const menuControl = useMenuControl()
-  const {gtMobile} = useBreakpoints()
-  const profile = useProfileShadow(profileUnshadowed)
-  const moderation = React.useMemo(
-    () => moderateProfile(profile, moderationOpts),
-    [profile, moderationOpts],
-  )
-  const playHaptic = useHaptics()
-
-  const blockInfo = React.useMemo(() => {
-    const modui = moderation.ui('profileView')
-    const blocks = modui.alerts.filter(alert => alert.type === 'blocking')
-    const listBlocks = blocks.filter(alert => alert.source.type === 'list')
-    const userBlock = blocks.find(alert => alert.source.type === 'user')
-    return {
-      listBlocks,
-      userBlock,
-    }
-  }, [moderation])
-
-  const isDeletedAccount = profile.handle === 'missing.invalid'
-  const displayName = isDeletedAccount
-    ? 'Deleted Account'
-    : sanitizeDisplayName(
-        profile.displayName || profile.handle,
-        moderation.ui('displayName'),
-      )
-
-  const isDimStyle = convo.muted || moderation.blocked || isDeletedAccount
-
-  const {lastMessage, lastMessageSentAt} = React.useMemo(() => {
-    let lastMessage = _(msg`No messages yet`)
-    let lastMessageSentAt: string | null = null
-
-    if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) {
-      const isFromMe = convo.lastMessage.sender?.did === currentAccount?.did
-
-      if (convo.lastMessage.text) {
-        if (isFromMe) {
-          lastMessage = _(msg`You: ${convo.lastMessage.text}`)
-        } else {
-          lastMessage = convo.lastMessage.text
-        }
-      } else if (convo.lastMessage.embed) {
-        const defaultEmbeddedContentMessage = _(
-          msg`(contains embedded content)`,
-        )
-
-        if (AppBskyEmbedRecord.isView(convo.lastMessage.embed)) {
-          const embed = convo.lastMessage.embed
-
-          if (AppBskyEmbedRecord.isViewRecord(embed.record)) {
-            const record = embed.record
-            const path = postUriToRelativePath(record.uri, {
-              handle: record.author.handle,
-            })
-            const href = path ? toBskyAppUrl(path) : undefined
-            const short = href
-              ? toShortUrl(href)
-              : defaultEmbeddedContentMessage
-            if (isFromMe) {
-              lastMessage = _(msg`You: ${short}`)
-            } else {
-              lastMessage = short
-            }
-          }
-        } else {
-          if (isFromMe) {
-            lastMessage = _(msg`You: ${defaultEmbeddedContentMessage}`)
-          } else {
-            lastMessage = defaultEmbeddedContentMessage
-          }
-        }
-      }
-
-      lastMessageSentAt = convo.lastMessage.sentAt
-    }
-    if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) {
-      lastMessage = isDeletedAccount
-        ? _(msg`Conversation deleted`)
-        : _(msg`Message deleted`)
-    }
-
-    return {
-      lastMessage,
-      lastMessageSentAt,
-    }
-  }, [_, convo.lastMessage, currentAccount?.did, isDeletedAccount])
-
-  const [showActions, setShowActions] = useState(false)
-
-  const onMouseEnter = useCallback(() => {
-    setShowActions(true)
-  }, [])
-
-  const onMouseLeave = useCallback(() => {
-    setShowActions(false)
-  }, [])
-
-  const onFocus = useCallback<React.FocusEventHandler>(e => {
-    if (e.nativeEvent.relatedTarget == null) return
-    setShowActions(true)
-  }, [])
-
-  const onPress = useCallback(
-    (e: GestureResponderEvent) => {
-      decrementBadgeCount(convo.unreadCount)
-      if (isDeletedAccount) {
-        e.preventDefault()
-        menuControl.open()
-        return false
-      } else {
-        logEvent('chat:open', {logContext: 'ChatsList'})
-      }
-    },
-    [convo.unreadCount, isDeletedAccount, menuControl],
-  )
-
-  const onLongPress = useCallback(() => {
-    playHaptic()
-    menuControl.open()
-  }, [playHaptic, menuControl])
-
-  return (
-    <View
-      // @ts-expect-error web only
-      onMouseEnter={onMouseEnter}
-      onMouseLeave={onMouseLeave}
-      onFocus={onFocus}
-      onBlur={onMouseLeave}
-      style={[a.relative]}>
-      <View
-        style={[
-          a.z_10,
-          a.absolute,
-          {top: tokens.space.md, left: tokens.space.lg},
-        ]}>
-        <PreviewableUserAvatar
-          profile={profile}
-          size={52}
-          moderation={moderation.ui('avatar')}
-        />
-      </View>
-
-      <Link
-        to={`/messages/${convo.id}`}
-        label={displayName}
-        accessibilityHint={
-          !isDeletedAccount
-            ? _(msg`Go to conversation with ${profile.handle}`)
-            : _(
-                msg`This conversation is with a deleted or a deactivated account. Press for options.`,
-              )
-        }
-        accessibilityActions={
-          isNative
-            ? [
-                {name: 'magicTap', label: _(msg`Open conversation options`)},
-                {name: 'longpress', label: _(msg`Open conversation options`)},
-              ]
-            : undefined
-        }
-        onPress={onPress}
-        onLongPress={isNative ? onLongPress : undefined}
-        onAccessibilityAction={onLongPress}>
-        {({hovered, pressed, focused}) => (
-          <View
-            style={[
-              a.flex_row,
-              isDeletedAccount ? a.align_center : a.align_start,
-              a.flex_1,
-              a.px_lg,
-              a.py_md,
-              a.gap_md,
-              (hovered || pressed || focused) && t.atoms.bg_contrast_25,
-              t.atoms.border_contrast_low,
-            ]}>
-            {/* Avatar goes here */}
-            <View style={{width: 52, height: 52}} />
-
-            <View style={[a.flex_1, a.justify_center, web({paddingRight: 45})]}>
-              <View style={[a.w_full, a.flex_row, a.align_end, a.pb_2xs]}>
-                <Text
-                  numberOfLines={1}
-                  style={[{maxWidth: '85%'}, web([a.leading_normal])]}>
-                  <Text
-                    emoji
-                    style={[
-                      a.text_md,
-                      t.atoms.text,
-                      a.font_bold,
-                      {lineHeight: 21},
-                      isDimStyle && t.atoms.text_contrast_medium,
-                    ]}>
-                    {displayName}
-                  </Text>
-                </Text>
-                {lastMessageSentAt && (
-                  <TimeElapsed timestamp={lastMessageSentAt}>
-                    {({timeElapsed}) => (
-                      <Text
-                        style={[
-                          a.text_sm,
-                          {lineHeight: 21},
-                          t.atoms.text_contrast_medium,
-                          web({whiteSpace: 'preserve nowrap'}),
-                        ]}>
-                        {' '}
-                        &middot; {timeElapsed}
-                      </Text>
-                    )}
-                  </TimeElapsed>
-                )}
-                {(convo.muted || moderation.blocked) && (
-                  <Text
-                    style={[
-                      a.text_sm,
-                      {lineHeight: 21},
-                      t.atoms.text_contrast_medium,
-                      web({whiteSpace: 'preserve nowrap'}),
-                    ]}>
-                    {' '}
-                    &middot;{' '}
-                    <BellStroke
-                      size="xs"
-                      style={[t.atoms.text_contrast_medium]}
-                    />
-                  </Text>
-                )}
-              </View>
-
-              {!isDeletedAccount && (
-                <Text
-                  numberOfLines={1}
-                  style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}>
-                  @{profile.handle}
-                </Text>
-              )}
-
-              <Text
-                emoji
-                numberOfLines={2}
-                style={[
-                  a.text_sm,
-                  a.leading_snug,
-                  convo.unreadCount > 0
-                    ? a.font_bold
-                    : t.atoms.text_contrast_high,
-                  isDimStyle && t.atoms.text_contrast_medium,
-                ]}>
-                {lastMessage}
-              </Text>
-
-              <PostAlerts
-                modui={moderation.ui('contentList')}
-                size="lg"
-                style={[a.pt_xs]}
-              />
-            </View>
-
-            {convo.unreadCount > 0 && (
-              <View
-                style={[
-                  a.absolute,
-                  a.rounded_full,
-                  {
-                    backgroundColor: isDimStyle
-                      ? t.palette.contrast_200
-                      : t.palette.primary_500,
-                    height: 7,
-                    width: 7,
-                    top: 15,
-                    right: 12,
-                  },
-                ]}
-              />
-            )}
-          </View>
-        )}
-      </Link>
-
-      <ConvoMenu
-        convo={convo}
-        profile={profile}
-        control={menuControl}
-        currentScreen="list"
-        showMarkAsRead={convo.unreadCount > 0}
-        hideTrigger={isNative}
-        blockInfo={blockInfo}
-        style={[
-          a.absolute,
-          a.h_full,
-          a.self_end,
-          a.justify_center,
-          {
-            right: tokens.space.lg,
-            opacity: !gtMobile || showActions || menuControl.isOpen ? 1 : 0,
-          },
-        ]}
-      />
-    </View>
-  )
-}
diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx
deleted file mode 100644
index efd717f0b..000000000
--- a/src/screens/Messages/List/index.tsx
+++ /dev/null
@@ -1,345 +0,0 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react'
-import {View} from 'react-native'
-import {ChatBskyConvoDefs} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useFocusEffect} from '@react-navigation/native'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
-
-import {useAppState} from '#/lib/hooks/useAppState'
-import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
-import {MessagesTabNavigatorParams} from '#/lib/routes/types'
-import {cleanError} from '#/lib/strings/errors'
-import {logger} from '#/logger'
-import {isNative} from '#/platform/detection'
-import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
-import {useMessagesEventBus} from '#/state/messages/events'
-import {useListConvosQuery} from '#/state/queries/messages/list-converations'
-import {List} from '#/view/com/util/List'
-import {ViewHeader} from '#/view/com/util/ViewHeader'
-import {CenteredView} from '#/view/com/util/Views'
-import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
-import {DialogControlProps, useDialogControl} from '#/components/Dialog'
-import {NewChat} from '#/components/dms/dialogs/NewChatDialog'
-import {MessagesNUX} from '#/components/dms/MessagesNUX'
-import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus'
-import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise'
-import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
-import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message'
-import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
-import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
-import {Link} from '#/components/Link'
-import {ListFooter} from '#/components/Lists'
-import {Loader} from '#/components/Loader'
-import {Text} from '#/components/Typography'
-import {ChatListItem} from './ChatListItem'
-
-type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'Messages'>
-
-function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) {
-  return <ChatListItem convo={item} />
-}
-
-function keyExtractor(item: ChatBskyConvoDefs.ConvoView) {
-  return item.id
-}
-
-export function MessagesScreen({navigation, route}: Props) {
-  const {_} = useLingui()
-  const t = useTheme()
-  const newChatControl = useDialogControl()
-  const {gtMobile} = useBreakpoints()
-  const pushToConversation = route.params?.pushToConversation
-
-  // Whenever we have `pushToConversation` set, it means we pressed a notification for a chat without being on
-  // this tab. We should immediately push to the conversation after pressing the notification.
-  // After we push, reset with `setParams` so that this effect will fire next time we press a notification, even if
-  // the conversation is the same as before
-  useEffect(() => {
-    if (pushToConversation) {
-      navigation.navigate('MessagesConversation', {
-        conversation: pushToConversation,
-      })
-      navigation.setParams({pushToConversation: undefined})
-    }
-  }, [navigation, pushToConversation])
-
-  // Request the poll interval to be 10s (or whatever the MESSAGE_SCREEN_POLL_INTERVAL is set to in the future)
-  // but only when the screen is active
-  const messagesBus = useMessagesEventBus()
-  const state = useAppState()
-  const isActive = state === 'active'
-  useFocusEffect(
-    useCallback(() => {
-      if (isActive) {
-        const unsub = messagesBus.requestPollInterval(
-          MESSAGE_SCREEN_POLL_INTERVAL,
-        )
-        return () => unsub()
-      }
-    }, [messagesBus, isActive]),
-  )
-
-  const renderButton = useCallback(() => {
-    return (
-      <Link
-        to="/messages/settings"
-        label={_(msg`Chat settings`)}
-        size="small"
-        variant="ghost"
-        color="secondary"
-        shape="square"
-        style={[a.justify_center]}>
-        <SettingsSlider size="md" style={[t.atoms.text_contrast_medium]} />
-      </Link>
-    )
-  }, [_, t])
-
-  const initialNumToRender = useInitialNumToRender({minItemHeight: 80})
-  const [isPTRing, setIsPTRing] = useState(false)
-
-  const {
-    data,
-    isLoading,
-    isFetchingNextPage,
-    hasNextPage,
-    fetchNextPage,
-    isError,
-    error,
-    refetch,
-  } = useListConvosQuery()
-
-  useRefreshOnFocus(refetch)
-
-  const conversations = useMemo(() => {
-    if (data?.pages) {
-      return data.pages.flatMap(page => page.convos)
-    }
-    return []
-  }, [data])
-
-  const onRefresh = useCallback(async () => {
-    setIsPTRing(true)
-    try {
-      await refetch()
-    } catch (err) {
-      logger.error('Failed to refresh conversations', {message: err})
-    }
-    setIsPTRing(false)
-  }, [refetch, setIsPTRing])
-
-  const onEndReached = useCallback(async () => {
-    if (isFetchingNextPage || !hasNextPage || isError) return
-    try {
-      await fetchNextPage()
-    } catch (err) {
-      logger.error('Failed to load more conversations', {message: err})
-    }
-  }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
-
-  const onNewChat = useCallback(
-    (conversation: string) =>
-      navigation.navigate('MessagesConversation', {conversation}),
-    [navigation],
-  )
-
-  const onNavigateToSettings = useCallback(() => {
-    navigation.navigate('MessagesSettings')
-  }, [navigation])
-
-  if (conversations.length < 1) {
-    return (
-      <View style={a.flex_1}>
-        <MessagesNUX />
-
-        <CenteredView sideBorders={gtMobile} style={[a.h_full_vh]}>
-          {gtMobile ? (
-            <DesktopHeader
-              newChatControl={newChatControl}
-              onNavigateToSettings={onNavigateToSettings}
-            />
-          ) : (
-            <ViewHeader
-              title={_(msg`Messages`)}
-              renderButton={renderButton}
-              showBorder
-              canGoBack={false}
-            />
-          )}
-
-          {isLoading ? (
-            <View style={[a.align_center, a.pt_3xl, web({paddingTop: '10vh'})]}>
-              <Loader size="xl" />
-            </View>
-          ) : (
-            <>
-              {isError ? (
-                <>
-                  <View style={[a.pt_3xl, a.align_center]}>
-                    <CircleInfo
-                      width={48}
-                      fill={t.atoms.border_contrast_low.borderColor}
-                    />
-                    <Text style={[a.pt_md, a.pb_sm, a.text_2xl, a.font_bold]}>
-                      <Trans>Whoops!</Trans>
-                    </Text>
-                    <Text
-                      style={[
-                        a.text_md,
-                        a.pb_xl,
-                        a.text_center,
-                        a.leading_snug,
-                        t.atoms.text_contrast_medium,
-                        {maxWidth: 360},
-                      ]}>
-                      {cleanError(error)}
-                    </Text>
-
-                    <Button
-                      label={_(msg`Reload conversations`)}
-                      size="large"
-                      color="secondary"
-                      variant="solid"
-                      onPress={() => refetch()}>
-                      <ButtonText>Retry</ButtonText>
-                      <ButtonIcon icon={Retry} position="right" />
-                    </Button>
-                  </View>
-                </>
-              ) : (
-                <>
-                  <View style={[a.pt_3xl, a.align_center]}>
-                    <Message width={48} fill={t.palette.primary_500} />
-                    <Text style={[a.pt_md, a.pb_sm, a.text_2xl, a.font_bold]}>
-                      <Trans>Nothing here</Trans>
-                    </Text>
-                    <Text
-                      style={[
-                        a.text_md,
-                        a.pb_xl,
-                        a.text_center,
-                        a.leading_snug,
-                        t.atoms.text_contrast_medium,
-                      ]}>
-                      <Trans>You have no conversations yet. Start one!</Trans>
-                    </Text>
-                  </View>
-                </>
-              )}
-            </>
-          )}
-        </CenteredView>
-
-        {!isLoading && !isError && (
-          <NewChat onNewChat={onNewChat} control={newChatControl} />
-        )}
-      </View>
-    )
-  }
-
-  return (
-    <View style={a.flex_1}>
-      <MessagesNUX />
-      {!gtMobile && (
-        <ViewHeader
-          title={_(msg`Messages`)}
-          renderButton={renderButton}
-          showBorder
-          canGoBack={false}
-        />
-      )}
-      <NewChat onNewChat={onNewChat} control={newChatControl} />
-      <List
-        data={conversations}
-        renderItem={renderItem}
-        keyExtractor={keyExtractor}
-        refreshing={isPTRing}
-        onRefresh={onRefresh}
-        onEndReached={onEndReached}
-        ListHeaderComponent={
-          <DesktopHeader
-            newChatControl={newChatControl}
-            onNavigateToSettings={onNavigateToSettings}
-          />
-        }
-        ListFooterComponent={
-          <ListFooter
-            isFetchingNextPage={isFetchingNextPage}
-            error={cleanError(error)}
-            onRetry={fetchNextPage}
-            style={{borderColor: 'transparent'}}
-            hasNextPage={hasNextPage}
-            showEndMessage={true}
-            endMessageText={_(msg`No more conversations to show`)}
-          />
-        }
-        onEndReachedThreshold={isNative ? 1.5 : 0}
-        initialNumToRender={initialNumToRender}
-        windowSize={11}
-        // @ts-ignore our .web version only -sfn
-        desktopFixedHeight
-      />
-    </View>
-  )
-}
-
-function DesktopHeader({
-  newChatControl,
-  onNavigateToSettings,
-}: {
-  newChatControl: DialogControlProps
-  onNavigateToSettings: () => void
-}) {
-  const t = useTheme()
-  const {_} = useLingui()
-  const {gtMobile, gtTablet} = useBreakpoints()
-
-  if (!gtMobile) {
-    return null
-  }
-
-  return (
-    <View
-      style={[
-        t.atoms.bg,
-        a.flex_row,
-        a.align_center,
-        a.justify_between,
-        a.gap_lg,
-        a.px_lg,
-        a.pr_md,
-        a.py_sm,
-        a.border_b,
-        t.atoms.border_contrast_low,
-      ]}>
-      <Text style={[a.text_2xl, a.font_bold]}>
-        <Trans>Messages</Trans>
-      </Text>
-      <View style={[a.flex_row, a.align_center, a.gap_sm]}>
-        <Button
-          label={_(msg`Message settings`)}
-          color="secondary"
-          size="small"
-          variant="ghost"
-          shape="square"
-          onPress={onNavigateToSettings}>
-          <SettingsSlider size="md" style={[t.atoms.text_contrast_medium]} />
-        </Button>
-        {gtTablet && (
-          <Button
-            label={_(msg`New chat`)}
-            color="primary"
-            size="small"
-            variant="solid"
-            onPress={newChatControl.open}>
-            <ButtonIcon icon={Plus} position="left" />
-            <ButtonText>
-              <Trans>New chat</Trans>
-            </ButtonText>
-          </Button>
-        )}
-      </View>
-    </View>
-  )
-}