From 01629b8343704d6f65658b0e258ce136394f922b Mon Sep 17 00:00:00 2001 From: Alex Risch Date: Fri, 9 Feb 2024 13:13:51 -0700 Subject: [PATCH] Update to beta 6 --- ios/Podfile.lock | 36 ++-- package.json | 2 +- src/components/AddGroupParticipantModal.tsx | 161 ++++++++++++++++++ src/components/GroupInfoModal.tsx | 104 +++++++++++ src/context/ClientContext.tsx | 36 ++-- src/hooks/useContacts.ts | 58 +++++++ src/hooks/useGroupMessages.ts | 6 +- src/i18n/locales/en.json | 4 +- src/screens/GroupScreen.tsx | 73 ++------ .../OnboardingEnableIdentityScreen.tsx | 48 ++---- yarn.lock | 8 +- 11 files changed, 397 insertions(+), 139 deletions(-) create mode 100644 src/components/AddGroupParticipantModal.tsx create mode 100644 src/components/GroupInfoModal.tsx create mode 100644 src/hooks/useContacts.ts diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 81afb94..1278871 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -106,16 +106,16 @@ PODS: - GenericJSON (2.0.2) - glog (0.3.5) - GzipSwift (5.1.1) - - hermes-engine (0.73.0): - - hermes-engine/Pre-built (= 0.73.0) - - hermes-engine/Pre-built (0.73.0) + - hermes-engine (0.73.2): + - hermes-engine/Pre-built (= 0.73.2) + - hermes-engine/Pre-built (0.73.2) - libevent (2.1.12) - - LibXMTP (0.4.1-beta0) + - LibXMTP (0.4.1-beta2) - Logging (1.0.0) - MessagePacker (0.4.7) - - MMKV (1.3.2): - - MMKVCore (~> 1.3.2) - - MMKVCore (1.3.2) + - MMKV (1.3.3): + - MMKVCore (~> 1.3.3) + - MMKVCore (1.3.3) - OpenSSL-Universal (1.1.1100) - RCT-Folly (2022.05.16.00): - boost @@ -1217,16 +1217,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.7.8-alpha0): + - XMTP (0.8.2): - Connect-Swift (= 0.3.0) - GzipSwift - - LibXMTP (= 0.4.1-beta0) + - LibXMTP (= 0.4.1-beta2) - web3.swift - - XMTPReactNative (1.27.0-beta.3): + - XMTPReactNative (1.27.0-beta.6): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.7.8-alpha0) + - XMTP (= 0.8.2) - Yoga (1.14.0) DEPENDENCIES: @@ -1532,7 +1532,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8 - boost: 26fad476bfa736552bbfa698a06cc530475c1505 + boost: d3f49c53809116a5d38da093a8aa78bf551aed09 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152 @@ -1563,13 +1563,13 @@ SPEC CHECKSUMS: GenericJSON: 79a840eeb77030962e8cf02a62d36bd413b67626 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa - hermes-engine: 34304f8c6e8fa68f63a5fe29af82f227d817d7a7 + hermes-engine: b361c9ef5ef3cda53f66e195599b47e1f84ffa35 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: a3bb8d00c275034e55f1f7bf632335821c792d3c + LibXMTP: a9c3d09126ad70443c991f439283dc95224a46ff Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 - MMKV: f21593c0af4b3f2a0ceb8f820f28bb639ea22bb7 - MMKVCore: 31b4cb83f8266467eef20a35b6d78e409a11060d + MMKV: f902fb6719da13c2ab0965233d8963a59416f911 + MMKVCore: d26e4d3edd5cb8588c2569222cbd8be4231374e9 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: 9b1e7e262745fb671e33c51c1078d093bd30e322 @@ -1635,8 +1635,8 @@ SPEC CHECKSUMS: SwiftProtobuf: b02b5075dcf60c9f5f403000b3b0c202a11b6ae1 WatermelonDB: 842d22ba555425aa9f3ce551239a001200c539bc web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: 7c308fde3213aa0b0ad8198c9984932260f22b65 - XMTPReactNative: 32916f42a52a58dec82fe7c844034bb930a627f1 + XMTP: b70e7b864e38d430d2b55e813f33eec775ed0f0d + XMTPReactNative: ca17e3be61be4744a89182c372228c4a776a4693 Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 PODFILE CHECKSUM: c765268d8eab018a5f4619e1d00ca4dab437bc4f diff --git a/package.json b/package.json index 9401d93..5a3bcca 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@tanstack/react-query": "^5.17.19", "@thirdweb-dev/react-native": "^0.5.4", "@thirdweb-dev/react-native-compat": "^0.5.4", - "@xmtp/react-native-sdk": "1.27.0-beta.3", + "@xmtp/react-native-sdk": "1.27.0-beta.6", "aws-sdk": "^2.1540.0", "ethers": "^5", "expo": ">=50.0.0-0 <51.0.0", diff --git a/src/components/AddGroupParticipantModal.tsx b/src/components/AddGroupParticipantModal.tsx new file mode 100644 index 0000000..c29049b --- /dev/null +++ b/src/components/AddGroupParticipantModal.tsx @@ -0,0 +1,161 @@ +import {Group} from '@xmtp/react-native-sdk/build/lib/Group'; +import {Box, FlatList, HStack, Input, Pressable, VStack} from 'native-base'; +import React, {FC, useCallback, useMemo, useState} from 'react'; +import {ListRenderItem, useWindowDimensions} from 'react-native'; +import {TestIds} from '../consts/TestIds'; +import {useContactInfo} from '../hooks/useContactInfo'; +import {useContacts} from '../hooks/useContacts'; +import {translate} from '../i18n'; +import {colors} from '../theme/colors'; +import {formatAddress} from '../utils/formatAddress'; +import {AvatarWithFallback} from './AvatarWithFallback'; +import {Button} from './common/Button'; +import {Icon} from './common/Icon'; +import {Modal} from './common/Modal'; +import {Text} from './common/Text'; + +export interface GroupInfoModalProps { + shown: boolean; + hide: () => void; + group: Group; +} + +const ListItem: FC<{ + address: string; + onPress: (address: string) => void; + index: number; +}> = ({address, onPress}) => { + const {avatarUrl, displayName} = useContactInfo(address); + const handlePress = () => { + onPress(address); + }; + return ( + + + + + + {displayName ?? formatAddress(address)} + + + {formatAddress(address)} + + + {/* {item.isConnected ? : null} */} + + + ); +}; + +export const AddGroupParticipantModal: FC = ({ + hide, + shown, + group, +}) => { + const {height} = useWindowDimensions(); + const [searchText, setSearchText] = useState(''); + const [participants, setParticipants] = useState([]); + const contacts = useContacts(); + + const onAdd = useCallback(async () => { + await group.addMembers(participants); + hide(); + }, [participants, group, hide]); + + const onItemPress = useCallback( + (address: string) => { + if (participants.includes(address)) { + return setParticipants(prev => prev.filter(it => it !== address)); + } + setParticipants([...participants, address]); + }, + [participants], + ); + + const renderItem: ListRenderItem<{address: string; name: string}> = ({ + item, + index, + }) => { + return ( + + ); + }; + + const items = useMemo(() => { + const filteredContacts = contacts.filter(contact => + contact.address.toLowerCase().includes(searchText.toLowerCase()), + ); + + return [ + ...(searchText + ? [ + { + name: formatAddress(searchText), + address: searchText, + }, + ] + : []), + ...filteredContacts, + ]; + }, [contacts, searchText]); + + return ( + + + + + + } + rightElement={ + searchText ? ( + setSearchText('')}> + + + ) : undefined + } + backgroundColor={colors.backgroundTertiary} + value={searchText} + onChangeText={setSearchText} + marginX={'16px'} + paddingY={'12px'} + paddingX={'8px'} + /> + + {participants.length > 0 && ( + + )} + + + ); +}; diff --git a/src/components/GroupInfoModal.tsx b/src/components/GroupInfoModal.tsx new file mode 100644 index 0000000..bb5f5f5 --- /dev/null +++ b/src/components/GroupInfoModal.tsx @@ -0,0 +1,104 @@ +import {Group} from '@xmtp/react-native-sdk/build/lib/Group'; +import {HStack, Pressable, VStack} from 'native-base'; +import React, {FC, useCallback} from 'react'; +import {useContactInfo} from '../hooks/useContactInfo'; +import {translate} from '../i18n'; +import {colors} from '../theme/colors'; +import {AvatarWithFallback} from './AvatarWithFallback'; +import {Button} from './common/Button'; +import {Icon} from './common/Icon'; +import {Modal} from './common/Modal'; +import {Text} from './common/Text'; + +export interface GroupInfoModalProps { + shown: boolean; + hide: () => void; + addresses: string[]; + onPlusPress: () => void; + group: Group; +} + +const GroupParticipant: React.FC<{ + address: string; + onRemove: (address: string) => void; +}> = ({address, onRemove}) => { + const {displayName, avatarUrl} = useContactInfo(address); + return ( + + + + {displayName} + + + onRemove(address)}> + + + + {translate('domain_origin')} + + + ); +}; + +export const GroupInfoModal: FC = ({ + hide, + shown, + addresses, + onPlusPress, + group, +}) => { + const onRemovePress = useCallback( + async (address: string) => { + await group.removeMembers([address]); + hide(); + }, + [group, hide], + ); + + return ( + + + + + {translate('group')} + + + + + + {addresses?.map(address => ( + + ))} + + + ); +}; diff --git a/src/context/ClientContext.tsx b/src/context/ClientContext.tsx index e0e93e9..d053ff8 100644 --- a/src/context/ClientContext.tsx +++ b/src/context/ClientContext.tsx @@ -38,32 +38,18 @@ export const ClientProvider: FC = ({children}) => { if (!keys) { return setLoading(false); } - if (AppConfig.GROUPS_ENABLED) { - Client.createRandom({ - codecs: [new RemoteAttachmentCodec()], - env: AppConfig.XMTP_ENV, + Client.createFromKeyBundle(keys, { + codecs: [new RemoteAttachmentCodec()], + enableAlphaMls: AppConfig.GROUPS_ENABLED, + env: AppConfig.XMTP_ENV, + }) + .then(newClient => { + setClient(newClient); + setLoading(false); }) - .then(newClient => { - setClient(newClient); - setLoading(false); - }) - .catch(() => { - setLoading(false); - }); - } else { - Client.createFromKeyBundle(keys, { - codecs: [new RemoteAttachmentCodec()], - enableAlphaMls: AppConfig.GROUPS_ENABLED, - env: AppConfig.XMTP_ENV, - }) - .then(newClient => { - setClient(newClient); - setLoading(false); - }) - .catch(() => { - setLoading(false); - }); - } + .catch(() => { + setLoading(false); + }); }) .catch(() => { return setLoading(false); diff --git a/src/hooks/useContacts.ts b/src/hooks/useContacts.ts new file mode 100644 index 0000000..9092d7e --- /dev/null +++ b/src/hooks/useContacts.ts @@ -0,0 +1,58 @@ +import {useSupportedChains, useWalletContext} from '@thirdweb-dev/react-native'; +import {useEffect, useMemo, useState} from 'react'; +import {formatAddress} from '../utils/formatAddress'; +import {getEnsInfo} from '../utils/getEnsInfo'; +import {useClient} from './useClient'; + +export const useContacts = () => { + const {client} = useClient(); + const [contacts, setContacts] = useState<{name: string; address: string}[]>( + [], + ); + const supportedChains = useSupportedChains(); + const {clientId} = useWalletContext(); + + useEffect(() => { + const fetchConsentList = async () => { + const list = await client?.contacts?.consentList(); + list?.forEach(async item => { + if (item.permissionType === 'allowed') { + getEnsInfo(item.value, supportedChains, clientId) + .then(({ens}) => { + setContacts(prev => [ + ...prev, + { + name: ens ?? formatAddress(item.value), + address: item.value, + }, + ]); + }) + .catch(() => { + setContacts(prev => [ + ...prev, + { + name: formatAddress(item.value), + address: item.value, + }, + ]); + }); + } + }); + }; + fetchConsentList(); + }, [client?.contacts, client?.conversations, clientId, supportedChains]); + + // Avoids hot refresh adding the same contact multiple times + const filterContacts = useMemo(() => { + const map = new Map(); + const filtered = contacts.filter(contact => { + if (map.has(contact.address)) { + return false; + } + map.set(contact.address, contact); + return true; + }); + return filtered; + }, [contacts]); + return filterContacts; +}; diff --git a/src/hooks/useGroupMessages.ts b/src/hooks/useGroupMessages.ts index 18ae32f..5040194 100644 --- a/src/hooks/useGroupMessages.ts +++ b/src/hooks/useGroupMessages.ts @@ -15,12 +15,14 @@ export const useGroupMessages = (id: string) => { }, [group]); useEffect(() => { - let cancelStream: (() => void) | undefined; getMessages(); + const cancelStream = group?.streamGroupMessages(async message => { + setMessages(prevMessages => [message, ...prevMessages]); + }); return () => { cancelStream?.(); }; - }, [getMessages]); + }, [getMessages, group]); return { messages, diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 8ab3a6e..ec187ee 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -55,5 +55,7 @@ "metamask": "Metamask", "coinbase_wallet": "Coinbase Wallet", "guest_wallet": "Guest Wallet", - "group_changed": "Group Changed" + "group_changed": "Group Changed", + "add_to_group": "Add", + "group": "Group" } \ No newline at end of file diff --git a/src/screens/GroupScreen.tsx b/src/screens/GroupScreen.tsx index 432a21e..64f19be 100644 --- a/src/screens/GroupScreen.tsx +++ b/src/screens/GroupScreen.tsx @@ -11,18 +11,16 @@ import { import React, {useCallback, useEffect, useState} from 'react'; import {ListRenderItem, Platform} from 'react-native'; import {Asset} from 'react-native-image-picker'; -import {AvatarWithFallback} from '../components/AvatarWithFallback'; +import {AddGroupParticipantModal} from '../components/AddGroupParticipantModal'; import {ConversationInput} from '../components/ConversationInput'; import {ConversationMessageContent} from '../components/ConversationMessageContent'; import {GroupHeader} from '../components/GroupHeader'; +import {GroupInfoModal} from '../components/GroupInfoModal'; import {Button} from '../components/common/Button'; import {Drawer} from '../components/common/Drawer'; -import {Icon} from '../components/common/Icon'; -import {Modal} from '../components/common/Modal'; import {Screen} from '../components/common/Screen'; import {Text} from '../components/common/Text'; import {useClient} from '../hooks/useClient'; -import {useContactInfo} from '../hooks/useContactInfo'; import {useGroup} from '../hooks/useGroup'; import {useGroupMessages} from '../hooks/useGroupMessages'; import {translate} from '../i18n'; @@ -30,38 +28,6 @@ import {getConsent, saveConsent} from '../services/mmkvStorage'; import {AWSHelper} from '../services/s3'; import {colors} from '../theme/colors'; -const GroupParticipant: React.FC<{address: string}> = ({address}) => { - const {displayName, avatarUrl} = useContactInfo(address); - return ( - - - - {displayName} - - - - {translate('domain_origin')} - - - - ); -}; - const getTimestamp = (timestamp: number) => { // If today, return hours and minutes if not return date const date = new Date(timestamp); @@ -83,16 +49,6 @@ const useData = (id: string) => { const {messages, refetch} = useGroupMessages(id); const {client} = useClient(); const {group} = useGroup(id); - // const cachedPeerAddress = getTopicAddresses(id)?.[0]; - // const {displayName, avatarUrl} = useContactInfo( - // conversation?.peerAddress || '', - // ); - - // useEffect(() => { - // if (id && conversation?.peerAddress) { - // saveTopicAddresses(id, [conversation?.peerAddress]); - // } - // }, [conversation?.peerAddress, id]); return { // profileImage: avatarUrl, @@ -123,6 +79,7 @@ export const GroupScreen = () => { const {myAddress, messages, addresses, group, client, refetch} = useData(id); const [showReply, setShowReply] = useState(false); const [showGroupModal, setShowGroupModal] = useState(false); + const [showAddModal, setShowAddModal] = useState(false); const [consent, setConsent] = useState<'allowed' | 'denied' | 'unknown'>( getInitialConsentState(myAddress ?? '', group?.id ?? ''), ); @@ -280,15 +237,21 @@ export const GroupScreen = () => { - setShowGroupModal(false)} - isOpen={showGroupModal}> - - {addresses?.map(address => ( - - ))} - - + setShowGroupModal(false)} + addresses={addresses ?? []} + onPlusPress={() => { + setShowGroupModal(false); + setShowAddModal(true); + }} + group={group!} + /> + setShowAddModal(false)} + group={group!} + /> ); }; diff --git a/src/screens/OnboardingEnableIdentityScreen.tsx b/src/screens/OnboardingEnableIdentityScreen.tsx index 4816927..401763e 100644 --- a/src/screens/OnboardingEnableIdentityScreen.tsx +++ b/src/screens/OnboardingEnableIdentityScreen.tsx @@ -53,39 +53,21 @@ export const OnboardingEnableIdentityScreen = () => { return; } try { - if (AppConfig.GROUPS_ENABLED) { - const client = await Client.createRandom({ - enableAlphaMls: AppConfig.GROUPS_ENABLED, - env: AppConfig.XMTP_ENV, - preEnableIdentityCallback: async () => { - await enableIdentityPromise; - }, - preCreateIdentityCallback: async () => { - await createIdentityPromise; - }, - codecs: [new RemoteAttachmentCodec()], - }); - const keys = await client.exportKeyBundle(); - const address = await signer.getAddress(); - saveClientKeys(address as `0x${string}`, keys); - setClient(client); - } else { - const client = await Client.create(signer, { - enableAlphaMls: AppConfig.GROUPS_ENABLED, - env: AppConfig.XMTP_ENV, - preEnableIdentityCallback: async () => { - await enableIdentityPromise; - }, - preCreateIdentityCallback: async () => { - await createIdentityPromise; - }, - codecs: [new RemoteAttachmentCodec()], - }); - const keys = await client.exportKeyBundle(); - const address = await signer.getAddress(); - saveClientKeys(address as `0x${string}`, keys); - setClient(client); - } + const client = await Client.create(signer, { + enableAlphaMls: AppConfig.GROUPS_ENABLED, + env: AppConfig.XMTP_ENV, + preEnableIdentityCallback: async () => { + await enableIdentityPromise; + }, + preCreateIdentityCallback: async () => { + await createIdentityPromise; + }, + codecs: [new RemoteAttachmentCodec()], + }); + const keys = await client.exportKeyBundle(); + const address = await signer.getAddress(); + saveClientKeys(address as `0x${string}`, keys); + setClient(client); } catch (e) { console.log('Error creating client', e); } diff --git a/yarn.lock b/yarn.lock index 055be87..e0977d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9525,10 +9525,10 @@ rxjs "^7.8.0" undici "^5.8.1" -"@xmtp/react-native-sdk@1.27.0-beta.3": - version "1.27.0-beta.3" - resolved "https://registry.yarnpkg.com/@xmtp/react-native-sdk/-/react-native-sdk-1.27.0-beta.3.tgz#3e8ea0deab4e3c571cbe6251d336a1989988d1a7" - integrity sha512-vGBhcUMtre3I2YUMI94bkYr2IB52R+m8XoDnt3SKgePJeD49QTeyzoVpenFnvRj47HOXXUIlqAnW6ay3bHzyxQ== +"@xmtp/react-native-sdk@1.27.0-beta.6": + version "1.27.0-beta.6" + resolved "https://registry.yarnpkg.com/@xmtp/react-native-sdk/-/react-native-sdk-1.27.0-beta.6.tgz#171956da15a70049c4040cd9a7f4440d0faacbcc" + integrity sha512-TdxdAuR8eCudNfZLNlYOFmMBcOCTrxCIYLNCqqlcyHiyxSGT4tBB2IwRzvbvZkJpF22qTnzWyPdwtyAo4Cx1mA== dependencies: "@ethersproject/bytes" "^5.7.0" "@msgpack/msgpack" "^3.0.0-beta2"