Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Headers and New Conversation #1237

Closed
wants to merge 1 commit into from
Closed

Conversation

alexrisch
Copy link
Collaborator

@alexrisch alexrisch commented Nov 21, 2024

Updated new conversation Handling

Moved components to be in features branch
Updated title components
Bumped RN SDK
Commented out api header work

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced GroupConversationTitle and DmConversationTitle components for enhanced conversation title display with avatars.
    • Added V3Conversation and V3ConversationFromPeer components for improved conversation management.
    • Implemented useConversationWithPeerQuery for fetching conversations based on peer identifiers.
  • Bug Fixes

    • Updated navigation and loading state management in various components to enhance user experience.
  • Chores

    • Updated dependencies in package.json and Podfile for improved performance and stability.
    • Removed unused components and refactored existing ones for better maintainability.

@alexrisch alexrisch added this to the Milestone 1 - V3 Only milestone Nov 21, 2024
@alexrisch alexrisch requested a review from a team as a code owner November 21, 2024 03:58
Updated new conversation Handling

Moved components to be in features branch
Updated title components
Bumped RN SDK
Commented out api header work
Copy link
Contributor

coderabbitai bot commented Nov 21, 2024

Walkthrough

This pull request introduces significant changes to the conversation components of the application. It removes the ConversationTitleDumb component and replaces it with a new GroupConversationTitle component, which is designed to handle group conversation titles and avatars. The V3Conversation component has been refactored to streamline its logic, removing dependencies on group-related data. Additionally, new hooks and components have been added to improve data fetching and user interactions, while several existing components have been updated to enhance their functionality and maintainability.

Changes

File Change Summary
components/Conversation/ConversationTitleDumb.tsx Component and type ConversationTitleDumb removed.
components/Conversation/GroupConversationTitle.tsx New component GroupConversationTitle added, along with associated types and hooks.
components/Conversation/V3Conversation.tsx Major refactor: imports updated, props changed, navigation logic modified, and unused variables removed.
components/Conversation/V3ConversationHeader.tsx Component V3ConversationHeader removed.
components/Search/NavigationChatButton.tsx Added skipLoading parameter to navigation and updated loading state handling.
components/V3DMListItem.tsx Simplified state management and logic for unread messages; removed debugging logs.
components/V3GroupConversationListItem.tsx Updated handling of topics data and context menu actions for deletion and restoration.
features/conversation-list/hooks/useMessageIsUnread.ts Refactored to retrieve topicsData directly from the chat store.
features/conversations/components/ConversationTitleDumb.tsx New component ConversationTitleDumb added with props for title and interaction handling.
features/conversations/components/DmConversationTitle.tsx New component DmConversationTitle added for displaying DM titles with user interactions.
features/conversations/components/V3Conversation.tsx New component added for managing conversations with hooks for state and data handling.
features/conversations/components/V3ConversationFromPeer.tsx New component added to render conversations from peers with loading state management.
features/conversations/hooks/useConversationTitleLongPress.ts New hook added for handling long press events on conversation titles.
features/conversations/hooks/useGroupMembersAvatarData.ts New hook added for managing group members' avatar data.
ios/Podfile Updated XMTP pod version from 3.0.4 to 3.0.6.
package.json Updated dependency @xmtp/react-native-sdk from ^3.0.0 to ^3.0.6.
queries/QueryKeys.ts Added new enum value CONVERSATION_WITH_PEER and function conversationWithPeerQueryKey.
queries/useConversationQuery.ts Introduced useConversationWithPeerQuery and modified existing query methods for better handling.
queries/useDmPeerAddressQuery.ts New hook added to fetch peer addresses based on account and topic.
screens/Conversation.tsx Updated component logic for rendering based on route parameters.
screens/Navigation/ConversationNav.tsx Added optional skipLoading property to navigation parameters.
utils/xmtpRN/api.ts Disabled signature generation logic and modified header return values.
utils/xmtpRN/conversations.ts Enhanced conversation management with new functions for peer-based retrieval.

Possibly related PRs

  • refactor and fix modal #1094: Changes in the V3Conversation component's logic for rendering conversation titles may relate to the removal of the ConversationTitleDumb component, as both involve displaying conversation titles and managing user interactions.
  • Fabri QA for frames & deeplinks #1098: Updates to the GroupConversationTitle component, which also handles conversation titles and user interactions, are relevant due to the shared focus on conversation display logic.
  • fix: Frames Renders on initial Send #1113: Modifications in the Message component regarding frame handling may connect to the overall conversation management, as both involve rendering and managing message-related data.
  • fix: Set is Frame when ending with slash #1129: Introduction of the isFrameMessage utility function for determining frame messages may relate to the overall logic of how messages are displayed and interacted with in conversation components.

🐰 In the garden of code, we hop and play,
New titles bloom bright, in a colorful array.
Group chats now shine, with avatars in tow,
Conversations refined, watch our features grow!
With each little change, a step we take,
In the world of messages, new paths we make! 🌼✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

🧹 Outside diff range and nitpick comments (40)
features/conversations/hooks/useConversationTitleLongPress.ts (2)

5-7: Consider adding JSDoc documentation for the hook.

Since this is a debug-only feature, it would be helpful to document its purpose and usage.

+/**
+ * A debug-only hook that enables copying conversation topic data to clipboard on long press.
+ * Only active when debug mode is enabled.
+ * @param topic The conversation topic to be copied
+ */
 export const useConversationTitleLongPress = (topic: string) => {

8-15: Add user feedback and error handling for clipboard operations.

The current implementation lacks user feedback and error handling for clipboard operations. Consider adding these improvements:

 return useCallback(() => {
   if (!debugEnabled) return;
-  Clipboard.setString(
-    JSON.stringify({
-      topic,
-    })
-  );
+  try {
+    const data = JSON.stringify({
+      topic,
+      timestamp: new Date().toISOString(),
+      debug: true
+    }, null, 2);
+    Clipboard.setString(data);
+    // Consider adding a toast or alert to indicate successful copy
+  } catch (error) {
+    console.warn('Failed to copy debug data:', error);
+    // Consider showing an error message to the user
+  }
 }, [debugEnabled, topic]);
features/conversation-list/hooks/useMessageIsUnread.ts (1)

17-18: Consider adding JSDoc for store selection keys

The type-safe store selection setup is good. For better maintainability, consider adding a JSDoc comment explaining the purpose of these keys.

+/** Keys required from ChatStore for conversation unread status determination */
const chatStoreSelectKeys: (keyof ChatStoreType)[] = ["topicsData"];
screens/Conversation.tsx (1)

14-19: Verify topic validation completeness

While the isV3Topic validation is in place, consider adding type validation for textPrefill to ensure data consistency.

+  if (route.params?.text && typeof route.params.text !== 'string') {
+    console.warn('Invalid textPrefill type provided');
+    return <VStack />;
+  }
   if (route.params?.topic && isV3Topic(route.params.topic)) {
     return (
       <V3Conversation
features/conversations/components/ConversationTitleDumb.tsx (1)

10-15: Consider renaming the component to better reflect its purpose

The suffix "Dumb" in the component name doesn't follow React best practices. Consider renaming to something more descriptive like BaseConversationTitle or PresentationalConversationTitle to better reflect its role as a presentational component.

Additionally, consider adding JSDoc comments to document the props:

+/**
+ * A presentational component for rendering conversation titles with optional avatar
+ * @param title - The conversation title to display
+ * @param avatarComponent - Optional avatar component to render
+ * @param onLongPress - Callback fired on long press
+ * @param onPress - Callback fired on press
+ */
type ConversationTitleDumbProps = {
  title?: string;
  avatarComponent?: React.ReactNode;
  onLongPress?: () => void;
  onPress?: () => void;
};
features/conversations/components/V3ConversationFromPeer.tsx (2)

10-14: Add JSDoc documentation for the props type.

Consider adding JSDoc documentation to describe the purpose and expected values of each prop.

+/**
+ * Props for the V3ConversationFromPeer component
+ * @param peer - The identifier of the peer to load the conversation with
+ * @param textPrefill - Optional text to prefill in the conversation
+ * @param skipLoading - Whether to skip the loading state and data fetching
+ */
 type V3ConversationFromPeerProps = {
   peer: string;
   textPrefill?: string;
   skipLoading: boolean;
 };

1-57: Consider enhancing component reusability.

The component effectively abstracts conversation loading logic, but consider these architectural improvements:

  1. Extract the loading and error states into reusable components.
  2. Consider implementing a higher-order component pattern to share this loading behavior with other conversation types.
  3. Add proper error boundaries for better error handling at the component level.
features/conversations/hooks/useGroupMembersAvatarData.ts (3)

9-11: Add JSDoc documentation for the type definition

Consider adding JSDoc documentation to describe the purpose of the type and what ConversationTopic represents.

+/**
+ * Props for the useGroupMembersAvatarData hook
+ * @property {ConversationTopic} topic - The conversation topic to fetch member data for
+ */
 type UseGroupMembersAvatarDataProps = {
   topic: ConversationTopic;
 };

22-34: Simplify member address processing using array methods

The current implementation can be made more concise and maintainable using array methods.

   const memberAddresses = useMemo(() => {
-    const addresses: string[] = [];
-    for (const memberId of members?.ids ?? []) {
-      const member = members?.byId[memberId];
-      if (
-        member?.addresses[0] &&
-        member?.addresses[0].toLowerCase() !== currentAccount?.toLowerCase()
-      ) {
-        addresses.push(member?.addresses[0]);
-      }
-    }
-    return addresses;
+    return (members?.ids ?? [])
+      .map(id => members?.byId[id]?.addresses[0])
+      .filter((address): address is string => 
+        !!address && address.toLowerCase() !== currentAccount.toLowerCase()
+      );
   }, [members, currentAccount]);

13-59: Consider adding error boundary support and loading state handling

While the hook implementation is solid, consider these architectural improvements:

  1. Implement a custom error type for better error handling across the application
  2. Add loading state handling for better UX
  3. Consider adding retry logic for failed social data fetches

Example error type:

type GroupMemberError = {
  code: 'NO_ACCOUNT' | 'FETCH_ERROR' | 'DATA_MISMATCH';
  message: string;
  details?: unknown;
};
screens/Navigation/ConversationNav.tsx (1)

27-27: Document the purpose of skipLoading parameter

While the type definition is correct, please add a JSDoc comment explaining when and why this flag should be used, as it affects the behavior of multiple components.

+  /** When true, skips the loading state during navigation. Used by GroupConversationTitle and V3ConversationFromPeer components. */
   skipLoading?: boolean;
components/Search/NavigationChatButton.tsx (3)

Line range hint 34-46: Consider replacing setTimeout with navigation events.

Using setTimeout for navigation timing can be fragile. Consider using navigation events or callbacks for more reliable navigation timing.

-  setTimeout(() => {
+  // Use navigation events for more reliable timing
+  const unsubscribe = navigation.addListener('transitionEnd', () => {
     navigate("Conversation", {
       mainConversationWithPeer: address,
       focus: true,
       skipLoading: true,
     });
-  }, 300);
+    unsubscribe();
+  });

Line range hint 49-65: Enhance error handling in addToGroupIfPossible.

The function could benefit from better error handling to prevent the loading state from getting stuck and to provide better feedback to users.

   const addToGroupIfPossible = useCallback(async () => {
     if (loading) return;
     setLoading(true);
-    const allowed = await canMessageByAccount({
-      account: currentAccount(),
-      peer: address,
-    });
-    setLoading(false);
-    // canGroupMessage() returns lowercase addresses
-    if (!allowed) {
-      Alert.alert(
-        translate("cannot_be_added_to_group_yet", {
-          name: preferredName,
-        })
-      );
-      return;
-    }
-    addToGroup?.();
+    try {
+      const allowed = await canMessageByAccount({
+        account: currentAccount(),
+        peer: address,
+      });
+      // canGroupMessage() returns lowercase addresses
+      if (!allowed) {
+        Alert.alert(
+          translate("cannot_be_added_to_group_yet", {
+            name: preferredName,
+          })
+        );
+        return;
+      }
+      await addToGroup?.();
+    } catch (error) {
+      Alert.alert(
+        translate("error_adding_to_group", {
+          name: preferredName,
+          error: error.message
+        })
+      );
+    } finally {
+      setLoading(false);
+    }
   }, [loading, address, addToGroup, preferredName]);

Line range hint 49-49: Add cleanup for loading state.

Consider adding a cleanup effect to reset the loading state when the component unmounts.

+  useEffect(() => {
+    return () => {
+      // Reset loading state on unmount to prevent memory leaks
+      setLoading(false);
+    };
+  }, []);
queries/useConversationQuery.ts (2)

56-56: Consider environment-specific logging.

The debug logging statement might be better suited for development environments only.

Consider wrapping it in a development check:

- logger.info("[Crash Debug] queryFn fetching conversation with peer");
+ if (__DEV__) {
+   logger.info("[Crash Debug] queryFn fetching conversation with peer");
+ }

45-93: Consider enhancing error handling and type safety.

While the hooks implement good null checking, consider these architectural improvements:

  1. Add error boundaries or error handling callbacks for failed queries
  2. Consider using discriminated unions for better type safety in the return types
  3. Add retry policies for failed network requests
  4. Consider implementing optimistic updates for better UX

Example error handling enhancement:

type ConversationError = {
  code: string;
  message: string;
};

const useConversationWithPeerQuery = (
  account: string,
  peer: string | undefined,
  options?: Partial<
    UseQueryOptions<
      ConversationWithCodecsType | null | undefined,
      ConversationError
    >
  >
) => {
  return useQuery({
    ...options,
    retry: (failureCount, error) => {
      // Custom retry logic
      return failureCount < 3 && error.code !== 'UNAUTHORIZED';
    },
    // ... rest of the implementation
  });
};
features/conversations/components/DmConversationTitle.tsx (4)

18-26: Consider adding runtime validation for the topic prop.

While the TypeScript types provide compile-time safety, consider adding runtime validation for the topic prop to handle potential undefined cases more gracefully, especially since useCurrentAccount is used with a non-null assertion later.

+import { z } from 'zod';
+
+const topicSchema = z.instanceof(ConversationTopic);
+
 type DmConversationTitleProps = {
   topic: ConversationTopic;
 };

66-68: Improve loading state handling.

The current implementation returns null during loading, which might cause layout shifts. Consider showing a skeleton loader instead.

-  if (!displayAvatar) return null;
+  if (!displayAvatar) {
+    return (
+      <ConversationTitleDumb
+        title="Loading..."
+        avatarComponent={<SkeletonAvatar size={AvatarSizes.conversationTitle} />}
+      />
+    );
+  }

88-91: Review platform-specific margin values.

The negative margin on Android (-theme.spacing.xxs) could potentially cause clipping issues. Consider using positive margins consistently across platforms and adjusting the container padding instead.

 const $avatar: ThemedStyle<ImageStyle> = (theme) => ({
-  marginRight: Platform.OS === "android" ? theme.spacing.lg : theme.spacing.xxs,
-  marginLeft: Platform.OS === "ios" ? theme.spacing.zero : -theme.spacing.xxs,
+  marginRight: theme.spacing.sm,
+  marginLeft: theme.spacing.zero,
 });

44-86: Consider implementing error boundaries and retry logic.

The component makes multiple API calls through hooks but lacks error boundaries and retry mechanisms. Consider:

  1. Implementing a higher-order error boundary component
  2. Adding retry logic for failed data fetches
  3. Implementing proper error states in the UI

This will improve the reliability and user experience of the conversation title component.

features/conversations/components/GroupConversationTitle.tsx (4)

29-29: Remove commented-out code.

The commented line referencing textInputRef should either be implemented or removed to maintain clean code.

-    // textInputRef?.current?.blur();

27-36: Consider moving useUserInteraction to a separate file.

The hook is well-implemented but should be moved to a separate file (e.g., hooks/useUserInteraction.ts) to improve modularity and reusability.


42-49: Remove redundant non-null assertions on topic.

The topic parameter is already validated through the props type definition, making these non-null assertions unnecessary.

-      topic!
+      topic
-      useGroupPhotoQuery(currentAccount, topic!);
+      useGroupPhotoQuery(currentAccount, topic);

42-50: Consider implementing data prefetching or combining queries.

The component makes multiple separate queries (groupName, groupPhoto, memberData) which could impact performance. Consider:

  1. Implementing data prefetching
  2. Combining these queries into a single hook for better performance
components/Conversation/GroupConversationTitle.tsx (2)

27-36: Remove or implement the commented code

There's a commented line referencing textInputRef?.current?.blur(). This should either be implemented or removed if no longer needed.

 const useUserInteraction = ({ navigation, topic }: UseUserInteractionProps) => {
   const onPress = useCallback(() => {
-    // textInputRef?.current?.blur();
     navigation.push("Group", { topic });
   }, [navigation, topic]);

38-89: Consider splitting data fetching logic into a separate hook

The component currently handles multiple responsibilities. Consider extracting the data fetching logic into a custom hook like useGroupConversationData to improve maintainability and reusability.

Example structure:

const useGroupConversationData = (currentAccount: Account, topic: ConversationTopic) => {
  const { data: groupName, isLoading: groupNameLoading } = useGroupNameQuery(
    currentAccount,
    topic
  );
  const { data: groupPhoto, isLoading: groupPhotoLoading } = useGroupPhotoQuery(
    currentAccount,
    topic
  );
  const { data: memberData } = useGroupMembersAvatarData({ topic });

  return {
    groupName,
    groupPhoto,
    memberData,
    isLoading: groupNameLoading || groupPhotoLoading
  };
};
components/V3DMListItem.tsx (7)

Line range hint 22-26: Consider using a more specific type for timestamp

The timestamp in UseDisplayInfoProps could benefit from a more specific type definition to better represent its Unix timestamp nature.

 type UseDisplayInfoProps = {
-  timestamp: number;
+  timestamp: number; // Unix timestamp in nanoseconds
   isUnread: boolean;
 };

73-74: Consider handling invalid timestamp values

The nullish coalescing operator handles undefined/null cases, but you might want to validate the timestamp value to ensure it's a valid number.

- const timestamp = conversation?.lastMessage?.sentNs ?? 0;
+ const timestamp = Number.isFinite(conversation?.lastMessage?.sentNs) 
+   ? conversation.lastMessage.sentNs 
+   : 0;

88-89: Fix typo in variable name

There's a typo in the variable name "preffered" which should be "preferred".

- const prefferedName = usePreferredInboxName(peer);
+ const preferredName = usePreferredInboxName(peer);

Line range hint 102-106: Remove or implement commented code

There's a commented out openConversation() call. Either implement the functionality or remove the comment if it's no longer needed.


Line range hint 107-146: Add error handling for delete operations

The delete operations perform several async operations without proper error handling. Consider wrapping these in try-catch blocks to handle potential failures gracefully.

 const handleDelete = useCallback(() => {
   const options = [
     translate("delete"),
     translate("delete_and_block"),
     translate("cancel"),
   ];
   const title = `${translate("delete_chat_with")} ${preferredName}?`;
   const actions = [
-    () => {
+    async () => {
+      try {
         saveTopicsData(currentAccount, {
           [topic]: {
             status: "deleted",
             timestamp: new Date().getTime(),
           },
         }),
         setTopicsData({
           [topic]: {
             status: "deleted",
             timestamp: new Date().getTime(),
           },
         });
+      } catch (error) {
+        // Handle error appropriately
+        console.error('Failed to delete conversation:', error);
+      }
     },
     async () => {
+      try {
         saveTopicsData(currentAccount, {
           [topic]: { status: "deleted" },
         });
         setTopicsData({
           [topic]: {
             status: "deleted",
             timestamp: new Date().getTime(),
           },
         });
         await conversation.updateConsent("denied");
         const peerInboxId = await conversation.peerInboxId();
         await consentToInboxIdsOnProtocolByAccount({
           account: currentAccount,
           inboxIds: [peerInboxId],
           consent: "deny",
         });
+      } catch (error) {
+        // Handle error appropriately
+        console.error('Failed to delete and block conversation:', error);
+      }
     },
   ];

Line range hint 252-269: Fix inconsistency in unread status handling

The isUnread prop is hardcoded to false while there's an isUnread state being tracked. This might cause incorrect rendering of the unread status.

       subtitle={`${timeToShow} ⋅ ${messageText}`}
-      isUnread={false}
+      isUnread={isUnread}
       contextMenuComponent={contextMenuComponent}
       rightIsDestructive={isBlockedChatView}

Line range hint 254-254: Remove or implement commented prop

The onRightActionPress prop is commented out. Either implement it or remove it if it's no longer needed.

components/V3GroupConversationListItem.tsx (2)

Line range hint 83-146: TODO comments need to be addressed in handleDelete function.

The implementation looks complete despite the TODO comment. Consider removing the TODO comment if the implementation is finished.

Would you like me to help remove these TODO comments or create an issue to track any remaining tasks?


Line range hint 147-189: TODO comments need to be addressed in handleRestore function.

Similar to handleDelete, the implementation appears complete despite the TODO comment.

Would you like me to help remove these TODO comments or create an issue to track any remaining tasks?

features/conversations/components/V3Conversation.tsx (3)

105-105: Address the TODO comment to update values

There's a TODO comment indicating that values need to be updated (line 105~). Please ensure these values are properly set before merging.

Do you need assistance in updating these values?


238-238: Implement the DM placeholder component

There's a TODO comment to add a DM placeholder (line 238~). Implementing this will enhance the user experience for direct messages.

Would you like help in creating the DM placeholder component?


287-287: Pass frameTextInputFocused state to ChatDumb

Currently, frameTextInputFocused is hardcoded to false when passed to ChatDumb (line 287~). If the intention is to reflect the component's state, consider passing the frameTextInputFocused state variable.

- frameTextInputFocused={false}
+ frameTextInputFocused={frameTextInputFocused}
utils/xmtpRN/conversations.ts (2)

193-194: Clarify log message to indicate 'peer' instead of 'topic'

When a conversation is not found in getConversationByPeer, the log message could be more precise. It currently states "Conversation ${peer} not found", which may be ambiguous. Consider clarifying that it's the conversation with the specified peer that was not found.

Proposed change:

        logger.debug(
-          `[XMTPRN Conversations] Conversation ${peer} not found, syncing conversations`
+          `[XMTPRN Conversations] Conversation with peer ${peer} not found, syncing conversations`
        );

177-225: Refactor duplicate code in conversation retrieval functions

The functions getConversationByPeer and getConversationByTopic contain similar logic for retrieving and syncing conversations. Consider refactoring the common logic into a shared helper function to reduce code duplication and improve maintainability.

For example, you could create a helper function like findConversationWithSync:

async function findConversationWithSync({
  findConversationFn,
  client,
  identifier,
  includeSync,
  errorMessage,
}: {
  findConversationFn: () => Promise<ConversationWithCodecsType | undefined>;
  client: ConverseXmtpClientType;
  identifier: string;
  includeSync: boolean;
  errorMessage: string;
}) {
  logger.debug(`[XMTPRN Conversations] Getting conversation: ${identifier}`);
  const start = new Date().getTime();
  let conversation = await findConversationFn();
  if (!conversation) {
    logger.debug(
      `[XMTPRN Conversations] Conversation ${identifier} not found, syncing conversations`
    );
    const syncStart = new Date().getTime();
    await client.conversations.sync();
    const syncEnd = new Date().getTime();
    logger.debug(
      `[XMTPRN Conversations] Synced conversations in ${
        (syncEnd - syncStart) / 1000
      } sec`
    );
    conversation = await findConversationFn();
  }
  if (!conversation) {
    throw new Error(errorMessage);
  }
  if (includeSync) {
    const syncStart = new Date().getTime();
    await conversation.sync();
    const syncEnd = new Date().getTime();
    logger.debug(
      `[XMTPRN Conversations] Synced conversation in ${
        (syncEnd - syncStart) / 1000
      } sec`
    );
  }
  const end = new Date().getTime();
  logger.debug(
    `[XMTPRN Conversations] Got conversation in ${(end - start) / 1000} sec`
  );
  return conversation;
}

Then refactor getConversationByPeer:

 export const getConversationByPeer = async ({
   client,
   peer,
   includeSync = false,
 }: GetConversationByPeerParams) => {
-  // Existing implementation
+  return findConversationWithSync({
+    findConversationFn: () => client.conversations.findDmByAddress(peer),
+    client,
+    identifier: peer,
+    includeSync,
+    errorMessage: `Conversation with peer ${peer} not found`,
+  });
 };

And refactor getConversationByTopic similarly.

Also applies to: 233-240

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4c10269 and 6489942.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (24)
  • components/Conversation/ConversationTitleDumb.tsx (0 hunks)
  • components/Conversation/GroupConversationTitle.tsx (1 hunks)
  • components/Conversation/V3Conversation.tsx (6 hunks)
  • components/Conversation/V3ConversationHeader.tsx (0 hunks)
  • components/Search/NavigationChatButton.tsx (2 hunks)
  • components/V3DMListItem.tsx (1 hunks)
  • components/V3GroupConversationListItem.tsx (1 hunks)
  • features/conversation-list/hooks/useMessageIsUnread.ts (1 hunks)
  • features/conversations/components/ConversationTitleDumb.tsx (1 hunks)
  • features/conversations/components/DmConversationTitle.tsx (1 hunks)
  • features/conversations/components/GroupConversationTitle.tsx (1 hunks)
  • features/conversations/components/V3Conversation.tsx (1 hunks)
  • features/conversations/components/V3ConversationFromPeer.tsx (1 hunks)
  • features/conversations/hooks/useConversationTitleLongPress.ts (1 hunks)
  • features/conversations/hooks/useGroupMembersAvatarData.ts (1 hunks)
  • ios/Podfile (1 hunks)
  • package.json (1 hunks)
  • queries/QueryKeys.ts (2 hunks)
  • queries/useConversationQuery.ts (2 hunks)
  • queries/useDmPeerAddressQuery.ts (1 hunks)
  • screens/Conversation.tsx (1 hunks)
  • screens/Navigation/ConversationNav.tsx (2 hunks)
  • utils/xmtpRN/api.ts (2 hunks)
  • utils/xmtpRN/conversations.ts (3 hunks)
💤 Files with no reviewable changes (2)
  • components/Conversation/ConversationTitleDumb.tsx
  • components/Conversation/V3ConversationHeader.tsx
✅ Files skipped from review due to trivial changes (1)
  • utils/xmtpRN/api.ts
🔇 Additional comments (32)
features/conversations/hooks/useConversationTitleLongPress.ts (2)

1-3: LGTM! Imports are well-organized and necessary.

The imports follow good practices with a clear separation between internal components, external packages, and React hooks.


5-16: Verify the hook's integration with conversation components.

The hook seems to be used by conversation title components. Let's verify its integration to ensure consistent usage.

✅ Verification successful

Let me check the implementation in the conversation title components to ensure consistent usage.


Hook integration is consistent across conversation components

The useConversationTitleLongPress hook is properly integrated and consistently used in both DM and Group conversation title components. All components:

  • Import the hook correctly
  • Pass the required topic parameter
  • Use the returned onLongPress handler in the same way
  • Only activate the clipboard functionality when debug mode is enabled
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how the hook is being used in conversation components

# Look for usage of useConversationTitleLongPress
rg -l "useConversationTitleLongPress"

# Check for other clipboard-related operations in conversation components
rg -A 3 "Clipboard\." "features/conversations/"

# Look for other debug-related hooks in conversation components
ast-grep --pattern 'useDebugEnabled'

Length of output: 1208


Script:

#!/bin/bash
# Check implementation in conversation title components
rg -A 10 "useConversationTitleLongPress" "features/conversations/components/GroupConversationTitle.tsx" "features/conversations/components/DmConversationTitle.tsx" "components/Conversation/GroupConversationTitle.tsx"

Length of output: 6443

queries/useDmPeerAddressQuery.ts (2)

1-4: LGTM! Well-structured imports.

The imports are clean, properly typed, and follow good practices.


6-15: Consider enhancing error handling and verify caching strategy.

The hook implementation follows React Query best practices and is well-typed. However, consider two improvements:

  1. Add explicit error handling for failed peer address lookups
  2. Verify if cache-only strategy meets real-time messaging requirements

Consider adding error handling like this:

 export const useDmPeerAddressQuery = (
   account: string,
   topic: ConversationTopic
 ) => {
   return useQuery({
     queryKey: ["dmPeerAddress", account, topic],
     queryFn: () => getPeerAddressFromTopic(account, topic),
+    retry: false,
+    onError: (error) => {
+      console.error('Failed to fetch peer address:', error);
+    },
     ...cacheOnlyQueryOptions,
   });
 };
✅ Verification successful

Cache-only strategy is appropriate, but error handling should be added

The cache-only configuration is consistently used across the codebase for XMTP-related queries (useConversationMessages, V3Message) and is appropriate for this use case as it prevents unnecessary refetches of peer addresses which are unlikely to change frequently.

However, error handling should be added since getPeerAddressFromTopic involves async operations that could fail (it calls getConversationByTopicByAccount and potentially getPeerAddressDm). The suggested error handling in the review comment is valid.

  • Add error handling to useDmPeerAddressQuery as suggested in the review comment
  • Keep the existing cache-only strategy as it's consistent with the codebase pattern
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of getPeerAddressFromTopic and cache strategy

# Check if there are other instances of real-time XMTP queries
echo "Checking for other XMTP query implementations..."
rg -l "useQuery.*xmtp" --type typescript

# Look for error handling patterns in other query hooks
echo "Checking error handling patterns in other query hooks..."
rg "useQuery.*error" --type typescript

# Check cache configuration patterns
echo "Checking cache configuration patterns..."
rg "cacheOnlyQueryOptions" --type typescript -A 5

Length of output: 559


Script:

#!/bin/bash
# Let's try without typescript file type specification

# Check if there are other instances of real-time XMTP queries
echo "Checking for other XMTP query implementations..."
rg -l "useQuery.*xmtp"

# Look for error handling patterns in other query hooks
echo "Checking error handling patterns in other query hooks..."
rg "useQuery.*error"

# Check cache configuration patterns
echo "Checking cache configuration patterns..."
rg "cacheOnlyQueryOptions" -A 5

# Check the implementation of getPeerAddressFromTopic
echo "Checking getPeerAddressFromTopic implementation..."
rg "getPeerAddressFromTopic" -A 10

# Look for any existing error handling in query hooks
echo "Checking existing error handling in query hooks..."
rg "onError.*useQuery"

Length of output: 4889

features/conversation-list/hooks/useMessageIsUnread.ts (2)

2-2: LGTM! Clean import organization

The imports are well-organized and properly scoped to the requirements of the refactored implementation.

Also applies to: 7-8


25-25: Consider test implications of store dependency

While moving topicsData to the store reduces prop drilling, it introduces an implicit store dependency. Ensure that tests are updated to mock the store appropriately.

Let's check for existing tests that might need updates:

screens/Conversation.tsx (2)

1-1: LGTM: Import changes align with feature organization

The relocation of conversation components to the features directory improves code organization and modularity.

Also applies to: 7-8


11-13: Verify removal of navigation prop impact

The navigation prop has been removed from the component parameters. While this aligns with the component refactoring, we should verify that child components don't require navigation functionality.

✅ Verification successful

Navigation prop removal is safe

The removal of the navigation prop from component parameters is safe because both V3Conversation components (in features/conversations/components/ and components/Conversation/) use the useNavigation hook internally to access navigation functionality. This is actually a better practice as it follows React Navigation's recommended approach of using hooks for navigation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for navigation usage in V3Conversation and V3ConversationFromPeer
ast-grep --pattern 'navigation.$_' ../features/conversations/components/V3Conversation.tsx ../features/conversations/components/V3ConversationFromPeer.tsx

# Search for useNavigation hook usage
rg -l "useNavigation" ../features/conversations/components/

Length of output: 508


Script:

#!/bin/bash
# First, let's find the correct paths using fd
fd -t f "V3Conversation.tsx|V3ConversationFromPeer.tsx" 

# Search for navigation usage in the entire codebase
ast-grep --pattern 'navigation.$_'

# Search for useNavigation hook usage
rg -l "useNavigation"

Length of output: 6385

features/conversations/components/ConversationTitleDumb.tsx (2)

1-56: Verify integration with other conversation components

The component implementation looks solid, but let's verify its integration with the new group conversation features mentioned in the PR description.

✅ Verification successful

Integration verified successfully

The component is properly integrated within the conversation feature:

  • Used as the base component by both DmConversationTitle and GroupConversationTitle
  • Correctly imported and implemented in both the new features directory and legacy components
  • No duplicate implementations found
  • Properly handles both group and direct message conversation scenarios
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check how this component is being used in other files
rg -t typescript -t javascript "ConversationTitleDumb|GroupConversationTitle" --glob "features/**"

# Verify no duplicate conversation title implementations exist
ast-grep --pattern 'export function $TITLE_COMPONENT({ $$$props }) { $$$body }' | rg -i 'conversation.*title'

Length of output: 247


Script:

#!/bin/bash
# Let's try a different approach to find usage and implementations
# Search for imports and usage of ConversationTitleDumb
rg "ConversationTitleDumb" -A 3 -B 3

# Search for any conversation title related components
fd -e tsx -e ts -e js . | xargs rg -l "conversation.*title" -i

# Look for similar components in the features directory
fd . features -e tsx -e ts | xargs rg -l "Title.*Conversation|Conversation.*Title" -i

Length of output: 6268


44-56: Improve style configuration and maintainability

The styles contain magic numbers and platform-specific adjustments that could be better organized.

Consider these improvements:

  1. Move the hardcoded Android offset to the theme:
-  left: Platform.OS === "android" ? -36 : spacing.zero,
+  left: Platform.OS === "android" ? spacing.androidTitleOffset : spacing.zero,
  1. Consider extracting platform-specific font sizes to a constant or theme:
-  fontSize: Platform.OS === "ios" ? 16 * getTitleFontScale() : 18,
+  fontSize: Platform.select({
+    ios: typography.sizes.medium * getTitleFontScale(),
+    android: typography.sizes.large,
+  }),

Let's check if the hardcoded values are used elsewhere:

features/conversations/components/V3ConversationFromPeer.tsx (3)

1-9: LGTM! Imports are well-organized and necessary.

The imports are logically grouped and all appear to be used within the component.


52-57: LGTM! Styles are well-structured and properly typed.

The container styles are appropriate for centering the loading indicator and follow the app's theming system.


16-21: Clarify the performance concerns in the documentation.

The comment mentions potential performance issues but doesn't specify what they are or how they might be addressed. Consider:

  1. What specific performance issues need attention?
  2. What metrics indicate a performance problem?
  3. What approaches could improve performance?

Let's check for potential performance bottlenecks in the component's usage:

features/conversations/hooks/useGroupMembersAvatarData.ts (2)

1-7: LGTM! Well-organized imports

The imports are logically organized and all are actively used within the hook implementation.


38-56: Verify array index correlation safety

The code assumes perfect correlation between data and memberAddresses arrays. Consider adding validation to prevent potential index mismatches.

   const memberData = useMemo(() => {
+    if (data.length !== memberAddresses.length) {
+      console.warn('Data length mismatch in useGroupMembersAvatarData');
+      return [];
+    }
     return data.map(({ data: socials }, index) =>
       socials
         ? {
             address: memberAddresses[index],
             uri: getPreferredAvatar(socials),
             name: getPreferredName(socials, memberAddresses[index]),
           }
         : {
             address: memberAddresses[index],
             uri: undefined,
             name: memberAddresses[index],
           }
     );
   }, [data, memberAddresses]);
✅ Verification successful

Let me check the implementation of useProfileSocialsQueries to understand how it maintains array correlation.


Let me check the implementation of profileSocialesQueryConfig to understand how the query results are structured.


Based on the implementation details found, I can now provide the final response:

Array index correlation is safely maintained

The implementation of useProfileSocialsQueries uses React Query's useQueries hook with a direct mapping between input addresses and query configurations, which guarantees index correlation. The array order is preserved through the mapping operation, making the current implementation safe.

  • The useProfileSocialsQueries maps each address to its corresponding query config using peerAddresses.map
  • React Query's useQueries maintains the order of results matching the input array
  • The subsequent mapping in useGroupMembersAvatarData preserves this correlation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of useProfilesSocials to understand if it maintains array order
rg "useProfilesSocials" -A 5 -B 5

Length of output: 3399


Script:

#!/bin/bash
# Search for useProfileSocialsQueries implementation
rg "useProfileSocialsQueries" -A 10 -B 2

# Also search for any tests that might reveal the behavior
rg "useProfileSocialsQueries.*test" --glob "*.{test,spec}.*"

Length of output: 1541


Script:

#!/bin/bash
# Search for profileSocialesQueryConfig implementation
rg "profileSocialesQueryConfig" -A 10 -B 2

# Also search for useQueries usage to understand array correlation
ast-grep --pattern 'useQueries({
  queries: $$$
})'

Length of output: 3460

screens/Navigation/ConversationNav.tsx (1)

Line range hint 44-67: Verify integration with GroupConversationTitle component

The empty navigation title suggests that the conversation title rendering has been moved to a different component. Let's verify this integration.

✅ Verification successful

Based on the code from V3Conversation.tsx, I can now generate the final response as we have sufficient evidence to verify the integration:

Empty navigation title is correct as GroupConversationTitle is properly integrated

The empty title in ConversationNav is intentional as the title is being set dynamically through navigation.setOptions in V3Conversation component:

  • GroupConversationTitle is rendered as headerTitle for group conversations
  • DmConversationTitle is rendered for direct message conversations
  • The title component selection is properly handled based on conversation version
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check how GroupConversationTitle is being used
ast-grep --pattern 'GroupConversationTitle'

# Check if there are any other navigation configurations that might conflict
rg -A 5 "headerTitle|title:" --type tsx

Length of output: 816


Script:

#!/bin/bash
# Search for navigation title configurations with proper file type
rg -t ts -t tsx -A 5 'headerTitle|title:' 

# Look for GroupConversationTitle implementation
ast-grep --pattern 'const GroupConversationTitle = memo($$$)'

# Check for other navigation configurations in ConversationNav
rg -t ts -t tsx -A 10 'navigationOptions.*=.*{' screens/Navigation/

Length of output: 9242


Script:

#!/bin/bash
# Check where GroupConversationTitle is rendered in navigation
rg -t ts -t tsx -B 5 -A 5 'headerTitle.*GroupConversationTitle'

# Look for navigation title setup in ConversationNav with fixed regex
rg -t ts -t tsx 'navigationOptions\s*=' screens/Navigation/

# Check V3Conversation component to see how GroupConversationTitle is integrated
cat components/Conversation/V3Conversation.tsx

Length of output: 8187

components/Search/NavigationChatButton.tsx (1)

44-44: Verify the skipLoading parameter handling in the Conversation screen.

The addition of skipLoading: true looks good, but let's verify how this parameter is handled in the target screen.

✅ Verification successful

The skipLoading parameter is properly integrated and used

The parameter is correctly:

  • Defined in the navigation params type (ConversationNavParams)
  • Passed through to the Conversation screen
  • Used with a fallback value (route.params.skipLoading ?? true)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of skipLoading parameter in the Conversation screen
# and related components to ensure proper handling.

# Check for skipLoading parameter usage
rg -A 5 "skipLoading" "screens/"

# Check the ConversationNavParams type definition
ast-grep --pattern 'type ConversationNavParams = {
  $$$
  skipLoading$_
  $$$
}'

Length of output: 707


Script:

#!/bin/bash
# Let's verify the component that actually uses the skipLoading prop
ast-grep --pattern 'function $_(props: {$$$skipLoading$$$}) {
  $$$
}'

# Also check for any other components passing this prop
rg "skipLoading={" -A 2

Length of output: 188


Script:

#!/bin/bash
# Let's verify the component that actually uses the skipLoading prop with proper escaping
rg 'skipLoading=\{' -A 2

# Also check for any skipLoading prop type definitions
ast-grep --pattern 'interface $_ {
  $$$
  skipLoading$_
  $$$
}'

Length of output: 240

queries/useConversationQuery.ts (2)

10-19: LGTM! Import statements are well-organized.

The new imports are properly structured and necessary for the added functionality.


73-73: Verify the impact of optional topic parameter.

The topic parameter has been made optional, which could affect dependent components. Ensure all callers handle the possibility of undefined topics correctly.

Also applies to: 80-80, 93-93

✅ Verification successful

Let me gather more context about the topic parameter and its handling.


Optional topic parameter is properly handled throughout the codebase

The codebase shows comprehensive handling of the optional topic parameter:

  • All query hooks have proper null checks with enabled: !!topic conditions
  • Components and mutations consistently check for topic existence before operations
  • The two direct usages in V3Conversation components use non-null assertion (topic!) indicating they're used in contexts where topic is guaranteed to exist
  • Multiple mutation handlers include explicit guards like if (!topic) { return; }
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all usages of useConversationScreenQuery to verify they handle undefined topics
ast-grep --pattern 'useConversationScreenQuery($$$)' 

# Also check for direct topic access that might not handle undefined
rg -A 2 'useConversationScreenQuery.*\)' 

Length of output: 1518


Script:

#!/bin/bash
# Check how topic is used in the useConversationQuery file
rg -A 3 "topic[?:]" queries/useConversationQuery.ts

# Check the type definition and usage of ConversationTopic
ast-grep --pattern 'type ConversationTopic = $$$'

# Check if there's any null checks for topic in the components
rg -A 3 "(!topic|topic\s*===\s*(undefined|null)|typeof\s+topic|topic\?)" 

Length of output: 12050

queries/QueryKeys.ts (2)

7-7: LGTM! Enum addition is well-placed and consistent.

The new CONVERSATION_WITH_PEER enum value follows the established naming convention and is appropriately placed in the Conversations section.


48-52: LGTM! Query key function implementation is consistent.

The conversationWithPeerQueryKey function follows the established patterns:

  • Consistent parameter types
  • Account case normalization
  • Array structure matching other query keys

Let's verify the usage of this new query key:

✅ Verification successful

Let me verify the implementation and usage by checking the actual implementation of the query hook.


Let me check the actual implementation of the query hook in useConversationQuery.ts to ensure proper parameter handling.


Query key implementation and usage are correctly implemented and integrated

The verification confirms that:

  • The query key is properly used in useConversationWithPeerQuery hook
  • The hook correctly handles the parameters (account and peer)
  • The component V3ConversationFromPeer uses the hook with correct parameters
  • The implementation follows the same pattern as other conversation queries in the file
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation and usage of the new query key
# Expected: Find references to the new query key in query hooks/components

# Search for direct usage of CONVERSATION_WITH_PEER
rg "CONVERSATION_WITH_PEER" --type ts

# Search for usage of conversationWithPeerQueryKey function
rg "conversationWithPeerQueryKey" --type ts

# Search for potential query hook implementation
rg "useConversationWithPeer" --type ts

Length of output: 861


Script:

#!/bin/bash
# Check the implementation of useConversationWithPeerQuery
ast-grep --pattern 'export const useConversationWithPeerQuery = ($_) => {
  $$$
}'

# Check the component usage for proper parameters
ast-grep --pattern 'useConversationWithPeerQuery($$$)'

Length of output: 753


Script:

#!/bin/bash
# Get the content of useConversationQuery.ts
cat queries/useConversationQuery.ts

Length of output: 2844

features/conversations/components/GroupConversationTitle.tsx (2)

92-95: LGTM! Well-structured platform-specific styling.

The styling implementation properly handles platform differences and follows the theme system.


1-90: Verify the impact of replacing ConversationTitleDumb.

According to the AI summary, this component replaces ConversationTitleDumb but still imports and uses it. Let's verify the migration status and potential breaking changes.

components/Conversation/GroupConversationTitle.tsx (2)

91-94: LGTM! Well-structured platform-specific styling

The styling implementation is clean, type-safe, and properly handles platform differences using theme values.


40-40: Consider handling the case where currentAccount is undefined

The non-null assertion (!) on currentAccount could lead to runtime errors if currentAccount is undefined.

-    const currentAccount = useCurrentAccount()!;
+    const currentAccount = useCurrentAccount();
+    if (!currentAccount) return null;
✅ Verification successful

Non-null assertion is safe here as the component is rendered only when authenticated

Looking at the codebase usage patterns, the non-null assertion on useCurrentAccount() in GroupConversationTitle.tsx is actually safe because:

  1. The hook implementation in data/store/accountsStore.ts returns undefined only when the account is a temporary one
  2. The component GroupConversationTitle is rendered within conversation views that are only accessible when authenticated
  3. The hook is consistently used with non-null assertion across similar authenticated contexts in the codebase (e.g., in V3Conversation.tsx, DmConversationTitle.tsx, etc.)
  4. The value is immediately used to query group name data, which would fail anyway if there was no authenticated account
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check how currentAccount is handled across the codebase
rg "useCurrentAccount" -A 5 --type typescript

Length of output: 80


Script:

#!/bin/bash
# Check how currentAccount is handled across the codebase, using ts extension
rg "useCurrentAccount" -A 5 --type ts,tsx

Length of output: 72


Script:

#!/bin/bash
# Search for useCurrentAccount usage patterns in all files
rg "useCurrentAccount" -A 5

# Also find the hook definition to understand its implementation
ast-grep --pattern 'const useCurrentAccount = $_'

Length of output: 71611

ios/Podfile (1)

21-21: Verify XMTP version compatibility across the project

The XMTP version has been bumped from 3.0.4 to 3.0.6. This change affects both the main app and notification extension targets.

Let's verify the version compatibility:

Consider adding version constraints in a central configuration file to ensure coordinated updates between package.json and Podfile in the future.

✅ Verification successful

✓ XMTP version update is properly synchronized

The version bump from 3.0.4 to 3.0.6 is correctly synchronized between package.json and Podfile:

  • package.json: "@xmtp/react-native-sdk": "^3.0.6"
  • Podfile: $xmtpVersion = '3.0.6'

The version is consistently applied to both the main app and notification extension targets through the shared variable in the Podfile.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if package.json has matching XMTP SDK version
rg -A 5 '"@xmtp/react-native-sdk"' package.json

# Check XMTP changelog for breaking changes
curl -s https://raw.githubusercontent.com/xmtp/xmtp-react-native/main/CHANGELOG.md | head -n 20

# Check for any security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: COCOAPODS, package: "XMTP") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 1145


Script:

#!/bin/bash
# Let's try a different approach to check for version compatibility and changes

# Check package.json version
echo "Package.json XMTP SDK version:"
rg '"@xmtp/react-native-sdk"' package.json

# Check Podfile version
echo -e "\nPodfile XMTP version:"
rg '\$xmtpVersion' ios/Podfile

# Look for any other XMTP version references
echo -e "\nOther XMTP version references:"
rg -g '!package-lock.json' -g '!yarn.lock' 'xmtp.*3\.0\.' .

# Check if there are any migration guides or breaking changes mentioned
echo -e "\nChecking for migration or breaking change mentions:"
rg -i 'breaking|migration|upgrade' -g '!{package-lock.json,yarn.lock}' -A 3 .

Length of output: 7295

package.json (1)

83-83: LGTM on SDK version bump, verify changelog for breaking changes.

The update of @xmtp/react-native-sdk from ^3.0.0 to ^3.0.6 is a patch version increment, which should only contain backwards-compatible bug fixes. This aligns with the PR's objectives for updating conversation components.

Let's verify the changelog and any potential breaking changes:

✅ Verification successful

SDK version bump is safe to proceed

The version update from 3.0.0 to 3.0.6 contains only a bug fix related to message processing, with no breaking changes. The changelog shows a single patch that improves message handling by properly awaiting processMessage and handling object types. No breaking changes were found in the repository's issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the changelog and git history for the SDK version update

# Fetch the latest release information for @xmtp/react-native-sdk
curl -s "https://api.github.com/repos/xmtp/xmtp-react-native/releases" | jq -r '.[] | select(.tag_name == "v3.0.6") | .body'

# Check if there are any issues or PRs mentioning potential breaking changes
gh api graphql -f query='
{
  search(query: "repo:xmtp/xmtp-react-native is:issue label:breaking-change", type: ISSUE, first: 5) {
    nodes {
      ... on Issue {
        title
        url
        labels(first: 5) {
          nodes {
            name
          }
        }
      }
    }
  }
}'

Length of output: 730

components/V3GroupConversationListItem.tsx (3)

Line range hint 190-367: Well-implemented user interaction patterns!

Great implementation of:

  • Haptic feedback for enhanced mobile experience
  • Clear action sheet options with proper destructive actions
  • Proper cleanup and state updates

Line range hint 368-428: Excellent component implementation!

Great job on:

  • Using memoization for performance optimization
  • Clean separation of concerns
  • Proper component composition with avatar fallbacks
  • Consistent styling approach

48-49: LGTM! State management optimization.

Good optimization by selecting only the required store actions. This reduces unnecessary re-renders.

Let's verify no other store actions are needed:

✅ Verification successful

State management hooks are correctly selected

The verification confirms that setTopicsData and setPinnedConversations are the appropriate store actions needed in this component. The component uses these actions for:

  • Updating conversation data (setTopicsData)
  • Managing pinned conversations (setPinnedConversations)

This matches the pattern used in similar components like V3DMListItem.tsx, which uses the same store actions for identical functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any other useChatStore usages in the component
rg "useChatStore" --type typescript

Length of output: 70


Script:

#!/bin/bash
# Search for useChatStore usages with ripgrep, without file type filter
rg "useChatStore"

# Also search for any other potential store actions being used
rg "setPinnedConversations|setTopicsData"

Length of output: 8082

components/Conversation/V3Conversation.tsx (2)

100-100: Ensure useRouter provides necessary navigation methods

You replaced the navigation prop with const navigation = useRouter();. Verify that the useRouter hook provides all the navigation methods used in this component, such as navigation.setOptions and event listeners, to maintain navigation functionality.

Run the following script to check the usage of navigation methods:

✅ Verification successful

Let me verify the Next.js Router API to ensure these methods are available.


Let me check the custom navigation hook implementation to verify the available methods.


useRouter is correctly implemented with required navigation methods

The custom useRouter hook is a wrapper around React Navigation's useNavigation hook that returns a NativeStackNavigationProp object. The implementation confirms it provides all the necessary methods used in the component, including setOptions and event listeners like addListener for "beforeRemove". The change from prop to hook is safe.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check all usages of `navigation` in `V3Conversation.tsx`.

# Test: Search for instances of `navigation.` in the file.
# Expect: Ensure that `useRouter` provides these methods.

rg 'navigation\.' components/Conversation/V3Conversation.tsx -A 2

Length of output: 267


Script:

#!/bin/bash
# Check imports to determine which router is being used
rg "useRouter" components/Conversation/V3Conversation.tsx -B 2 -A 2

# Check if this is a Next.js or React Navigation project
fd package.json --exec cat {} | grep -E "next|@react-navigation"

Length of output: 1098


Script:

#!/bin/bash
# Check the implementation of custom useNavigation hook
rg -g "useNavigation.{ts,tsx,js,jsx}" "export" -A 20

Length of output: 1806


Script:

#!/bin/bash
# Get more context of the useRouter implementation to see all methods
rg -g "useNavigation.{ts,tsx,js,jsx}" "export function useRouter" -A 50

Length of output: 2501


98-98: Verify all usages of V3Conversation are updated with new props

The V3Conversation component now accepts topic and an optional textPrefill instead of route and navigation. Ensure that all instances where V3Conversation is used have been updated to pass the correct props to prevent runtime errors due to incorrect prop usage.

Run the following script to find all usages of V3Conversation and check their props:

/>
);
}
return <VStack />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding error state handling

The empty VStack fallback provides no feedback to users when route parameters are invalid or missing.

-  return <VStack />;
+  return (
+    <VStack spacing={4} p={4}>
+      <Text>Unable to load conversation</Text>
+      <Text variant="caption">Invalid or missing conversation parameters</Text>
+    </VStack>
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return <VStack />;
return (
<VStack spacing={4} p={4}>
<Text>Unable to load conversation</Text>
<Text variant="caption">Invalid or missing conversation parameters</Text>
</VStack>
);

Comment on lines +21 to +29
if (route.params?.mainConversationWithPeer) {
return (
<V3ConversationFromPeer
peer={route.params.mainConversationWithPeer}
textPrefill={route.params.text}
skipLoading={route.params.skipLoading ?? true}
/>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Document skipLoading behavior and add peer validation

The skipLoading parameter defaults to true, but its purpose and implications aren't clear. Additionally, peer validation should be added.

+  // Validate peer parameter
+  if (!route.params?.mainConversationWithPeer?.id) {
+    console.warn('Invalid peer provided');
+    return <VStack />;
+  }
   if (route.params?.mainConversationWithPeer) {
     return (
       <V3ConversationFromPeer
         peer={route.params.mainConversationWithPeer}
         textPrefill={route.params.text}
+        // skipLoading: true skips initial loading state for better UX
         skipLoading={route.params.skipLoading ?? true}
       />
     );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (route.params?.mainConversationWithPeer) {
return (
<V3ConversationFromPeer
peer={route.params.mainConversationWithPeer}
textPrefill={route.params.text}
skipLoading={route.params.skipLoading ?? true}
/>
);
}
// Validate peer parameter
if (!route.params?.mainConversationWithPeer?.id) {
console.warn('Invalid peer provided');
return <VStack />;
}
if (route.params?.mainConversationWithPeer) {
return (
<V3ConversationFromPeer
peer={route.params.mainConversationWithPeer}
textPrefill={route.params.text}
// skipLoading: true skips initial loading state for better UX
skipLoading={route.params.skipLoading ?? true}
/>
);
}

Comment on lines +26 to +38
// Add an animation since we are loading certain information and makes it feel faster
<AnimatedHStack style={$container} entering={animations.fadeInUpSlow()}>
<TouchableOpacity
onLongPress={onLongPress}
onPress={onPress}
style={themed($touchableContainer)}
>
{avatarComponent}
<Text style={themed($title)} numberOfLines={1} allowFontScaling={false}>
{title}
</Text>
</TouchableOpacity>
</AnimatedHStack>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility and documentation

The component should include accessibility attributes for better screen reader support.

Apply these improvements:

 <TouchableOpacity
   onLongPress={onLongPress}
   onPress={onPress}
   style={themed($touchableContainer)}
+  accessibilityRole="button"
+  accessibilityLabel={`Conversation ${title}`}
+  accessibilityHint="Double tap to view conversation details"
 >
   {avatarComponent}
   <Text 
     style={themed($title)} 
     numberOfLines={1} 
     allowFontScaling={false}
+    accessibilityRole="header"
   >
     {title}
   </Text>
 </TouchableOpacity>

Also, improve the animation comment to better explain its purpose:

-// Add an animation since we are loading certain information and makes it feel faster
+// Animate the title entry to provide visual feedback while conversation data is loading
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Add an animation since we are loading certain information and makes it feel faster
<AnimatedHStack style={$container} entering={animations.fadeInUpSlow()}>
<TouchableOpacity
onLongPress={onLongPress}
onPress={onPress}
style={themed($touchableContainer)}
>
{avatarComponent}
<Text style={themed($title)} numberOfLines={1} allowFontScaling={false}>
{title}
</Text>
</TouchableOpacity>
</AnimatedHStack>
// Animate the title entry to provide visual feedback while conversation data is loading
<AnimatedHStack style={$container} entering={animations.fadeInUpSlow()}>
<TouchableOpacity
onLongPress={onLongPress}
onPress={onPress}
style={themed($touchableContainer)}
accessibilityRole="button"
accessibilityLabel={`Conversation ${title}`}
accessibilityHint="Double tap to view conversation details"
>
{avatarComponent}
<Text
style={themed($title)}
numberOfLines={1}
allowFontScaling={false}
accessibilityRole="header"
>
{title}
</Text>
</TouchableOpacity>
</AnimatedHStack>

Comment on lines +22 to +50
export const V3ConversationFromPeer = memo(
({ peer, textPrefill, skipLoading }: V3ConversationFromPeerProps) => {
const currentAccount = useCurrentAccount()!;

const { data: conversation, isLoading } = useConversationWithPeerQuery(
currentAccount,
peer,
{
enabled: !skipLoading,
}
);

const { themed } = useAppTheme();
if (isLoading && !skipLoading) {
return (
<VStack style={themed($container)}>
<ActivityIndicator />
</VStack>
);
}
return (
<V3Conversation
peerAddress={peer}
topic={conversation?.topic}
textPrefill={textPrefill}
/>
);
}
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Address potential runtime issues and add error handling.

Several improvements could make the component more robust:

  1. The non-null assertion on currentAccount could lead to runtime errors.
  2. Missing error handling for the query.
  3. The topic prop type should be verified.

Consider applying these changes:

 export const V3ConversationFromPeer = memo(
   ({ peer, textPrefill, skipLoading }: V3ConversationFromPeerProps) => {
-    const currentAccount = useCurrentAccount()!;
+    const currentAccount = useCurrentAccount();
+    if (!currentAccount) {
+      return null; // or an error state component
+    }
 
-    const { data: conversation, isLoading } = useConversationWithPeerQuery(
+    const { data: conversation, isLoading, error } = useConversationWithPeerQuery(
       currentAccount,
       peer,
       {
         enabled: !skipLoading,
       }
     );
 
     const { themed } = useAppTheme();
+    if (error) {
+      return (
+        <VStack style={themed($container)}>
+          <Text>Error loading conversation</Text>
+        </VStack>
+      );
+    }
     if (isLoading && !skipLoading) {

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +16 to +20
const currentAccount = useCurrentAccount()!;
const { data: members, ...query } = useGroupMembersConversationScreenQuery(
currentAccount,
topic
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for undefined currentAccount

The non-null assertion (!) on currentAccount could lead to runtime errors if the account is undefined. Consider adding proper error handling.

-  const currentAccount = useCurrentAccount()!;
+  const currentAccount = useCurrentAccount();
+  if (!currentAccount) {
+    return { data: [], isLoading: false, isError: true, error: new Error('No current account found') };
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const currentAccount = useCurrentAccount()!;
const { data: members, ...query } = useGroupMembersConversationScreenQuery(
currentAccount,
topic
);
const currentAccount = useCurrentAccount();
if (!currentAccount) {
return { data: [], isLoading: false, isError: true, error: new Error('No current account found') };
}
const { data: members, ...query } = useGroupMembersConversationScreenQuery(
currentAccount,
topic
);

Comment on lines +261 to +276
const conversationContextValue = useMemo(
() => ({
topic,
conversation: undefined,
messageToPrefill,
inputRef: textInputRef,
mediaPreviewToPrefill: null,
mediaPreviewRef,
isBlockedPeer: false,
onReadyToFocus,
frameTextInputFocused,
setFrameTextInputFocused,
tagsFetchedOnceForMessage,
}),
[topic, messageToPrefill, onReadyToFocus, frameTextInputFocused]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Pass the actual conversation to ConversationContext.Provider

In conversationContextValue, conversation is set to undefined (line 264~). If child components rely on conversation from the context, this may cause errors.

Consider passing the conversation object obtained from useData:

const conversationContextValue = useMemo(
  () => ({
    topic,
-   conversation: undefined,
+   conversation,
    messageToPrefill,
    inputRef: textInputRef,
    mediaPreviewToPrefill: null,
    mediaPreviewRef,
    isBlockedPeer: false,
    onReadyToFocus,
    frameTextInputFocused,
    setFrameTextInputFocused,
    tagsFetchedOnceForMessage,
  }),
- [topic, messageToPrefill, onReadyToFocus, frameTextInputFocused]
+ [topic, conversation, messageToPrefill, onReadyToFocus, frameTextInputFocused]
);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +220 to +224
`[XMTPRN Conversations] Got conversation by topic in ${
(end - start) / 1000
} sec`
);
return conversation;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct log message to reflect 'peer' instead of 'topic'

The log message in the getConversationByPeer function incorrectly states "Got conversation by topic". It should reflect "Got conversation by peer" to match the function's purpose.

Apply this diff to correct the log message:

      logger.debug(
-        `[XMTPRN Conversations] Got conversation by topic in ${
+        `[XMTPRN Conversations] Got conversation by peer in ${
          (end - start) / 1000
        } sec`
      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`[XMTPRN Conversations] Got conversation by topic in ${
(end - start) / 1000
} sec`
);
return conversation;
`[XMTPRN Conversations] Got conversation by peer in ${
(end - start) / 1000
} sec`
);
return conversation;

Comment on lines +238 to +240
const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
return getConversationByPeer({ client, peer, includeSync });
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for 'client' to prevent potential runtime errors

In getConversationByPeerByAccount, there is no check to ensure that client is not null before proceeding. This could lead to runtime errors if client is undefined.

Apply this diff to add a null check:

      const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
+     if (!client) {
+       throw new Error("Client not found");
+     }
      return getConversationByPeer({ client, peer, includeSync });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
return getConversationByPeer({ client, peer, includeSync });
};
const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
if (!client) {
throw new Error("Client not found");
}
return getConversationByPeer({ client, peer, includeSync });
};

Comment on lines +500 to +505
if (dm.version === ConversationVersion.DM) {
const peerAddress = await getPeerAddressDm(dm);
return peerAddress;
}
throw new Error("Conversation is not a DM");
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure 'peerAddress' is defined before returning

In getPeerAddressFromTopic, the function returns the peerAddress obtained from getPeerAddressDm. If peerAddress is undefined, it could lead to unexpected behavior. Consider handling the case where peerAddress is undefined.

Apply this diff to handle the undefined case:

      if (dm.version === ConversationVersion.DM) {
        const peerAddress = await getPeerAddressDm(dm);
+       if (!peerAddress) {
+         throw new Error("Peer address not found");
+       }
        return peerAddress;
      }
      throw new Error("Conversation is not a DM");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (dm.version === ConversationVersion.DM) {
const peerAddress = await getPeerAddressDm(dm);
return peerAddress;
}
throw new Error("Conversation is not a DM");
};
if (dm.version === ConversationVersion.DM) {
const peerAddress = await getPeerAddressDm(dm);
if (!peerAddress) {
throw new Error("Peer address not found");
}
return peerAddress;
}
throw new Error("Conversation is not a DM");
};

Comment on lines +483 to +489
export const getPeerAddressDm = async (dm: DmWithCodecsType) => {
const peerInboxId = await dm.peerInboxId();
const peerAddress = (await dm.members()).find(
(member) => member.inboxId === peerInboxId
)?.addresses[0];
return peerAddress;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle the case where 'peerAddress' might be undefined

In getPeerAddressDm, if the peer address is not found, the function may return undefined. Ensure that the calling code handles this possibility to prevent runtime errors.

Consider adding a check to handle cases when peerAddress is undefined:

      const peerAddress = (await dm.members()).find(
        (member) => member.inboxId === peerInboxId
      )?.addresses[0];
+     if (!peerAddress) {
+       throw new Error("Peer address not found");
+     }
      return peerAddress;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getPeerAddressDm = async (dm: DmWithCodecsType) => {
const peerInboxId = await dm.peerInboxId();
const peerAddress = (await dm.members()).find(
(member) => member.inboxId === peerInboxId
)?.addresses[0];
return peerAddress;
};
export const getPeerAddressDm = async (dm: DmWithCodecsType) => {
const peerInboxId = await dm.peerInboxId();
const peerAddress = (await dm.members()).find(
(member) => member.inboxId === peerInboxId
)?.addresses[0];
if (!peerAddress) {
throw new Error("Peer address not found");
}
return peerAddress;
};

@alexrisch alexrisch closed this Nov 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant