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

Feature: V3 split towards a V3 Only World #1189

Closed
wants to merge 27 commits into from

Conversation

alexrisch
Copy link
Collaborator

@alexrisch alexrisch commented Nov 13, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a mock implementation for Sentry to facilitate testing without actual service calls.
    • Added functions for managing consent states in conversations, addresses, and inbox IDs.
    • Enhanced notification handling with new methods for processing V3 messages.
    • Implemented a new component for rendering chat interfaces with improved structure and performance.
    • Added a new component for displaying pinned group and direct message conversations.
  • Bug Fixes

    • Removed unused imports and streamlined state management for better performance.
    • Fixed issues with handling group and direct message conversations.
  • Refactor

    • Simplified the codebase by removing deprecated functions and consolidating logic for clarity.
    • Updated components to utilize new hooks and context for state management.
    • Refactored conversation-related components for improved modularity and reusability.
  • Documentation

    • Improved type definitions and component props for better clarity and usability.

@alexrisch alexrisch requested a review from a team as a code owner November 13, 2024 18:58
Copy link
Contributor

github-actions bot commented Nov 13, 2024

Performance Comparison Report

  • Current: d03d023 - 2024-11-20 17:20:22Z
  • Baseline: release/3.0.0 (fb58aa4) - 2024-11-20 17:19:15Z

Significant Changes To Duration

There are no entries

Meaningless Changes To Duration

Show entries
Name Type Duration Count
Avatar Image 10 runs render 1.2 ms → 1.2 ms 1 → 1
Avatar Image 50 runs render 1.2 ms → 1.1 ms (-0.1 ms, -6.9%) 1 → 1
Empty Avatar 10 runs render 1.0 ms → 1.1 ms (+0.1 ms, +10.0%) 1 → 1
Empty Avatar 50 runs render 0.8 ms → 0.8 ms 1 → 1
Text Component with color prop - 10 runs render 0.4 ms → 0.1 ms (-0.3 ms, -75.0%) 🟢 1 → 1
Text Component with default props - 10 runs render 0.2 ms → 0.0 ms (-0.2 ms, -100.0%) 🟢 1 → 1
Text Component with translation key - 10 runs render 0.1 ms → 0.5 ms (+0.4 ms, +400.0%) 🔴 1 → 1
Text Component with weight and size - 10 runs render 0.2 ms → 0.5 ms (+0.3 ms, +150.0%) 🔴 1 → 1
Show details
Name Type Duration Count
Avatar Image 10 runs render Baseline
Mean: 1.2 ms
Stdev: 0.4 ms (35.1%)
Runs: 1 1 1 1 1 1 1 2 2 1
Warmup runs: 2

Current
Mean: 1.2 ms
Stdev: 0.4 ms (35.1%)
Runs: 2 1 1 1 1 1 1 1 2 1
Warmup runs: 2
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Avatar Image 50 runs render Baseline
Mean: 1.2 ms
Stdev: 0.4 ms (36.4%)
Runs: 1 1 1 1 2 1 2 1 1 2 1 1 1 0 1 1 2 1 1 2 1 1 1 1 1 1 2 1 1 1 2 1 1 2 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1
Warmup runs: 1

Current
Mean: 1.1 ms
Stdev: 0.3 ms (25.4%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 10 runs render Baseline
Mean: 1.0 ms
Stdev: 0.0 ms (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Warmup runs: 2

Current
Mean: 1.1 ms
Stdev: 0.3 ms (28.7%)
Runs: 2 1 1 1 1 1 1 1 1 1
Warmup runs: 8
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 50 runs render Baseline
Mean: 0.8 ms
Stdev: 0.4 ms (53.6%)
Runs: 1 1 1 1 1 1 0 1 0 1 1 0 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 1 1 1 1 0 1 1 0 0
Warmup runs: 1

Current
Mean: 0.8 ms
Stdev: 0.4 ms (53.6%)
Runs: 1 1 0 0 0 1 0 1 0 1 1 1 1 0 1 1 0 1 1 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with color prop - 10 runs render Baseline
Mean: 0.4 ms
Stdev: 0.5 ms (129.1%)
Runs: 1 0 0 1 0 1 0 0 1 0
Warmup runs: 0

Current
Mean: 0.1 ms
Stdev: 0.3 ms (316.2%)
Runs: 0 0 0 0 0 1 0 0 0 0
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with default props - 10 runs render Baseline
Mean: 0.2 ms
Stdev: 0.4 ms (210.8%)
Runs: 1 0 0 0 0 1 0 0 0 0
Warmup runs: 0

Current
Mean: 0.0 ms
Stdev: 0.0 ms (NaN%)
Runs: 0 0 0 0 0 0 0 0 0 0
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with translation key - 10 runs render Baseline
Mean: 0.1 ms
Stdev: 0.3 ms (316.2%)
Runs: 0 0 0 0 1 0 0 0 0 0
Warmup runs: 0

Current
Mean: 0.5 ms
Stdev: 0.5 ms (105.4%)
Runs: 1 0 1 0 1 1 1 0 0 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with weight and size - 10 runs render Baseline
Mean: 0.2 ms
Stdev: 0.4 ms (210.8%)
Runs: 0 0 0 0 1 0 0 1 0 0
Warmup runs: 1

Current
Mean: 0.5 ms
Stdev: 0.5 ms (105.4%)
Runs: 0 0 1 0 1 1 0 0 1 1
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Render Count Changes

There are no entries

Render Issues

There are no entries

Added Scenarios

There are no entries

Removed Scenarios

There are no entries

Generated by 🚫 dangerJS against 4c10269

@alexrisch alexrisch changed the title Feature/v3 split Feature: V3 split towards a V3 Only World Nov 13, 2024
@alexrisch alexrisch force-pushed the release/3.0.0 branch 2 times, most recently from 07b66ae to 514e4b8 Compare November 22, 2024 17:05
Copy link
Contributor

coderabbitai bot commented Nov 25, 2024

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (6)
  • data/store/chatStore.ts
  • data/store/settingsStore.ts
  • features/conversations/utils/tests/search.test.ts
  • features/conversations/utils/search.ts
  • queries/useInboxProfileSocialsQuery.ts
  • screens/ConversationList.tsx
⛔ Files ignored due to path filters (1)
  • queries/__snapshots__/QueryKeys.test.ts.snap is excluded by !**/*.snap

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes in this pull request involve significant refactoring across multiple components and files within the application. Key modifications include the removal of unused imports and functionality, simplification of state management, and the introduction of new components and hooks. Notable updates include the elimination of the xmtpCron functionality, a shift towards a more modular design in handling conversations, and enhancements in type safety through the introduction of specific types like ConversationTopic. Overall, the changes aim to streamline the codebase and improve maintainability.

Changes

File Path Change Summary
App.tsx Removed unused imports and hooks, simplified state management, and preserved core authentication logic.
__mocks__/@sentry/react-native.ts Added mock functions for Sentry library to facilitate testing without actual service calls.
android/app/src/main/java/com/converse/Preferences.kt Introduced suspend functions to check consent states for conversations, addresses, and inbox IDs.
android/app/src/main/java/com/converse/PushNotificationsService.kt Refactored notification handling to accommodate new V3 message processing logic and updated envelope creation methods.
android/app/src/main/java/com/converse/Spam.kt Renamed and modified spam score computation functions for direct messages and group messages, enhancing clarity and modularity.
android/app/src/main/java/com/converse/Utils.kt Updated function names to reflect changes in messaging system versioning.
android/app/src/main/java/com/converse/xmtp/Client.kt Removed key retrieval function and simplified client initialization logic.
android/app/src/main/java/com/converse/xmtp/Conversations.kt Refactored conversation handling logic to focus on syncing conversations rather than groups.
android/app/src/main/java/com/converse/xmtp/Messages.kt Modified message handling functions to streamline processing and improve error handling.
components/AccountSettingsButton.tsx Commented out profile refresh functionality while retaining other onPress logic.
components/Chat/Attachment/AddAttachmentButton.tsx Refactored to a standalone function, improved error handling, and integrated theming.
components/Chat/Attachment/AttachmentMessagePreview.tsx Introduced a new hook for fetching remote attachments, refactoring the component for modularity.
components/Chat/Attachment/SendAttachmentPreview.tsx Simplified prop structure and updated layout components for improved consistency.
components/Chat/Chat.tsx Completely removed the Chat component implementation and related logic.
components/Chat/ChatDumb.tsx Introduced a new component for chat interfaces with optimized rendering and interaction handling.
components/Chat/ChatGroupUpdatedMessage.tsx Updated to handle group updates more directly with improved type safety and rendering logic.
components/Chat/ChatPlaceholder/ChatPlaceholder.tsx Commented out entire component implementation, indicating a temporary removal.
components/Chat/ChatPlaceholder/GroupChatPlaceholder.tsx Removed component for group chat placeholder functionality.
components/Chat/ConsentPopup/ConsentPopup.tsx Simplified consent logic and updated prop handling for direct conversation access.
components/Chat/ConsentPopup/GroupConsentPopup.tsx Updated to directly accept a group prop, simplifying logic for consent display.
components/Chat/Frame/FrameBottom.tsx Renamed message ownership parameter for clarity and updated associated logic.
components/Chat/Message/Message.tsx Removed unused imports and deprecated types, indicating a shift in expected structures.
components/Chat/Message/Message.utils.tsx Introduced utility functions for message type handling and profile retrieval.
components/Chat/Message/MessageActions.tsx Completely removed the ChatMessageActions component implementation.
components/Chat/Message/MessageBubble.tsx Added new components for bubble rendering with improved styling based on message ownership.
components/Chat/Message/MessageContext.tsx Introduced context for managing message time indicator visibility.
components/Chat/Message/MessageContextMenu.tsx Updated animation handling and simplified context management.
components/Chat/Message/MessageSender.tsx Introduced components for rendering message sender information with improved data retrieval logic.
components/Chat/Message/MessageSenderAvatar.tsx Added components for rendering sender avatars with navigation capabilities.
components/Chat/Message/MessageStatusDumb.tsx Introduced a component for displaying message status with animations.
components/Chat/Message/RepliableMessageWrapper.tsx New component for wrapping messages with swipe-to-reply functionality.
components/Chat/Message/TextMessage.tsx New component for rendering text messages with customizable styling based on theme.
components/Chat/Message/V3Message.tsx Introduced a comprehensive message rendering component for various message types.
components/Chat/Message/messageContextStore.tsx New context store for managing message-related state in the application.
components/Chat/Transaction/TransactionInput.tsx Removed component for handling transaction input, indicating a shift in transaction management.
components/Chat/Transaction/TransactionPreview.tsx Commented out profile refresh logic during transaction simulation.
components/V3DMListItem.tsx New component for displaying direct message list items with interaction handling.
components/V3GroupConversationListItem.tsx New component for displaying group conversation list items with enhanced interaction capabilities.
components/XmtpEngine.tsx Significant refactor, removing unused functionality and streamlining synchronization logic.
components/__tests__/PressableProfileWithText.perf-test.tsx Removed performance tests for the PressableProfileWithText component.
config.ts Updated environment configuration for type safety and immutability.
containers/GroupPendingRequestsTable.tsx Updated prop types for enhanced type safety regarding conversation topics.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant XmtpClient
    participant NotificationService
    participant Preferences

    User->>App: Open App
    App->>XmtpClient: Initialize Client
    XmtpClient->>Preferences: Load Preferences
    Preferences-->>XmtpClient: Return Preferences
    XmtpClient-->>App: Client Initialized
    App->>NotificationService: Check Notifications
    NotificationService-->>App: Return Notifications
    App->>User: Display Notifications
Loading

Possibly related PRs

Suggested labels

2.0.7

Suggested reviewers

  • thierryskoda
  • nmalzieu

Poem

🐇 In the meadow where code does play,
Changes bloom like flowers in May.
Unused imports tossed away,
Simplified paths light the way.
With every tweak, our app's a delight,
Hopping forward, ready for flight! 🌼


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.

alexrisch and others added 20 commits November 25, 2024 10:58
Started some work on V3 Message Component
Cleaned up several Components
Cleaned up list merging
Better query handling for messages
Message Status
Chat message work
Message Status
Chat message work
* feat: Update to use inbox id endpoint

Added new query to use inbox endpoint
Updated usage for group avatar/members
Updated socials type to match what is being returned from the server

* feat: Remove Inbox Id store

Remove inbox id store

* Updates

Added tests
Split util files
Added helper util functions
Added helper hooks

* Updated to map and filter
Added helpers
Updated naming to match v3 rather than just groups
Updated Pinned Conversations and Conversation List
Fix for missing function
Moved util functions to separate files
Removed helpers/index files
Prepared media attachments for split from V2 handling

:
Added V3 Message and V3Message to Display
Added conversation context to help reduce overall changes
Added blocked view support
Cleaned up tsc
Added helper query config
Added API setting pinned conversations
* feat: Remove V2

* feat: Remove V2

Removes almost all V2 Handling
Removes App Database
Cleans up queries
Bumps to V3 SDK
Updated native iOS notifications to work with V3 only
Removes some frames functionality

Leaves the app utterly broken
Fix Android Build
Update Conversation List to have hooks
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* remove unused rn patches

* wip

* wip

* fix: New DM Work

Updated handling when sending new Conversations

---------

Co-authored-by: Thierry <skodathierry@gmail.com>
@alexrisch alexrisch added the 3.0.0 Release 3.0.0 label Nov 25, 2024
alexrisch and others added 3 commits November 25, 2024 12:27
Updated Tests to work on V3 Only
* fix tsc

* remove unused test
Added new query
Updated use of permissions
Added new translations
Updated hooks
Added new utils
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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (65)
components/PinnedConversations/PinnedConversations.tsx (1)

11-20: ⚠️ Potential issue

Handle potential null currentAccount case

The non-null assertion (currentAccount!) could lead to runtime errors if the account is not available.

Consider adding a null check:

-  const { isLoading } = useV3ConversationListQuery(
-    currentAccount!,
+  if (!currentAccount) return null;
+  const { isLoading } = useV3ConversationListQuery(
+    currentAccount,
     {
       refetchOnWindowFocus: false,
       refetchOnMount: false,
     },
     "PinnedConversations"
   );
📝 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.

export const PinnedConversations = ({ topics }: Props) => {
  const currentAccount = useCurrentAccount();
  if (!currentAccount) return null;
  const { isLoading } = useV3ConversationListQuery(
    currentAccount,
    {
      refetchOnWindowFocus: false,
      refetchOnMount: false,
    },
    "PinnedConversations"
  );
components/PinnedConversations/PinnedV3Conversation.tsx (2)

9-11: 🛠️ Refactor suggestion

Strengthen type safety in props definition.

The topic prop is defined as string but later cast to ConversationTopic. Consider using ConversationTopic directly in the props type to catch type mismatches at compile time.

 type PinnedV3ConversationProps = {
-  topic: string;
+  topic: ConversationTopic;
 };

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


18-25: 🛠️ Refactor suggestion

Improve type safety and type discrimination.

The component uses type casting which could be unsafe. Consider using type guards or discriminated unions for safer type narrowing.

+type ConversationType = GroupWithCodecsType | DmWithCodecsType;

+function isGroupConversation(
+  conversation: ConversationType
+): conversation is GroupWithCodecsType {
+  return conversationIsGroup(conversation);
+}

 export const PinnedV3Conversation = ({ topic }: PinnedV3ConversationProps) => {
   const conversation = useConversationListGroupItem(topic as ConversationTopic);
   if (!conversation) {
     return null;
   }
-  if (conversationIsGroup(conversation)) {
+  if (isGroupConversation(conversation)) {
     return (
-      <PinnedV3GroupConversation group={conversation as GroupWithCodecsType} />
+      <PinnedV3GroupConversation group={conversation} />
     );
   }
   return (
-    <PinnedV3DMConversation conversation={conversation as DmWithCodecsType} />
+    <PinnedV3DMConversation conversation={conversation} />
   );
 };

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

components/StateHandlers/HydrationStateHandler.tsx (2)

23-25: 🛠️ Refactor suggestion

Remove commented code block

Remove the commented code block as it's no longer needed. If this is part of the V3 migration, document the removal reason in the commit message or add a TODO comment explaining the transition plan.

-      // accounts.map((address) => {
-      //   refreshProfileForAddress(address, address);
-      // });
📝 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.



3-3: 🛠️ Refactor suggestion

Remove commented import statement

Instead of commenting out the import, it should be removed entirely if it's no longer needed. If this is part of a gradual migration to V3, consider adding a TODO comment explaining the transition plan.

-// import { refreshProfileForAddress } from "../../data/helpers/profiles/profilesUpdate";
📝 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.


components/Chat/Message/MessageContext.tsx (2)

46-48: 🛠️ Refactor suggestion

Add context existence check in the custom hook

The hook should verify that it's being used within a provider context.

 export function useMessageContext(): IMessageContextType {
-  return useContext(MessageContext);
+  const context = useContext(MessageContext);
+  if (!context) {
+    throw new Error('useMessageContext must be used within a MessageContextProvider');
+  }
+  return context;
 }
📝 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.

export function useMessageContext(): IMessageContextType {
  const context = useContext(MessageContext);
  if (!context) {
    throw new Error('useMessageContext must be used within a MessageContextProvider');
  }
  return context;
}

15-17: 🛠️ Refactor suggestion

Improve context initialization safety

The current initialization with an empty object cast to IMessageContextType could lead to runtime errors if the context is accidentally used outside of the provider. Consider providing meaningful defaults or throwing an error.

-const MessageContext = createContext<IMessageContextType>(
-  {} as IMessageContextType
-);
+const MessageContext = createContext<IMessageContextType>({
+  showTime: () => {
+    throw new Error('MessageContext not initialized');
+  },
+  hideTime: () => {
+    throw new Error('MessageContext not initialized');
+  },
+  toggleTime: () => {
+    throw new Error('MessageContext not initialized');
+  },
+  showTimeAV: { value: false } as SharedValue<boolean>,
+});

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

components/Chat/Message/MessageSender.tsx (1)

26-36: 🛠️ Refactor suggestion

Add error handling for missing profile data

Consider handling cases where profile data might be undefined to prevent potential runtime errors.

 export const MessageSender = ({ senderAddress }: MessageSenderProps) => {
   const senderSocials = useProfilesStore(
     (s) => getProfile(senderAddress, s.profiles)?.socials
   );
   const name = getPreferredName(senderSocials, senderAddress);
-  return <MessageSenderDumb name={name} />;
+  if (!name) {
+    return null; // or a fallback UI
+  }
+  return <MessageSenderDumb name={name} />;
 };
📝 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.

type MessageSenderProps = {
  senderAddress: string;
};

export const MessageSender = ({ senderAddress }: MessageSenderProps) => {
  const senderSocials = useProfilesStore(
    (s) => getProfile(senderAddress, s.profiles)?.socials
  );
  const name = getPreferredName(senderSocials, senderAddress);
  if (!name) {
    return null; // or a fallback UI
  }
  return <MessageSenderDumb name={name} />;
};
components/Conversation/ConversationTitleDumb.tsx (1)

13-18: 🛠️ Refactor suggestion

Consider making title prop required and adding prop validation

As a presentational component, rendering without a title could lead to undesirable UI states. Consider:

  1. Making the title prop required
  2. Adding validation for empty strings
 type ConversationTitleDumbProps = {
-  title?: string;
+  title: string;
   avatarComponent?: React.ReactNode;
   onLongPress?: () => void;
   onPress?: () => void;
 };
📝 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.

type ConversationTitleDumbProps = {
  title: string;
  avatarComponent?: React.ReactNode;
  onLongPress?: () => void;
  onPress?: () => void;
};
components/PinnedConversations/PinnedV3DMConversation.tsx (2)

17-27: ⚠️ Potential issue

Consider safer handling of currentAccount

The non-null assertion (!) on currentAccount could lead to runtime errors if the account is not available. Consider adding a guard clause or error boundary.

 export const PinnedV3DMConversation = ({
   conversation,
 }: PinnedV3DMConversationProps) => {
   const currentAccount = useCurrentAccount();
+  if (!currentAccount) {
+    return null; // or an error state component
+  }
   const { data: peerInboxId } = useDmPeerInboxOnConversationList(
-    currentAccount!,
+    currentAccount,
     conversation
   );
📝 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.

export const PinnedV3DMConversation = ({
  conversation,
}: PinnedV3DMConversationProps) => {
  const currentAccount = useCurrentAccount();
  if (!currentAccount) {
    return null; // or an error state component
  }
  const { data: peerInboxId } = useDmPeerInboxOnConversationList(
    currentAccount,
    conversation
  );
  const preferredName = usePreferredInboxName(peerInboxId);
  const preferredAvatar = usePreferredInboxAvatar(peerInboxId);

34-35: 💡 Codebase verification

The hardcoded showUnread flag should use the showUnreadOnConversation utility

The showUnread flag is incorrectly hardcoded to false when it should be using the available showUnreadOnConversation utility. The component has access to the conversation prop and can access the current user's address, which are required parameters for the unread status calculation.

  • In PinnedV3DMConversation.tsx, replace the hardcoded showUnread = false with the showUnreadOnConversation utility
  • Required data for the utility:
    • conversation (already available as prop)
    • currentAccount (already available from useCurrentAccount)
    • Need to add access to topicsData, initialLoadDoneOnce, and lastMessagePreview
🔗 Analysis chain

Verify the hardcoded showUnread flag

The showUnread flag is hardcoded to false. Is this intentional or should it be derived from the conversation state?

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for unread status implementations in other conversation components
rg -A 5 "showUnread" --type typescript

Length of output: 73


Script:

#!/bin/bash
# Search for unread status implementations in other conversation components
rg -A 5 "showUnread"

Length of output: 3784


Script:

#!/bin/bash
# Search for unread-related implementations in the codebase
rg -A 5 "unread" --type ts --type tsx

Length of output: 65


Script:

#!/bin/bash
# Find conversation-related components
fd -e tsx -e ts "Conversation"

Length of output: 2176


Script:

#!/bin/bash
# Check the implementation of showUnreadOnConversation utility
cat utils/conversation/showUnreadOnConversation.ts

Length of output: 950


Script:

#!/bin/bash
# Check if there are any props or state related to unread status in parent components
rg -A 5 "PinnedV3DMConversation" 

Length of output: 1837


Script:

#!/bin/bash
# Check for any usage of showUnreadOnConversation utility in the component or its parent
rg -l "showUnreadOnConversation" components/PinnedConversations/

# Check the full implementation of PinnedV3DMConversation to see available props and data
cat components/PinnedConversations/PinnedV3DMConversation.tsx

Length of output: 2035

components/PinnedConversations/PinnedV3GroupConversation.tsx (2)

19-23: ⚠️ Potential issue

Add null check for currentAccount

The non-null assertion (!) on currentAccount could lead to runtime errors if the account is not available.

 const currentAccount = useCurrentAccount();
+ if (!currentAccount) {
+   return null; // or handle the no-account case appropriately
+ }
 const { memberData } = useGroupConversationListAvatarInfo(
-  currentAccount!,
+  currentAccount,
   group
 );
📝 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.

  const currentAccount = useCurrentAccount();
  if (!currentAccount) {
    return null; // or handle the no-account case appropriately
  }
  const { memberData } = useGroupConversationListAvatarInfo(
    currentAccount,
    group
  );

33-36: ⚠️ Potential issue

Remove empty useEffect

The useEffect hook contains only early returns without any side effects. This appears to be unused code.

- useEffect(() => {
-   if (!group) return;
-   if (group.imageUrlSquare) return;
- }, [group]);
📝 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.


components/Chat/Message/messageContextStore.tsx (1)

29-32: 🛠️ Refactor suggestion

Implement prop comparison for store updates

The current implementation updates the store on every prop change without checking if the values actually changed. This could lead to unnecessary re-renders.

Consider implementing a comparison:

 useEffect(() => {
-  // TODO: Check if the props have made something change in the store?
-  storeRef.current?.setState(props);
+  const currentState = storeRef.current?.getState();
+  const hasChanges = Object.entries(props).some(
+    ([key, value]) => currentState[key as keyof IMessageContextStoreProps] !== value
+  );
+  if (hasChanges) {
+    storeRef.current?.setState(props);
+  }
 }, [props]);

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

components/ParsedText/ParsedText.utils.ts (2)

29-34: ⚠️ Potential issue

Address potential security and safety concerns

Two issues need attention:

  1. User-provided regex patterns could lead to ReDoS attacks
  2. Non-null assertion on match.index should be handled more safely

Consider adding:

  1. Timeout protection for regex execution
  2. Safe fallback for index
-          const regex = new RegExp(
-            pattern.pattern.source,
-            pattern.pattern.flags.includes("g")
-              ? pattern.pattern.flags
-              : pattern.pattern.flags + "g"
-          );
+          const regex = createSafeRegex(pattern.pattern);
+          
-            const index = match.index!;
+            const index = match.index ?? lastIndex;

// Add this helper function
+ function createSafeRegex(pattern: RegExp): RegExp {
+   const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
+   // Consider adding pattern validation or using a regex safety library
+   return new RegExp(pattern.source, flags);
+ }

Also applies to: 42-42


86-89: 🛠️ Refactor suggestion

Remove @ts-ignore by improving type definitions

The ts-ignore comment suggests a typing issue with the value function call.

Consider improving the type definitions:

+ type MatchFunction = (text: string, index: number) => void;
+ type PatternValue = string | boolean | MatchFunction;

  if (typeof value === "function") {
-    // @ts-ignore
-    acc[key] = () => value(text, index);
+    acc[key] = () => (value as MatchFunction)(text, index);
  }
📝 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.

        type MatchFunction = (text: string, index: number) => void;
        type PatternValue = string | boolean | MatchFunction;

        acc[key] = () => (value as MatchFunction)(text, index);
      } else {
components/Chat/Message/MessageStatusDumb.tsx (1)

53-84: ⚠️ Potential issue

Review animation timing and dependencies

There are several concerns with the current implementation:

  1. The setTimeout delay of 100ms lacks explanation
  2. The nested requestAnimationFrame might be unnecessary
  3. The effect is missing dependencies (status, opacity, height, scale, timingConfig)

Consider these improvements:

 useEffect(
   () => {
     const prevStatus = prevStatusRef.current;
     prevStatusRef.current = status;

+    // Allow time for layout calculations before starting animations
     setTimeout(() => {
-      requestAnimationFrame(() => {
         if (
           isSentOrDelivered &&
           (prevStatus === "sending" || prevStatus === "prepared")
         ) {
           opacity.value = withTiming(1, timingConfig);
           height.value = withTiming(22, timingConfig);
           scale.value = withTiming(1, timingConfig);
           setRenderText(true);
         } else if (isSentOrDelivered && !isLatestSettledFromMe) {
           opacity.value = withTiming(0, timingConfig);
           height.value = withTiming(0, timingConfig);
           scale.value = withTiming(0, timingConfig);
           setTimeout(() => setRenderText(false), timingConfig.duration);
         } else if (isLatestSettledFromMe) {
           opacity.value = 1;
           height.value = 22;
           scale.value = 1;
           setRenderText(true);
         }
-      });
     }, 100);
   },
-  // eslint-disable-next-line react-hooks/exhaustive-deps
-  [isLatestSettledFromMe, isSentOrDelivered]
+  [
+    isLatestSettledFromMe,
+    isSentOrDelivered,
+    status,
+    opacity,
+    height,
+    scale,
+    timingConfig
+  ]
 );
📝 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.

  useEffect(
    () => {
      const prevStatus = prevStatusRef.current;
      prevStatusRef.current = status;

      // Allow time for layout calculations before starting animations
      setTimeout(() => {
        if (
          isSentOrDelivered &&
          (prevStatus === "sending" || prevStatus === "prepared")
        ) {
          opacity.value = withTiming(1, timingConfig);
          height.value = withTiming(22, timingConfig);
          scale.value = withTiming(1, timingConfig);
          setRenderText(true);
        } else if (isSentOrDelivered && !isLatestSettledFromMe) {
          opacity.value = withTiming(0, timingConfig);
          height.value = withTiming(0, timingConfig);
          scale.value = withTiming(0, timingConfig);
          setTimeout(() => setRenderText(false), timingConfig.duration);
        } else if (isLatestSettledFromMe) {
          opacity.value = 1;
          height.value = 22;
          scale.value = 1;
          setRenderText(true);
        }
      }, 100);
    },
    [
      isLatestSettledFromMe,
      isSentOrDelivered,
      status,
      opacity,
      height,
      scale,
      timingConfig
    ]
  );
components/Chat/Message/MessageSenderAvatar.tsx (1)

82-85: 🛠️ Refactor suggestion

Add runtime check for currentAccount

The non-null assertion on currentAccount! could lead to runtime errors. Consider adding a proper runtime check.

-  const { data: senderSocials } = useInboxProfileSocialsQuery(
-    currentAccount!,
-    inboxId
-  );
+  if (!currentAccount) {
+    return null;
+  }
+  const { data: senderSocials } = useInboxProfileSocialsQuery(
+    currentAccount,
+    inboxId
+  );
📝 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.

  if (!currentAccount) {
    return null;
  }
  const { data: senderSocials } = useInboxProfileSocialsQuery(
    currentAccount,
    inboxId
  );
components/Chat/Message/Message.utils.tsx (2)

111-118: 🛠️ Refactor suggestion

Handle potential null case explicitly instead of using non-null assertion.

The getCurrentAccountInboxIdQueryOptions function uses a non-null assertion (!) which could lead to runtime errors if currentAccount is null.

Consider this safer approach:

 function getCurrentAccountInboxIdQueryOptions() {
   const currentAccount = getCurrentAccount();
   return {
     queryKey: ["inboxId", currentAccount],
-    queryFn: () => getInboxId(currentAccount!),
+    queryFn: () => {
+      if (!currentAccount) {
+        throw new Error("No current account found");
+      }
+      return getInboxId(currentAccount);
+    },
     enabled: !!currentAccount,
   };
 }
📝 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.

function getCurrentAccountInboxIdQueryOptions() {
  const currentAccount = getCurrentAccount();
  return {
    queryKey: ["inboxId", currentAccount],
    queryFn: () => {
      if (!currentAccount) {
        throw new Error("No current account found");
      }
      return getInboxId(currentAccount);
    },
    enabled: !!currentAccount,
  };
}

39-93: 🛠️ Refactor suggestion

Improve type safety by replacing any with proper types.

The type guard functions currently use any type for the message parameter, while commented code suggests DecodedMessageWithCodecsType was intended. This reduces type safety.

Apply this pattern to all type guard functions:

-export function isTextMessage(
-  message: any
-): message is DecodedMessage<[TextCodec]> {
+export function isTextMessage(
+  message: DecodedMessageWithCodecsType
+): message is DecodedMessage<[TextCodec]> {

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

components/ConversationFlashList.tsx (1)

62-85: 🛠️ Refactor suggestion

Improve renderItem type safety and maintainability.

The current implementation could benefit from:

  1. Type guards for better type safety
  2. Extraction of rendering logic into separate components
  3. Error boundary protection
+type V3Conversation = GroupWithCodecsType | DmWithCodecsType;
+
+const isV3Conversation = (item: any): item is V3Conversation => {
+  return "lastMessage" in item && "version" in unwrapConversationContainer(item);
+};
+
+const isHiddenRequestsButton = (
+  item: any
+): item is ConversationFlatListHiddenRequestItem => {
+  return "topic" in item && item.topic === "hiddenRequestsButton";
+};
+
 const renderItem = useCallback(({ item }: { item: FlatListItemType }) => {
-  if ("lastMessage" in item) {
+  try {
+    if (isV3Conversation(item)) {
       const conversation = unwrapConversationContainer(item);
       if (conversation.version === ConversationVersion.GROUP) {
         return (
           <V3GroupConversationListItem
             group={conversation as GroupWithCodecsType}
           />
         );
       } else {
         return <V3DMListItem conversation={conversation as DmWithCodecsType} />;
       }
     }
-    if (item.topic === "hiddenRequestsButton") {
-      const hiddenRequestItem = item as ConversationFlatListHiddenRequestItem;
+    if (isHiddenRequestsButton(item)) {
       return (
         <HiddenRequestsButton
-          spamCount={hiddenRequestItem.spamCount}
-          toggleActivated={hiddenRequestItem.toggleActivated}
+          spamCount={item.spamCount}
+          toggleActivated={item.toggleActivated}
         />
       );
     }
     return null;
+  } catch (error) {
+    console.error('Error rendering item:', error);
+    return null;
+  }
 }, []);
📝 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.

type V3Conversation = GroupWithCodecsType | DmWithCodecsType;

const isV3Conversation = (item: any): item is V3Conversation => {
  return "lastMessage" in item && "version" in unwrapConversationContainer(item);
};

const isHiddenRequestsButton = (
  item: any
): item is ConversationFlatListHiddenRequestItem => {
  return "topic" in item && item.topic === "hiddenRequestsButton";
};

const renderItem = useCallback(({ item }: { item: FlatListItemType }) => {
  try {
    if (isV3Conversation(item)) {
      const conversation = unwrapConversationContainer(item);
      if (conversation.version === ConversationVersion.GROUP) {
        return (
          <V3GroupConversationListItem
            group={conversation as GroupWithCodecsType}
          />
        );
      } else {
        return <V3DMListItem conversation={conversation as DmWithCodecsType} />;
      }
    }
    if (isHiddenRequestsButton(item)) {
      return (
        <HiddenRequestsButton
          spamCount={item.spamCount}
          toggleActivated={item.toggleActivated}
        />
      );
    }
    return null;
  } catch (error) {
    console.error('Error rendering item:', error);
    return null;
  }
}, []);
components/Onboarding/init-xmtp-client.ts (2)

33-39: ⚠️ Potential issue

Ensure error in async callback is properly propagated

Throwing an error inside the async callback passed to createXmtpClientFromSigner may not propagate to the outer try...catch block as expected. Ensure that the createXmtpClientFromSigner function is designed to handle errors thrown in the callback and that these errors will correctly trigger the catch block in initXmtpClient.


87-87: ⚠️ Potential issue

Implement missing logic in performLogoutAndSaveKey function

The performLogoutAndSaveKey function currently contains only logging statements without actual logic to perform logout or save the XMTP key. Ensure that the necessary functionality is implemented to handle logout operations and key management.

 async function performLogoutAndSaveKey(address: string) {
   logger.debug("Waiting for logout tasks");
   await waitForLogoutTasksDone(500);
   logger.debug("Logout tasks done, saving xmtp key");
+  // TODO: Implement logic to save XMTP key and perform logout tasks
+  // Example:
+  // await saveXmtpKey(address);
+  // Additional logout operations as needed
   logger.debug("XMTP Key saved");
 }

Also applies to: 99-104

components/Chat/Message/RepliableMessageWrapper.tsx (2)

76-78: ⚠️ Potential issue

Avoid accessing internal properties of Swipeable component

Accessing internal properties like swipeableRef.current?.state.rowTranslation and (translation as any)._value is not recommended because these are not part of the public API and may change in future versions, leading to potential breakage.

Consider using publicly exposed APIs or alternative approaches to achieve the desired functionality.


76-77: ⚠️ Potential issue

Potential null reference when accessing _value of undefined

If swipeableRef.current or translation is undefined, accessing (translation as any)._value will result in a runtime error.

Consider adding a null check before accessing _value.

Apply this diff to ensure safety:

 const translation = swipeableRef.current?.state.rowTranslation;
-const translationValue = (translation as any)._value;
+const translationValue = translation ? (translation as any)._value : 0;
📝 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.

        const translation = swipeableRef.current?.state.rowTranslation;
        const translationValue = translation ? (translation as any)._value : 0;
components/ParsedText/ParsedText.tsx (2)

11-11: ⚠️ Potential issue

Ensure consistency of the parse prop's optionality

The parse prop is currently marked as required in ParsedTextProps (line 11), but you check for its existence in getParsedText (lines 46-48). This inconsistency might lead to errors or confusion. Consider making parse optional in the type definition or ensure it's always provided.

Also applies to: 46-48


17-17: 🛠️ Refactor suggestion

Provide a default value for the parse prop

To prevent parse from being undefined, you can assign a default empty array when destructuring:

- const { parse, childrenProps = {}, ...rest } = props;
+ const { parse = [], childrenProps = {}, ...rest } = props;

This ensures parse is always an array, simplifying checks in your functions.

📝 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.

  const { parse = [], childrenProps = {}, ...rest } = props;
components/Chat/ConsentPopup/ConsentPopup.tsx (2)

83-89: 🛠️ Refactor suggestion

⚠️ Potential issue

Inconsistent navigation and missing await in consent acceptance

In the consent acceptance handler, consentToInboxIdsOnProtocolByAccount is called without await, which may cause the consent action to not complete before other operations occur. Additionally, unlike the block action, there's no navigation after accepting consent, which could lead to an inconsistent user experience.

Apply this diff to await the function and ensure consistent navigation:

onPress={async () => {
-  const inboxId = await conversation.peerInboxId();
-  consentToInboxIdsOnProtocolByAccount({
+  try {
+    const inboxId = await conversation.peerInboxId();
+    await consentToInboxIdsOnProtocolByAccount({
      account: currentAccount(),
      inboxIds: [inboxId],
      consent: "allow",
    });
+    navigation.pop();
+  } catch (error) {
+    // Handle error appropriately, e.g., show an alert
+    console.error(error);
+  }
}}

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


64-71: 🛠️ Refactor suggestion

⚠️ Potential issue

Missing await and error handling for asynchronous consent action

The consentToInboxIdsOnProtocolByAccount function appears to be asynchronous but is not awaited. This could lead to unexpected behavior if the consent action hasn't completed before navigation.pop() is called. Additionally, there's no error handling for potential exceptions from the asynchronous calls.

Apply this diff to await the function and add error handling:

async (selectedIndex?: number) => {
  if (selectedIndex === 0) {
-   const inboxId = await conversation.peerInboxId();
-   consentToInboxIdsOnProtocolByAccount({
+   try {
+     const inboxId = await conversation.peerInboxId();
+     await consentToInboxIdsOnProtocolByAccount({
      account: currentAccount(),
      inboxIds: [inboxId],
      consent: "deny",
    });
    navigation.pop();
+   } catch (error) {
+     // Handle error appropriately, e.g., show an alert
+     console.error(error);
+   }
  }
}
📝 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.

              async (selectedIndex?: number) => {
                if (selectedIndex === 0) {
                  try {
                    const inboxId = await conversation.peerInboxId();
                    await consentToInboxIdsOnProtocolByAccount({
                      account: currentAccount(),
                      inboxIds: [inboxId],
                      consent: "deny",
                    });
                    navigation.pop();
                  } catch (error) {
                    // Handle error appropriately, e.g., show an alert
                    console.error(error);
                  }
                }
android/app/src/main/java/com/converse/xmtp/Conversations.kt (1)

68-68: 🛠️ Refactor suggestion

Update error messages to reflect 'conversation' instead of 'group'

In the sentryTrackError calls within getNewConversation and getConversation, the error message says "Could not sync new group":

  • In getNewConversation (line 68)
  • In getConversation (line 79)

Since these functions handle conversations, please update the error messages to accurately reflect the context and avoid confusion.

Apply this diff to update the error messages:

-        sentryTrackError(error, mapOf("message" to "Could not sync new group"))
+        sentryTrackError(error, mapOf("message" to "Could not sync new conversation"))

Also applies to: 79-79

components/Chat/Attachment/AddAttachmentButton.tsx (1)

174-179: ⚠️ Potential issue

Handle upload errors by updating status and notifying the user

When an error occurs during the attachment upload process, the composerMediaPreviewStatus remains as "uploading", and the user isn't informed of the failure. Consider updating the status to reflect the error and providing user feedback.

Apply this diff to update the status and notify the user:

          } catch (error) {
            sentryTrackMessage("ATTACHMENT_UPLOAD_ERROR", { error });
+           setComposerMediaPreviewStatus("error");
+           showToast("Attachment upload failed. Please try again.");
          }
        } catch (error) {
          sentryTrackError(error);
+         setComposerMediaPreviewStatus("error");
+         showToast("An error occurred while processing the attachment.");
        }

Note: Ensure that a user notification method like showToast is available and appropriate for your application's UI to inform users about the error.

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

components/Chat/ChatGroupUpdatedMessage.tsx (2)

63-103: 🛠️ Refactor suggestion

Refactor duplicated logic in ChatGroupMemberLeft and ChatGroupMemberJoined components

The ChatGroupMemberLeft and ChatGroupMemberJoined components share a significant amount of duplicated code. Consider refactoring to extract the shared logic into a single reusable component to improve maintainability and reduce code duplication.

For example, you could create a generic ChatGroupMemberUpdate component that accepts props for the action type (e.g., "joined" or "left") and the corresponding translation key. This would reduce redundancy and make future updates easier.

Also applies to: 109-149


99-100: ⚠️ Potential issue

Incorrect translation key in ChatGroupMemberLeft component

In the ChatGroupMemberLeft component, the translation key used is "group_member_joined", but it should be "group_member_left" to accurately reflect that a member has left the group.

Apply this diff to fix the translation key:

-            {translate("group_member_joined")}
+            {translate("group_member_left")}

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

components/Chat/ChatDumb.tsx (2)

161-167: ⚠️ Potential issue

Handle cases where messageId is not found in items

In the scrollToMessage function, if the messageId is not found, findIndex returns -1. Attempting to scroll to index -1 can cause errors. The current condition if (index !== undefined) does not prevent this.

Update the condition to check if index is greater than or equal to 0:

- if (index !== undefined) {
+ if (index !== undefined && index >= 0) {
    messageListRef.current?.scrollToIndex({
      index,
      viewPosition: 0.5,
      animated: !!data.animated,
    });
  }
📝 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.

      if (index !== undefined && index >= 0) {
        messageListRef.current?.scrollToIndex({
          index,
          viewPosition: 0.5,
          animated: !!data.animated,
        });
      }

189-214: ⚠️ Potential issue

Assign the messageListRef to the ReanimatedFlashList component

The messageListRef is defined but not attached to the ReanimatedFlashList. Without assigning the ref, the scrollToMessage function will not work as intended because it won't have access to the list methods.

Apply this diff to fix the issue:

<ReanimatedFlashList
  contentContainerStyle={styles.chat}
  data={items}
  refreshing={refreshing}
  extraData={extraData}
  renderItem={renderItem}
  onLayout={handleOnLayout}
  keyboardDismissMode="interactive"
  automaticallyAdjustContentInsets={false}
  contentInsetAdjustmentBehavior="never"
  inverted
  keyExtractor={keyExtractor}
  getItemType={getItemType}
  keyboardShouldPersistTaps="handled"
  estimatedItemSize={34}
  showsVerticalScrollIndicator={Platform.OS === "ios"}
  pointerEvents="auto"
  ListFooterComponent={ListFooterComponent}
+ ref={messageListRef}
}
/>
📝 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.

          <ReanimatedFlashList
            contentContainerStyle={styles.chat}
            data={items}
            refreshing={refreshing}
            extraData={extraData}
            renderItem={renderItem}
            onLayout={handleOnLayout}
            keyboardDismissMode="interactive"
            automaticallyAdjustContentInsets={false}
            contentInsetAdjustmentBehavior="never"
            // Causes a glitch on Android, no sure we need it for now
            // maintainVisibleContentPosition={{
            //   minIndexForVisible: 0,
            //   autoscrollToTopThreshold: 100,
            // }}
            // estimatedListSize={Dimensions.get("screen")}
            inverted
            keyExtractor={keyExtractor}
            getItemType={getItemType}
            keyboardShouldPersistTaps="handled"
            estimatedItemSize={34}
            // Size glitch on Android
            showsVerticalScrollIndicator={Platform.OS === "ios"}
            pointerEvents="auto"
            ListFooterComponent={ListFooterComponent}
            ref={messageListRef}
          />
android/app/src/main/java/com/converse/Spam.kt (2)

57-59: 🛠️ Refactor suggestion

Avoid blocking the thread with runBlocking in computeDmSpamScore

Using runBlocking can block the current thread, potentially causing performance issues, especially if this function is called on the main thread. Consider converting computeDmSpamScore into a suspending function to leverage coroutines properly and prevent blocking.

Apply this diff to refactor the function:

-fun computeDmSpamScore(address: String, message: String?, contentType: String, apiURI: String?, appContext: Context): Double {
-    var senderScore = runBlocking {
-        getSenderSpamScore(appContext, address, apiURI);
-    }
+suspend fun computeDmSpamScore(address: String, message: String?, contentType: String, apiURI: String?, appContext: Context): Double {
+    val senderScore = getSenderSpamScore(appContext, address, apiURI)
     return senderScore + getMessageSpamScore(message, contentType)
 }
📝 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.

suspend fun computeDmSpamScore(address: String, message: String?, contentType: String, apiURI: String?, appContext: Context): Double {
    val senderScore = getSenderSpamScore(appContext, address, apiURI)
    return senderScore + getMessageSpamScore(message, contentType)
}

158-160: ⚠️ Potential issue

Avoid swallowing exceptions without handling or logging

Swallowing exceptions without proper handling or logging can make debugging difficult and obscure the root cause of issues. Consider logging the exception or providing more specific error handling to improve maintainability.

Apply this diff to log the exception:

 } catch (e: Exception) {
-    return 0.0
+    Log.e("SpamKt", "Error in computeSpamScoreDmWelcome", e)
+    return 0.0
 }
📝 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.

    } catch (e: Exception) {
        Log.e("SpamKt", "Error in computeSpamScoreDmWelcome", e)
        return 0.0
    }
🧰 Tools
🪛 detekt (1.23.7)

[warning] 158-158: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

components/V3DMListItem.tsx (2)

280-280: ⚠️ Potential issue

Pass the correct isUnread prop to ConversationListItemDumb

The isUnread prop is currently hardcoded to false. To properly reflect the unread status of the conversation, you should pass the computed isUnread value.

Apply this diff to fix the issue:

-  isUnread={false}
+  isUnread={isUnread}

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


48-48: ⚠️ Potential issue

Avoid using non-null assertion ! without proper null checks

Using the non-null assertion operator ! on useCurrentAccount() can lead to runtime errors if it returns null or undefined. Consider adding a null check or handling the case when currentAccount is not available.

Apply this diff to safely handle the potential null value:

-  const currentAccount = useCurrentAccount()!;
+  const currentAccount = useCurrentAccount();
+  if (!currentAccount) {
+    // Handle the null case, e.g., navigate to login or return null
+    return null;
+  }

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

components/Conversation/V3Conversation.tsx (3)

220-223: ⚠️ Potential issue

Prevent index out-of-bounds errors in Message component

Accessing messages.ids[index + 1] and messages.ids[index - 1] without bounds checking may cause runtime errors when index is at the start or end of the array.

Apply this diff to prevent out-of-bounds access:

220  <V3Message
221    messageId={messageId}
222-   previousMessageId={messages.ids[index + 1]}
223-   nextMessageId={messages.ids[index - 1]}
+     previousMessageId={index + 1 < messages.ids.length ? messages.ids[index + 1] : null}
+     nextMessageId={index > 0 ? messages.ids[index - 1] : null}
224  />
📝 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.

        messageId={messageId}
        previousMessageId={index + 1 < messages.ids.length ? messages.ids[index + 1] : null}
        nextMessageId={index > 0 ? messages.ids[index - 1] : null}
      />

54-60: ⚠️ Potential issue

Handle undefined topic to prevent potential crashes

The topic may be undefined, as indicated by the TODO comment. Calling initializeCurrentConversation with an undefined topic could lead to unexpected behavior or runtime errors.

Apply this diff to handle the case when topic is undefined:

54  // TODO: Handle when topic is not defined
55  const messageToPrefill = textPrefill ?? getDraftMessage(topic) ?? "";
56+ if (topic) {
57    initializeCurrentConversation({
58      topic,
59      peerAddress,
60      inputValue: messageToPrefill,
61    });
62+ } else {
63+   // Handle the undefined topic case appropriately
64+ }

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


177-179: 🛠️ Refactor suggestion

Avoid using @ts-ignore by properly typing Animated.FlatList

Using @ts-ignore suppresses TypeScript errors and can hide legitimate issues. It's better to correctly type Animated.FlatList to resolve the TypeScript error without ignoring it.

Consider updating the import to include the correct type definitions:

16  import Animated, {
17    AnimatedProps,
18    useAnimatedKeyboard,
19    useAnimatedStyle,
20  } from "react-native-reanimated";
+ import { FlatList } from "react-native";
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) as typeof FlatList;

Then, update the usage:

175-   <Animated.FlatList
+     <AnimatedFlatList

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

components/Chat/Attachment/AttachmentMessagePreview.tsx (6)

65-68: ⚠️ Potential issue

Add validation to prevent division by zero when calculating aspect ratio

When computing the aspectRatio, there's a potential for a division by zero if attachment.imageSize.height is zero. This would lead to a runtime error. Consider adding a check to ensure attachment.imageSize.height is not zero before performing the division.

Apply this diff to add the necessary validation:

 const aspectRatio =
   fitAspectRatio && attachment.imageSize
+    && attachment.imageSize.height !== 0
     ? attachment.imageSize.width / attachment.imageSize.height
     : undefined;
📝 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.

  const aspectRatio =
    fitAspectRatio && attachment.imageSize
    && attachment.imageSize.height !== 0
      ? attachment.imageSize.width / attachment.imageSize.height
      : undefined;

27-30: ⚠️ Potential issue

Include remoteMessageContent in queryKey to prevent caching issues

In the useRemoteAttachment hook, the queryKey currently includes only messageId. Since remoteMessageContent is a dependency for fetchAttachment, it should be included in the queryKey to ensure proper caching and data consistency. This prevents stale or incorrect data when remoteMessageContent changes but messageId remains the same.

Apply this diff to include remoteMessageContent in the queryKey:

 return useQuery({
-  queryKey: ["attachment", messageId],
+  queryKey: ["attachment", messageId, remoteMessageContent],
   queryFn: () => fetchAttachment(messageId, remoteMessageContent),
 });
📝 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.

  return useQuery({
    queryKey: ["attachment", messageId, remoteMessageContent],
    queryFn: () => fetchAttachment(messageId, remoteMessageContent),
  });

306-307: ⚠️ Potential issue

Validate content.contentLength to handle invalid values

Parsing content.contentLength using parseFloat without validation could result in NaN, leading to unexpected behavior in the comparison. Ensure that content.contentLength is a valid number before performing the comparison to MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE.

Apply this diff to add validation:

 if (
   content.contentLength &&
+  !isNaN(parseFloat(content.contentLength)) &&
   parseFloat(content.contentLength) <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE
 ) {

Alternatively, ensure that content.contentLength is always provided as a number, eliminating the need for parsing.

📝 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.

    content.contentLength &&
    !isNaN(parseFloat(content.contentLength)) &&
    parseFloat(content.contentLength) <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE

172-174: ⚠️ Potential issue

Implement the openInWebview functionality or remove the unused onPress handler

The onPress handler for the PressableScale component is currently empty and includes a commented-out reference to openInWebview. This may cause confusion and indicates incomplete functionality. If the intent is to allow users to view unsupported attachments in a web browser, implement the openInWebview function. Otherwise, remove the onPress handler to clean up the code.

Apply this diff if you intend to implement openInWebview:

-  onPress={() => {
-    // openInWebview
-  }}
+  onPress={openInWebview}

Ensure that openInWebview is defined and handles the navigation appropriately.

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


98-101: ⚠️ Potential issue

Include remoteMessageContent in queryKey to ensure proper data fetching

In the RemoteAttachmentPreview component, the useQuery hook's queryKey does not include remoteMessageContent, which is a dependency for fetchAttachment. Omitting it can lead to inappropriate caching behavior when remoteMessageContent changes.

Apply this diff to update the queryKey:

 useQuery({
-  queryKey: ["attachment", messageId],
+  queryKey: ["attachment", messageId, remoteMessageContent],
   queryFn: () => fetchAttachment(messageId, remoteMessageContent),
 });
📝 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.

  } = useQuery({
    queryKey: ["attachment", messageId, remoteMessageContent],
    queryFn: () => fetchAttachment(messageId, remoteMessageContent),
  });

293-325: ⚠️ Potential issue

Add error handling to fetchAttachment to gracefully handle failures

The fetchAttachment function performs several asynchronous operations that may fail, such as fetching the local attachment or fetching and decoding the remote attachment. Currently, there's no try-catch block to handle exceptions, which could lead to unhandled promise rejections.

Apply this diff to wrap the asynchronous operations in a try-catch block:

 async function fetchAttachment(
   messageId: string,
   content: RemoteAttachmentContent
 ) {
+  try {
     const localAttachment = await getLocalAttachmentForMessageId(messageId);

     if (localAttachment) {
       return localAttachment;
     }

     if (
       content.contentLength &&
       !isNaN(parseFloat(content.contentLength)) &&
       parseFloat(content.contentLength) <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE
     ) {
       const decryptedLocalAttachment = await fetchAndDecodeRemoteAttachment({
         account: getCurrentAccount()!,
         messageId: messageId,
         remoteAttachmentContent: content,
       });

       const result = await handleDecryptedLocalAttachment({
         messageId: messageId,
         decryptedLocalAttachment: decryptedLocalAttachment,
       });

       return result;
     }

+    throw new Error("Attachment exceeds maximum automatic download size.");
+  } catch (error) {
+    console.error("Error fetching attachment:", error);
+    throw error;
+  }
 }

This ensures that any errors are caught and can be handled appropriately by the calling code.

📝 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.

async function fetchAttachment(
  messageId: string,
  content: RemoteAttachmentContent
) {
  try {
    const localAttachment = await getLocalAttachmentForMessageId(messageId);

    console.log("localAttachment:", localAttachment);

    if (localAttachment) {
      return localAttachment;
    }

    if (
      content.contentLength &&
      parseFloat(content.contentLength) <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE
    ) {
      const decryptedLocalAttachment = await fetchAndDecodeRemoteAttachment({
        account: getCurrentAccount()!,
        messageId: messageId,
        remoteAttachmentContent: content,
      });

      console.log("decryptedLocalAttachment", decryptedLocalAttachment);

      const result = await handleDecryptedLocalAttachment({
        messageId: messageId,
        decryptedLocalAttachment: decryptedLocalAttachment,
      });

      console.log("result:", result);

      return result;
    }

    throw new Error("Attachment exceeds maximum automatic download size.");
  } catch (error) {
    console.error("Error fetching attachment:", error);
    throw error;
  }
}
components/ConversationListItem/ConversationListItemDumb.tsx (1)

303-331: ⚠️ Potential issue

Potential logical error in rightIsDestructive usage in renderRightActions

The renderRightActions function seems to have the logic inverted for the rightIsDestructive prop. When rightIsDestructive is true, it currently renders a non-destructive style with a checkmark icon. Conversely, when rightIsDestructive is false, it renders a destructive style with a trash icon. This may lead to unintended behavior where destructive actions are not properly indicated.

Apply this diff to correct the logic:

- if (rightIsDestructive) {
-   return (
-     <RectButton style={styles.rightAction} onPress={onRightActionPress}>
-       <Icon
-         icon="checkmark"
-         color={themedInversePrimaryColor}
-         size={PictoSizes.swipableItem}
-       />
-     </RectButton>
-   );
- } else {
-   return (
-     <RectButton
-       style={styles.rightActionRed}
-       onPress={onRightActionPress}
-     >
-       <Icon icon="trash" color="white" size={PictoSizes.swipableItem} />
-     </RectButton>
-   );
- }
+ if (rightIsDestructive) {
+   return (
+     <RectButton
+       style={styles.rightActionRed}
+       onPress={onRightActionPress}
+     >
+       <Icon icon="trash" color="white" size={PictoSizes.swipableItem} />
+     </RectButton>
+   );
+ } else {
+   return (
+     <RectButton style={styles.rightAction} onPress={onRightActionPress}>
+       <Icon
+         icon="checkmark"
+         color={themedInversePrimaryColor}
+         size={PictoSizes.swipableItem}
+       />
+     </RectButton>
+   );
+ }
📝 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.

      const renderRightActions = useCallback(() => {
        if (rightIsDestructive) {
          return (
            <RectButton
              style={styles.rightActionRed}
              onPress={onRightActionPress}
            >
              <Icon icon="trash" color="white" size={PictoSizes.swipableItem} />
            </RectButton>
          );
        } else {
          return (
            <RectButton style={styles.rightAction} onPress={onRightActionPress}>
              <Icon
                icon="checkmark"
                color={themedInversePrimaryColor}
                size={PictoSizes.swipableItem}
              />
            </RectButton>
          );
        }
      }, [
        rightIsDestructive,
        styles.rightAction,
        styles.rightActionRed,
        onRightActionPress,
        themedInversePrimaryColor,
      ]);
components/V3GroupConversationListItem.tsx (5)

47-47: ⚠️ Potential issue

Ensure currentAccount is not null before usage

The usage of useCurrentAccount() assumes currentAccount is always available by using the non-null assertion operator !. If currentAccount can be null or undefined, this may lead to runtime errors.

Apply this diff to add a null check:

-const currentAccount = useCurrentAccount()!;
+const currentAccount = useCurrentAccount();
+if (!currentAccount) {
+  // Handle the null case appropriately, e.g., show an error or redirect
+  return null;
+}

Ensure this check is applied at both occurrences in the code.

Also applies to: 274-274


283-285: ⚠️ Potential issue

Handle asynchronous operations properly with await

The functions Haptics.mediumImpactAsync() and Haptics.successNotificationAsync() are asynchronous but are not being awaited. Not awaiting these promises may lead to unhandled promise rejections or unexpected behavior.

Apply this diff to properly await the asynchronous calls:

const triggerHapticFeedback = useCallback(() => {
-  return Haptics.mediumImpactAsync();
+  Haptics.mediumImpactAsync();
}, []);

const onWillLeftSwipe = useCallback(() => {
-  Haptics.successNotificationAsync();
+  Haptics.successNotificationAsync();
}, []);

Alternatively, if you want to handle any potential errors:

const triggerHapticFeedback = useCallback(async () => {
+  try {
     await Haptics.mediumImpactAsync();
+  } catch (error) {
+    console.error('Haptic feedback failed', error);
+  }
}, []);

const onWillLeftSwipe = useCallback(async () => {
+  try {
     await Haptics.successNotificationAsync();
+  } catch (error) {
+    console.error('Haptic feedback failed', error);
+  }
}, []);

Also applies to: 308-309


143-146: ⚠️ Potential issue

Await asynchronous action functions in callbacks

In the showActionSheetWithOptions callbacks within handleDelete and handleRestore, the selected actions may be asynchronous, but they are not being awaited. This could result in unhandled promise rejections or actions not completing as expected.

Modify the callbacks to await the actions:

showActionSheetWithOptions(
  // ...existing code...
-  async (selectedIndex?: number) => {
+  (selectedIndex?: number) => {
     if (selectedIndex !== undefined && selectedIndex < actions.length) {
-      actions[selectedIndex]();
+      const actionResult = actions[selectedIndex]();
+      if (actionResult instanceof Promise) {
+        actionResult.catch(error => console.error('Action failed', error));
+      }
     }
   }
);

Or make the callback function async and use await:

showActionSheetWithOptions(
  // ...existing code...
  async (selectedIndex?: number) => {
     if (selectedIndex !== undefined && selectedIndex < actions.length) {
-      actions[selectedIndex]();
+      await actions[selectedIndex]();
     }
   }
);

Ensure this change is applied to both handleDelete and handleRestore.

Also applies to: 190-193


202-204: ⚠️ Potential issue

Correct the pinning functionality to avoid overwriting existing pins

The setPinnedConversations([topic]) action overwrites the existing list of pinned conversations with a new array containing only the current topic. This could unintentionally remove previously pinned conversations.

Apply this diff to append the current topic to the existing list:

-action: () => {
-  setPinnedConversations([topic]);
-  closeContextMenu();
+action: () => {
+  setPinnedConversations((prevPinned) => [...prevPinned, topic]);
+  closeContextMenu();
📝 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.

          setPinnedConversations((prevPinned) => [...prevPinned, topic]);
          closeContextMenu();
        },

288-289: 🛠️ Refactor suggestion

Review the usage of runOnJS for calling JavaScript functions

The runOnJS function is intended to be used within Reanimated worklets to call JavaScript functions. If onLongPress is not a worklet, using runOnJS is unnecessary and may cause confusion.

Consider removing runOnJS to directly call the functions:

-runOnJS(triggerHapticFeedback)();
-runOnJS(showContextMenu)();
+triggerHapticFeedback();
+showContextMenu();
📝 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.

    triggerHapticFeedback();
    showContextMenu();
android/app/src/main/java/com/converse/PushNotificationsService.kt (2)

123-135: ⚠️ Potential issue

Ensure proper comparison of NotificationDataResult instances

Comparing result with a new instance of NotificationDataResult() using != may not reliably determine if result contains meaningful data. This comparison checks for instance equality rather than content equality.

Consider implementing an isEmpty() method or overriding the equals() method in NotificationDataResult to provide a more accurate comparison.


103-107: ⚠️ Potential issue

Potential issue with timestamp calculation and incorrect comment

  • Timestamp conversion concern: The assignment timestampNs = notificationData.timestampNs.toLong() / 1_000_000 divides the timestamp by 1,000,000, converting nanoseconds to milliseconds. However, if timestampNs expects nanoseconds, this division might lead to incorrect timestamp values. Verify whether this conversion is necessary or if the original nanosecond value should be used.

  • Incorrect comment correction: The comment // Convert ByteString to byte array is misleading. The code actually converts a ByteArray to a ByteString using .toByteString(). Update the comment to accurately describe the operation.

Apply this diff to correct the comment:

- message = encryptedMessageData.toByteString()// Convert ByteString to byte array
+ message = encryptedMessageData.toByteString() // Convert ByteArray to ByteString

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

components/Chat/Frame/FramePreview.tsx (1)

7-415: 🛠️ Refactor suggestion

Consider removing commented-out code to improve maintainability.

The entire code from lines 7 to 415 is commented out, effectively disabling the FramePreview component. Keeping large blocks of commented code can clutter the codebase and hinder readability. If this code is no longer needed, consider removing it entirely since version control systems preserve the history. If it's temporarily commented out during refactoring, add a comment explaining its purpose and anticipated reintroduction.

android/app/src/main/java/com/converse/xmtp/Messages.kt (5)

99-99: ⚠️ Potential issue

Verify parameters in handleMessageByContentType call

The function call at line 99 seems to have an extra trailing comma or may be missing parameters. This could lead to a syntax error or unintended behavior.

Ensure that the parameters are correctly specified:

 val decodedMessageResult = handleMessageByContentType(
     appContext,
     decodedMessage,
     xmtpClient
-    ,
 )

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


62-62: ⚠️ Potential issue

Add exception handling around conversation.sync()

The call to conversation.sync() may throw exceptions due to network issues or other errors. Without handling these exceptions, the app may crash unexpectedly.

Wrap the conversation.sync() call in a try-catch block to handle potential exceptions gracefully:

 try {
     conversation.sync()
+} catch (e: Exception) {
+    Log.e(TAG, "Error syncing conversation: ${e.message}", e)
+    // Consider returning an appropriate NotificationDataResult or rethrowing the exception
+    return NotificationDataResult()
 }

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


369-370: ⚠️ Potential issue

Add exception handling when synchronizing consent

Calls to xmtpClient.syncConsent() and accessing xmtpClient.preferences.consentList may throw exceptions due to network errors or data issues.

Wrap these calls in a try-catch block to handle potential exceptions and maintain application stability:

 try {
     xmtpClient.syncConsent()
     val consentList = xmtpClient.preferences.consentList
+} catch (e: Exception) {
+    Log.e(TAG, "Error syncing consent: ${e.message}", e)
+    // Handle the exception or return an appropriate result
+    return NotificationDataResult(shouldShowNotification = false)
 }
📝 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.

        try {
            xmtpClient.syncConsent()
            val consentList = xmtpClient.preferences.consentList
        } catch (e: Exception) {
            Log.e(TAG, "Error syncing consent: ${e.message}", e)
            // Handle the exception or return an appropriate result
            return NotificationDataResult(shouldShowNotification = false)
        }

92-94: ⚠️ Potential issue

Prevent potential IndexOutOfBoundsException when accessing addresses

When accessing addresses.get(0), there is a risk of an IndexOutOfBoundsException if addresses is empty. To ensure robustness, use firstOrNull() instead.

Apply this diff to safely access the first address:

 dm.members().firstOrNull { it.inboxId == decodedMessage.senderAddress }?.addresses
-    ?.get(0)
+    ?.firstOrNull()
     ?.let { senderAddress ->
         decodedMessage.senderAddress = senderAddress
     }
📝 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.

    dm.members().firstOrNull { it.inboxId == decodedMessage.senderAddress }?.addresses?.firstOrNull()?.let { senderAddress ->
        decodedMessage.senderAddress = senderAddress
    }

142-143: ⚠️ Potential issue

Prevent potential NullPointerException when accessing group members

In handleGroupMessage, when accessing group.members(), there is a risk of a NullPointerException if the group or its members are null.

Ensure that you safely access the group members:

 val group = convoGroup.group
+if (group == null) {
+    Log.e(TAG, "Group is null in handleGroupMessage")
+    return NotificationDataResult()
+}
 group.members()?.firstOrNull { it.inboxId == decodedMessage.senderAddress }?.addresses
     ?.firstOrNull()
     ?.let { senderAddress ->
         decodedMessage.senderAddress = senderAddress
     }

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

components/Chat/Message/V3Message.tsx (3)

272-273: ⚠️ Potential issue

Remove unreachable code after throw statement

The return null; statement after throw new Error("Unknown message type"); is unreachable because the throw will exit the function. This line should be removed to clean up the code.

Apply this diff to remove the unreachable code:

        throw new Error("Unknown message type");
-       return null;
📝 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.

    throw new Error("Unknown message type");
🧰 Tools
🪛 Biome (1.9.4)

[error] 273-273: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)


95-111: 🛠️ Refactor suggestion

Remove unnecessary empty useEffect

An empty useEffect with dependencies does nothing and can be safely removed to clean up the code. If you plan to add functionality later, consider adding a TODO comment to indicate future implementation.

Apply this diff to remove the empty useEffect:

-        useEffect(() => {
-          // const unsubscribe = subscribeToGroupMessages({
-          //   account: currentAccount,
-          //   topic,
-          //   callback: ({ data }) => {
-          //     if (data) {
-          //       const message = data.byId[messageId];
-          //       const nextMessage = data.byId[nextMessageId];
-          //       const previousMessage = data.byId[previousMessageId];
-          //       // If the updated at changed, update the message
-          //     }
-          //   },
-          // });
-          // return () => {
-          //   unsubscribe();
-          // };
-        }, [currentAccount, topic, messageId, nextMessageId, previousMessageId]);
📝 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.



330-331: ⚠️ Potential issue

Use dynamic 'fromMe' value instead of hardcoded true

In RemoteAttachmentMessage, the fromMe prop is hardcoded to true, which may not accurately represent whether the message is from the current user. You should use the fromMe value derived from the message context to ensure correct rendering.

Apply this diff to use the dynamic fromMe value:

-            <MessageContainer fromMe={true}>
-              <MessageContentContainer fromMe={true}>
+            <MessageContainer fromMe={fromMe}>
+              <MessageContentContainer fromMe={fromMe}>

Ensure you obtain fromMe from the message context or pass it as a prop to RemoteAttachmentMessage.

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

Added context menu store
Added handling for context menus with pinned conversations
Fixed dark mode messages
Fixed timestamps
Fixed Unread
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: 14

🧹 Outside diff range and nitpick comments (19)
components/PinnedConversations/PinnedV3GroupConversation.tsx (3)

24-26: Consider moving utility function to shared location.

If closeContextMenu is used across multiple components, consider moving it to a shared utilities file for better reusability.


125-133: Improve title fallback handling.

Consider using a more meaningful default for the title when it's undefined.

-      title={title ?? ""}
+      title={title ?? translate("unnamed_group")}

28-134: Consider adding error boundary.

As this component is part of the V3 migration and handles complex group conversation logic, consider wrapping it with an error boundary to gracefully handle potential runtime errors.

This would prevent the entire conversation list from crashing if there are issues with a single pinned conversation.

components/PinnedConversations/PinnedV3DMConversation.tsx (2)

25-28: Consider memoizing the closeContextMenu function.

Since this function is used in multiple context menu item callbacks, consider memoizing it with useCallback to prevent unnecessary recreations.

-const closeContextMenu = () => {
+const closeContextMenu = useCallback(() => {
   resetConversationListContextMenuStore();
-};
+}, []);

113-133: Consider a more meaningful fallback for the title prop.

Instead of falling back to an empty string when the title is undefined, consider using a more meaningful placeholder.

-      title={title ?? ""}
+      title={title ?? translate("unnamed_conversation")}
components/Conversation/V3Conversation.tsx (3)

7-7: Remove commented out import

The commented import DmChatPlaceholder appears to be unused. Either remove it or use it in the TODO placeholders at lines 119 and 124.


180-181: Address performance optimization TODOs and type issues

Several performance optimizations are commented out:

  1. The @ts-ignore comment lacks explanation
  2. The estimatedItemSize is TODO'd
  3. maintainVisibleContentPosition is disabled for Android

Consider implementing these optimizations or document why they're disabled.

Also applies to: 189-199


301-303: Consider making the greeting message configurable

The hardcoded "👋" emoji might not be appropriate for all contexts or cultures. Consider making this configurable or using a translation key.

  sendMessage({
-   text: "👋",
+   text: translate("default_greeting"),
  });
components/V3DMListItem.tsx (4)

175-178: Remove empty callbacks to clean up the code.

The callbacks onWillRightSwipe and onRightSwipe are defined but contain empty functions. If these callbacks are optional in ConversationListItemDumb, you can omit them to reduce clutter.

Apply this diff to remove unnecessary callbacks:

- const onWillRightSwipe = useCallback(() => {}, []);
- const onRightSwipe = useCallback(() => {}, []);

And remove them from the component props:

<ConversationListItemDumb
  ref={ref}
  onPress={onPress}
  onLongPress={onLongPress}
  onLeftSwipe={onLeftSwipe}
- onRightSwipe={onRightSwipe}
  onWillLeftSwipe={onWillLeftSwipe}
- onWillRightSwipe={onWillRightSwipe}
  leftActionIcon={leftActionIcon}
  // ... other props
/>

128-134: Include translate in the dependency array of useMemo.

The translate function is used within the useMemo hook but is not included in the dependency array. If translate can change (e.g., when changing the app's language), the memoized value may not update correctly. Ensure all dependencies are included to maintain consistent behavior.

Apply this diff to update the dependency array:

const contextMenuItems = useMemo(
  () => [
    {
      title: translate("pin"),
      // ... other properties
    },
    // ... other menu items
  ],
  [
    topic,
    setPinnedConversations,
    handleDelete,
    closeContextMenu,
    isUnread,
    toggleReadStatus,
+   translate,
  ]
);

184-184: Remove commented-out prop onRightActionPress.

The prop onRightActionPress is commented out in the JSX. If it's no longer needed, consider removing it to keep the code clean. If it will be used later, add a TODO comment explaining its future implementation.

Apply this diff to remove the commented line:

<ConversationListItemDumb
  ref={ref}
  onPress={onPress}
- // onRightActionPress={onRightPress}
  onLongPress={onLongPress}
  // ... other props
/>

67-72: Optimize parameters passed to useConversationIsUnread.

Both conversation and conversation.lastMessage are passed to useConversationIsUnread. If lastMessage can be accessed from conversation within the hook, consider passing only conversation to reduce redundancy.

components/ConversationContextMenu.tsx (3)

41-44: Consider the impact of using context instead of props on component reusability

By fetching isVisible, conversationTopic, and contextMenuItems directly from context hooks within ConversationContextMenuComponent, the component becomes tightly coupled to the context implementation. This tight coupling may reduce reusability and make unit testing more challenging, as the component now depends on the presence of specific context providers.

Consider passing these values as props to maintain loose coupling. This approach enhances the component's reusability in different contexts and makes it easier to mock data during testing.


92-97: Add resetConversationListContextMenuStore to the dependency array

In the closeMenu function, you're using resetConversationListContextMenuStore inside a useCallback hook. To ensure that the callback updates correctly if resetConversationListContextMenuStore changes, it's best practice to include it in the dependency array.

Consider modifying the dependency array as follows:

}, [height, translateY]);
+}, [height, translateY, resetConversationListContextMenuStore]);

143-145: Avoid unnecessary type assertions with as ConversationTopic

Using a type assertion like conversationTopic as ConversationTopic can mask potential type mismatches and may lead to runtime errors if conversationTopic is undefined or of an unexpected type. Instead, consider ensuring that conversationTopic is correctly typed when it's obtained from the context.

For example, you can adjust the context hook to provide a default value or handle the case when conversationTopic might be undefined:

const conversationTopic = useConversationListContextMenuConversationTopic()!;

Or add conditional rendering to handle undefined values:

{conversationTopic && (
  <ConversationReadOnly topic={conversationTopic} />
)}

This approach enhances type safety and reduces the likelihood of runtime errors.

components/V3GroupConversationListItem.tsx (1)

205-213: Update the id of the context menu item to match the action

In the context menu items, the id for marking as read/unread is statically set to "markAsUnread", even though the title and action change based on the isUnread state. For clarity and consistency, update the id to reflect the actual action.

Apply this change:

          {
            title: isUnread
              ? translate("mark_as_read")
              : translate("mark_as_unread"),
            action: () => {
              toggleReadStatus();
              closeContextMenu();
            },
-           id: "markAsUnread",
+           id: isUnread ? "markAsRead" : "markAsUnread",
          },

This modification ensures that the id accurately represents the action being performed.

components/ConversationListItem/ConversationListItemDumb.tsx (3)

237-246: Remove unused itemRect and containerRef

The shared value itemRect and the containerRef are not used elsewhere in the code. The onLayoutView function updates itemRect, but since it's unused, this code can be safely removed to simplify the component.

Apply this diff to remove the unused code:

- const itemRect = useSharedValue({ x: 0, y: 0, width: 0, height: 0 });
- const containerRef = useAnimatedRef<View>();
-
- const onLayoutView = useCallback(
-   (event: LayoutChangeEvent) => {
-     const { x, y, width, height } = event.nativeEvent.layout;
-     itemRect.value = { x, y, width, height };
-   },
-   [itemRect]
- );

...

- <Animated.View ref={containerRef} onLayout={onLayoutView}>
+ <View>

Also applies to: 331-331


386-386: Disable left swipe actions properly

Setting leftThreshold={10000} to prevent left swipe actions is a workaround that may not be reliable. If left swipe actions are not intended, consider removing the renderLeftActions prop to disable them completely.

Apply this diff to remove left swipe actions:

<Swipeable
-  renderLeftActions={renderLeftActions}
-  leftThreshold={10000} // Never trigger opening
   overshootFriction={4}
   ref={swipeableRef}
   onSwipeableWillClose={onSwipeableWillClose}
   onSwipeableClose={onSwipeableClose}
   hitSlop={{ left: -6 }}
>

Also, remove the renderLeftActions function and any related props like onLeftSwipe and onWillLeftSwipe if they are no longer needed.


39-39: Make leftActionIcon prop optional

Since the left swipe actions are disabled, the leftActionIcon prop is no longer used and can be made optional in the ConversationListItemDumbProps to reflect its usage.

Apply this diff to update the prop type:

- leftActionIcon: IIconName;
+ leftActionIcon?: IIconName;

Also applies to: 216-216

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cac75a9 and b46a58a.

📒 Files selected for processing (7)
  • components/Conversation/V3Conversation.tsx (1 hunks)
  • components/ConversationContextMenu.tsx (5 hunks)
  • components/ConversationListItem/ConversationListItemDumb.tsx (1 hunks)
  • components/PinnedConversations/PinnedV3DMConversation.tsx (1 hunks)
  • components/PinnedConversations/PinnedV3GroupConversation.tsx (1 hunks)
  • components/V3DMListItem.tsx (1 hunks)
  • components/V3GroupConversationListItem.tsx (1 hunks)
🔇 Additional comments (6)
components/PinnedConversations/PinnedV3GroupConversation.tsx (3)

1-22: LGTM! Well-organized imports and clear type definition.

The imports are logically grouped and the props interface is well-defined.


61-101: LGTM! Well-implemented context menu with proper memoization.

The context menu implementation follows React best practices with appropriate use of useMemo and useCallback. Dependencies are correctly specified and actions are well-organized.


103-105: Verify the hardcoded showUnread flag.

The showUnread flag is hardcoded to false despite having an isUnread state. This might be preventing unread status indicators from appearing.

components/PinnedConversations/PinnedV3DMConversation.tsx (3)

1-23: LGTM! Well-organized imports and clear type definition.

The imports are logically grouped and the props interface is well-defined with proper typing.


70-100: LGTM! Well-implemented context menu with proper memoization.

The context menu items are well-structured with unique IDs, clear actions, and proper translation usage. The memoization dependencies are correctly specified.


102-112: Verify the hardcoded showUnread value.

The showUnread flag is hardcoded to false while there's an isUnread state being calculated. This seems inconsistent with the component's purpose.

✅ Verification successful

Hardcoded showUnread is intentional, no changes needed

The hardcoded showUnread = false is correct. While there is an isUnread state being calculated, it's used for different purposes - specifically for context menu actions (marking conversations as read/unread) and not for visual indication in pinned conversations. This is evident from the codebase where:

  1. The isUnread state is used in the context menu to toggle read status
  2. Pinned conversations consistently set showUnread to false, as seen in both PinnedV3DMConversation and PinnedV3GroupConversation
  3. The showUnreadOnConversation utility is used elsewhere in the application for non-pinned conversations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of showUnread prop in PinnedConversation
rg -A 5 "showUnread.*?=.*?(true|false)" --type typescript

Length of output: 94


Script:

#!/bin/bash
# Search for showUnread usage in the codebase
rg "showUnread" --type ts --type tsx -A 5

# Also search for the component definition to understand its props
ast-grep --pattern 'interface $_ {
  $$$
  showUnread$_
  $$$
}'

Length of output: 136


Script:

#!/bin/bash
# Search for showUnread usage without file type restrictions
rg "showUnread" -A 5

# Look for the component's interface/type definition
rg "PinnedV3DMConversation" -A 10

Length of output: 6815


Script:

#!/bin/bash
# Search for isUnread usage in the component and its utility function
rg "isUnread" -A 5 components/PinnedConversations/PinnedV3DMConversation.tsx

# Look for the showUnreadOnConversation utility implementation
cat utils/conversation/showUnreadOnConversation.ts

Length of output: 1706

export const PinnedV3GroupConversation = ({
group,
}: PinnedV3GroupConversationProps) => {
const currentAccount = useCurrentAccount()!;
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

Replace non-null assertion with proper type guard.

The non-null assertion (!) on currentAccount could lead to runtime errors if the account is not available.

Consider adding a type guard:

-const currentAccount = useCurrentAccount()!;
+const currentAccount = useCurrentAccount();
+if (!currentAccount) {
+  return null; // or handle the no-account case appropriately
+}
📝 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 currentAccount = useCurrentAccount();
if (!currentAccount) {
return null; // or handle the no-account case appropriately
}

Comment on lines +29 to +69
export const PinnedV3DMConversation = ({
conversation,
}: PinnedV3DMConversationProps) => {
const currentAccount = useCurrentAccount()!;

const topic = conversation.topic;

const { data: peerInboxId } = useDmPeerInboxOnConversationList(
currentAccount,
conversation
);

const preferredName = usePreferredInboxName(peerInboxId);

const preferredAvatar = usePreferredInboxAvatar(peerInboxId);

const { setPinnedConversations } = useChatStore(
useSelect(["setPinnedConversations"])
);

const timestamp = conversation?.lastMessage?.sentNs ?? 0;

const isUnread = useConversationIsUnread({
topic,
lastMessage: conversation.lastMessage,
conversation: conversation,
timestamp,
});

const toggleReadStatus = useToggleReadStatus({
topic,
isUnread,
currentAccount,
});

const handleDelete = useHandleDeleteDm({
topic,
preferredName,
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

Consider safer alternatives to non-null assertion.

The non-null assertion on currentAccount could lead to runtime errors if the account is not available.

-const currentAccount = useCurrentAccount()!;
+const currentAccount = useCurrentAccount();
+if (!currentAccount) {
+  return null; // or handle the no-account case appropriately
+}

Simplify useConversationIsUnread props.

The hook receives redundant information since lastMessage is already part of the conversation object.

 const isUnread = useConversationIsUnread({
   topic,
-  lastMessage: conversation.lastMessage,
   conversation: conversation,
   timestamp,
 });
📝 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 PinnedV3DMConversation = ({
conversation,
}: PinnedV3DMConversationProps) => {
const currentAccount = useCurrentAccount()!;
const topic = conversation.topic;
const { data: peerInboxId } = useDmPeerInboxOnConversationList(
currentAccount,
conversation
);
const preferredName = usePreferredInboxName(peerInboxId);
const preferredAvatar = usePreferredInboxAvatar(peerInboxId);
const { setPinnedConversations } = useChatStore(
useSelect(["setPinnedConversations"])
);
const timestamp = conversation?.lastMessage?.sentNs ?? 0;
const isUnread = useConversationIsUnread({
topic,
lastMessage: conversation.lastMessage,
conversation: conversation,
timestamp,
});
const toggleReadStatus = useToggleReadStatus({
topic,
isUnread,
currentAccount,
});
const handleDelete = useHandleDeleteDm({
topic,
preferredName,
conversation,
});
export const PinnedV3DMConversation = ({
conversation,
}: PinnedV3DMConversationProps) => {
const currentAccount = useCurrentAccount();
if (!currentAccount) {
return null; // or handle the no-account case appropriately
}
const topic = conversation.topic;
const { data: peerInboxId } = useDmPeerInboxOnConversationList(
currentAccount,
conversation
);
const preferredName = usePreferredInboxName(peerInboxId);
const preferredAvatar = usePreferredInboxAvatar(peerInboxId);
const { setPinnedConversations } = useChatStore(
useSelect(["setPinnedConversations"])
);
const timestamp = conversation?.lastMessage?.sentNs ?? 0;
const isUnread = useConversationIsUnread({
topic,
conversation: conversation,
timestamp,
});
const toggleReadStatus = useToggleReadStatus({
topic,
isUnread,
currentAccount,
});
const handleDelete = useHandleDeleteDm({
topic,
preferredName,
conversation,
});

Comment on lines +246 to +248
navigation.setOptions({
headerTitle: () => <NewConversationTitle peerAddress={peerAddress!} />,
});
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

Remove non-null assertions in header hooks

The header hooks use non-null assertions (!) for peerAddress and topic. Consider adding proper type guards or error handling.

- headerTitle: () => <NewConversationTitle peerAddress={peerAddress!} />,
+ headerTitle: () => peerAddress ? <NewConversationTitle peerAddress={peerAddress} /> : null,

Also applies to: 259-260, 271-272

Comment on lines +211 to +213
const messages = getCurrentConversationMessages()!;

return (
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 getCurrentConversationMessages

The code assumes getCurrentConversationMessages() will return a non-null value but doesn't handle the error case despite the non-null assertion.

- const messages = getCurrentConversationMessages()!;
+ const messages = getCurrentConversationMessages();
+ if (!messages) {
+   return null;
+ }
📝 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 messages = getCurrentConversationMessages()!;
return (
const messages = getCurrentConversationMessages();
if (!messages) {
return null;
}
return (

Comment on lines +118 to +126
if (conversationNotFound) {
// TODO: Add DM placeholder
return null;
}

if (messages?.ids.length === 0 && !messagesLoading) {
// TODO: Add DM placeholder
return null;
}
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

Implement DM placeholders and loading states

The DM content has two TODO comments for placeholders but returns null in both cases. This could lead to a poor user experience. Consider:

  1. Implementing proper loading states
  2. Using appropriate placeholder components
  if (conversationNotFound) {
-   // TODO: Add DM placeholder
-   return null;
+   return (
+     <Center>
+       <Text>{translate("dm_not_found")}</Text>
+     </Center>
+   );
  }

  if (messages?.ids.length === 0 && !messagesLoading) {
-   // TODO: Add DM placeholder
-   return null;
+   return (
+     <Center>
+       <Text>{translate("dm_empty")}</Text>
+     </Center>
+   );
  }

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

Comment on lines +164 to +192
async () => {
await group.updateConsent("allowed");
},
async () => {
await group.updateConsent("allowed");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "allow",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "allowed",
});
},
];
showActionSheetWithOptions(
{
options,
cancelButtonIndex: options.length - 1,
title,
...actionSheetColors(colorScheme),
},
async (selectedIndex?: number) => {
if (selectedIndex !== undefined && selectedIndex < actions.length) {
actions[selectedIndex]();
}
}
);
}, [colorScheme, currentAccount, group, setInboxIdPeerStatus]);
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

Properly await asynchronous actions in handleRestore

In the handleRestore function, the actions array consists of asynchronous functions. When invoking actions[selectedIndex](), you should await the promises to ensure proper execution flow and error handling.

Modify the callback to await the asynchronous actions:

        showActionSheetWithOptions(
          {
            options,
            cancelButtonIndex: options.length - 1,
            title,
            ...actionSheetColors(colorScheme),
          },
          async (selectedIndex?: number) => {
            if (selectedIndex !== undefined && selectedIndex < actions.length) {
-             actions[selectedIndex]();
+             await actions[selectedIndex]().catch((error) => {
+               // Handle error appropriately
+               console.error("Action failed:", error);
+             });
            }
          }
        );

This ensures that any asynchronous operations complete before the function exits and errors are properly handled.

📝 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
async () => {
await group.updateConsent("allowed");
},
async () => {
await group.updateConsent("allowed");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "allow",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "allowed",
});
},
];
showActionSheetWithOptions(
{
options,
cancelButtonIndex: options.length - 1,
title,
...actionSheetColors(colorScheme),
},
async (selectedIndex?: number) => {
if (selectedIndex !== undefined && selectedIndex < actions.length) {
actions[selectedIndex]();
}
}
);
}, [colorScheme, currentAccount, group, setInboxIdPeerStatus]);
async () => {
await group.updateConsent("allowed");
},
async () => {
await group.updateConsent("allowed");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "allow",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "allowed",
});
},
];
showActionSheetWithOptions(
{
options,
cancelButtonIndex: options.length - 1,
title,
...actionSheetColors(colorScheme),
},
async (selectedIndex?: number) => {
if (selectedIndex !== undefined && selectedIndex < actions.length) {
await actions[selectedIndex]().catch((error) => {
// Handle error appropriately
console.error("Action failed:", error);
});
}
}
);
}, [colorScheme, currentAccount, group, setInboxIdPeerStatus]);

🛠️ Refactor suggestion

Add error handling to asynchronous actions in handleRestore

Similarly, the asynchronous actions in the actions array of handleRestore lack error handling. This could lead to unhandled promise rejections if any operation fails.

Include error handling using try-catch blocks:

          async () => {
+           try {
              await group.updateConsent("allowed");
+           } catch (error) {
+             // Handle error appropriately
+             console.error("Failed to restore:", error);
+           }
          },
          async () => {
+           try {
              await group.updateConsent("allowed");
              await consentToInboxIdsOnProtocolByAccount({
                account: currentAccount,
                inboxIds: [group.addedByInboxId],
                consent: "allow",
              });
              setInboxIdPeerStatus({
                [group.addedByInboxId]: "allowed",
              });
+           } catch (error) {
+             // Handle error appropriately
+             console.error("Failed to restore and unblock inviter:", error);
+           }
          },

This ensures robustness by handling any errors that may occur during the asynchronous operations.

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

Comment on lines +95 to +145
const actions = [
() => {
saveTopicsData(currentAccount, {
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
}),
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
},
async () => {
saveTopicsData(currentAccount, {
[topic]: { status: "deleted" },
});
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
await group.updateConsent("denied");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "deny",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "denied",
});
},
];
// TODO: Implement
showActionSheetWithOptions(
{
options,
cancelButtonIndex: options.length - 1,
destructiveButtonIndex: [0, 1],
title,
...actionSheetColors(colorScheme),
},
async (selectedIndex?: number) => {
if (selectedIndex !== undefined && selectedIndex < actions.length) {
actions[selectedIndex]();
}
}
);
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 proper handling of asynchronous actions in handleDelete

In the handleDelete function, the actions array contains both synchronous and asynchronous functions. When invoking actions[selectedIndex](), the asynchronous functions are not awaited, which may lead to unhandled promise rejections or unexpected behavior.

Consider modifying the callback to properly handle asynchronous actions:

        showActionSheetWithOptions(
          {
            options,
            cancelButtonIndex: options.length - 1,
            destructiveButtonIndex: [0, 1],
            title,
            ...actionSheetColors(colorScheme),
          },
-         async (selectedIndex?: number) => {
+         (selectedIndex?: number) => {
            if (selectedIndex !== undefined && selectedIndex < actions.length) {
-             actions[selectedIndex]();
+             const result = actions[selectedIndex]();
+             if (result instanceof Promise) {
+               result.catch((error) => {
+                 // Handle error appropriately
+                 console.error("Action failed:", error);
+               });
+             }
            }
          }
        );

Alternatively, ensure all actions are asynchronous and await them:

        showActionSheetWithOptions(
          {
            options,
            cancelButtonIndex: options.length - 1,
            destructiveButtonIndex: [0, 1],
            title,
            ...actionSheetColors(colorScheme),
          },
          async (selectedIndex?: number) => {
            if (selectedIndex !== undefined && selectedIndex < actions.length) {
-             actions[selectedIndex]();
+             await actions[selectedIndex]().catch((error) => {
+               // Handle error appropriately
+               console.error("Action failed:", error);
+             });
            }
          }
        );

Ensure that all functions in the actions array are asynchronous and handle any errors appropriately.

📝 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 actions = [
() => {
saveTopicsData(currentAccount, {
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
}),
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
},
async () => {
saveTopicsData(currentAccount, {
[topic]: { status: "deleted" },
});
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
await group.updateConsent("denied");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "deny",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "denied",
});
},
];
// TODO: Implement
showActionSheetWithOptions(
{
options,
cancelButtonIndex: options.length - 1,
destructiveButtonIndex: [0, 1],
title,
...actionSheetColors(colorScheme),
},
async (selectedIndex?: number) => {
if (selectedIndex !== undefined && selectedIndex < actions.length) {
actions[selectedIndex]();
}
}
);
const actions = [
() => {
saveTopicsData(currentAccount, {
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
}),
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
},
async () => {
saveTopicsData(currentAccount, {
[topic]: { status: "deleted" },
});
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
await group.updateConsent("denied");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "deny",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "denied",
});
},
];
// TODO: Implement
showActionSheetWithOptions(
{
options,
cancelButtonIndex: options.length - 1,
destructiveButtonIndex: [0, 1],
title,
...actionSheetColors(colorScheme),
},
async (selectedIndex?: number) => {
if (selectedIndex !== undefined && selectedIndex < actions.length) {
await actions[selectedIndex]().catch((error) => {
// Handle error appropriately
console.error("Action failed:", error);
});
}
}
);

Comment on lines +96 to +130
() => {
saveTopicsData(currentAccount, {
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
}),
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
},
async () => {
saveTopicsData(currentAccount, {
[topic]: { status: "deleted" },
});
setTopicsData({
[topic]: {
status: "deleted",
timestamp: new Date().getTime(),
},
});
await group.updateConsent("denied");
await consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [group.addedByInboxId],
consent: "deny",
});
setInboxIdPeerStatus({
[group.addedByInboxId]: "denied",
});
},
];
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

Add error handling to asynchronous actions in handleDelete

The asynchronous functions within the actions array lack error handling. If any promise rejects, it could lead to unhandled promise rejections.

Wrap the asynchronous code in try-catch blocks to handle potential errors gracefully:

          async () => {
+           try {
              saveTopicsData(currentAccount, {
                [topic]: { status: "deleted" },
              });
              setTopicsData({
                [topic]: {
                  status: "deleted",
                  timestamp: new Date().getTime(),
                },
              });
              await group.updateConsent("denied");
              await consentToInboxIdsOnProtocolByAccount({
                account: currentAccount,
                inboxIds: [group.addedByInboxId],
                consent: "deny",
              });
              setInboxIdPeerStatus({
                [group.addedByInboxId]: "denied",
              });
+           } catch (error) {
+             // Handle error appropriately
+             console.error("Failed to delete and block:", error);
+           }
          },

Ensure that each asynchronous action within the actions array includes error handling.

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


const renderLeftActions = useCallback(() => {
return (
<RectButton style={styles.leftAction}>
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

Attach onLeftActionPress to the left action button

The left action button in renderLeftActions lacks an onPress handler, so user interactions won't trigger any action. Since onLeftActionPress is provided as a prop, it should be connected to the button to handle left swipe actions.

Apply this diff to attach the onPress handler:

<RectButton
  style={styles.leftAction}
+ onPress={onLeftActionPress}
>
📝 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
<RectButton style={styles.leftAction}>
<RectButton style={styles.leftAction} onPress={onLeftActionPress}>

Comment on lines +302 to +327
if (rightIsDestructive) {
return (
<RectButton style={styles.rightAction} onPress={onRightActionPress}>
<Icon
icon="checkmark"
color={themedInversePrimaryColor}
size={PictoSizes.swipableItem}
/>
</RectButton>
);
} else {
return (
<RectButton
style={styles.rightActionRed}
onPress={onRightActionPress}
>
<Icon icon="trash" color="white" size={PictoSizes.swipableItem} />
</RectButton>
);
}
}, [
rightIsDestructive,
styles.rightAction,
styles.rightActionRed,
onRightActionPress,
themedInversePrimaryColor,
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 the rightIsDestructive logic to display appropriate swipe actions

The logic in renderRightActions appears inverted. When rightIsDestructive is true, it should display a destructive action (e.g., a trash icon with a red background). Currently, it shows a checkmark icon with a non-destructive style, which might confuse users.

Apply this diff to fix the logic:

const renderRightActions = useCallback(() => {
-  if (rightIsDestructive) {
+  if (!rightIsDestructive) {
     return (
-      <RectButton style={styles.rightAction} onPress={onRightActionPress}>
+      <RectButton style={styles.rightActionRed} onPress={onRightActionPress}>
         <Icon
-          icon="checkmark"
+          icon="trash"
           color={themedInversePrimaryColor}
           size={PictoSizes.swipableItem}
         />
       </RectButton>
     );
-  } else {
+  } else {
     return (
-      <RectButton style={styles.rightActionRed} onPress={onRightActionPress}>
+      <RectButton style={styles.rightAction} onPress={onRightActionPress}>
         <Icon
-          icon="trash"
+          icon="checkmark"
           color="white"
           size={PictoSizes.swipableItem}
         />
       </RectButton>
     );
   }
}, [
  rightIsDestructive,
  styles.rightAction,
  styles.rightActionRed,
  onRightActionPress,
  themedInversePrimaryColor,
]);

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

Added notification subscribing for V3 only
* feat: V3 Search

Added search for V3 Only

Added tests
Updated Snapshots

* remove checks
Removed async
Removed await

* remove inboxId check
@alexrisch alexrisch closed this Dec 2, 2024
@alexrisch alexrisch deleted the feature/v3-split branch December 2, 2024 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.0.0 Release 3.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants