diff --git a/components/Conversation/MessageComposer.tsx b/components/Conversation/MessageComposer.tsx index 13c29409..c53134c4 100644 --- a/components/Conversation/MessageComposer.tsx +++ b/components/Conversation/MessageComposer.tsx @@ -4,27 +4,56 @@ import messageComposerStyles from '../../styles/MessageComposer.module.css' import upArrowGreen from '../../public/up-arrow-green.svg' import upArrowGrey from '../../public/up-arrow-grey.svg' import { useRouter } from 'next/router' +import { AudioRecorder } from 'react-audio-voice-recorder' import Image from 'next/image' type MessageComposerProps = { - onSend: (msg: string) => Promise + onSend: (msg: object) => Promise } +type Message = { content: string | ArrayBuffer; contentType: string } + const MessageComposer = ({ onSend }: MessageComposerProps): JSX.Element => { - const [message, setMessage] = useState('') + const [message, setMessage] = useState({ + content: '', + contentType: '', + }) + const router = useRouter() - useEffect(() => setMessage(''), [router.query.recipientWalletAddr]) + useEffect( + () => setMessage({ ...message, content: '' }), + [router.query.recipientWalletAddr] + ) - const onMessageChange = (e: React.FormEvent) => - setMessage(e.currentTarget.value) + const onMessageChange = (e: React.FormEvent) => { + setMessage({ ...message, content: e.currentTarget.value }) + } + + const addAudioElement = (blob: Blob) => { + const reader = new FileReader() + reader.readAsDataURL(blob) + return new Promise(() => { + reader.onloadend = () => { + if (reader.result !== null) { + setMessage({ + ...message, + content: reader.result, + contentType: 'voiceMemo', + }) + } + } + }) + } const onSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!message) { + const contentMessage = message.content + + if (!contentMessage) { return } - setMessage('') + setMessage({ ...message, content: '', contentType: '' }) await onSend(message) } @@ -46,6 +75,7 @@ const MessageComposer = ({ onSend }: MessageComposerProps): JSX.Element => { autoComplete="off" onSubmit={onSubmit} > + { messageComposerStyles.input )} name="message" - value={message} + value={message.content} onChange={onMessageChange} required /> diff --git a/components/Conversation/MessagesList.tsx b/components/Conversation/MessagesList.tsx index b9d54527..cb6c1d2e 100644 --- a/components/Conversation/MessagesList.tsx +++ b/components/Conversation/MessagesList.tsx @@ -24,6 +24,23 @@ const isOnSameDay = (d1?: Date, d2?: Date): boolean => { const formatDate = (d?: Date) => d?.toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) +const TypeOfMessage = ({ message }: MessageTileProps): JSX.Element => { + const contentTypeId = message.contentType.typeId + const isVoiceMemo = contentTypeId === 'voice-key' + + if (isVoiceMemo) { + return ( + + ) + } else if (message.error) { + return
{message.error.message}
+ } else { + return + } +} + const MessageTile = ({ message }: MessageTileProps): JSX.Element => (
@@ -35,11 +52,12 @@ const MessageTile = ({ message }: MessageTileProps): JSX.Element => (
- {message.error ? ( + + {/* {message.error ? ( `Error: ${message.error?.message}` ) : ( - )} + )} */} diff --git a/components/ConversationsList.tsx b/components/ConversationsList.tsx index 0bf158f2..1d31bbcc 100644 --- a/components/ConversationsList.tsx +++ b/components/ConversationsList.tsx @@ -43,6 +43,8 @@ const ConversationTile = ({ const latestMessage = previewMessages.get(getConversationKey(conversation)) + const contentTypeId = latestMessage?.contentType.typeId + const conversationDomain = conversation.context?.conversationId.split('/')[0] ?? '' @@ -103,8 +105,10 @@ const ConversationTile = ({ - {address === latestMessage?.senderAddress && 'You: '}{' '} - {latestMessage?.content} + {address === latestMessage?.senderAddress && 'You: '} + {contentTypeId === 'voice-key' + ? 'Audio File' + : latestMessage?.content} diff --git a/hooks/useInitXmtpClient.ts b/hooks/useInitXmtpClient.ts index 49a7061c..35a6dd86 100644 --- a/hooks/useInitXmtpClient.ts +++ b/hooks/useInitXmtpClient.ts @@ -1,4 +1,4 @@ -import { Client } from '@xmtp/xmtp-js' +import { Client, ContentTypeId } from '@xmtp/xmtp-js' import { Signer } from 'ethers' import { useCallback, useEffect, useState } from 'react' import { @@ -25,6 +25,33 @@ const useInitXmtpClient = (cacheOnly = false) => { } } + const ContentTypeVoiceKey = new ContentTypeId({ + authorityId: 'xmtp.test', + typeId: 'voice-key', + versionMajor: 1, + versionMinor: 0, + }) + + class voiceCodec { + get contentType() { + return ContentTypeVoiceKey + } + + encode(key: string | undefined) { + return { + type: ContentTypeVoiceKey, + parameters: {}, + content: new TextEncoder().encode(key), + } + } + + decode(content: { content: any }) { + const uint8Array = content.content + const key = new TextDecoder().decode(uint8Array) + return key + } + } + const initClient = useCallback( async (wallet: Signer) => { if (wallet && !client) { @@ -45,6 +72,7 @@ const useInitXmtpClient = (cacheOnly = false) => { env: getEnv(), appVersion: getAppVersion(), privateKeyOverride: keys, + codecs: [new voiceCodec()], }) setClient(xmtp) setIsRequestPending(false) diff --git a/hooks/useSendMessage.ts b/hooks/useSendMessage.ts index 22d2ebef..64e9e08a 100644 --- a/hooks/useSendMessage.ts +++ b/hooks/useSendMessage.ts @@ -1,10 +1,27 @@ -import { Conversation } from '@xmtp/xmtp-js' +import { Conversation, ContentTypeId } from '@xmtp/xmtp-js' import { useCallback } from 'react' const useSendMessage = (selectedConversation?: Conversation) => { const sendMessage = useCallback( - async (message: string) => { - await selectedConversation?.send(message) + async (message: object) => { + const ContentTypeVoiceKey = new ContentTypeId({ + authorityId: 'xmtp.test', + typeId: 'voice-key', + versionMajor: 1, + versionMinor: 0, + }) + + const messageText = message.content + const isVoiceMemo = message.contentType === 'voiceMemo' + + if (isVoiceMemo) { + await selectedConversation?.send(messageText, { + contentType: ContentTypeVoiceKey, + contentFallback: 'This is a voice memo', + }) + } else { + await selectedConversation?.send(messageText) + } }, [selectedConversation] ) diff --git a/package-lock.json b/package-lock.json index 2fd1c558..0a23bd6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "imagemin-svgo": "^9.0.0", "next": "13.0.5", "react": "18.2.0", + "react-audio-voice-recorder": "^1.0.4", "react-blockies": "^1.4.1", "react-dom": "18.2.0", "react-emoji-render": "^1.2.4", @@ -11692,6 +11693,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-audio-voice-recorder": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-audio-voice-recorder/-/react-audio-voice-recorder-1.0.4.tgz", + "integrity": "sha512-NqMQqZZIau8cmzKUsf43s8rWF5lJPPebj+o6tkcX83dv2lLDdJL+jUFspynMSEZq+/T8NJYH2QUqqDAv9XF4Xw==", + "peerDependencies": { + "react": ">=16.2.0", + "react-dom": ">=16.2.0" + } + }, "node_modules/react-blockies": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/react-blockies/-/react-blockies-1.4.1.tgz", @@ -22483,6 +22493,12 @@ "loose-envify": "^1.1.0" } }, + "react-audio-voice-recorder": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-audio-voice-recorder/-/react-audio-voice-recorder-1.0.4.tgz", + "integrity": "sha512-NqMQqZZIau8cmzKUsf43s8rWF5lJPPebj+o6tkcX83dv2lLDdJL+jUFspynMSEZq+/T8NJYH2QUqqDAv9XF4Xw==", + "requires": {} + }, "react-blockies": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/react-blockies/-/react-blockies-1.4.1.tgz", diff --git a/package.json b/package.json index d46895a0..680489d4 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "imagemin-svgo": "^9.0.0", "next": "13.0.5", "react": "18.2.0", + "react-audio-voice-recorder": "^1.0.4", "react-blockies": "^1.4.1", "react-dom": "18.2.0", "react-emoji-render": "^1.2.4",