From 1d9a3e69a152264618bcf07b0143572e8613bba8 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 22 Nov 2024 17:26:56 +0100 Subject: [PATCH 01/14] Checkpoint for conversion ui changes, add initial framework for currency selection modal --- .../ConvertCardModal/ConvertCardModal.js | 128 +++++++++++++ .../ConvertCardSelectCurrency.js | 93 +++++++++ src/containers/Convert/Convert.js | 181 +++++++++++++++++- src/utils/CoinData/CoinsList.js | 23 +-- src/utils/constants/convert.js | 4 + src/utils/constants/intervalConstants.js | 1 + 6 files changed, 416 insertions(+), 14 deletions(-) create mode 100644 src/components/ConvertCardModal/ConvertCardModal.js create mode 100644 src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js create mode 100644 src/utils/constants/convert.js diff --git a/src/components/ConvertCardModal/ConvertCardModal.js b/src/components/ConvertCardModal/ConvertCardModal.js new file mode 100644 index 00000000..1f24b7da --- /dev/null +++ b/src/components/ConvertCardModal/ConvertCardModal.js @@ -0,0 +1,128 @@ +import React, { useState, useEffect } from "react"; +import { Platform, SafeAreaView, View } from "react-native"; +import { Text, Portal, Button, IconButton } from "react-native-paper"; +import Colors from "../../globals/colors"; +import SemiModal from "../SemiModal"; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; +import { NavigationContainer } from "@react-navigation/native"; +import { createStackNavigator } from "@react-navigation/stack"; +import AnimatedActivityIndicatorBox from "../AnimatedActivityIndicatorBox"; + +import ConvertCardSelectCurrency from "./ConvertCardModalTabs/ConvertCardSelectCurrency"; +import { useSelector } from "react-redux"; +import { USD } from "../../utils/constants/currencies"; +import { extractLedgerData } from "../../utils/ledger/extractLedgerData"; +import { API_GET_BALANCES } from "../../utils/constants/intervalConstants"; + +const TopTabs = createMaterialTopTabNavigator(); +const Root = createStackNavigator(); + +const ConvertCardModal = ({ onClose, visible, setVisible, totalBalances, mode, currencies, onSelectCurrency }) => { + const [loading, setLoading] = useState(false); + const [preventExit, setPreventExit] = useState(false); + const [modalHeight, setModalHeight] = useState(600); // Adjust as needed + + const activeCoinsForUser = useSelector(state => state.coins.activeCoinsForUser); + const [modalTitle, setModalTitle] = useState("Title"); + + useEffect(() => { + // Handle side effects here if necessary + }, []); + + const cancel = () => { + if (!preventExit) { + setVisible(false); + if (onClose) { + onClose(); + } + } + }; + + const showHelpModal = () => { + // Implement your help modal logic here + }; + + return ( + + + + + ( + + + + {modalTitle} + + + ), + headerStyle: { + height: 52, + }, + }} + > + + {() => ( + , + }} + > + { + e.preventDefault(); + }, + }} + > + {tabProps => ( + + )} + + + )} + + + + + + + ); +}; + +export default ConvertCardModal; \ No newline at end of file diff --git a/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js b/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js new file mode 100644 index 00000000..b48009aa --- /dev/null +++ b/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js @@ -0,0 +1,93 @@ +import React, { useMemo, useState } from 'react'; +import { View, TextInput, FlatList } from 'react-native'; +import { List, Text } from 'react-native-paper'; +import { RenderCircleCoinLogo } from '../../../utils/CoinData/Graphics'; +import Colors from '../../../globals/colors'; + +const ConvertCardSelectCurrency = (props) => { + const [searchQuery, setSearchQuery] = useState(''); + const currencies = props.currencies ? props.currencies : []; + + // Filter the data based on the search query + const filteredData = useMemo(() => { + return currencies.filter(item => + item.title.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [searchQuery]); + + // Render each item in the FlatList + const renderItem = ({ item }) => ( + props.onSelect(item.key)} + left={() => ( + + {RenderCircleCoinLogo(item.logo)} + + )} + right={props => + + {item.rightTitle && + {item.rightTitle} + } + {item.rightDescription && + {item.rightDescription} + } + + } + /> + ); + + return ( + + setSearchQuery(text)} + /> + item.id} + renderItem={renderItem} + ListEmptyComponent={ + + No items found + + } + /> + + ); +}; + +export default ConvertCardSelectCurrency; \ No newline at end of file diff --git a/src/containers/Convert/Convert.js b/src/containers/Convert/Convert.js index 3f79f9d6..a6dd96fc 100644 --- a/src/containers/Convert/Convert.js +++ b/src/containers/Convert/Convert.js @@ -4,11 +4,172 @@ import Styles from "../../styles"; import { Button } from "react-native-paper"; import Colors from "../../globals/colors"; import ConvertCard from "./ConvertCard/ConvertCard"; +import { useSelector } from "react-redux"; +import { extractLedgerData } from "../../utils/ledger/extractLedgerData"; +import { API_GET_BALANCES, GENERAL, IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE } from "../../utils/constants/intervalConstants"; +import { USD } from "../../utils/constants/currencies"; +import BigNumber from 'bignumber.js'; +import ConvertCardModal from "../../components/ConvertCardModal/ConvertCardModal"; +import { CONVERT_CARD_MODAL_MODES } from "../../utils/constants/convert"; +import { normalizeNum } from "../../utils/normalizeNum"; +import { formatCurrency } from "react-native-format-currency"; +import { useObjectSelector } from "../../hooks/useObjectSelector"; const Convert = (props) => { + const displayCurrency = useSelector(state => state.settings.generalWalletSettings.displayCurrency || USD); + + const allSubWallets = useObjectSelector(state => state.coinMenus.allSubWallets); + const activeCoinsForUser = useObjectSelector(state => state.coins.activeCoinsForUser); + const rates = useObjectSelector(state => state.ledger.rates); + const balances = useObjectSelector(state => extractLedgerData(state, 'balances', API_GET_BALANCES)); + + const [totalBalances, setTotalBalances] = useState({}); + + const [convertCardModalMode, setConvertCardModalMode] = useState(CONVERT_CARD_MODAL_MODES.SEND); + const [convertCardModalVisible, setConvertCardModalVisible] = useState(false); + + // These key value maps that contain the data that the form needs to execute on a user's selection + const [sourceCurrencyMap, setSourceCurrencyMap] = useState({}); + const [destCurrencyMap, setDestCurrencyMap] = useState({}); + + // These are in display format, with props: title, description, rightTitle, rightDescription, logo, and key + const [sourceCurrencyOptionsList, setSourceCurrencyOptionsList] = useState([]); + const [destCurrencyOptionsList, setDestCurrencyOptionsList] = useState([]); + + const [selectedSourceCurrency, setSelectedSourceCurrency] = useState(null); + + const getTotalBalances = () => { + let coinBalances = {}; + + activeCoinsForUser.map(coinObj => { + coinBalances[coinObj.id] = { + crypto: BigNumber('0'), + rate: null + }; + + allSubWallets[coinObj.id].map(wallet => { + if ( + balances[coinObj.id] != null && + balances[coinObj.id][wallet.id] != null + ) { + const cryptoBalance = coinBalances[coinObj.id].crypto.plus( + balances[coinObj.id] && + balances[coinObj.id][wallet.id] && + balances[coinObj.id][wallet.id].total != null + ? BigNumber(balances[coinObj.id][wallet.id].total) + : null, + ); + + const uniRate = rates[GENERAL] && rates[GENERAL][coinObj.id] ? rates[GENERAL][coinObj.id][displayCurrency] : null + + coinBalances[coinObj.id] = { + crypto: cryptoBalance, + rate: uniRate != null ? BigNumber(uniRate) : null + } + } + }); + }); + + return coinBalances; + }; + + const getSourceCurrencyMap = () => { + const currencyMap = {}; + + for (const coinObj of activeCoinsForUser) { + if (coinObj.proto === 'vrsc') { + currencyMap[coinObj.currency_id] = [coinObj]; + + if (coinObj.mapped_to != null && coinObj.tags.includes(IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE)) { + const mappedCoinObj = activeCoinsForUser.find(x => x.id === coinObj.mapped_to); + + if (mappedCoinObj != null) { + currencyMap[coinObj.currency_id].push(mappedCoinObj); + } + } + } + } + + return currencyMap; + }; + + const getSourceCurrencyOptionsList = () => { + const currencies = []; + + for (const coinObjs of Object.values(sourceCurrencyMap)) { + const rootCoinObj = coinObjs[0]; + const mappedCoinObj = coinObjs.length > 1 ? coinObjs[1] : null; + + const titleCoinObj = mappedCoinObj && mappedCoinObj.display_name.length > rootCoinObj.display_name.length ? + mappedCoinObj + : + rootCoinObj; + + const title = titleCoinObj.display_name; + const description = coinObjs.map(x => x.display_ticker).join(' / '); + + let rightTitle = '-'; + let rightDescription = '-'; + + let totalCryptoBalance = BigNumber(0); + let fiatRate; + + if (totalBalances[rootCoinObj.id]) { + if (totalBalances[rootCoinObj.id].crypto) { + totalCryptoBalance = totalCryptoBalance.plus(totalBalances[rootCoinObj.id].crypto); + } + + if (totalBalances[rootCoinObj.id].rate) fiatRate = totalBalances[rootCoinObj.id].rate; + } + + if (mappedCoinObj && totalBalances[mappedCoinObj.id]) { + if (totalBalances[mappedCoinObj.id].crypto) { + totalCryptoBalance = totalCryptoBalance.plus(totalBalances[mappedCoinObj.id].crypto); + } + + if (!fiatRate && totalBalances[mappedCoinObj.id].rate) fiatRate = totalBalances[mappedCoinObj.id].rate; + } + + const totalFiatBalance = fiatRate != null ? totalCryptoBalance.multipliedBy(fiatRate) : null; + + if (totalFiatBalance != null) { + const rawFiatDisplayValue = normalizeNum( + Number(totalFiatBalance.toString()), + 2, + )[3]; + + const [valueFormattedWithSymbol, valueFormattedWithoutSymbol, symbol] = + formatCurrency({amount: rawFiatDisplayValue, code: displayCurrency}); + + rightTitle = valueFormattedWithSymbol; + } + + rightDescription = `${Number(totalCryptoBalance.toString())} ${titleCoinObj.display_ticker}`; + + currencies.push({ + title, + description, + rightDescription, + rightTitle, + key: rootCoinObj.currency_id, + logo: rootCoinObj.id + }); + } + + return currencies; + }; + + useEffect(() => { + setTotalBalances(getTotalBalances()); + }, [allSubWallets, activeCoinsForUser, balances, displayCurrency, rates]); + + useEffect(() => { + setSourceCurrencyMap(getSourceCurrencyMap()); + }, [activeCoinsForUser]); + useEffect(() => { - - }, []) + setSourceCurrencyOptionsList(getSourceCurrencyOptionsList()); + }, [sourceCurrencyMap, totalBalances]); return ( { justifyContent: 'space-between' }} > + setConvertCardModalVisible(false)} + mode={convertCardModalMode} + totalBalances={totalBalances} + currencies={convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND ? sourceCurrencyOptionsList : destCurrencyOptionsList} + onSelectCurrency={(currencyId) => setSelectedSourceCurrency(currencyId)} + setVisible={setConvertCardModalVisible} + /> - + { + setConvertCardModalMode(CONVERT_CARD_MODAL_MODES.SEND); + setConvertCardModalVisible(true) + }} + /> Date: Tue, 26 Nov 2024 11:01:40 +0100 Subject: [PATCH 02/14] Checkpoint for currency conversion ui rework, add source network selection to convert card modal --- .../ConvertCardModal/ConvertCardModal.js | 44 +++++- ...rrency.js => ConvertCardSelectFromList.js} | 56 +++++--- src/containers/Convert/Convert.js | 129 +++++++++++++++++- .../HomeTabScreens/HomeTabScreens.js | 4 +- 4 files changed, 201 insertions(+), 32 deletions(-) rename src/components/ConvertCardModal/ConvertCardModalTabs/{ConvertCardSelectCurrency.js => ConvertCardSelectFromList.js} (59%) diff --git a/src/components/ConvertCardModal/ConvertCardModal.js b/src/components/ConvertCardModal/ConvertCardModal.js index 1f24b7da..909c4305 100644 --- a/src/components/ConvertCardModal/ConvertCardModal.js +++ b/src/components/ConvertCardModal/ConvertCardModal.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Platform, SafeAreaView, View } from "react-native"; import { Text, Portal, Button, IconButton } from "react-native-paper"; import Colors from "../../globals/colors"; @@ -8,7 +8,7 @@ import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import AnimatedActivityIndicatorBox from "../AnimatedActivityIndicatorBox"; -import ConvertCardSelectCurrency from "./ConvertCardModalTabs/ConvertCardSelectCurrency"; +import ConvertCardSelectFromList from "./ConvertCardModalTabs/ConvertCardSelectFromList"; import { useSelector } from "react-redux"; import { USD } from "../../utils/constants/currencies"; import { extractLedgerData } from "../../utils/ledger/extractLedgerData"; @@ -17,7 +17,17 @@ import { API_GET_BALANCES } from "../../utils/constants/intervalConstants"; const TopTabs = createMaterialTopTabNavigator(); const Root = createStackNavigator(); -const ConvertCardModal = ({ onClose, visible, setVisible, totalBalances, mode, currencies, onSelectCurrency }) => { +const ConvertCardModal = ({ + onClose, + visible, + setVisible, + totalBalances, + mode, + currencies, + onSelectCurrency, + networks, + onSelectNetwork +}) => { const [loading, setLoading] = useState(false); const [preventExit, setPreventExit] = useState(false); const [modalHeight, setModalHeight] = useState(600); // Adjust as needed @@ -97,7 +107,7 @@ const ConvertCardModal = ({ onClose, visible, setVisible, totalBalances, mode, c { @@ -106,11 +116,33 @@ const ConvertCardModal = ({ onClose, visible, setVisible, totalBalances, mode, c }} > {tabProps => ( - + )} + + { + e.preventDefault(); + }, + }} + > + {tabProps => ( + )} diff --git a/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js b/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectFromList.js similarity index 59% rename from src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js rename to src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectFromList.js index b48009aa..4b0e1d13 100644 --- a/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectCurrency.js +++ b/src/components/ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectFromList.js @@ -3,29 +3,44 @@ import { View, TextInput, FlatList } from 'react-native'; import { List, Text } from 'react-native-paper'; import { RenderCircleCoinLogo } from '../../../utils/CoinData/Graphics'; import Colors from '../../../globals/colors'; +import { useNavigation } from '@react-navigation/native'; -const ConvertCardSelectCurrency = (props) => { +const ConvertCardSelectFromList = (props) => { const [searchQuery, setSearchQuery] = useState(''); - const currencies = props.currencies ? props.currencies : []; + const items = props.items ? props.items : []; + + const navigation = useNavigation(); // Filter the data based on the search query const filteredData = useMemo(() => { - return currencies.filter(item => - item.title.toLowerCase().includes(searchQuery.toLowerCase()) + return items.filter(item => + ( + item.title.toLowerCase().includes(searchQuery.toLowerCase()) || + item.description.toLowerCase().includes(searchQuery.toLowerCase()) + ) ); - }, [searchQuery]); + }, [searchQuery, items]); + + const handleSelect = (key) => { + if (props.onSelect) props.onSelect(key); + if (props.nextScreen) navigation.navigate(props.nextScreen) + } // Render each item in the FlatList const renderItem = ({ item }) => ( props.onSelect(item.key)} + descriptionStyle={{ + color: Colors.lightGrey + }} + onPress={() => handleSelect(item.key)} left={() => ( { {...props} style={{ display: "flex", - flexDirection: "row" + flexDirection: "column", + alignItems: "flex-end", + justifyContent: "center" }}> - {item.rightTitle && - {item.rightTitle} - } - {item.rightDescription && - {item.rightDescription} - } + {item.rightTitle && ( + + {item.rightTitle} + + )} + {item.rightDescription && ( + + {item.rightDescription} + + )} } /> @@ -62,7 +83,8 @@ const ConvertCardSelectCurrency = (props) => { height: 40, marginVertical: 10, paddingHorizontal: 15, - borderRadius: 20, + marginHorizontal: 8, + borderRadius: 8, backgroundColor: '#f0f0f0', color: '#000', }} @@ -90,4 +112,4 @@ const ConvertCardSelectCurrency = (props) => { ); }; -export default ConvertCardSelectCurrency; \ No newline at end of file +export default ConvertCardSelectFromList; \ No newline at end of file diff --git a/src/containers/Convert/Convert.js b/src/containers/Convert/Convert.js index a6dd96fc..c73f9874 100644 --- a/src/containers/Convert/Convert.js +++ b/src/containers/Convert/Convert.js @@ -6,7 +6,7 @@ import Colors from "../../globals/colors"; import ConvertCard from "./ConvertCard/ConvertCard"; import { useSelector } from "react-redux"; import { extractLedgerData } from "../../utils/ledger/extractLedgerData"; -import { API_GET_BALANCES, GENERAL, IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE } from "../../utils/constants/intervalConstants"; +import { API_GET_BALANCES, GENERAL, IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE, VRPC } from "../../utils/constants/intervalConstants"; import { USD } from "../../utils/constants/currencies"; import BigNumber from 'bignumber.js'; import ConvertCardModal from "../../components/ConvertCardModal/ConvertCardModal"; @@ -14,6 +14,7 @@ import { CONVERT_CARD_MODAL_MODES } from "../../utils/constants/convert"; import { normalizeNum } from "../../utils/normalizeNum"; import { formatCurrency } from "react-native-format-currency"; import { useObjectSelector } from "../../hooks/useObjectSelector"; +import { CoinDirectory } from "../../utils/CoinData/CoinDirectory"; const Convert = (props) => { const displayCurrency = useSelector(state => state.settings.generalWalletSettings.displayCurrency || USD); @@ -36,7 +37,14 @@ const Convert = (props) => { const [sourceCurrencyOptionsList, setSourceCurrencyOptionsList] = useState([]); const [destCurrencyOptionsList, setDestCurrencyOptionsList] = useState([]); + const [sourceNetworkOptionsList, setSourceNetworkOptionsList] = useState([]); + const [destNetworkOptionsList, setDestNetworkOptionsList] = useState([]); + const [selectedSourceCurrency, setSelectedSourceCurrency] = useState(null); + const [selectedSourceNetwork, setSelectedSourceNetwork] = useState(null); + + const [selectedDestCurrency, setSelectedDestCurrency] = useState(null); + const [selectedDestNetwork, setSelectedDestNetwork] = useState(null); const getTotalBalances = () => { let coinBalances = {}; @@ -75,15 +83,17 @@ const Convert = (props) => { const getSourceCurrencyMap = () => { const currencyMap = {}; + const usedCoins = []; - for (const coinObj of activeCoinsForUser) { - if (coinObj.proto === 'vrsc') { + for (const coinObj of activeCoinsForUser.sort(x => (x.mapped_to ? -1 : 1))) { + if (!usedCoins.includes(coinObj.id) && (coinObj.proto === 'vrsc' || coinObj.tags.includes(IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE))) { currencyMap[coinObj.currency_id] = [coinObj]; if (coinObj.mapped_to != null && coinObj.tags.includes(IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE)) { const mappedCoinObj = activeCoinsForUser.find(x => x.id === coinObj.mapped_to); if (mappedCoinObj != null) { + usedCoins.push(mappedCoinObj.id) currencyMap[coinObj.currency_id].push(mappedCoinObj); } } @@ -100,7 +110,7 @@ const Convert = (props) => { const rootCoinObj = coinObjs[0]; const mappedCoinObj = coinObjs.length > 1 ? coinObjs[1] : null; - const titleCoinObj = mappedCoinObj && mappedCoinObj.display_name.length > rootCoinObj.display_name.length ? + const titleCoinObj = mappedCoinObj && mappedCoinObj.display_name.length < rootCoinObj.display_name.length ? mappedCoinObj : rootCoinObj; @@ -144,7 +154,7 @@ const Convert = (props) => { rightTitle = valueFormattedWithSymbol; } - rightDescription = `${Number(totalCryptoBalance.toString())} ${titleCoinObj.display_ticker}`; + rightDescription = `${Number(totalCryptoBalance.toString())}`; currencies.push({ title, @@ -159,6 +169,105 @@ const Convert = (props) => { return currencies; }; + const getSourceNetworkOptionsList = () => { + const networks = []; + + function getRightText (coinId, walletIds) { + let title = '-'; + let description = '-'; + + if (balances[coinId] != null) { + const uniRate = rates[GENERAL] && rates[GENERAL][coinId] ? BigNumber(rates[GENERAL][coinId][displayCurrency]) : null; + + let totalCryptoBalance = BigNumber(0); + + for (const walletId of walletIds) { + const cryptoBalance = balances[coinId][walletId]; + + if (cryptoBalance != null) { + totalCryptoBalance = totalCryptoBalance.plus(BigNumber(cryptoBalance.confirmed)); + } + } + + description = `${Number(totalCryptoBalance.toString())}`; + + if (uniRate != null) { + const rawFiatDisplayValue = normalizeNum( + Number((totalCryptoBalance.multipliedBy(uniRate)).toString()), + 2, + )[3]; + + const [valueFormattedWithSymbol] = formatCurrency({amount: rawFiatDisplayValue, code: displayCurrency}); + + title = valueFormattedWithSymbol; + } + } + + return { title, description }; + } + + if (selectedSourceCurrency) { + for (const alias of selectedSourceCurrency) { + if (alias.proto === 'vrsc') { + if (allSubWallets[alias.id]) { + const networkWallets = {}; + + for (const subWallet of allSubWallets[alias.id]) { + const [channelName, addr, network] = subWallet.channel.split('.'); + + if (channelName === VRPC) { + if (!networkWallets[network]) networkWallets[network] = [subWallet.id]; + else networkWallets[network].push(subWallet.id); + } + } + + for (const network in networkWallets) { + try { + const networkObj = CoinDirectory.getBasicCoinObj(network); + + if (networkObj) { + const rightText = getRightText(alias.id, networkWallets[network]); + + networks.push({ + title: `From ${networkObj.display_name} network`, + description: `as ${alias.display_ticker}`, + logo: networkObj.id, + key: networkObj.id, + rightTitle: rightText.title, + rightDescription: rightText.description + }); + } + } catch(e) { + console.warn(e) + } + } + } + } else if (alias.proto === 'eth' || alias.proto === 'erc20') { + const rightText = getRightText(alias.id, ['MAIN_WALLET']); + + networks.push({ + title: 'From Ethereum network', + description: `as ${alias.display_ticker}`, + logo: alias.testnet ? 'GETH' : 'ETH', + key: alias.testnet ? 'GETH' : 'ETH', + rightTitle: rightText.title, + rightDescription: rightText.description + }); + } + } + } + + return networks; + }; + + const handleCurrencySelection = (key) => { + if (convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND) { + setSelectedSourceCurrency(sourceCurrencyMap[key]); + } else { + setSelectedDestCurrency(destCurrencyMap[key]); + } + } + useEffect(() => { setTotalBalances(getTotalBalances()); }, [allSubWallets, activeCoinsForUser, balances, displayCurrency, rates]); @@ -171,6 +280,10 @@ const Convert = (props) => { setSourceCurrencyOptionsList(getSourceCurrencyOptionsList()); }, [sourceCurrencyMap, totalBalances]); + useEffect(() => { + setSourceNetworkOptionsList(getSourceNetworkOptionsList()); + }, [selectedSourceCurrency, totalBalances]); + return ( { mode={convertCardModalMode} totalBalances={totalBalances} currencies={convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND ? sourceCurrencyOptionsList : destCurrencyOptionsList} - onSelectCurrency={(currencyId) => setSelectedSourceCurrency(currencyId)} + networks={convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND ? sourceNetworkOptionsList : destNetworkOptionsList} + onSelectCurrency={(currencyId) => handleCurrencySelection(currencyId)} setVisible={setConvertCardModalVisible} /> { }}> { + setSelectedSourceCurrency(null); setConvertCardModalMode(CONVERT_CARD_MODAL_MODES.SEND); - setConvertCardModalVisible(true) + setConvertCardModalVisible(true); }} /> diff --git a/src/containers/RootStack/HomeTabScreens/HomeTabScreens.js b/src/containers/RootStack/HomeTabScreens/HomeTabScreens.js index 34b03114..a5ab7ce5 100644 --- a/src/containers/RootStack/HomeTabScreens/HomeTabScreens.js +++ b/src/containers/RootStack/HomeTabScreens/HomeTabScreens.js @@ -54,7 +54,7 @@ const HomeTabScreens = props => { ), }} /> - {/* { /> ), }} - /> */} + /> Date: Tue, 26 Nov 2024 11:40:16 +0100 Subject: [PATCH 03/14] Update info.plist to avoid App Store submission issues due to react-native-permissions --- ios/verusMobile/Info.plist | 40 ++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/ios/verusMobile/Info.plist b/ios/verusMobile/Info.plist index db904dcb..2a642ed7 100755 --- a/ios/verusMobile/Info.plist +++ b/ios/verusMobile/Info.plist @@ -48,16 +48,36 @@ - NSCameraUsageDescription - Verus Mobile needs access to the camera to scan QR codes, and to allow you to add images to your locally encrypted personal profile - NSFaceIDUsageDescription - Enabling Face ID allows you quick and secure access to your account. - NSMicrophoneUsageDescription - Verus Mobile needs access to the camera to scan QR codes - NSPhotoLibraryAddUsageDescription - The user can save QR code invoices to their photo library. - NSPhotoLibraryUsageDescription - The user can use the photo library to add images to their locally encrypted personal profile + NSAppleMusicUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSBluetoothAlwaysUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSBluetoothPeripheralUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSCalendarsUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSCameraUsageDescription + Verus Mobile needs access to the camera to scan QR codes, and to allow you to add images to your locally encrypted personal profile + NSContactsUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSFaceIDUsageDescription + Enabling Face ID allows you quick and secure access to your account. + NSLocationAlwaysUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSLocationWhenInUseUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSMicrophoneUsageDescription + Verus Mobile needs access to the camera to scan QR codes + NSMotionUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSPhotoLibraryAddUsageDescription + The user can save QR code invoices to their photo library. + NSPhotoLibraryUsageDescription + The user can use the photo library to add images to their locally encrypted personal profile + NSSiriUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. + NSSpeechRecognitionUsageDescription + The app does not request this permission or utilize this functionality but it is included in our info.plist since our app utilizes the react-native-permissions library, which references this permission in its code. UIAppFonts AntDesign.ttf From d19ddc94825c7308f7a24833341e7be7d53e88c2 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Tue, 26 Nov 2024 15:29:27 +0100 Subject: [PATCH 04/14] Checkpoint for conversion ui refactor, implement first conversion card --- .../ConvertCardModal/ConvertCardModal.js | 34 ++- src/containers/Convert/Convert.js | 197 +++++++++++++++--- .../Convert/ConvertCard/ConvertCard.js | 34 ++- 3 files changed, 214 insertions(+), 51 deletions(-) diff --git a/src/components/ConvertCardModal/ConvertCardModal.js b/src/components/ConvertCardModal/ConvertCardModal.js index 909c4305..7507c507 100644 --- a/src/components/ConvertCardModal/ConvertCardModal.js +++ b/src/components/ConvertCardModal/ConvertCardModal.js @@ -22,11 +22,13 @@ const ConvertCardModal = ({ visible, setVisible, totalBalances, - mode, - currencies, - onSelectCurrency, + mode, networks, - onSelectNetwork + currencies, + addresses, + onSelectNetwork, + onSelectAddress, + onSelectCurrency, }) => { const [loading, setLoading] = useState(false); const [preventExit, setPreventExit] = useState(false); @@ -141,8 +143,28 @@ const ConvertCardModal = ({ {...tabProps} items={networks} setModalTitle={setModalTitle} - onSelect={onSelectCurrency} - nextScreen="SelectNetwork" + onSelect={onSelectNetwork} + nextScreen="SelectAddress" + /> + )} + + { + e.preventDefault(); + }, + }} + > + {tabProps => ( + )} diff --git a/src/containers/Convert/Convert.js b/src/containers/Convert/Convert.js index c73f9874..20a2be2f 100644 --- a/src/containers/Convert/Convert.js +++ b/src/containers/Convert/Convert.js @@ -6,7 +6,7 @@ import Colors from "../../globals/colors"; import ConvertCard from "./ConvertCard/ConvertCard"; import { useSelector } from "react-redux"; import { extractLedgerData } from "../../utils/ledger/extractLedgerData"; -import { API_GET_BALANCES, GENERAL, IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE, VRPC } from "../../utils/constants/intervalConstants"; +import { API_GET_BALANCES, ERC20, ETH, GENERAL, IS_CONVERTABLE_WITH_VRSC_ETH_BRIDGE, VRPC } from "../../utils/constants/intervalConstants"; import { USD } from "../../utils/constants/currencies"; import BigNumber from 'bignumber.js'; import ConvertCardModal from "../../components/ConvertCardModal/ConvertCardModal"; @@ -40,11 +40,20 @@ const Convert = (props) => { const [sourceNetworkOptionsList, setSourceNetworkOptionsList] = useState([]); const [destNetworkOptionsList, setDestNetworkOptionsList] = useState([]); + const [sourceWalletOptionsList, setSourceWalletOptionsList] = useState([]); + const [destAddressOptionsList, setDestAddressOptionsList] = useState([]); + const [selectedSourceCurrency, setSelectedSourceCurrency] = useState(null); + const [selectedSourceCoinObj, setSelectedSourceCoinObj] = useState(null); const [selectedSourceNetwork, setSelectedSourceNetwork] = useState(null); + const [selectedSourceWallet, setSelectedSourceWallet] = useState(null); const [selectedDestCurrency, setSelectedDestCurrency] = useState(null); const [selectedDestNetwork, setSelectedDestNetwork] = useState(null); + const [selectedDestAddress, setSelectedDestAddress] = useState(null); + + const [sendAmount, setSendAmount] = useState(null); + const [sourceBalance, setSourceBalance] = useState(null); const getTotalBalances = () => { let coinBalances = {}; @@ -169,43 +178,43 @@ const Convert = (props) => { return currencies; }; - const getSourceNetworkOptionsList = () => { - const networks = []; + const getRightText = (coinId, walletIds) => { + let title = '-'; + let description = '-'; - function getRightText (coinId, walletIds) { - let title = '-'; - let description = '-'; + if (balances[coinId] != null) { + const uniRate = rates[GENERAL] && rates[GENERAL][coinId] ? BigNumber(rates[GENERAL][coinId][displayCurrency]) : null; - if (balances[coinId] != null) { - const uniRate = rates[GENERAL] && rates[GENERAL][coinId] ? BigNumber(rates[GENERAL][coinId][displayCurrency]) : null; - - let totalCryptoBalance = BigNumber(0); + let totalCryptoBalance = BigNumber(0); - for (const walletId of walletIds) { - const cryptoBalance = balances[coinId][walletId]; + for (const walletId of walletIds) { + const cryptoBalance = balances[coinId][walletId]; - if (cryptoBalance != null) { - totalCryptoBalance = totalCryptoBalance.plus(BigNumber(cryptoBalance.confirmed)); - } + if (cryptoBalance != null) { + totalCryptoBalance = totalCryptoBalance.plus(BigNumber(cryptoBalance.confirmed)); } + } - description = `${Number(totalCryptoBalance.toString())}`; + description = `${Number(totalCryptoBalance.toString())}`; + + if (uniRate != null) { + const rawFiatDisplayValue = normalizeNum( + Number((totalCryptoBalance.multipliedBy(uniRate)).toString()), + 2, + )[3]; + + const [valueFormattedWithSymbol] = formatCurrency({amount: rawFiatDisplayValue, code: displayCurrency}); - if (uniRate != null) { - const rawFiatDisplayValue = normalizeNum( - Number((totalCryptoBalance.multipliedBy(uniRate)).toString()), - 2, - )[3]; - - const [valueFormattedWithSymbol] = formatCurrency({amount: rawFiatDisplayValue, code: displayCurrency}); - - title = valueFormattedWithSymbol; - } + title = valueFormattedWithSymbol; } - - return { title, description }; } + return { title, description }; + } + + const getSourceNetworkOptionsList = () => { + const networks = []; + if (selectedSourceCurrency) { for (const alias of selectedSourceCurrency) { if (alias.proto === 'vrsc') { @@ -260,6 +269,41 @@ const Convert = (props) => { return networks; }; + const getSourceWalletOptionsList = () => { + const wallets = []; + + if (selectedSourceCurrency && selectedSourceNetwork) { + if (selectedSourceCoinObj && allSubWallets[selectedSourceCoinObj.id]) { + for (const wallet of allSubWallets[selectedSourceCoinObj.id]) { + const [channelName, addr, network] = wallet.channel.split('.'); + + if ( + channelName === ERC20 || + channelName === ETH || + (channelName === VRPC && selectedSourceNetwork.currency_id === network) + ) { + const rightText = getRightText(selectedSourceCoinObj.id, [wallet.id]); + + wallets.push({ + title: wallet.name, + description: `as ${selectedSourceCoinObj.display_ticker}`, + logo: selectedSourceNetwork.id, + key: wallet.id, + rightTitle: rightText.title, + rightDescription: rightText.description + }) + } + } + } + } + + return wallets; + }; + + const getSourceBalance = () => { + return balances[selectedSourceCoinObj.id][selectedSourceWallet.id].confirmed; + } + const handleCurrencySelection = (key) => { if (convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND) { setSelectedSourceCurrency(sourceCurrencyMap[key]); @@ -268,10 +312,63 @@ const Convert = (props) => { } } + const handleNetworkSelection = (key) => { + try { + if (convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND) { + const networkObj = CoinDirectory.getBasicCoinObj(key); + + setSelectedSourceNetwork(networkObj); + } else { + setSelectedDestNetwork(null); + } + } catch (e) { + console.warn(e) + } + } + + const handleAddressSelection = (key) => { + try { + if (convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND) { + if (selectedSourceCoinObj) { + const subWallets = allSubWallets[selectedSourceCoinObj.id]; + + if (subWallets) { + const selectedWallet = subWallets.find(x => x.id === key); + + setSelectedSourceWallet(selectedWallet); + setConvertCardModalVisible(false); + } + } + } else { + setSelectedDestAddress(null); + } + } catch (e) { + console.warn(e) + } + } + + const handleSelectPressed = () => { + setSelectedSourceCoinObj(null); + setSelectedSourceWallet(null); + setSelectedSourceNetwork(null); + setSelectedSourceCurrency(null); + setConvertCardModalMode(CONVERT_CARD_MODAL_MODES.SEND); + setConvertCardModalVisible(true); + } + useEffect(() => { setTotalBalances(getTotalBalances()); }, [allSubWallets, activeCoinsForUser, balances, displayCurrency, rates]); + useEffect(() => { + if (selectedSourceCoinObj && + selectedSourceWallet && + balances[selectedSourceCoinObj.id] && + balances[selectedSourceCoinObj.id][selectedSourceWallet.id]) { + setSourceBalance(getSourceBalance()) + } + }, [selectedSourceCoinObj, selectedSourceWallet, balances]) + useEffect(() => { setSourceCurrencyMap(getSourceCurrencyMap()); }, [activeCoinsForUser]); @@ -284,6 +381,21 @@ const Convert = (props) => { setSourceNetworkOptionsList(getSourceNetworkOptionsList()); }, [selectedSourceCurrency, totalBalances]); + useEffect(() => { + if (selectedSourceCurrency && selectedSourceNetwork) { + const sourceCoinObj = selectedSourceCurrency.find(x => { + const networkProtocol = x.proto === 'erc20' ? 'eth' : x.proto; + return networkProtocol === selectedSourceNetwork.proto + }); + + setSelectedSourceCoinObj(sourceCoinObj); + } else setSelectedSourceCoinObj(null); + }, [selectedSourceCurrency, selectedSourceNetwork]); + + useEffect(() => { + setSourceWalletOptionsList(getSourceWalletOptionsList()); + }, [selectedSourceCoinObj, totalBalances]); + return ( { totalBalances={totalBalances} currencies={convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND ? sourceCurrencyOptionsList : destCurrencyOptionsList} networks={convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND ? sourceNetworkOptionsList : destNetworkOptionsList} + addresses={convertCardModalMode === CONVERT_CARD_MODAL_MODES.SEND ? sourceWalletOptionsList : destAddressOptionsList} onSelectCurrency={(currencyId) => handleCurrencySelection(currencyId)} + onSelectNetwork={(networkId) => handleNetworkSelection(networkId)} + onSelectAddress={(addressKey) => handleAddressSelection(addressKey)} setVisible={setConvertCardModalVisible} /> { - setSelectedSourceCurrency(null); - setConvertCardModalMode(CONVERT_CARD_MODAL_MODES.SEND); - setConvertCardModalVisible(true); - }} + onSelectPressed={handleSelectPressed} + coinObj={selectedSourceCoinObj} + networkObj={selectedSourceNetwork} + address={selectedSourceWallet ? selectedSourceWallet.name : null} + amount={sendAmount} + setAmount={setSendAmount} + balance={sourceBalance} + onMaxPressed={() => setSendAmount(sourceBalance ? sourceBalance.toString() : 0)} + /> + setSendAmount(sourceBalance ? sourceBalance.toString() : 0)} /> { + setCardActive(false); + + if (onSelectPressed) onSelectPressed(); + } + return ( setAmount(x)} mode="outlined" keyboardType="numeric" + disabled={!setAmount} /> {!cardActive ? : - + {RenderCircleCoinLogo(coinObj.id)} - + } - {!cardActive ? '' : `${balance == null ? balance : '-'} ${coinObj.display_ticker}`} + {!cardActive ? '' : `${balance != null ? balance : '-'} ${coinObj.display_ticker}`} + )} + {generalWalletSettings.enableSendCoinCameraToggle && ( + )} diff --git a/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js b/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js index ef562ce5..2ec31c03 100644 --- a/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js +++ b/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js @@ -51,6 +51,9 @@ const WalletSettings = props => { const [allowSettingVerusPaySlippage, setAllowSettingVerusPaySlippage] = useState( !!generalWalletSettings.allowSettingVerusPaySlippage ); + const [enableSendCoinCameraToggle, setEnableSendCoinCameraToggle] = useState( + !!generalWalletSettings.enableSendCoinCameraToggle + ); const [errors, setErrors] = useState({ maxTxCount: false, @@ -80,13 +83,14 @@ const WalletSettings = props => { setSettings({ ...settings, homeCardDragDetection, - allowSettingVerusPaySlippage + allowSettingVerusPaySlippage, + enableSendCoinCameraToggle }); setHasChanges(true); } else { isMounted.current = true; } - }, [homeCardDragDetection, allowSettingVerusPaySlippage]); + }, [homeCardDragDetection, allowSettingVerusPaySlippage, enableSendCoinCameraToggle]); const describeSlippage = () => { createAlert( @@ -108,6 +112,10 @@ const WalletSettings = props => { setAllowSettingVerusPaySlippage(!allowSettingVerusPaySlippage); } + const toggleEnableSendCoinCameraToggle = () => { + setEnableSendCoinCameraToggle(!enableSendCoinCameraToggle); + } + const saveSettings = async () => { setLoading(true); try { @@ -118,6 +126,7 @@ const WalletSettings = props => { settings.defaultAccount === NO_DEFAULT ? null : settings.defaultAccount, homeCardDragDetection, allowSettingVerusPaySlippage, + enableSendCoinCameraToggle, ackedCurrencyDisclaimer: settings.ackedCurrencyDisclaimer, addressBlocklistDefinition: settings.addressBlocklistDefinition == null @@ -327,6 +336,30 @@ const WalletSettings = props => { /> + + ( + + + + )} + /> + + {'Start Settings'} openDefaultProfileModal()} diff --git a/src/containers/VerusPay/VerusPay.js b/src/containers/VerusPay/VerusPay.js index e1da7a63..5edad38d 100644 --- a/src/containers/VerusPay/VerusPay.js +++ b/src/containers/VerusPay/VerusPay.js @@ -76,7 +76,7 @@ const VerusPay = (props) => { const activeAlert = useObjectSelector((state) => state.alert.active); const sendModal = useObjectSelector((state) => state.sendModal); - const { containerStyle, acceptAddressOnly, channel, coinObj: propCoinObj, button, maskProps } = props; + const { containerStyle, acceptAddressOnly, channel, coinObj: propCoinObj, button, maskProps, cameraDisabled } = props; useEffect(() => { return () => { @@ -390,6 +390,7 @@ const VerusPay = (props) => { : 'Scan a QR code' } onScan={(codes) => onSuccess(codes)} + cameraDisabled={cameraDisabled} button={button} maskProps={maskProps} /> diff --git a/src/reducers/settings.js b/src/reducers/settings.js index f1b78aa6..6e2828a2 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -27,6 +27,7 @@ export const settings = (state = { defaultAccount: null, homeCardDragDetection: false, allowSettingVerusPaySlippage: false, + enableSendCoinCameraToggle: false, ackedCurrencyDisclaimer: false, addressBlocklistDefinition: { type: ADDRESS_BLOCKLIST_FROM_WEBSERVER, From 4faa59c22cdacfc4382cf3a40ee2072e5c5da2aa Mon Sep 17 00:00:00 2001 From: michaeltout Date: Thu, 5 Dec 2024 19:24:47 +0100 Subject: [PATCH 11/14] Refactor self button to show list of own addresses in convert or cross chain send form --- .../ConvertCardModal/ConvertCardModal.js | 13 ++- ...ardSelectFromList.js => SearchableList.js} | 10 +-- .../ConvertOrCrossChainSendForm.js | 88 ++++++++++++++++++- 3 files changed, 94 insertions(+), 17 deletions(-) rename src/components/{ConvertCardModal/ConvertCardModalTabs/ConvertCardSelectFromList.js => SearchableList.js} (91%) diff --git a/src/components/ConvertCardModal/ConvertCardModal.js b/src/components/ConvertCardModal/ConvertCardModal.js index 4b6bd48d..d98f6da9 100644 --- a/src/components/ConvertCardModal/ConvertCardModal.js +++ b/src/components/ConvertCardModal/ConvertCardModal.js @@ -8,12 +8,9 @@ import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import AnimatedActivityIndicatorBox from "../AnimatedActivityIndicatorBox"; -import ConvertCardSelectFromList from "./ConvertCardModalTabs/ConvertCardSelectFromList"; import { useSelector } from "react-redux"; -import { USD } from "../../utils/constants/currencies"; -import { extractLedgerData } from "../../utils/ledger/extractLedgerData"; -import { API_GET_BALANCES } from "../../utils/constants/intervalConstants"; import { CONVERT_CARD_MODAL_MODES } from "../../utils/constants/convert"; +import SearchableList from "../SearchableList"; const TopTabs = createMaterialTopTabNavigator(); const Root = createStackNavigator(); @@ -122,7 +119,7 @@ const ConvertCardModal = ({ }} > {tabProps => ( - {tabProps => ( - {tabProps => ( - {tabProps => ( - { +const SearchableList = (props) => { const [searchQuery, setSearchQuery] = useState(''); const items = props.items ? props.items : []; @@ -95,7 +95,7 @@ const ConvertCardSelectFromList = (props) => { /> item.id} + keyExtractor={item => item.key} renderItem={renderItem} ListEmptyComponent={ { ); }; -export default ConvertCardSelectFromList; \ No newline at end of file +export default SearchableList; \ No newline at end of file diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index de4b6ac9..23b848fd 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -4,7 +4,7 @@ import { Alert, View, TouchableWithoutFeedback, Keyboard, FlatList, Animated, To import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { TextInput, Button, Divider, Checkbox, List, Text, IconButton } from "react-native-paper"; import { createAlert } from "../../../../actions/actions/alert/dispatchers/alert"; -import { API_SEND, DLIGHT_PRIVATE, ERC20, ETH } from "../../../../utils/constants/intervalConstants"; +import { API_SEND, DLIGHT_PRIVATE, ERC20, ETH, VRPC } from "../../../../utils/constants/intervalConstants"; import { SEND_MODAL_ADVANCED_FORM, SEND_MODAL_AMOUNT_FIELD, @@ -57,6 +57,7 @@ import { useObjectSelector } from "../../../../hooks/useObjectSelector"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const { height } = Dimensions.get('window'); const sendModal = useObjectSelector(state => state.sendModal); + const allSubWallets = useObjectSelector(state => state.coinMenus.allSubWallets); const activeUser = useObjectSelector(state => state.authentication.activeAccount); const addresses = useObjectSelector(state => selectAddresses(state)); const activeAccount = useObjectSelector(state => state.authentication.activeAccount); @@ -76,7 +77,8 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor [SEND_MODAL_EXPORTTO_FIELD]: "Destination network", [SEND_MODAL_VIA_FIELD]: "Convert via", [SEND_MODAL_CONVERTTO_FIELD]: sendModal.data[SEND_MODAL_IS_PRECONVERT] ? "Preconvert to" : "Convert to", - [SEND_MODAL_MAPPING_FIELD]: "Receive as" + [SEND_MODAL_MAPPING_FIELD]: "Receive as", + [SEND_MODAL_TO_ADDRESS_FIELD]: "Recipient address" } const [searchMode, setSearchMode] = useState(false); @@ -529,6 +531,80 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor } }; + const processSelfSuggestionPaths = () => { + const addresses = []; + const seen = new Set(); + + if (activeAccount) { + if (allSubWallets[coinsList.VRSC.id]) { + const vrscKeys = activeAccount.keys[coinsList.VRSC.id]; + + for (const channelId in vrscKeys) { + const [channelName, addr, network] = channelId.split('.'); + + if (channelName === VRPC && !seen.has(addr)) { + const walletId = `SUBWALLET_${channelId}`; + + if (allSubWallets[coinsList.VRSC.id]) { + const wallet = allSubWallets[coinsList.VRSC.id].find(x => x.id === walletId); + + if (wallet) { + seen.add(addr); + addresses.push({ + title: wallet.name, + logoid: coinsList.VRSC.id, + key: addr, + description: addr, + values: { + [SEND_MODAL_TO_ADDRESS_FIELD]: addr + }, + right: "", + keywords: [ + addr, + wallet.name + ], + }) + } + } + } + } + } + + for (const coinId in activeAccount.keys) { + if (activeAccount.keys[coinId] && (activeAccount.keys[coinId][ETH] || activeAccount.keys[coinId][ERC20])) { + const ethAddresses = activeAccount.keys[coinId][ETH] ? + activeAccount.keys[coinId][ETH].addresses : activeAccount.keys[coinId][ERC20].addresses; + + if (ethAddresses && ethAddresses.length > 0) { + const addr = ethAddresses[0]; + const addrTitle = addr.substring(0, 8) + '...' + addr.substring(addr.length - 8); + + if (!seen.has(addr)) { + seen.add(addr); + addresses.push({ + title: addrTitle, + logoid: coinsList.ETH.id, + key: addr, + description: addr, + values: { + [SEND_MODAL_TO_ADDRESS_FIELD]: addr + }, + right: "", + keywords: [ + addr + ] + }) + } + + break; + } + } + } + } + + return addresses; + }; + const fetchSuggestionsBase = async (field) => { if (loadingSuggestions) return; let newSuggestionsBase = [] @@ -582,6 +658,9 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor case SEND_MODAL_MAPPING_FIELD: newSuggestionsBase = await processMappingSuggestionPaths(flatPaths, sendModal.coinObj); setSuggestionBase(newSuggestionsBase); + case SEND_MODAL_TO_ADDRESS_FIELD: + newSuggestionsBase = processSelfSuggestionPaths(); + setSuggestionBase(newSuggestionsBase); default: setSuggestionBase(newSuggestionsBase); break; @@ -1018,7 +1097,8 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor } right={() => selectedField !== SEND_MODAL_EXPORTTO_FIELD && - selectedField !== SEND_MODAL_MAPPING_FIELD ? ( + selectedField !== SEND_MODAL_MAPPING_FIELD && + selectedField !== SEND_MODAL_TO_ADDRESS_FIELD ? ( updateSendFormData(SEND_MODAL_TO_ADDRESS_FIELD, text) } - onSelfPress={() => setAddressSelf()} + onSelfPress={() => handleFieldFocus(SEND_MODAL_TO_ADDRESS_FIELD)} amountValue={sendModal.data[SEND_MODAL_AMOUNT_FIELD]} onAmountChange={text => updateSendFormData(SEND_MODAL_AMOUNT_FIELD, text) From 451620cc0c3928d0f7e9fc77c1375d9c2785f7fe Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 6 Dec 2024 10:33:39 +0100 Subject: [PATCH 12/14] Prevent unecessary loading for self address suggestions --- .../ConvertOrCrossChainSendForm.js | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 23b848fd..0db05f9b 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -81,6 +81,13 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor [SEND_MODAL_TO_ADDRESS_FIELD]: "Recipient address" } + const CONVERSION_PATH_FIELDS = [ + SEND_MODAL_EXPORTTO_FIELD, + SEND_MODAL_VIA_FIELD, + SEND_MODAL_CONVERTTO_FIELD, + SEND_MODAL_MAPPING_FIELD + ]; + const [searchMode, setSearchMode] = useState(false); const [selectedField, setSelectedField] = useState(""); @@ -623,25 +630,27 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor // mapping: boolean; // bounceback: boolean; // }>} - - const paths = conversionPaths - ? conversionPaths - : await getConversionPaths( - sendModal.coinObj, - sendModal.subWallet.api_channels[API_SEND], - { - src: sendModal.coinObj.currency_id, - }, - ); let flatPaths = [] + + if (CONVERSION_PATH_FIELDS.includes(field)) { + const paths = conversionPaths + ? conversionPaths + : await getConversionPaths( + sendModal.coinObj, + sendModal.subWallet.api_channels[API_SEND], + { + src: sendModal.coinObj.currency_id, + }, + ); - for (const destinationid in paths) { - flatPaths = flatPaths.concat(paths[destinationid]) + for (const destinationid in paths) { + flatPaths = flatPaths.concat(paths[destinationid]) + } + + setConversionPaths(paths) } - setConversionPaths(paths) - switch (field) { case SEND_MODAL_CONVERTTO_FIELD: newSuggestionsBase = await processConverttoSuggestionPaths(flatPaths, sendModal.coinObj); From bce34e052d69b893a9b7ea89be7bcfce64d65a5d Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sun, 8 Dec 2024 16:53:26 +0100 Subject: [PATCH 13/14] Add more descriptive error message if failed to find destination addr VerusID --- .../ConvertOrCrossChainSendForm.js | 2 +- src/utils/api/channels/vrpc/requests/preflight.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 0db05f9b..e2f408d7 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -905,7 +905,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor if (addr.endsWith("@")) { const identityRes = await getIdentity(coinObj, activeAccount, channel, addr); - if (identityRes.error) throw new Error("Failed to fetch " + addr); + if (identityRes.error) throw new Error(`Failed to get information about ${addr}. Try using the i-address of this VerusID.`); keyhash = identityRes.result.identity.identityaddress; } else keyhash = addr; diff --git a/src/utils/api/channels/vrpc/requests/preflight.js b/src/utils/api/channels/vrpc/requests/preflight.js index 41cb794a..1326fc10 100644 --- a/src/utils/api/channels/vrpc/requests/preflight.js +++ b/src/utils/api/channels/vrpc/requests/preflight.js @@ -30,7 +30,7 @@ export const preflight = async (coinObj, activeUser, address, amount, params, ch if (addr.endsWith("@")) { const identityRes = await getIdentity(systemId, addr); - if (identityRes.error) throw new Error("Failed to fetch " + addr); + if (identityRes.error) throw new Error(`Failed to get information about ${addr}. Try using the i-address of this VerusID.`); keyhash = identityRes.result.identity.identityaddress; } else keyhash = addr; @@ -584,7 +584,7 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, const nativeFeeAmount = satsToCoins(nativeFeesPaid).toString(); message = `Insufficient funds. Ensure you have at least ${ - output.feecurrency === systemId + (output.feecurrency == null || output.feecurrency === systemId) ? nativeFeeAmount : satsToCoins(BigNumber(_feeamount)).toString() } ${feeCurrencyName} on the ${systemName} network to fund the fee for transaction.`; From e71ace8cee43df49cb7f0e501afea5138584a750 Mon Sep 17 00:00:00 2001 From: Asher Dawes Date: Sun, 8 Dec 2024 16:35:01 -0800 Subject: [PATCH 14/14] Update version --- android/app/build.gradle | 2 +- env/main.android.json | 2 +- env/main.ios.json | 2 +- ios/assets/env/main.json | 2 +- ios/verusMobile.xcodeproj/project.pbxproj | 4 ++-- package.json | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 36acb3d4..f09a69fd 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -5,7 +5,7 @@ import com.android.build.OutputFile def versionMajor = 1 def versionMinor = 0 -def versionRevision = 22 +def versionRevision = 23 def versionBuild = 0 def keystorePropertiesFile = rootProject.file("keystore.properties"); diff --git a/env/main.android.json b/env/main.android.json index 2540c6c6..e9f671c7 100644 --- a/env/main.android.json +++ b/env/main.android.json @@ -1,5 +1,5 @@ { - "APP_VERSION": "1.0.22", + "APP_VERSION": "1.0.23", "ELECTRUM_PROTOCOL_CHANGE": 1.4, "KEY_DERIVATION_VERSION": 1, diff --git a/env/main.ios.json b/env/main.ios.json index 4c9e72e1..cf93259c 100644 --- a/env/main.ios.json +++ b/env/main.ios.json @@ -1,5 +1,5 @@ { - "APP_VERSION": "1.0.22", + "APP_VERSION": "1.0.23", "ELECTRUM_PROTOCOL_CHANGE": 1.4, "KEY_DERIVATION_VERSION": 1, diff --git a/ios/assets/env/main.json b/ios/assets/env/main.json index 4c9e72e1..cf93259c 100644 --- a/ios/assets/env/main.json +++ b/ios/assets/env/main.json @@ -1,5 +1,5 @@ { - "APP_VERSION": "1.0.22", + "APP_VERSION": "1.0.23", "ELECTRUM_PROTOCOL_CHANGE": 1.4, "KEY_DERIVATION_VERSION": 1, diff --git a/ios/verusMobile.xcodeproj/project.pbxproj b/ios/verusMobile.xcodeproj/project.pbxproj index eb32e3be..622298c7 100644 --- a/ios/verusMobile.xcodeproj/project.pbxproj +++ b/ios/verusMobile.xcodeproj/project.pbxproj @@ -745,7 +745,7 @@ "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-webview\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/rn-fetch-blob\"", ); - MARKETING_VERSION = 1.0.22; + MARKETING_VERSION = 1.0.23; PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.verusmobile; PRODUCT_NAME = verusmobile; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -817,7 +817,7 @@ "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-webview\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/rn-fetch-blob\"", ); - MARKETING_VERSION = 1.0.22; + MARKETING_VERSION = 1.0.23; PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.verusmobile; PRODUCT_NAME = verusmobile; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/package.json b/package.json index 2a0057c0..f7905d11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "verusmobile", - "version": "1.0.22", + "version": "1.0.23", "private": true, "scripts": { "postinstall": "./node_modules/.bin/rn-nodeify --hack --install --yarn && npx jetify",