about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/dms/MessageItem.tsx136
-rw-r--r--src/components/dms/MessageReportDialog.tsx8
-rw-r--r--src/screens/Messages/Conversation/MessageListError.tsx1
-rw-r--r--src/screens/Messages/Conversation/MessagesList.tsx8
-rw-r--r--src/state/messages/convo/agent.ts36
-rw-r--r--src/state/messages/convo/types.ts19
6 files changed, 121 insertions, 87 deletions
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx
index f8ab85188..cafd7ca5a 100644
--- a/src/components/dms/MessageItem.tsx
+++ b/src/components/dms/MessageItem.tsx
@@ -1,40 +1,44 @@
 import React, {useCallback, useMemo, useRef} from 'react'
-import {LayoutAnimation, StyleProp, TextStyle, View} from 'react-native'
+import {
+  GestureResponderEvent,
+  LayoutAnimation,
+  StyleProp,
+  TextStyle,
+  View,
+} from 'react-native'
 import {ChatBskyConvoDefs, RichText as RichTextAPI} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {ConvoItem} from '#/state/messages/convo/types'
 import {useSession} from '#/state/session'
 import {TimeElapsed} from 'view/com/util/TimeElapsed'
 import {atoms as a, useTheme} from '#/alf'
 import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
+import {InlineLinkText} from '#/components/Link'
 import {Text} from '#/components/Typography'
 import {RichText} from '../RichText'
 
 let MessageItem = ({
   item,
-  next,
-  pending,
 }: {
-  item: ChatBskyConvoDefs.MessageView
-  next:
-    | ChatBskyConvoDefs.MessageView
-    | ChatBskyConvoDefs.DeletedMessageView
-    | null
-  pending?: boolean
+  item: ConvoItem & {type: 'message' | 'pending-message'}
 }): React.ReactNode => {
   const t = useTheme()
   const {currentAccount} = useSession()
 
-  const isFromSelf = item.sender?.did === currentAccount?.did
+  const {message, nextMessage} = item
+  const isPending = item.type === 'pending-message'
+
+  const isFromSelf = message.sender?.did === currentAccount?.did
 
   const isNextFromSelf =
-    ChatBskyConvoDefs.isMessageView(next) &&
-    next.sender?.did === currentAccount?.did
+    ChatBskyConvoDefs.isMessageView(nextMessage) &&
+    nextMessage.sender?.did === currentAccount?.did
 
   const isLastInGroup = useMemo(() => {
-    // TODO this means it's a placeholder. Let's figure out the right way to do this though!
-    if (item.id.length > 13) {
+    // if this message is pending, it means the next message is pending too
+    if (isPending && nextMessage) {
       return false
     }
 
@@ -44,9 +48,9 @@ let MessageItem = ({
     }
 
     // or, if there's a 3 minute gap between this message and the next
-    if (ChatBskyConvoDefs.isMessageView(next)) {
-      const thisDate = new Date(item.sentAt)
-      const nextDate = new Date(next.sentAt)
+    if (ChatBskyConvoDefs.isMessageView(nextMessage)) {
+      const thisDate = new Date(message.sentAt)
+      const nextDate = new Date(nextMessage.sentAt)
 
       const diff = nextDate.getTime() - thisDate.getTime()
 
@@ -55,7 +59,7 @@ let MessageItem = ({
     }
 
     return true
-  }, [item, next, isFromSelf, isNextFromSelf])
+  }, [message, nextMessage, isFromSelf, isNextFromSelf, isPending])
 
   const lastInGroupRef = useRef(isLastInGroup)
   if (lastInGroupRef.current !== isLastInGroup) {
@@ -67,12 +71,12 @@ let MessageItem = ({
     t.name === 'light' ? t.palette.primary_200 : t.palette.primary_800
 
   const rt = useMemo(() => {
-    return new RichTextAPI({text: item.text, facets: item.facets})
-  }, [item.text, item.facets])
+    return new RichTextAPI({text: message.text, facets: message.facets})
+  }, [message.text, message.facets])
 
   return (
     <View>
-      <ActionsWrapper isFromSelf={isFromSelf} message={item}>
+      <ActionsWrapper isFromSelf={isFromSelf} message={message}>
         <View
           style={[
             a.py_sm,
@@ -82,7 +86,7 @@ let MessageItem = ({
               paddingLeft: 14,
               paddingRight: 14,
               backgroundColor: isFromSelf
-                ? pending
+                ? isPending
                   ? pendingColor
                   : t.palette.primary_500
                 : t.palette.contrast_50,
@@ -98,18 +102,20 @@ let MessageItem = ({
               a.text_md,
               a.leading_snug,
               isFromSelf && {color: t.palette.white},
-              pending && t.name !== 'light' && {color: t.palette.primary_300},
+              isPending && t.name !== 'light' && {color: t.palette.primary_300},
             ]}
             interactiveStyle={a.underline}
             enableTags
           />
         </View>
       </ActionsWrapper>
-      <MessageItemMetadata
-        message={item}
-        isLastInGroup={isLastInGroup}
-        style={isFromSelf ? a.text_right : a.text_left}
-      />
+
+      {isLastInGroup && (
+        <MessageItemMetadata
+          item={item}
+          style={isFromSelf ? a.text_right : a.text_left}
+        />
+      )}
     </View>
   )
 }
@@ -117,16 +123,26 @@ MessageItem = React.memo(MessageItem)
 export {MessageItem}
 
 let MessageItemMetadata = ({
-  message,
-  isLastInGroup,
+  item,
   style,
 }: {
-  message: ChatBskyConvoDefs.MessageView
-  isLastInGroup: boolean
+  item: ConvoItem & {type: 'message' | 'pending-message'}
   style: StyleProp<TextStyle>
 }): React.ReactNode => {
   const t = useTheme()
   const {_} = useLingui()
+  const {message} = item
+
+  const handleRetry = useCallback(
+    (e: GestureResponderEvent) => {
+      if (item.type === 'pending-message' && item.retry) {
+        e.preventDefault()
+        item.retry()
+        return false
+      }
+    },
+    [item],
+  )
 
   const relativeTimestamp = useCallback(
     (timestamp: string) => {
@@ -169,25 +185,47 @@ let MessageItemMetadata = ({
     [_],
   )
 
-  if (!isLastInGroup) {
-    return null
-  }
-
   return (
-    <TimeElapsed timestamp={message.sentAt} timeToString={relativeTimestamp}>
-      {({timeElapsed}) => (
-        <Text
-          style={[
-            t.atoms.text_contrast_medium,
-            a.text_xs,
-            a.mt_2xs,
-            a.mb_lg,
-            style,
-          ]}>
-          {timeElapsed}
-        </Text>
+    <Text
+      style={[
+        a.text_xs,
+        a.mt_2xs,
+        a.mb_lg,
+        t.atoms.text_contrast_medium,
+        style,
+      ]}>
+      <TimeElapsed timestamp={message.sentAt} timeToString={relativeTimestamp}>
+        {({timeElapsed}) => (
+          <Text style={[a.text_xs, t.atoms.text_contrast_medium]}>
+            {timeElapsed}
+          </Text>
+        )}
+      </TimeElapsed>
+
+      {item.type === 'pending-message' && item.retry && (
+        <>
+          {' '}
+          &middot;{' '}
+          <Text
+            style={[
+              a.text_xs,
+              {
+                color: t.palette.negative_400,
+              },
+            ]}>
+            {_(msg`Failed to send`)}
+          </Text>{' '}
+          &middot;{' '}
+          <InlineLinkText
+            label={_(msg`Click to retry failed message`)}
+            to="#"
+            onPress={handleRetry}
+            style={[a.text_xs]}>
+            {_(msg`Retry`)}
+          </InlineLinkText>
+        </>
       )}
-    </TimeElapsed>
+    </Text>
   )
 }
 
diff --git a/src/components/dms/MessageReportDialog.tsx b/src/components/dms/MessageReportDialog.tsx
index 6071312e9..cc25732af 100644
--- a/src/components/dms/MessageReportDialog.tsx
+++ b/src/components/dms/MessageReportDialog.tsx
@@ -245,8 +245,12 @@ function PreviewMessage({message}: {message: ChatBskyConvoDefs.MessageView}) {
         />
       </View>
       <MessageItemMetadata
-        message={message}
-        isLastInGroup
+        item={{
+          type: 'message',
+          message,
+          key: '',
+          nextMessage: null,
+        }}
         style={[a.text_left, a.mb_0]}
       />
     </View>
diff --git a/src/screens/Messages/Conversation/MessageListError.tsx b/src/screens/Messages/Conversation/MessageListError.tsx
index 38a63b0f1..c6e246a3f 100644
--- a/src/screens/Messages/Conversation/MessageListError.tsx
+++ b/src/screens/Messages/Conversation/MessageListError.tsx
@@ -26,7 +26,6 @@ export function MessageListError({
         msg`This chat was disconnected due to a network error.`,
       ),
       [ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
-      [ConvoItemError.PendingFailed]: _(msg`Failed to send message(s).`),
     }[item.code]
   }, [_, item.code])
 
diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx
index dac534cd4..0c1bce17d 100644
--- a/src/screens/Messages/Conversation/MessagesList.tsx
+++ b/src/screens/Messages/Conversation/MessagesList.tsx
@@ -35,13 +35,7 @@ function MaybeLoader({isLoading}: {isLoading: boolean}) {
 
 function renderItem({item}: {item: ConvoItem}) {
   if (item.type === 'message' || item.type === 'pending-message') {
-    return (
-      <MessageItem
-        item={item.message}
-        next={item.nextMessage}
-        pending={item.type === 'pending-message'}
-      />
-    )
+    return <MessageItem item={item} />
   } else if (item.type === 'deleted-message') {
     return <Text>Deleted message</Text>
   } else if (item.type === 'error-recoverable') {
diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts
index 6d59e1369..420eff349 100644
--- a/src/state/messages/convo/agent.ts
+++ b/src/state/messages/convo/agent.ts
@@ -735,6 +735,8 @@ export class Convo {
     }
   }
 
+  private pendingFailed = false
+
   async sendMessage(message: ChatBskyConvoSendMessage.InputSchema['message']) {
     // Ignore empty messages for now since they have no other purpose atm
     if (!message.text.trim()) return
@@ -747,11 +749,9 @@ export class Convo {
       id: tempId,
       message,
     })
-    // remove on each send, it might go through now without user having to click
-    this.footerItems.delete(ConvoItemError.PendingFailed)
     this.commit()
 
-    if (!this.isProcessingPendingMessages) {
+    if (!this.isProcessingPendingMessages && !this.pendingFailed) {
       this.processPendingMessages()
     }
   }
@@ -805,16 +805,7 @@ export class Convo {
       this.commit()
     } catch (e: any) {
       logger.error(e, {context: `Convo: failed to send message`})
-      this.footerItems.set(ConvoItemError.PendingFailed, {
-        type: 'error-recoverable',
-        key: ConvoItemError.PendingFailed,
-        code: ConvoItemError.PendingFailed,
-        retry: () => {
-          this.footerItems.delete(ConvoItemError.PendingFailed)
-          this.commit()
-          this.batchRetryPendingMessages()
-        },
-      })
+      this.pendingFailed = true
       this.commit()
     } finally {
       this.isProcessingPendingMessages = false
@@ -868,16 +859,7 @@ export class Convo {
       )
     } catch (e: any) {
       logger.error(e, {context: `Convo: failed to batch retry messages`})
-      this.footerItems.set(ConvoItemError.PendingFailed, {
-        type: 'error-recoverable',
-        key: ConvoItemError.PendingFailed,
-        code: ConvoItemError.PendingFailed,
-        retry: () => {
-          this.footerItems.delete(ConvoItemError.PendingFailed)
-          this.commit()
-          this.batchRetryPendingMessages()
-        },
-      })
+      this.pendingFailed = true
       this.commit()
     }
   }
@@ -958,6 +940,7 @@ export class Convo {
         key: m.id,
         message: {
           ...m.message,
+          $type: 'chat.bsky.convo.defs#messageView',
           id: nanoid(),
           rev: '__fake__',
           sentAt: new Date().toISOString(),
@@ -968,6 +951,13 @@ export class Convo {
           sender: this.sender!,
         },
         nextMessage: null,
+        retry: this.pendingFailed
+          ? () => {
+              this.pendingFailed = false
+              this.commit()
+              this.batchRetryPendingMessages()
+            }
+          : undefined,
       })
     })
 
diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts
index 6ce4d40bd..3fb0eb6ad 100644
--- a/src/state/messages/convo/types.ts
+++ b/src/state/messages/convo/types.ts
@@ -35,10 +35,6 @@ export enum ConvoItemError {
    * Error fetching past messages
    */
   HistoryFailed = 'historyFailed',
-  /**
-   * Error sending new message
-   */
-  PendingFailed = 'pendingFailed',
 }
 
 export enum ConvoErrorCode {
@@ -83,13 +79,26 @@ export type ConvoDispatch =
 
 export type ConvoItem =
   | {
-      type: 'message' | 'pending-message'
+      type: 'message'
+      key: string
+      message: ChatBskyConvoDefs.MessageView
+      nextMessage:
+        | ChatBskyConvoDefs.MessageView
+        | ChatBskyConvoDefs.DeletedMessageView
+        | null
+    }
+  | {
+      type: 'pending-message'
       key: string
       message: ChatBskyConvoDefs.MessageView
       nextMessage:
         | ChatBskyConvoDefs.MessageView
         | ChatBskyConvoDefs.DeletedMessageView
         | null
+      /**
+       * Retry sending the message. If present, the message is in a failed state.
+       */
+      retry?: () => void
     }
   | {
       type: 'deleted-message'