From 5eb3e9e987af3e5a629a7a95ca9f6596e05ff34c Mon Sep 17 00:00:00 2001 From: Thierry Date: Mon, 9 Dec 2024 13:42:37 -0500 Subject: [PATCH 1/7] wip --- components/Chat/Message/message-utils.tsx | 3 +- components/Conversation/V3Conversation.tsx | 36 +- .../conversation/conversation-context.tsx | 84 ++-- .../conversation-persisted-stores.ts | 30 +- features/conversation/conversation-service.ts | 18 +- features/conversation/conversation-store.ts | 32 -- features/conversation/conversation-store.tsx | 76 ++++ .../conversation/dm-conversation.store.ts | 0 .../dm-conversation.screen.tsx | 55 +++ .../existing-dm-conversation-content.tsx | 91 +++++ .../new-dm-conversation-content.tsx} | 138 +------ .../group-conversation.screen.tsx | 386 ++++++++++++++++++ .../conversation/group-conversation.store.tsx | 0 .../components/V3ConversationFromPeer.tsx | 1 - queries/useConversationWithPeerQuery.ts | 13 +- screens/Conversation.tsx | 1 - screens/Navigation/Navigation.tsx | 10 +- utils/xmtpRN/send.ts | 17 - 18 files changed, 716 insertions(+), 275 deletions(-) delete mode 100644 features/conversation/conversation-store.ts create mode 100644 features/conversation/conversation-store.tsx create mode 100644 features/conversation/dm-conversation.store.ts create mode 100644 features/conversation/dm-conversation/dm-conversation.screen.tsx create mode 100644 features/conversation/dm-conversation/existing-dm-conversation-content.tsx rename features/conversation/{dm-conversation.screen.tsx => dm-conversation/new-dm-conversation-content.tsx} (60%) create mode 100644 features/conversation/group-conversation.screen.tsx create mode 100644 features/conversation/group-conversation.store.tsx delete mode 100644 utils/xmtpRN/send.ts diff --git a/components/Chat/Message/message-utils.tsx b/components/Chat/Message/message-utils.tsx index 4d6717878..26849a725 100644 --- a/components/Chat/Message/message-utils.tsx +++ b/components/Chat/Message/message-utils.tsx @@ -1,6 +1,7 @@ import { getCurrentConversationMessages, getCurrentConversationTopic, + useConversationCurrentTopic, } from "@/features/conversation/conversation-service"; import { getConversationMessageQueryOptions } from "@/queries/useConversationMessage"; import { useConversationMessages } from "@/queries/useConversationMessages"; @@ -221,7 +222,7 @@ export function useConversationMessageById(messageId: MessageId) { export function useConversationMessageReactions(messageId: MessageId) { const currentAccount = useCurrentAccount()!; - const topic = getCurrentConversationTopic()!; + const topic = useConversationCurrentTopic(); const { data: messages } = useConversationMessages(currentAccount, topic); diff --git a/components/Conversation/V3Conversation.tsx b/components/Conversation/V3Conversation.tsx index 76d0f4363..2d1cc0882 100644 --- a/components/Conversation/V3Conversation.tsx +++ b/components/Conversation/V3Conversation.tsx @@ -51,6 +51,7 @@ import Animated, { useAnimatedStyle, } from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { ConversationStoreProvider } from "@/features/conversation/conversation-store"; const keyExtractor = (item: string) => item; @@ -67,25 +68,36 @@ export const V3Conversation = ({ }: V3ConversationProps) => { // TODO: Handle when topic is not defined const messageToPrefill = textPrefill ?? ""; - initializeCurrentConversation({ - topic, - peerAddress, - inputValue: messageToPrefill, - }); + + // initializeCurrentConversation({ + // topic, + // peerAddress, + // inputValue: messageToPrefill, + // }); + + if (topic) { + // Existing conversation + } + + if (peerAddress) { + // New conversation + } return ( - - - - - + + + + + + + ); }; const Content = memo(function Content() { const { theme } = useAppTheme(); - const isNewConversation = useConversationContext("isNewConversation"); - const conversationVersion = useConversationContext("conversationVersion"); + // const isNewConversation = useConversationContext("isNewConversation"); + // const conversationVersion = useConversationContext("conversationVersion"); return ( diff --git a/features/conversation/conversation-context.tsx b/features/conversation/conversation-context.tsx index baf85c300..299f2e4d0 100644 --- a/features/conversation/conversation-context.tsx +++ b/features/conversation/conversation-context.tsx @@ -1,9 +1,7 @@ import { captureErrorWithToast } from "@/utils/capture-error"; import { useCurrentAccount } from "@data/store/accountsStore"; import { useConversationQuery } from "@queries/useConversationQuery"; -import { addConversationToConversationListQuery } from "@queries/useV3ConversationListQuery"; import { navigate } from "@utils/navigation"; -import { createConversationByAccount } from "@utils/xmtpRN/conversations"; import { ConversationId, ConversationVersion, @@ -11,13 +9,8 @@ import { RemoteAttachmentContent, } from "@xmtp/react-native-sdk"; import React, { useCallback, useEffect, useMemo } from "react"; -import { SharedValue, useSharedValue } from "react-native-reanimated"; import { createContext, useContextSelector } from "use-context-selector"; -import { - updateNewConversation, - useConversationCurrentPeerAddress, - useConversationCurrentTopic, -} from "./conversation-service"; +import { useConversationCurrentTopic } from "./conversation-service"; export type ISendMessageParams = { content: { @@ -33,13 +26,8 @@ export type ISendMessageParams = { export type IConversationContextType = { isAllowedConversation: boolean; isBlockedConversation: boolean; - isLoadingConversationConsent: boolean; - isNewConversation: boolean; conversationNotFound: boolean; - conversationVersion: ConversationVersion | null; conversationId: ConversationId | null; - peerAddress: string | null; - composerHeightAV: SharedValue; sendMessage: (message: ISendMessageParams) => Promise; reactOnMessage: (args: { messageId: MessageId; @@ -64,14 +52,11 @@ export const ConversationContextProvider = ( ) => { const { children } = props; - const topic = useConversationCurrentTopic(); - const peerAddress = useConversationCurrentPeerAddress(); + const topic = useConversationCurrentTopic()!; const currentAccount = useCurrentAccount()!; const { data: conversation, isLoading: isLoadingConversation } = - useConversationQuery(currentAccount, topic!); - - const composerHeightAV = useSharedValue(0); + useConversationQuery(currentAccount, topic); useEffect(() => { const checkActive = async () => { @@ -130,31 +115,35 @@ export const ConversationContextProvider = ( const sendMessage = useCallback( async ({ referencedMessageId, content }: ISendMessageParams) => { - const sendCallback = async (payload: any) => { - if (!conversation && !peerAddress) { - return; - } - - if (!conversation && peerAddress) { - const newConversation = await createConversationByAccount( - currentAccount, - peerAddress - ); - updateNewConversation(newConversation.topic); - await newConversation.send(payload); - addConversationToConversationListQuery( - currentAccount, - newConversation - ); - return; - } - - await conversation?.send(payload); - }; + // const sendCallback = async (payload: any) => { + // // if (!conversation && !peerAddress) { + // // return; + // // } + + // // if (!conversation && peerAddress) { + // // const newConversation = await createConversationByAccount( + // // currentAccount, + // // peerAddress + // // ); + // // updateNewConversation(newConversation.topic); + // // await newConversation.send(payload); + // // addConversationToConversationListQuery( + // // currentAccount, + // // newConversation + // // ); + // // return; + // // } + + // // await conversation?.send(payload); + // }; + + if (!conversation) { + return; + } if (referencedMessageId) { if (content.remoteAttachment) { - await sendCallback({ + await conversation.send({ reply: { reference: referencedMessageId, content: { remoteAttachment: content.remoteAttachment }, @@ -162,7 +151,7 @@ export const ConversationContextProvider = ( }); } if (content.text) { - await sendCallback({ + await conversation.send({ reply: { reference: referencedMessageId, content: { text: content.text }, @@ -173,16 +162,18 @@ export const ConversationContextProvider = ( } if (content.remoteAttachment) { - await sendCallback({ + await conversation.send({ remoteAttachment: content.remoteAttachment, }); } if (content.text) { - await sendCallback(content.text); + await conversation?.send({ + text: content.text, + }); } }, - [conversation, currentAccount, peerAddress] + [conversation] ); const isAllowedConversation = useMemo(() => { @@ -195,15 +186,10 @@ export const ConversationContextProvider = ( return ( ) ); -const newConversationStore = creatConversationStore("new_conversation"); +// const newConversationStore = creatConversationStore("new_conversation"); // Custom persistence logic // Factory function to create a store for each conversation with persistence -const createConversationPersistedStore = (conversationTopic: string) => - creatConversationStore(`conversation_${conversationTopic}`); +const createConversationPersistedStore = ( + conversationTopic: ConversationTopic +) => creatConversationStore(`conversation_${conversationTopic}`); // Hook to get or create the store for a conversation -export const getConversationPersistedStore = (conversationTopic: string) => { +export const getConversationPersistedStore = ( + conversationTopic: ConversationTopic +) => { if (!conversationPersistedStores.has(conversationTopic)) { conversationPersistedStores.set( conversationTopic, @@ -78,33 +80,23 @@ export const getConversationPersistedStore = (conversationTopic: string) => { return conversationPersistedStores.get(conversationTopic)!; }; -export function useConversationPersistedStore(topic: string) { - if (!topic) { - return newConversationStore; - } +export function useConversationPersistedStore(topic: ConversationTopic) { return getConversationPersistedStore(topic); } export function useConversationPersistedStoreState( - conversationTopic: ConversationTopic | null, + conversationTopic: ConversationTopic, selector: (state: IConversationPersistedStore) => T ) { - const store = conversationTopic - ? getConversationPersistedStore(conversationTopic) - : newConversationStore; + const store = getConversationPersistedStore(conversationTopic); return useStore(store, selector); } -// Maybe put somewhere else export function getCurrentConversationPersistedStore() { const topic = useConversationStore.getState().topic; - if (!topic) { - return newConversationStore; - } return getConversationPersistedStore(topic); } -// Maybe put somewhere else export function useCurrentConversationPersistedStoreState( selector: (state: IConversationPersistedStore) => T ) { diff --git a/features/conversation/conversation-service.ts b/features/conversation/conversation-service.ts index 2a7049f2b..eeea01935 100644 --- a/features/conversation/conversation-service.ts +++ b/features/conversation/conversation-service.ts @@ -19,7 +19,11 @@ import { getCurrentConversationPersistedStore, useCurrentConversationPersistedStoreState, } from "./conversation-persisted-stores"; -import { IConversationStore, useConversationStore } from "./conversation-store"; +import { + IConversationStore, + useConversationStore, + useConversationStoreContext, +} from "./conversation-store"; import logger from "@/utils/logger"; export function initializeCurrentConversation(args: { @@ -197,16 +201,12 @@ export function getCurrentConversationReplyToMessageId() { } export function useConversationCurrentTopic() { - return useConversationStore((state) => state.topic); -} - -export function useConversationCurrentPeerAddress() { - return useConversationStore((state) => state.peerAddress); + return useConversationStoreContext((state) => state.topic); } -export function getCurrentConversationTopic() { - return useConversationStore.getState().topic; -} +// export function getCurrentConversationTopic() { +// return useConversationStore.getState().topic; +// } export function useConversationComposerMediaPreview() { return useCurrentConversationPersistedStoreState( diff --git a/features/conversation/conversation-store.ts b/features/conversation/conversation-store.ts deleted file mode 100644 index a895c3ee1..000000000 --- a/features/conversation/conversation-store.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - ConversationTopic, - MessageId, - RemoteAttachmentContent, -} from "@xmtp/react-native-sdk"; -import { create } from "zustand"; -import { subscribeWithSelector } from "zustand/middleware"; - -export type IConversationStore = { - topic: ConversationTopic | null; - peerAddress: string | null; - uploadedRemoteAttachment: RemoteAttachmentContent | null; - messageContextMenuData: { - messageId: MessageId; - itemRectX: number; - itemRectY: number; - itemRectHeight: number; - itemRectWidth: number; - messageComponent: React.ReactNode; - } | null; - pickingEmojiForMessageId: MessageId | null; -}; - -export const useConversationStore = create()( - subscribeWithSelector(() => ({ - topic: null, - peerAddress: null, - uploadedRemoteAttachment: null, - messageContextMenuData: null, - pickingEmojiForMessageId: null, - })) -); diff --git a/features/conversation/conversation-store.tsx b/features/conversation/conversation-store.tsx new file mode 100644 index 000000000..e90c9766e --- /dev/null +++ b/features/conversation/conversation-store.tsx @@ -0,0 +1,76 @@ +/** + * Store to handle the conversation state. + * Will be used for both DM and group conversations. + */ + +import { + ConversationTopic, + MessageId, + RemoteAttachmentContent, +} from "@xmtp/react-native-sdk"; +import { createContext, memo, useContext, useRef } from "react"; +import { createStore, useStore } from "zustand"; +import { subscribeWithSelector } from "zustand/middleware"; + +type IConversationStoreProps = { + topic: ConversationTopic; +}; + +type IConversationStoreState = IConversationStoreProps & { + uploadedRemoteAttachment: RemoteAttachmentContent | null; + messageContextMenuData: { + messageId: MessageId; + itemRectX: number; + itemRectY: number; + itemRectHeight: number; + itemRectWidth: number; + messageComponent: React.ReactNode; + } | null; + pickingEmojiForMessageId: MessageId | null; +}; + +type IConversationStoreProviderProps = + React.PropsWithChildren; + +type IConversationStore = ReturnType; + +export const ConversationStoreProvider = memo( + ({ children, ...props }: IConversationStoreProviderProps) => { + const storeRef = useRef(); + if (!storeRef.current) { + storeRef.current = createConversationStore(props); + } + return ( + + {children} + + ); + } +); + +const createConversationStore = (initProps: IConversationStoreProps) => { + return createStore()( + subscribeWithSelector((set) => ({ + uploadedRemoteAttachment: null, + messageContextMenuData: null, + pickingEmojiForMessageId: null, + ...initProps, + })) + ); +}; + +const ConversationStoreContext = createContext(null); + +export function useConversationStoreContext( + selector: (state: IConversationStoreState) => T +): T { + const store = useContext(ConversationStoreContext); + if (!store) throw new Error("Missing ConversationStore.Provider in the tree"); + return useStore(store, selector); +} + +export function useConversationStore() { + const store = useContext(ConversationStoreContext); + if (!store) throw new Error(); + return store; +} diff --git a/features/conversation/dm-conversation.store.ts b/features/conversation/dm-conversation.store.ts new file mode 100644 index 000000000..e69de29bb diff --git a/features/conversation/dm-conversation/dm-conversation.screen.tsx b/features/conversation/dm-conversation/dm-conversation.screen.tsx new file mode 100644 index 000000000..66ff8ae3e --- /dev/null +++ b/features/conversation/dm-conversation/dm-conversation.screen.tsx @@ -0,0 +1,55 @@ +import { Screen } from "@/components/Screen/ScreenComp/Screen"; +import { useCurrentAccount } from "@/data/store/accountsStore"; +import { Center } from "@/design-system/Center"; +import { Loader } from "@/design-system/loader"; +import { useConversationWithPeerQuery } from "@/queries/useConversationWithPeerQuery"; +import { NavigationParamList } from "@/screens/Navigation/Navigation"; +import { $globalStyles } from "@/theme/styles"; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import React, { memo } from "react"; +import { ExistingDmConversationContent } from "./existing-dm-conversation-content"; +import { NewDmConversationContent } from "./new-dm-conversation-content"; + +export const DmConversationScreen = memo(function DmConversationScreen( + props: NativeStackScreenProps +) { + const { peerAddress } = props.route.params; + + const currentAccount = useCurrentAccount()!; + + // Check if we have a conversation with the peer + const { data: conversation, isLoading } = useConversationWithPeerQuery( + currentAccount, + peerAddress, + { + enabled: !!peerAddress, + } + ); + + if (!peerAddress) { + // TODO: Add error state. We should always have a peer address + return null; + } + + return ( + + {isLoading ? ( +
+ +
+ ) : !conversation ? ( + + ) : ( + + )} +
+ ); +}); diff --git a/features/conversation/dm-conversation/existing-dm-conversation-content.tsx b/features/conversation/dm-conversation/existing-dm-conversation-content.tsx new file mode 100644 index 000000000..920bc4faf --- /dev/null +++ b/features/conversation/dm-conversation/existing-dm-conversation-content.tsx @@ -0,0 +1,91 @@ +import { DmConsentPopup } from "@/components/Chat/ConsentPopup/dm-consent-popup"; +import { MessageReactionsDrawer } from "@/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer"; +import { MessageContextMenu } from "@/components/Chat/Message/message-context-menu/message-context-menu"; +import { + KeyboardFiller, + MessagesList, +} from "@/components/Conversation/V3Conversation"; +import { useCurrentAccount } from "@/data/store/accountsStore"; +import { Composer } from "@/features/conversation/composer/composer"; +import { + ConversationContextProvider, + useConversationContext, +} from "@/features/conversation/conversation-context"; +import { useConversationCurrentTopic } from "@/features/conversation/conversation-service"; +import { ConversationStoreProvider } from "@/features/conversation/conversation-store"; +import { DmConversationTitle } from "@/features/conversations/components/DmConversationTitle"; +import { useRouter } from "@/navigation/useNavigation"; +import { useConversationMessages } from "@/queries/useConversationMessages"; +import { useDmPeerInboxId } from "@/queries/useDmPeerInbox"; +import { ConversationTopic } from "@xmtp/react-native-sdk"; +import React, { memo, useEffect } from "react"; + +export const ExistingDmConversationContent = memo( + function ExistingDmConversationContent(props: { topic: ConversationTopic }) { + const { topic } = props; + + const navigation = useRouter(); + + useEffect(() => { + navigation.setOptions({ + headerTitle: () => , + }); + }, [topic, navigation]); + + return ( + + + ; + + + + + + + ); + } +); + +const ComposerWrapper = memo(function ComposerWrapper() { + const sendMessage = useConversationContext("sendMessage"); + return ; +}); + +const Messages = memo(function Messages() { + const topic = useConversationCurrentTopic(); + const currentAccount = useCurrentAccount()!; + + const { + data: messages, + isLoading: messagesLoading, + isRefetching: isRefetchingMessages, + refetch: refetchMessages, + } = useConversationMessages(currentAccount, topic); + + const { data: peerInboxId } = useDmPeerInboxId(currentAccount, topic); + + const isAllowedConversation = useConversationContext("isAllowedConversation"); + const conversationId = useConversationContext("conversationId"); + + if (messages?.ids.length === 0 && !messagesLoading) { + // TODO: Add empty state + return null; + } + + return ( + + ) : undefined + } + /> + ); +}); diff --git a/features/conversation/dm-conversation.screen.tsx b/features/conversation/dm-conversation/new-dm-conversation-content.tsx similarity index 60% rename from features/conversation/dm-conversation.screen.tsx rename to features/conversation/dm-conversation/new-dm-conversation-content.tsx index 2f1f958f7..b2f2eeeb5 100644 --- a/features/conversation/dm-conversation.screen.tsx +++ b/features/conversation/dm-conversation/new-dm-conversation-content.tsx @@ -1,28 +1,11 @@ -/** - * - * WORK IN PROGRESS! - * This is to decouple group conversations from DM conversations and maybe even new DM conversation - * - */ -import { - KeyboardFiller, - MessagesList, -} from "@/components/Conversation/V3Conversation"; -import { Screen } from "@/components/Screen/ScreenComp/Screen"; +import { KeyboardFiller } from "@/components/Conversation/V3Conversation"; import { showSnackbar } from "@/components/Snackbar/Snackbar.service"; -import { - getCurrentAccount, - useCurrentAccount, -} from "@/data/store/accountsStore"; -import { Center } from "@/design-system/Center"; +import { getCurrentAccount } from "@/data/store/accountsStore"; import { VStack } from "@/design-system/VStack"; -import { Loader } from "@/design-system/loader"; import { Composer, IComposerSendArgs, } from "@/features/conversation/composer/composer"; -import { useConversationCurrentTopic } from "@/features/conversation/conversation-service"; -import { DmConversationTitle } from "@/features/conversations/components/DmConversationTitle"; import { NewConversationTitle } from "@/features/conversations/components/NewConversationTitle"; import { useRouter } from "@/navigation/useNavigation"; import { @@ -30,60 +13,34 @@ import { conversationsQueryKey, } from "@/queries/QueryKeys"; import { queryClient } from "@/queries/queryClient"; -import { useConversationMessages } from "@/queries/useConversationMessages"; -import { useConversationWithPeerQuery } from "@/queries/useConversationWithPeerQuery"; import { V3ConversationListType } from "@/queries/useV3ConversationListQuery"; -import { NavigationParamList } from "@/screens/Navigation/Navigation"; import { sentryTrackError } from "@/utils/sentry"; import { ConversationWithCodecsType } from "@/utils/xmtpRN/client"; import { createConversationByAccount } from "@/utils/xmtpRN/conversations"; -import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { useMutation } from "@tanstack/react-query"; import { MessageId, RemoteAttachmentContent } from "@xmtp/react-native-sdk"; -import React, { memo, useCallback, useEffect } from "react"; +import { memo, useCallback, useEffect } from "react"; -export const DmConversationScreen = memo(function DmConversationScreen( - props: NativeStackScreenProps -) { - // @ts-ignore - const { peerAddress } = props.route.params; +export const NewDmConversationContent = memo( + function NewDmConversationContent(props: { peerAddress: string }) { + const { peerAddress } = props; - const currentAccount = useCurrentAccount()!; + useNewConversationHeader(peerAddress); - const { data: conversation, isLoading } = useConversationWithPeerQuery( - currentAccount, - peerAddress, - { - enabled: !!peerAddress, - } - ); - - if (isLoading) { return ( - -
+ {/* TODO: Add empty state */} + - -
-
+ /> + + + ); } - - return ( - - {!!conversation ? ( - - ) : ( - - )} - - - - ); -}); +); const ComposerWrapper = memo(function ComposerWrapper(props: { peerAddress: string; @@ -208,23 +165,6 @@ const ComposerWrapper = memo(function ComposerWrapper(props: { return ; }); -const NewDmConversation = memo(function NewDmConversation(props: { - peerAddress: string; -}) { - const { peerAddress } = props; - - useNewConversationHeader(peerAddress); - - // TODO: Add empty state - return ( - - ); -}); - function useNewConversationHeader(peerAddresss: string) { const navigation = useRouter(); @@ -234,51 +174,3 @@ function useNewConversationHeader(peerAddresss: string) { }); }, [peerAddresss, navigation]); } - -const ExistingDmConversation = memo(function ExistingDmConversation(props: { - conversation: ConversationWithCodecsType; -}) { - const { conversation } = props; - - const currentAccount = useCurrentAccount()!; - - const { - data: messages, - isLoading: messagesLoading, - isRefetching: isRefetchingMessages, - refetch, - } = useConversationMessages(currentAccount, conversation.topic!); - - useDmHeader(); - - if (messages?.ids.length === 0 && !messagesLoading) { - // TODO: Add empty state - return null; - } - - return ( - - - - ); -}); - -function useDmHeader() { - const navigation = useRouter(); - - const topic = useConversationCurrentTopic(); - - useEffect(() => { - navigation.setOptions({ - headerTitle: () => , - }); - }, [topic, navigation]); -} diff --git a/features/conversation/group-conversation.screen.tsx b/features/conversation/group-conversation.screen.tsx new file mode 100644 index 000000000..91bd2119e --- /dev/null +++ b/features/conversation/group-conversation.screen.tsx @@ -0,0 +1,386 @@ +import { useCurrentAccount } from "@data/store/accountsStore"; +import { useConversationMessages } from "@queries/useConversationMessages"; +import { + ConversationTopic, + ConversationVersion, + MessageId, +} from "@xmtp/react-native-sdk"; +import { memo, useCallback, useEffect } from "react"; +import { FlatListProps, Platform } from "react-native"; +// import { DmChatPlaceholder } from "@components/Chat/ChatPlaceholder/ChatPlaceholder"; +import { DmConsentPopup } from "@/components/Chat/ConsentPopup/dm-consent-popup"; +import { GroupConsentPopup } from "@/components/Chat/ConsentPopup/group-consent-popup"; +import { MessageReactionsDrawer } from "@/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer"; +import { MessageContextMenu } from "@/components/Chat/Message/message-context-menu/message-context-menu"; +import { ExternalWalletPicker } from "@/features/ExternalWalletPicker/ExternalWalletPicker"; +import { ExternalWalletPickerContextProvider } from "@/features/ExternalWalletPicker/ExternalWalletPicker.context"; +import { ConversationStoreProvider } from "@/features/conversation/conversation-store"; +import { useDmPeerInboxId } from "@/queries/useDmPeerInbox"; +import { V3Message } from "@components/Chat/Message/V3Message"; +import { Screen } from "@components/Screen/ScreenComp/Screen"; +import { Button } from "@design-system/Button/Button"; +import { Center } from "@design-system/Center"; +import { Text } from "@design-system/Text"; +import { AnimatedVStack, VStack } from "@design-system/VStack"; +import { + Composer, + IComposerSendArgs, +} from "@features/conversation/composer/composer"; +import { + ConversationContextProvider, + useConversationContext, +} from "@features/conversation/conversation-context"; +import { + ConversationGroupContextProvider, + useConversationGroupContext, +} from "@features/conversation/conversation-group-context"; +import { useConversationCurrentTopic } from "@features/conversation/conversation-service"; +import { DmConversationTitle } from "@features/conversations/components/DmConversationTitle"; +import { GroupConversationTitle } from "@features/conversations/components/GroupConversationTitle"; +import { NewConversationTitle } from "@features/conversations/components/NewConversationTitle"; +import { translate } from "@i18n/translate"; +import { useRouter } from "@navigation/useNavigation"; +import { useAppTheme } from "@theme/useAppTheme"; +import Animated, { + AnimatedProps, + useAnimatedKeyboard, + useAnimatedStyle, +} from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +const keyExtractor = (item: string) => item; + +type TopicConversationProps = { + topic: ConversationTopic; + textPrefill?: string; +}; + +export const GroupConversation = ({ + topic, + textPrefill, +}: TopicConversationProps) => { + // TODO: Handle when topic is not defined + const messageToPrefill = textPrefill ?? ""; + + // initializeCurrentConversation({ + // topic, + // peerAddress, + // inputValue: messageToPrefill, + // }); + + if (!topic) { + // TODO: Add placeholder + return null; + } + + if (topic) { + // Existing conversation + } + + // New conversation + + return ( + + + + + + + + ); +}; + +const Content = memo(function Content() { + const { theme } = useAppTheme(); + // const isNewConversation = useConversationContext("isNewConversation"); + // const conversationVersion = useConversationContext("conversationVersion"); + + return ( + + + {isNewConversation ? ( + + ) : conversationVersion === ConversationVersion.DM ? ( + + ) : ( + + + + )} + + + + + + + + ); +}); + +const ComposerWrapper = memo(function ComposerWrapper() { + const sendMessage = useConversationContext("sendMessage"); + + const handleSend = useCallback( + async (args: IComposerSendArgs) => { + sendMessage(args); + }, + [sendMessage] + ); + + return ; +}); + +const NewConversationContent = memo(function NewConversationContent() { + useNewConversationHeader(); + + return ; +}); + +const DmContent = memo(function DmContent() { + const currentAccount = useCurrentAccount()!; + const topic = useConversationCurrentTopic()!; + const conversationNotFound = useConversationContext("conversationNotFound"); + const isAllowedConversation = useConversationContext("isAllowedConversation"); + // const peerAddress = useConversationContext("peerAddress")!; + const conversationId = useConversationContext("conversationId")!; + const isLoadingConversationConsent = useConversationContext( + "isLoadingConversationConsent" + ); + + const { + data: messages, + isLoading: messagesLoading, + isRefetching: isRefetchingMessages, + refetch: refetchMessages, + } = useConversationMessages(currentAccount, topic!); + + const { data: peerInboxId } = useDmPeerInboxId(currentAccount, topic!); + + useDmHeader(); + + if (conversationNotFound) { + // TODO: Add DM placeholder + return null; + } + + if (messages?.ids.length === 0 && !messagesLoading) { + // TODO: Add DM placeholder + return null; + } + + return ( + + ) : undefined + } + /> + ); +}); + +const GroupContent = memo(function GroupContent() { + const currentAccount = useCurrentAccount()!; + const topic = useConversationCurrentTopic()!; + const conversationNotFound = useConversationContext("conversationNotFound"); + const isAllowedConversation = useConversationContext("isAllowedConversation"); + const isLoadingConversationConsent = useConversationContext( + "isLoadingConversationConsent" + ); + + const { + data: messages, + isLoading: messagesLoading, + isRefetching: isRefetchingMessages, + refetch, + } = useConversationMessages(currentAccount, topic!); + + useGroupHeader(); + + if (conversationNotFound) { + return ; + } + + if (messages?.ids.length === 0 && !messagesLoading) { + return ; + } + + return ( + + ) : undefined + } + /> + ); +}); + +export const MessagesList = memo(function MessagesList( + props: Omit>, "renderItem" | "data"> & { + messageIds: MessageId[]; + } +) { + const { messageIds, ...rest } = props; + + return ( + // @ts-ignore + { + return ( + + ); + }} + keyboardDismissMode="interactive" + automaticallyAdjustContentInsets={false} + contentInsetAdjustmentBehavior="never" + keyExtractor={keyExtractor} + keyboardShouldPersistTaps="handled" + // estimatedItemSize={34} // TODO + showsVerticalScrollIndicator={Platform.OS === "ios"} // Size glitch on Android + pointerEvents="auto" + /** + * Causes a glitch on Android, no sure we need it for now + */ + // maintainVisibleContentPosition={{ + // minIndexForVisible: 0, + // autoscrollToTopThreshold: 100, + // }} + // estimatedListSize={Dimensions.get("screen")} + {...rest} + /> + ); +}); + +export const KeyboardFiller = memo(function KeyboardFiller() { + const { height: keyboardHeightAV } = useAnimatedKeyboard(); + const insets = useSafeAreaInsets(); + + const as = useAnimatedStyle(() => ({ + height: Math.max(keyboardHeightAV.value - insets.bottom, 0), + })); + + return ; +}); + +function useNewConversationHeader() { + const navigation = useRouter(); + + const peerAddress = useConversationContext("peerAddress"); + + useEffect(() => { + navigation.setOptions({ + headerTitle: () => , + }); + }, [peerAddress, navigation]); +} + +function useDmHeader() { + const navigation = useRouter(); + + const topic = useConversationCurrentTopic(); + + useEffect(() => { + navigation.setOptions({ + headerTitle: () => , + }); + }, [topic, navigation]); +} + +function useGroupHeader() { + const navigation = useRouter(); + + const topic = useConversationCurrentTopic(); + + useEffect(() => { + navigation.setOptions({ + headerTitle: () => , + }); + }, [topic, navigation]); +} + +const GroupConversationMissing = memo(() => { + const topic = useConversationCurrentTopic(); + + return ( + + + {topic + ? translate("group_not_found") + : translate("opening_conversation")} + + + ); +}); + +const GroupConversationEmpty = memo(() => { + const { theme } = useAppTheme(); + + const groupName = useConversationGroupContext("groupName"); + const sendMessage = useConversationContext("sendMessage"); + + const handleSend = useCallback(() => { + sendMessage({ + content: { + text: "👋", + }, + }); + }, [sendMessage]); + + return ( +
+ + {translate("group_placeholder.placeholder_text", { + groupName, + })} + + +
+ ); +}); diff --git a/features/conversation/group-conversation.store.tsx b/features/conversation/group-conversation.store.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/features/conversations/components/V3ConversationFromPeer.tsx b/features/conversations/components/V3ConversationFromPeer.tsx index 832c401cb..cb3306293 100644 --- a/features/conversations/components/V3ConversationFromPeer.tsx +++ b/features/conversations/components/V3ConversationFromPeer.tsx @@ -10,7 +10,6 @@ import { V3Conversation } from "@components/Conversation/V3Conversation"; type V3ConversationFromPeerProps = { peer: string; textPrefill?: string; - skipLoading: boolean; }; /** diff --git a/queries/useConversationWithPeerQuery.ts b/queries/useConversationWithPeerQuery.ts index 175c02ad2..953a09560 100644 --- a/queries/useConversationWithPeerQuery.ts +++ b/queries/useConversationWithPeerQuery.ts @@ -1,3 +1,4 @@ +import { setConversationQueryData } from "@/queries/useConversationQuery"; import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import logger from "@utils/logger"; import { ConversationWithCodecsType } from "@utils/xmtpRN/client"; @@ -6,10 +7,8 @@ import { conversationWithPeerQueryKey } from "./QueryKeys"; export const useConversationWithPeerQuery = ( account: string, - peer: string | undefined, - options?: Partial< - UseQueryOptions - > + peer: string, + options?: Partial> ) => { return useQuery({ ...options, @@ -25,6 +24,12 @@ export const useConversationWithPeerQuery = ( includeSync: true, }); + if (!conversation) { + return null; + } + + setConversationQueryData(account, conversation.topic, conversation); + return conversation; }, enabled: !!peer, diff --git a/screens/Conversation.tsx b/screens/Conversation.tsx index 3aa17fe2c..cfbd792a0 100644 --- a/screens/Conversation.tsx +++ b/screens/Conversation.tsx @@ -23,7 +23,6 @@ const ConversationHoc = ({ ); } diff --git a/screens/Navigation/Navigation.tsx b/screens/Navigation/Navigation.tsx index f819e8e3e..2e5c00d4d 100644 --- a/screens/Navigation/Navigation.tsx +++ b/screens/Navigation/Navigation.tsx @@ -79,13 +79,9 @@ export type NavigationParamList = { NewConversation: NewConversationNavParams; // WIP - DmConversation: - | { - peerAddress: string; - } - | { - topic: string; - }; + DmConversation: { + peerAddress: string; + }; NewGroupSummary: undefined; ConverseMatchMaker: undefined; diff --git a/utils/xmtpRN/send.ts b/utils/xmtpRN/send.ts deleted file mode 100644 index dfb7def08..000000000 --- a/utils/xmtpRN/send.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ConversationWithCodecsType, SendMessageWithCodecs } from "./client"; -import logger from "@utils/logger"; - -export type SendMessageParams = { - conversation: ConversationWithCodecsType; - message: SendMessageWithCodecs; -}; - -export const sendMessage = ({ conversation, message }: SendMessageParams) => { - logger.debug("[XMTPRN Send] Sending Message"); - const start = new Date().getTime(); - conversation; - const end = new Date().getTime(); - logger.debug(`[XMTPRN Send] Sent messaage in ${(end - start) / 1000} sec`); -}; - -export const sendMessageByConversationId = () => {}; From 156f33a47472dcf878178677c21887da55fb75f5 Mon Sep 17 00:00:00 2001 From: Thierry Date: Tue, 10 Dec 2024 18:35:54 -0500 Subject: [PATCH 2/7] wip --- components/Chat/Chat.tsx | 638 ----------------- components/Chat/ChatDumb.tsx | 244 ------- .../Chat/ChatPlaceholder/ChatPlaceholder.tsx | 165 ----- components/Chat/Message/Message.tsx | 639 ------------------ components/Chat/Message/MessageActions.tsx | 499 -------------- components/Chat/Message/V3Message.tsx | 113 ---- .../Message/components/message-layout.tsx | 95 --- .../message-simple-text.tsx | 136 ---- .../message-context-menu.tsx | 145 ---- .../Chat/Message/stores/message-store.tsx | 73 -- .../Chat/Transaction/TransactionPreview.tsx | 2 +- components/Conversation/V3Conversation.tsx | 445 ------------ .../Conversation/V3ConversationHeader.tsx | 1 - .../ChatNullState.tsx | 2 +- .../ConnectViaWallet.store.tsx | 3 +- .../PinnedMessagePreview.tsx | 2 +- .../PinnedV3DMConversation.tsx | 4 +- .../PinnedV3GroupConversation.tsx | 4 +- .../Recommendations/Recommendations.tsx | 2 +- .../StateHandlers/HydrationStateHandler.tsx | 4 + components/V3DMListItem.tsx | 2 +- components/V3GroupConversationListItem.tsx | 2 +- data/store/chatStore.ts | 63 +- design-system/touchable-without-feedback.tsx | 11 + .../hooks/useMessageIsUnread.ts | 6 +- .../conversation-list/hooks/useMessageText.ts | 2 +- .../useV3ConversationItems.ts | 3 +- features/conversation/ChatPlaceholder.tsx | 169 +++++ .../conversation-attachment-container.tsx | 0 .../conversation-attachment-loading.tsx | 1 - .../conversation-attachment-remote-image.tsx | 8 +- ...sation-composer-add-attachment-button.tsx} | 164 ++--- .../conversation-composer-reply-preview.tsx | 319 +++++++++ ...tion-composer-send-attachment-preview.tsx} | 0 .../conversation-composer.store-context.tsx | 142 ++++ .../conversation-composer.tsx | 343 ++++++++++ .../conversation-consent-popup-dm.tsx | 36 +- .../conversation-consent-popup-group.tsx | 8 +- ...nversation-consent-popup.design-system.tsx | 0 .../conversation/conversation-context.tsx | 207 ------ .../conversation/conversation-dm-context.tsx | 0 .../conversation-group-context.tsx | 61 -- .../conversation-keyboard-filler.tsx | 15 + .../conversation-message-date-change.tsx | 3 +- .../conversation-message-gestures.tsx | 0 .../conversation-message-sender-avatar.tsx | 6 +- .../conversation-message-sender.tsx | 0 .../conversation-message-status-dumb.tsx | 0 .../conversation-message-status.tsx | 0 .../conversation-message-timestamp.tsx | 2 +- .../conversation-message.store-context.tsx | 112 +++ .../conversation-message-bubble.tsx | 0 .../conversation-message-container.tsx | 8 +- ...conversation-message-content-container.tsx | 11 +- ...conversation-message-chat-group-update.tsx | 10 +- ...conversation-message-remote-attachment.tsx | 29 +- .../conversation-message-reply.tsx | 117 ++-- .../conversation-message-simple-text.tsx | 32 + ...conversation-message-static-attachment.tsx | 11 +- ...e-context-menu-above-message-reactions.tsx | 4 +- ...ersation-message-context-menu-backdrop.tsx | 0 ...ersation-message-context-menu-constant.tsx | 0 ...rsation-message-context-menu-container.tsx | 4 +- ...message-context-menu-emoji-picker-list.tsx | 2 +- ...-message-context-menu-emoji-picker-row.tsx | 0 ...message-context-menu-emoji-picker-utils.ts | 0 ...tion-message-context-menu-emoji-picker.tsx | 4 +- ...onversation-message-context-menu-items.tsx | 2 +- ...ersation-message-context-menu-reactors.tsx | 2 +- ...onversation-message-context-menu.store.tsx | 73 ++ .../conversation-message-context-menu.tsx | 272 ++++++++ ...onversation-message-context-menu.utils.tsx | 20 +- .../conversation-message-layout.tsx | 46 ++ ...sation-message-reaction-drawer.service..ts | 4 +- ...ersation-message-reaction-drawer.store.tsx | 2 +- .../conversation-message-reaction-drawer.tsx | 2 +- .../conversation-message-reactions.tsx | 111 +-- .../conversation-message-reactions.types.ts | 2 +- .../conversation-message-repliable.tsx | 0 ...rsation-message-space-between-messages.tsx | 0 .../conversation-message-text.tsx | 0 .../conversation-message.tsx | 71 ++ .../conversation-message.utils.tsx | 48 +- .../conversation-messages-list.tsx | 53 ++ ...on-content.tsx => conversation-new-dm.tsx} | 104 +-- .../conversation-persisted-stores.ts | 105 --- features/conversation/conversation-service.ts | 251 ------- .../conversation/conversation-title.tsx | 2 +- features/conversation/conversation.service.ts | 9 + ...ore.tsx => conversation.store-context.tsx} | 44 +- features/conversation/conversation.tsx | 334 +++++++++ .../conversation/dm-conversation.store.ts | 0 .../dm-conversation.screen.tsx | 55 -- .../existing-dm-conversation-content.tsx | 91 --- .../group-conversation.screen.tsx | 386 ----------- .../conversation/group-conversation.store.tsx | 0 .../use-mark-as-read-on-enter.tsx | 20 + .../components/DmConversationTitle.tsx | 4 +- .../components/GroupConversationTitle.tsx | 28 +- .../components/NewConversationTitle.tsx | 4 +- .../components/V3ConversationFromPeer.tsx | 56 -- .../hooks/use-react-on-message.ts | 34 + .../hooks/use-remove-reaction-on-message.ts | 31 + .../conversations/hooks/use-send-message.ts | 63 ++ .../utils/hasNextMessageInSeries.ts | 3 +- .../utils/isConversationAllowed.ts | 7 + .../conversations/utils/isConversationDm.ts | 6 + .../utils/isConversationGroup.ts | 6 + .../utils/messageIsFromCurrentUser.ts | 2 +- .../components/NavigationChatButton.tsx | 2 +- hooks/useCurrentAccountXmtpClient.ts | 13 + hooks/useGroupConsent.ts | 2 + ios/Converse.xcodeproj/project.pbxproj | 4 - ios/Podfile.lock | 19 +- queries/useConversationMessages.ts | 16 +- queries/useConversationQuery.ts | 11 + queries/useConversationWithPeerQuery.ts | 72 +- screens/Conversation.tsx | 103 ++- screens/ConversationList.tsx | 2 +- screens/ConversationReadOnly.tsx | 80 ++- screens/Navigation/ConversationNav.tsx | 70 +- screens/Navigation/Navigation.tsx | 14 +- screens/Navigation/navHelpers.test.ts | 8 +- screens/Navigation/navHelpers.ts | 7 +- screens/Profile.tsx | 28 +- utils/attachment/attachment.utils.ts | 32 +- utils/attachment/types.ts | 6 + utils/xmtpRN/client.types.ts | 2 +- utils/xmtpRN/sync.ts | 30 +- 129 files changed, 2883 insertions(+), 5124 deletions(-) delete mode 100644 components/Chat/Chat.tsx delete mode 100644 components/Chat/ChatDumb.tsx delete mode 100644 components/Chat/ChatPlaceholder/ChatPlaceholder.tsx delete mode 100644 components/Chat/Message/Message.tsx delete mode 100644 components/Chat/Message/MessageActions.tsx delete mode 100644 components/Chat/Message/V3Message.tsx delete mode 100644 components/Chat/Message/components/message-layout.tsx delete mode 100644 components/Chat/Message/message-content-types/message-simple-text.tsx delete mode 100644 components/Chat/Message/message-context-menu/message-context-menu.tsx delete mode 100644 components/Chat/Message/stores/message-store.tsx delete mode 100644 components/Conversation/V3Conversation.tsx delete mode 100644 components/Conversation/V3ConversationHeader.tsx rename components/{Chat => ConversationList}/ChatNullState.tsx (98%) create mode 100644 design-system/touchable-without-feedback.tsx create mode 100644 features/conversation/ChatPlaceholder.tsx rename components/Chat/Attachment/attachment-container.tsx => features/conversation/conversation-attachment/conversation-attachment-container.tsx (100%) rename components/Chat/Attachment/attachment-loading.tsx => features/conversation/conversation-attachment/conversation-attachment-loading.tsx (99%) rename components/Chat/Attachment/remote-attachment-image.tsx => features/conversation/conversation-attachment/conversation-attachment-remote-image.tsx (94%) rename features/conversation/{composer/add-attachment-button.tsx => conversation-composer/conversation-composer-add-attachment-button.tsx} (56%) create mode 100644 features/conversation/conversation-composer/conversation-composer-reply-preview.tsx rename features/conversation/{composer/send-attachment-preview.tsx => conversation-composer/conversation-composer-send-attachment-preview.tsx} (100%) create mode 100644 features/conversation/conversation-composer/conversation-composer.store-context.tsx create mode 100644 features/conversation/conversation-composer/conversation-composer.tsx rename components/Chat/ConsentPopup/dm-consent-popup.tsx => features/conversation/conversation-consent-popup/conversation-consent-popup-dm.tsx (84%) rename components/Chat/ConsentPopup/group-consent-popup.tsx => features/conversation/conversation-consent-popup/conversation-consent-popup-group.tsx (88%) rename components/Chat/ConsentPopup/consent-popup.design-system.tsx => features/conversation/conversation-consent-popup/conversation-consent-popup.design-system.tsx (100%) delete mode 100644 features/conversation/conversation-context.tsx delete mode 100644 features/conversation/conversation-dm-context.tsx delete mode 100644 features/conversation/conversation-group-context.tsx create mode 100644 features/conversation/conversation-keyboard-filler.tsx rename components/Chat/Message/message-date-change.tsx => features/conversation/conversation-message-date-change.tsx (94%) rename components/Chat/Message/message-gestures.tsx => features/conversation/conversation-message-gestures.tsx (100%) rename components/Chat/Message/MessageSenderAvatar.tsx => features/conversation/conversation-message-sender-avatar.tsx (94%) rename components/Chat/Message/MessageSender.tsx => features/conversation/conversation-message-sender.tsx (100%) rename components/Chat/Message/MessageStatusDumb.tsx => features/conversation/conversation-message-status-dumb.tsx (100%) rename components/Chat/Message/MessageStatus.tsx => features/conversation/conversation-message-status.tsx (100%) rename components/Chat/Message/message-timestamp.tsx => features/conversation/conversation-message-timestamp.tsx (97%) create mode 100644 features/conversation/conversation-message.store-context.tsx rename components/Chat/Message/components/message-bubble.tsx => features/conversation/conversation-message/conversation-message-bubble.tsx (100%) rename components/Chat/Message/components/message-container.tsx => features/conversation/conversation-message/conversation-message-container.tsx (64%) rename components/Chat/Message/components/message-content-container.tsx => features/conversation/conversation-message/conversation-message-content-container.tsx (62%) rename components/Chat/ChatGroupUpdatedMessage.tsx => features/conversation/conversation-message/conversation-message-content-types/conversation-message-chat-group-update.tsx (96%) rename components/Chat/Message/message-content-types/message-remote-attachment.tsx => features/conversation/conversation-message/conversation-message-content-types/conversation-message-remote-attachment.tsx (53%) rename components/Chat/Message/message-content-types/message-reply.tsx => features/conversation/conversation-message/conversation-message-content-types/conversation-message-reply.tsx (70%) create mode 100644 features/conversation/conversation-message/conversation-message-content-types/conversation-message-simple-text.tsx rename components/Chat/Message/message-content-types/message-static-attachment.tsx => features/conversation/conversation-message/conversation-message-content-types/conversation-message-static-attachment.tsx (86%) rename components/Chat/Message/message-context-menu/message-context-menu-above-message-reactions.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-above-message-reactions.tsx (97%) rename components/Chat/Message/message-context-menu/message-context-menu-backdrop.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-backdrop.tsx (100%) rename components/Chat/Message/message-context-menu/message-context-menu-constant.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-constant.tsx (100%) rename components/Chat/Message/message-context-menu/message-context-menu-container.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-container.tsx (96%) rename components/Chat/Message/message-context-menu/message-context-menu-emoji-picker/message-context-menu-emoji-picker-list.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-emoji-picker/conversation-message-context-menu-emoji-picker-list.tsx (97%) rename components/Chat/Message/message-context-menu/message-context-menu-emoji-picker/message-context-menu-emoji-picker-row.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-emoji-picker/conversation-message-context-menu-emoji-picker-row.tsx (100%) rename components/Chat/Message/message-context-menu/message-context-menu-emoji-picker/message-context-menu-emoji-picker-utils.ts => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-emoji-picker/conversation-message-context-menu-emoji-picker-utils.ts (100%) rename components/Chat/Message/message-context-menu/message-context-menu-emoji-picker/message-context-menu-emoji-picker.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-emoji-picker/conversation-message-context-menu-emoji-picker.tsx (91%) rename components/Chat/Message/message-context-menu/message-context-menu-items.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-items.tsx (96%) rename components/Chat/Message/message-context-menu/message-context-menu-reactors.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu-reactors.tsx (96%) create mode 100644 features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu.store.tsx create mode 100644 features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu.tsx rename components/Chat/Message/message-context-menu/message-context-menu-utils.tsx => features/conversation/conversation-message/conversation-message-context-menu/conversation-message-context-menu.utils.tsx (83%) create mode 100644 features/conversation/conversation-message/conversation-message-layout.tsx rename components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts => features/conversation/conversation-message/conversation-message-reactions/conversation-message-reaction-drawer/conversation-message-reaction-drawer.service..ts (90%) rename components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx => features/conversation/conversation-message/conversation-message-reactions/conversation-message-reaction-drawer/conversation-message-reaction-drawer.store.tsx (91%) rename components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx => features/conversation/conversation-message/conversation-message-reactions/conversation-message-reaction-drawer/conversation-message-reaction-drawer.tsx (99%) rename components/Chat/Message/MessageReactions/MessageReactions.tsx => features/conversation/conversation-message/conversation-message-reactions/conversation-message-reactions.tsx (65%) rename components/Chat/Message/MessageReactions/MessageReactions.types.ts => features/conversation/conversation-message/conversation-message-reactions/conversation-message-reactions.types.ts (94%) rename components/Chat/Message/components/message-repliable.tsx => features/conversation/conversation-message/conversation-message-repliable.tsx (100%) rename components/Chat/Message/components/message-space-between-messages.tsx => features/conversation/conversation-message/conversation-message-space-between-messages.tsx (100%) rename components/Chat/Message/components/message-text.tsx => features/conversation/conversation-message/conversation-message-text.tsx (100%) create mode 100644 features/conversation/conversation-message/conversation-message.tsx rename components/Chat/Message/message-utils.tsx => features/conversation/conversation-message/conversation-message.utils.tsx (87%) create mode 100644 features/conversation/conversation-messages-list.tsx rename features/conversation/{dm-conversation/new-dm-conversation-content.tsx => conversation-new-dm.tsx} (67%) delete mode 100644 features/conversation/conversation-persisted-stores.ts delete mode 100644 features/conversation/conversation-service.ts rename components/Conversation/ConversationTitleDumb.tsx => features/conversation/conversation-title.tsx (97%) create mode 100644 features/conversation/conversation.service.ts rename features/conversation/{conversation-store.tsx => conversation.store-context.tsx} (59%) create mode 100644 features/conversation/conversation.tsx delete mode 100644 features/conversation/dm-conversation.store.ts delete mode 100644 features/conversation/dm-conversation/dm-conversation.screen.tsx delete mode 100644 features/conversation/dm-conversation/existing-dm-conversation-content.tsx delete mode 100644 features/conversation/group-conversation.screen.tsx delete mode 100644 features/conversation/group-conversation.store.tsx create mode 100644 features/conversation/use-mark-as-read-on-enter.tsx delete mode 100644 features/conversations/components/V3ConversationFromPeer.tsx create mode 100644 features/conversations/hooks/use-react-on-message.ts create mode 100644 features/conversations/hooks/use-remove-reaction-on-message.ts create mode 100644 features/conversations/hooks/use-send-message.ts create mode 100644 features/conversations/utils/isConversationAllowed.ts create mode 100644 features/conversations/utils/isConversationDm.ts create mode 100644 features/conversations/utils/isConversationGroup.ts create mode 100644 hooks/useCurrentAccountXmtpClient.ts diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx deleted file mode 100644 index 58e0d30ca..000000000 --- a/components/Chat/Chat.tsx +++ /dev/null @@ -1,638 +0,0 @@ -// import { useSelect } from "@data/store/storeHelpers"; -// import { FlashList } from "@shopify/flash-list"; -// import { itemSeparatorColor, tertiaryBackgroundColor } from "@styles/colors"; -// import { useAppTheme } from "@theme/useAppTheme"; -// import { getCleanAddress } from "@utils/evm/address"; -// import { FrameWithType, messageHasFrames } from "@utils/frames"; -// import differenceInCalendarDays from "date-fns/differenceInCalendarDays"; -// import React, { useCallback, useEffect, useMemo, useRef } from "react"; -// import { -// ColorSchemeName, -// Dimensions, -// FlatList, -// Keyboard, -// Platform, -// StyleSheet, -// View, -// useColorScheme, -// } from "react-native"; -// import Animated, { -// useAnimatedStyle, -// useDerivedValue, -// useSharedValue, -// } from "react-native-reanimated"; -// import { useSafeAreaInsets } from "react-native-safe-area-context"; -// import { useShallow } from "zustand/react/shallow"; - -// import { -// useChatStore, -// useCurrentAccount, -// useProfilesStore, -// useRecommendationsStore, -// } from "../../data/store/accountsStore"; -// import { XmtpConversationWithUpdate } from "../../data/store/chatStore"; -// import { useFramesStore } from "../../data/store/framesStore"; -// import { ExternalWalletPicker } from "../../features/ExternalWalletPicker/ExternalWalletPicker"; -// import { ExternalWalletPickerContextProvider } from "../../features/ExternalWalletPicker/ExternalWalletPicker.context"; -// import { -// ReanimatedFlashList, -// ReanimatedFlatList, -// ReanimatedView, -// } from "../../utils/animations"; -// import { useKeyboardAnimation } from "../../utils/animations/keyboardAnimation"; -// import { isAttachmentMessage } from "@utils/attachment/isAttachmentMessage"; -// import { useConversationContext } from "../../utils/conversation"; -// import { converseEventEmitter } from "../../utils/events"; -// import { getProfile, getProfileData } from "../../utils/profile"; -// import { UUID_REGEX } from "../../utils/regex"; -// import { isContentType } from "../../utils/xmtpRN/contentTypes"; -// import { Recommendation } from "../Recommendations/Recommendation"; -// import ChatPlaceholder from "./ChatPlaceholder/ChatPlaceholder"; -// import ConsentPopup from "./ConsentPopup/ConsentPopup"; -// import { GroupConsentPopup } from "./ConsentPopup/GroupConsentPopup"; -// import ChatInput from "./Input/Input"; -// import CachedChatMessage, { MessageToDisplay } from "./Message/Message"; -// import { useMessageReactionsStore } from "./Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store"; -// import { MessageReactionsDrawer } from "./Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer"; - -// const usePeerSocials = () => { -// const conversation = useConversationContext("conversation"); -// const peerSocials = useProfilesStore( -// useShallow((s) => -// conversation?.peerAddress -// ? getProfile(conversation.peerAddress, s.profiles)?.socials -// : undefined -// ) -// ); - -// return peerSocials; -// }; - -// const useRenderItem = ({ -// xmtpAddress, -// conversation, -// messageFramesMap, -// colorScheme, -// }: { -// xmtpAddress: string; -// conversation: XmtpConversationWithUpdate | undefined; -// messageFramesMap: { -// [messageId: string]: FrameWithType[]; -// }; -// colorScheme: ColorSchemeName; -// }) => { -// return useCallback( -// ({ item }: { item: MessageToDisplay }) => { -// return ( -// -// ); -// }, -// [colorScheme, xmtpAddress, conversation?.isGroup, messageFramesMap] -// ); -// }; - -// const getItemType = (item: MessageToDisplay) => { -// const fromMeString = item.fromMe ? "fromMe" : "notFromMe"; -// return `${item.contentType}-${fromMeString}`; -// }; - -// const getListArray = ( -// xmtpAddress?: string, -// conversation?: XmtpConversationWithUpdate, -// lastMessages?: number // Optional parameter to limit the number of messages -// ) => { -// const messageAttachments = useChatStore.getState().messageAttachments; - -// const isAttachmentLoading = (messageId: string) => { -// const attachment = messageAttachments && messageAttachments[messageId]; -// return attachment?.loading; -// }; - -// if (!conversation) return []; -// const reverseArray = []; -// // Filter out unwanted content types before list or reactions out of order can mess up the logic -// const filteredMessageIds = conversation.messagesIds.filter((messageId) => { -// const message = conversation.messages.get(messageId) as MessageToDisplay; -// if (!message || (!message.content && !message.contentFallback)) -// return false; - -// // Reactions & read receipts are not displayed in the flow -// const notDisplayedContentTypes = [ -// "xmtp.org/reaction:", -// "xmtp.org/readReceipt:", -// ]; - -// if (isAttachmentMessage(message.contentType) && UUID_REGEX.test(message.id)) -// return false; - -// return !notDisplayedContentTypes.some((c) => -// message.contentType.startsWith(c) -// ); -// }); - -// let latestSettledFromMeIndex = -1; -// let latestSettledFromPeerIndex = -1; - -// for (let index = filteredMessageIds.length - 1; index >= 0; index--) { -// const messageId = filteredMessageIds[index]; -// const message = conversation.messages.get(messageId) as MessageToDisplay; - -// message.fromMe = -// !!xmtpAddress && -// xmtpAddress.toLowerCase() === message.senderAddress.toLowerCase(); - -// message.hasNextMessageInSeries = false; -// message.hasPreviousMessageInSeries = false; - -// if (index > 0) { -// const previousMessageId = filteredMessageIds[index - 1]; -// const previousMessage = conversation.messages.get(previousMessageId); - -// if (previousMessage) { -// message.dateChange = -// differenceInCalendarDays(message.sent, previousMessage.sent) > 0; - -// if ( -// previousMessage.senderAddress.toLowerCase() === -// message.senderAddress.toLowerCase() && -// !message.dateChange && -// !isContentType("groupUpdated", previousMessage.contentType) -// ) { -// message.hasPreviousMessageInSeries = true; -// } -// } -// } else { -// message.dateChange = true; -// } - -// if (index < filteredMessageIds.length - 1) { -// const nextMessageId = filteredMessageIds[index + 1]; -// const nextMessage = conversation.messages.get(nextMessageId); - -// if (nextMessage) { -// // Here we need to check if next message has a date change -// const nextMessageDateChange = -// differenceInCalendarDays(nextMessage.sent, message.sent) > 0; - -// if ( -// nextMessage.senderAddress.toLowerCase() === -// message.senderAddress.toLowerCase() && -// !nextMessageDateChange && -// !isContentType("groupUpdated", nextMessage.contentType) -// ) { -// message.hasNextMessageInSeries = true; -// } -// } -// } - -// if ( -// message.fromMe && -// message.status !== "sending" && -// message.status !== "prepared" && -// latestSettledFromMeIndex === -1 -// ) { -// latestSettledFromMeIndex = reverseArray.length; -// } - -// if (!message.fromMe && latestSettledFromPeerIndex === -1) { -// latestSettledFromPeerIndex = reverseArray.length; -// } - -// message.isLatestSettledFromMe = -// reverseArray.length === latestSettledFromMeIndex; -// message.isLatestSettledFromPeer = -// reverseArray.length === latestSettledFromPeerIndex; - -// if (index === filteredMessageIds.length - 1) { -// message.isLoadingAttachment = -// isAttachmentMessage(message.contentType) && -// isAttachmentLoading(message.id); -// } - -// if (index === filteredMessageIds.length - 2) { -// const nextMessageId = filteredMessageIds[index + 1]; -// const nextMessage = conversation.messages.get(nextMessageId); -// message.nextMessageIsLoadingAttachment = -// isAttachmentMessage(nextMessage?.contentType) && -// isAttachmentLoading(nextMessageId); -// } - -// reverseArray.push(message); -// } - -// // If lastMessages is defined, slice the array to return only the last n messages -// if (lastMessages !== undefined) { -// return reverseArray.slice(0, lastMessages); -// } - -// return reverseArray; -// }; - -// const useAnimatedListView = ( -// conversation: XmtpConversationWithUpdate | undefined -// ) => { -// // The first message was really buggy on iOS & Android and this is due to FlashList -// // so we keep FlatList for new convos and switch to FlashList for bigger convos -// // that need great perf. -// return useMemo(() => { -// const isConversationNotPending = conversation && !conversation.pending; -// return isConversationNotPending ? ReanimatedFlashList : ReanimatedFlatList; -// }, [conversation]); -// }; - -// const useIsShowingPlaceholder = ({ -// messages, -// isBlockedPeer, -// conversation, -// }: { -// messages: MessageToDisplay[]; -// isBlockedPeer: boolean; -// conversation: XmtpConversationWithUpdate | undefined; -// }): boolean => { -// return messages.length === 0 || isBlockedPeer || !conversation; -// }; - -// const keyExtractor = (item: MessageToDisplay) => item.id; - -// export function Chat() { -// const conversation = useConversationContext("conversation"); -// const AnimatedListView = useAnimatedListView(conversation); -// const isBlockedPeer = useConversationContext("isBlockedPeer"); -// const onReadyToFocus = useConversationContext("onReadyToFocus"); -// const frameTextInputFocused = useConversationContext("frameTextInputFocused"); -// const rolledUpReactions = -// useMessageReactionsStore.getState().rolledUpReactions; - -// const xmtpAddress = useCurrentAccount() as string; -// const peerSocials = usePeerSocials(); -// const recommendationsData = useRecommendationsStore( -// useShallow((s) => -// conversation?.peerAddress ? s.frens[conversation.peerAddress] : undefined -// ) -// ); - -// const colorScheme = useColorScheme(); -// const styles = useStyles(); - -// const messageAttachmentsLength = useChatStore( -// useShallow((s) => Object.keys(s.messageAttachments).length) -// ); - -// const listArray = useMemo( -// () => getListArray(xmtpAddress, conversation), -// // eslint-disable-next-line react-hooks/exhaustive-deps -// [ -// xmtpAddress, -// conversation, -// conversation?.lastUpdateAt, -// messageAttachmentsLength, -// ] -// ); - -// const DEFAULT_INPUT_HEIGHT = 58; -// const chatInputHeight = useSharedValue(0); -// const chatInputDisplayedHeight = useDerivedValue(() => { -// return frameTextInputFocused -// ? 0 -// : chatInputHeight.value + DEFAULT_INPUT_HEIGHT; -// }); - -// const insets = useSafeAreaInsets(); - -// const { height: keyboardHeight } = useKeyboardAnimation(); -// const tertiary = tertiaryBackgroundColor(colorScheme); - -// const showChatInput = !!( -// conversation && -// !isBlockedPeer && -// (!conversation.isGroup || -// conversation.groupMembers.includes(getCleanAddress(xmtpAddress))) -// ); - -// const textInputStyle = useAnimatedStyle( -// () => ({ -// position: "absolute", -// width: "100%", -// backgroundColor: tertiary, -// height: "auto", -// zIndex: 1, -// transform: [ -// { translateY: -Math.max(insets.bottom, keyboardHeight.value) }, -// ] as any, -// }), -// [keyboardHeight, insets.bottom] -// ); - -// useEffect(() => { -// const unsubscribe = useMessageReactionsStore.subscribe((state) => { -// if (state.rolledUpReactions) { -// Keyboard.dismiss(); -// } -// }); -// return unsubscribe; -// }, [rolledUpReactions]); - -// const chatContentStyle = useAnimatedStyle( -// () => ({ -// ...styles.chatContent, -// paddingBottom: showChatInput -// ? chatInputDisplayedHeight.value + -// Math.max(insets.bottom, keyboardHeight.value) -// : insets.bottom, -// }), -// [showChatInput, keyboardHeight, chatInputDisplayedHeight, insets.bottom] -// ); - -// const ListFooterComponent = useMemo(() => { -// const recommendationData = getProfileData(recommendationsData, peerSocials); -// if (!recommendationData || !conversation?.peerAddress) return null; -// return ( -// -// -// -// ); -// }, [ -// conversation?.peerAddress, -// peerSocials, -// recommendationsData, -// styles.inChatRecommendations, -// ]); - -// const { messageFramesMap, frames: framesStore } = useFramesStore( -// useSelect(["messageFramesMap", "frames"]) -// ); - -// const showPlaceholder = useIsShowingPlaceholder({ -// messages: listArray, -// isBlockedPeer, -// conversation, -// }); - -// const renderItem = useRenderItem({ -// xmtpAddress, -// conversation, -// messageFramesMap, -// colorScheme, -// }); - -// const messageListRef = useRef< -// FlatList | FlashList | undefined -// >(); - -// const scrollToMessage = useCallback( -// (data: { messageId?: string; index?: number; animated?: boolean }) => { -// let index = data.index; - -// if (index === undefined && data.messageId) { -// index = listArray.findIndex((m) => m.id === data.messageId); -// } - -// if (index !== undefined) { -// messageListRef.current?.scrollToIndex({ -// index, -// viewPosition: 0.5, -// animated: !!data.animated, -// }); -// } -// }, -// [listArray] -// ); - -// useEffect(() => { -// converseEventEmitter.on("scrollChatToMessage", scrollToMessage); - -// return () => { -// converseEventEmitter.off("scrollChatToMessage", scrollToMessage); -// }; -// }, [scrollToMessage]); - -// const handleOnLayout = useCallback(() => { -// setTimeout(() => { -// onReadyToFocus(); -// }, 50); -// }, [onReadyToFocus]); - -// return ( -// -// -// -// {conversation && listArray.length > 0 && !isBlockedPeer && ( -// { -// if (r) { -// messageListRef.current = r; -// } -// }} -// 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={80} -// // Size glitch on Android -// showsVerticalScrollIndicator={Platform.OS === "ios"} -// pointerEvents="auto" -// ListFooterComponent={ListFooterComponent} -// /> -// )} -// {conversation?.isGroup ? : } -// -// {showChatInput && ( -// <> -// -// -// -// -// -// )} -// -// -// -// -// ); -// } - -// // Lightweight chat preview component used for longpress on chat -// export function ChatPreview() { -// const conversation = useConversationContext("conversation"); -// const AnimatedListView = useAnimatedListView(conversation); -// const isBlockedPeer = useConversationContext("isBlockedPeer"); -// const onReadyToFocus = useConversationContext("onReadyToFocus"); - -// const xmtpAddress = useCurrentAccount() as string; -// const peerSocials = usePeerSocials(); - -// const colorScheme = useColorScheme(); -// const styles = useStyles(); -// const messageAttachmentsLength = useChatStore( -// useShallow((s) => Object.keys(s.messageAttachments).length) -// ); - -// const listArray = useMemo( -// // Get only the last 20 messages for performance in preview -// () => getListArray(xmtpAddress, conversation, 20), -// // eslint-disable-next-line react-hooks/exhaustive-deps -// [ -// xmtpAddress, -// conversation, -// conversation?.lastUpdateAt, -// messageAttachmentsLength, -// ] -// ); - -// const { frames: framesStore, messageFramesMap } = useFramesStore( -// useSelect(["frames", "messageFramesMap"]) -// ); - -// const showPlaceholder = useIsShowingPlaceholder({ -// messages: listArray, -// isBlockedPeer, -// conversation, -// }); - -// const renderItem = useRenderItem({ -// xmtpAddress, -// conversation, -// messageFramesMap, -// colorScheme, -// }); - -// const keyExtractor = useCallback((item: MessageToDisplay) => item.id, []); - -// const messageListRef = useRef< -// FlatList | FlashList | undefined -// >(); - -// const handleOnLayout = useCallback(() => { -// setTimeout(() => { -// onReadyToFocus(); -// }, 50); -// }, [onReadyToFocus]); - -// return ( -// -// -// {conversation && listArray.length > 0 && !isBlockedPeer && ( -// { -// if (r) { -// messageListRef.current = r; -// } -// }} -// keyboardDismissMode="interactive" -// automaticallyAdjustContentInsets={false} -// contentInsetAdjustmentBehavior="never" -// estimatedListSize={Dimensions.get("screen")} -// inverted -// keyExtractor={keyExtractor} -// getItemType={getItemType} -// keyboardShouldPersistTaps="handled" -// estimatedItemSize={80} -// showsVerticalScrollIndicator={false} -// pointerEvents="none" -// /> -// )} -// {showPlaceholder && !conversation?.isGroup && ( -// -// )} -// -// -// ); -// } - -// const useStyles = () => { -// const colorScheme = useColorScheme(); -// const { theme } = useAppTheme(); - -// return useMemo( -// () => -// StyleSheet.create({ -// chatContainer: { -// flex: 1, -// justifyContent: "flex-end", -// backgroundColor: theme.colors.background.surface, -// }, -// chatContent: { -// backgroundColor: theme.colors.background.surface, -// flex: 1, -// }, -// chatPreviewContent: { -// backgroundColor: theme.colors.background.surface, -// flex: 1, -// paddingBottom: 0, -// }, -// chat: { -// backgroundColor: theme.colors.background.surface, -// }, -// inputBottomFiller: { -// position: "absolute", -// width: "100%", -// bottom: 0, -// backgroundColor: theme.colors.background.surface, -// zIndex: 0, -// }, -// inChatRecommendations: { -// borderBottomWidth: 0.5, -// borderBottomColor: itemSeparatorColor(colorScheme), -// marginHorizontal: 20, -// marginBottom: 10, -// }, -// }), -// [colorScheme, theme] -// ); -// }; diff --git a/components/Chat/ChatDumb.tsx b/components/Chat/ChatDumb.tsx deleted file mode 100644 index c96644195..000000000 --- a/components/Chat/ChatDumb.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import { FlashList, ListRenderItem } from "@shopify/flash-list"; -import { - backgroundColor, - itemSeparatorColor, - tertiaryBackgroundColor, -} from "@styles/colors"; -import React, { useCallback, useEffect, useMemo, useRef } from "react"; -import { - FlatList, - Platform, - StyleSheet, - View, - useColorScheme, -} from "react-native"; -import Animated, { - useAnimatedStyle, - useDerivedValue, - useSharedValue, -} from "react-native-reanimated"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { RemoteAttachmentContent } from "@xmtp/react-native-sdk"; -import { ReanimatedFlashList } from "../../utils/animations"; -import { useKeyboardAnimation } from "../../utils/animations/keyboardAnimation"; -import { converseEventEmitter } from "../../utils/events"; - -type ChatDumbProps = { - onReadyToFocus: () => void; - frameTextInputFocused: boolean; - items: T[]; - renderItem: ListRenderItem; - keyExtractor: (item: T) => string; - showChatInput: boolean; - ListFooterComponent: React.JSX.Element | null; - showPlaceholder: boolean; - key?: string; - displayList: boolean; - refreshing: boolean; - getItemType: ( - item: T, - index: number, - extraData?: any - ) => string | number | undefined; - placeholderComponent: React.JSX.Element | null; - extraData?: any; - itemToId: (id: T) => string; - onSend: (payload: { - text?: string; - referencedMessageId?: string; - attachment?: RemoteAttachmentContent; - }) => Promise; -}; - -const useStyles = () => { - const colorScheme = useColorScheme(); - return useMemo( - () => - StyleSheet.create({ - chatContainer: { - flex: 1, - justifyContent: "flex-end", - backgroundColor: backgroundColor(colorScheme), - }, - chatContent: { - backgroundColor: backgroundColor(colorScheme), - flex: 1, - }, - chatPreviewContent: { - backgroundColor: backgroundColor(colorScheme), - flex: 1, - paddingBottom: 0, - }, - chat: { - backgroundColor: backgroundColor(colorScheme), - }, - inputBottomFiller: { - position: "absolute", - width: "100%", - bottom: 0, - backgroundColor: backgroundColor(colorScheme), - zIndex: 0, - }, - inChatRecommendations: { - borderBottomWidth: 0.5, - borderBottomColor: itemSeparatorColor(colorScheme), - marginHorizontal: 20, - marginBottom: 10, - }, - }), - [colorScheme] - ); -}; - -export function ChatDumb({ - onReadyToFocus, - frameTextInputFocused, - items, - renderItem, - keyExtractor, - showChatInput, - showPlaceholder, - key, - displayList, - refreshing, - ListFooterComponent, - getItemType, - placeholderComponent, - extraData, - itemToId, - onSend, -}: ChatDumbProps) { - const colorScheme = useColorScheme(); - const styles = useStyles(); - - const hideInputIfFrameFocused = Platform.OS !== "web"; - - const DEFAULT_INPUT_HEIGHT = 58; - const chatInputHeight = useSharedValue(0); - const chatInputDisplayedHeight = useDerivedValue(() => { - return frameTextInputFocused && hideInputIfFrameFocused - ? 0 - : chatInputHeight.value + DEFAULT_INPUT_HEIGHT; - }); - - const insets = useSafeAreaInsets(); - - const { height: keyboardHeight } = useKeyboardAnimation(); - const tertiary = tertiaryBackgroundColor(colorScheme); - - const textInputStyle = useAnimatedStyle( - () => ({ - position: "absolute", - width: "100%", - backgroundColor: tertiary, - height: "auto", - zIndex: 1, - transform: [ - { translateY: -Math.max(insets.bottom, keyboardHeight.value) }, - ] as any, - }), - [keyboardHeight, colorScheme, insets.bottom] - ); - - const chatContentStyle = useAnimatedStyle( - () => ({ - ...styles.chatContent, - paddingBottom: showChatInput - ? chatInputDisplayedHeight.value + - Math.max(insets.bottom, keyboardHeight.value) - : insets.bottom, - }), - [showChatInput, keyboardHeight, chatInputDisplayedHeight, insets.bottom] - ); - - const messageListRef = useRef | FlashList | undefined>(); - - const scrollToMessage = useCallback( - (data: { messageId?: string; index?: number; animated?: boolean }) => { - let index = data.index; - if (index === undefined && data.messageId) { - index = items.findIndex((m) => itemToId(m) === data.messageId); - } - if (index !== undefined) { - messageListRef.current?.scrollToIndex({ - index, - viewPosition: 0.5, - animated: !!data.animated, - }); - } - }, - [itemToId, items] - ); - - useEffect(() => { - converseEventEmitter.on("scrollChatToMessage", scrollToMessage); - return () => { - converseEventEmitter.off("scrollChatToMessage", scrollToMessage); - }; - }, [scrollToMessage]); - - const handleOnLayout = useCallback(() => { - setTimeout(() => { - onReadyToFocus(); - }, 50); - }, [onReadyToFocus]); - - return ( - - - {displayList && ( - - )} - {showPlaceholder && placeholderComponent} - - {/* {showChatInput && ( - <> - - - - - - )} */} - - ); -} diff --git a/components/Chat/ChatPlaceholder/ChatPlaceholder.tsx b/components/Chat/ChatPlaceholder/ChatPlaceholder.tsx deleted file mode 100644 index 0849e2416..000000000 --- a/components/Chat/ChatPlaceholder/ChatPlaceholder.tsx +++ /dev/null @@ -1,165 +0,0 @@ -// import { Button } from "@design-system/Button/Button"; -// import { translate } from "@i18n"; -// import { actionSheetColors, textPrimaryColor } from "@styles/colors"; -// import { isV3Topic } from "@utils/groupUtils/groupId"; -// import { -// Keyboard, -// Platform, -// StyleSheet, -// Text, -// TouchableWithoutFeedback, -// useColorScheme, -// View, -// } from "react-native"; - -// import { -// currentAccount, -// useProfilesStore, -// useRecommendationsStore, -// useSettingsStore, -// } from "../../../data/store/accountsStore"; -// import { useConversationContext } from "../../../utils/conversation"; -// import { sendMessage } from "../../../utils/message"; -// import { getProfile, getProfileData } from "../../../utils/profile"; -// import { conversationName } from "../../../utils/str"; -// import ActivityIndicator from "../../ActivityIndicator/ActivityIndicator"; -// import { Recommendation } from "../../Recommendations/Recommendation"; -// import { showActionSheetWithOptions } from "../../StateHandlers/ActionSheetStateHandler"; -// import { consentToAddressesOnProtocolByAccount } from "@utils/xmtpRN/contacts"; -// import { DmWithCodecsType } from "@utils/xmtpRN/client"; -// import { useInboxProfileSocials } from "@hooks/useInboxProfileSocials"; - -// type Props = { -// messagesCount: number; -// dm: DmWithCodecsType | undefined | null; -// }; - -// export function DmChatPlaceholder({ messagesCount, dm }: Props) { -// const topic = useConversationContext("topic"); -// const onReadyToFocus = useConversationContext("onReadyToFocus"); -// const colorScheme = useColorScheme(); -// const styles = useStyles(); -// const { peerAddress } = useInboxProfileSocials(dm?.); -// const peerSocials = useProfilesStore((s) => -// dm?.peerAddress -// ? getProfile(conversation.peerAddress, s.profiles)?.socials -// : undefined -// ); -// const profileData = getProfileData(recommendationData, peerSocials); -// return ( -// { -// Keyboard.dismiss(); -// }} -// > -// { -// if (conversation && !isBlockedPeer && messagesCount === 0) { -// onReadyToFocus(); -// } -// }} -// style={styles.chatPlaceholder} -// > -// {!conversation && ( -// -// {!topic && } -// -// {topic -// ? isV3Topic(topic) -// ? translate("group_not_found") -// : translate("conversation_not_found") -// : translate("opening_conversation")} -// -// -// )} -// {conversation && isBlockedPeer && ( -// -// This user is blocked -//