From c222054cac8bef58b40dd1faeb122945735edbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Mon, 23 Aug 2021 13:34:28 -0400 Subject: [PATCH 1/7] ADD: Button capability for Tooltip --- components/TooltipMenu.ios.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/components/TooltipMenu.ios.js b/components/TooltipMenu.ios.js index 229122291..ea4c72c14 100644 --- a/components/TooltipMenu.ios.js +++ b/components/TooltipMenu.ios.js @@ -1,5 +1,5 @@ import React from 'react'; -import { ContextMenuView } from 'react-native-ios-context-menu'; +import { ContextMenuView, ContextMenuButton } from 'react-native-ios-context-menu'; import PropTypes from 'prop-types'; const ToolTipMenu = (props, ref) => { @@ -13,7 +13,19 @@ const ToolTipMenu = (props, ref) => { })); const menuTitle = props.title ?? ''; const submenu = props.submenu; - return ( + const isButton = props.isButton ? true : false; + const isMenuPrimaryAction = props.isMenuPrimaryAction ? props.isMenuPrimaryAction : false; + return isButton ? + { + props.onPress(nativeEvent.actionKey); + }} + isMenuPrimaryAction={isMenuPrimaryAction} + menuConfig={{ + menuTitle, + menuItems: menuItems.concat(submenu), + }} + >{props.children}: ( { props.onPress(nativeEvent.actionKey); From 000f75ee2914010bd93edc6062c15f89e40c4260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Mon, 23 Aug 2021 15:39:52 -0400 Subject: [PATCH 2/7] REF: Use ContextButton for advanced options --- components/TooltipMenu.ios.js | 56 +++++++----- screen/send/details.js | 156 ++++++++++++++++++++++++++++++---- 2 files changed, 174 insertions(+), 38 deletions(-) diff --git a/components/TooltipMenu.ios.js b/components/TooltipMenu.ios.js index ea4c72c14..dca051c89 100644 --- a/components/TooltipMenu.ios.js +++ b/components/TooltipMenu.ios.js @@ -2,30 +2,42 @@ import React from 'react'; import { ContextMenuView, ContextMenuButton } from 'react-native-ios-context-menu'; import PropTypes from 'prop-types'; -const ToolTipMenu = (props, ref) => { - const menuItems = props.actions.map(action => ({ - actionKey: action.id, - actionTitle: action.text, - actionOnPress: action.onPress, - icon: action.icon, - menuOptions: action.menuOptions, - menuTitle: action.menuTitle, - })); +const ToolTipMenu = props => { + const menuItems = props.actions.map(action => { + const item = { + actionKey: action.id, + actionTitle: action.text, + actionOnPress: action.onPress, + icon: action.icon, + menuOptions: action.menuOptions, + menuTitle: action.menuTitle, + }; + if (action.menuStateOn) { + item.menuState = action.menuStateOn ? 'on' : 'off'; + } + if (action.disabled) { + item.menuAttributes = ['disabled']; + } + return item; + }); const menuTitle = props.title ?? ''; const submenu = props.submenu; - const isButton = props.isButton ? true : false; + const isButton = !!props.isButton; const isMenuPrimaryAction = props.isMenuPrimaryAction ? props.isMenuPrimaryAction : false; - return isButton ? - { - props.onPress(nativeEvent.actionKey); - }} - isMenuPrimaryAction={isMenuPrimaryAction} - menuConfig={{ - menuTitle, - menuItems: menuItems.concat(submenu), - }} - >{props.children}: ( + return isButton ? ( + { + props.onPress(nativeEvent.actionKey); + }} + isMenuPrimaryAction={isMenuPrimaryAction} + menuConfig={{ + menuTitle, + menuItems: menuItems.concat(submenu), + }} + > + {props.children} + + ) : ( { props.onPress(nativeEvent.actionKey); @@ -47,4 +59,6 @@ ToolTipMenu.propTypes = { submenu: PropTypes.object, children: PropTypes.node.isRequired, onPress: PropTypes.func.isRequired, + isMenuPrimaryAction: PropTypes.bool, + isButton: PropTypes.bool, }; diff --git a/screen/send/details.js b/screen/send/details.js index 2d66fb275..3ebb0a85e 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -40,6 +40,7 @@ import AmountInput from '../../components/AmountInput'; import InputAccessoryAllFunds from '../../components/InputAccessoryAllFunds'; import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet'; import { BlueStorageContext } from '../../blue_modules/storage-context'; +import ToolTipMenu from '../../components/TooltipMenu'; const currency = require('../../blue_modules/currency'); const prompt = require('../../blue_modules/prompt'); const fs = require('../../blue_modules/fs'); @@ -92,6 +93,13 @@ const SendDetails = () => { return initialFee; }, [customFee, feePrecalc, networkTransactionFees]); + useEffect(() => { + if (wallet) { + setHeaderRightOptions(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [colors, wallet, isTransactionReplaceable, balance, addresses]); + // keyboad effects useEffect(() => { const _keyboardDidShow = () => { @@ -177,12 +185,6 @@ const SendDetails = () => { useEffect(() => { if (!wallet) return; setSelectedWallet(wallet.getID()); - navigation.setParams({ - advancedOptionsMenuButtonAction: () => { - Keyboard.dismiss(); - setOptionsVisible(true); - }, - }); // reset other values setUtxo(null); @@ -787,10 +789,116 @@ const SendDetails = () => { setOptionsVisible(false); }; + // Header Right Button + + const headerRightOnPress = id => { + if (id === SendDetails.actionKeys.AddRecipient) { + handleAddRecipient(); + } else if (id === SendDetails.actionKeys.RemoveRecipient) { + handleRemoveRecipient(); + } else if (id === SendDetails.actionKeys.SignPSBT) { + handlePsbtSign(); + } else if (id === SendDetails.actionKeys.SendMax) { + onUseAllPressed(); + } else if (id === SendDetails.actionKeys.AllowRBF) { + onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable); + } else if (id === SendDetails.actionKeys.ImportTransaction) { + importTransaction(); + } else if (id === SendDetails.actionKeys.ImportTransactionQR) { + importQrTransaction(); + } else if (id === SendDetails.actionKeys.ImportTransactionMultsig) { + importTransactionMultisig(); + } else if (id === SendDetails.actionKeys.CoSignTransaction) { + importTransactionMultisigScanQr(); + } else if (id === SendDetails.actionKeys.CoinControl) { + handleCoinControl(); + } + }; + + const headerRightActions = () => { + const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX); + + const actions = [{ id: SendDetails.actionKeys.SendMax, text: loc.send.details_adv_full, disabled: balance === 0 || isSendMaxUsed }]; + if (wallet.type === HDSegwitBech32Wallet.type) { + actions.push({ id: SendDetails.actionKeys.AllowRBF, text: loc.send.details_adv_fee_bump, menuStateOn: isTransactionReplaceable }); + } + if (wallet.type === WatchOnlyWallet.type && wallet.isHd()) { + actions.push( + { + id: SendDetails.actionKeys.ImportTransaction, + text: loc.send.details_adv_import, + icon: SendDetails.actionIcons.ImportTransaction, + }, + { + id: SendDetails.actionKeys.ImportTransactionQR, + text: loc.send.details_adv_import + ' (QR)', + icon: SendDetails.actionIcons.ImportTransactionQR, + }, + ); + } + if (wallet.type === MultisigHDWallet.type) { + actions.push({ + id: SendDetails.actionKeys.ImportTransactionMultsig, + text: loc.send.details_adv_import, + icon: SendDetails.actionIcons.ImportTransactionMultsig, + }); + } + if (wallet.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0) { + actions.push({ + id: SendDetails.actionKeys.CoSignTransaction, + text: loc.multisig.co_sign_transaction, + icon: SendDetails.actionIcons.SignPSBT, + }); + } + if (wallet.allowCosignPsbt()) { + actions.push({ id: SendDetails.actionKeys.SignPSBT, text: loc.send.psbt_sign, icon: SendDetails.actionIcons.SignPSBT }); + } + actions.push({ + id: SendDetails.actionKeys.AddRecipient, + text: loc.send.details_add_rec_add, + icon: SendDetails.actionIcons.AddRecipient, + }); + actions.push({ + id: SendDetails.actionKeys.RemoveRecipient, + text: loc.send.details_add_rec_rem, + disabled: addresses.length < 2, + icon: SendDetails.actionIcons.RemoveRecipient, + }); + actions.push({ id: SendDetails.actionKeys.CoinControl, text: loc.cc.header, icon: SendDetails.actionIcons.CoinControl }); + + return actions; + }; + const setHeaderRightOptions = () => { + navigation.setOptions({ + headerRight: Platform.select({ + ios: () => ( + + + + ), + default: () => ( + { + Keyboard.dismiss(); + setOptionsVisible(true); + }} + testID="advancedOptionsMenuButton" + > + + + ), + }), + }); + }; + const onReplaceableFeeSwitchValueChanged = value => { setIsTransactionReplaceable(value); }; + // + // because of https://github.com/facebook/react-native/issues/21718 we use // onScroll for android and onMomentumScrollEnd for iOS const handleRecipientsScrollEnds = e => { @@ -1308,6 +1416,30 @@ const SendDetails = () => { export default SendDetails; +SendDetails.actionKeys = { + SignPSBT: 'SignPSBT', + SendMax: 'SendMax', + AddRecipient: 'AddRecipient', + RemoveRecipient: 'RemoveRecipient', + AllowRBF: 'AllowRBF', + ImportTransaction: 'ImportTransaction', + ImportTransactionMultsig: 'ImportTransactionMultisig', + ImportTransactionQR: 'ImportTransactionQR', + CoinControl: 'CoinControl', +}; + +SendDetails.actionIcons = { + SignPSBT: { iconType: 'SYSTEM', iconValue: 'signature' }, + SendMax: 'SendMax', + AddRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.plus' }, + RemoveRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.minus' }, + AllowRBF: 'AllowRBF', + ImportTransaction: { iconType: 'SYSTEM', iconValue: 'square.and.arrow.down' }, + ImportTransactionMultsig: { iconType: 'SYSTEM', iconValue: 'square.and.arrow.down.on.square' }, + ImportTransactionQR: { iconType: 'SYSTEM', iconValue: 'qrcode.viewfinder' }, + CoinControl: { iconType: 'SYSTEM', iconValue: 'bitcoinsign.circle' }, +}; + const styles = StyleSheet.create({ loading: { flex: 1, @@ -1444,17 +1576,7 @@ const styles = StyleSheet.create({ }, }); -SendDetails.navigationOptions = navigationStyleTx({}, (options, { theme, navigation, route }) => ({ +SendDetails.navigationOptions = navigationStyleTx({}, options => ({ ...options, - headerRight: () => ( - - - - ), title: loc.send.header, })); From e2ed4cf00ba76d55fa80ac5e4115c51357ee93b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Mon, 23 Aug 2021 15:54:18 -0400 Subject: [PATCH 3/7] OPS: Deepscan --- screen/send/details.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/screen/send/details.js b/screen/send/details.js index 3ebb0a85e..ad42d6fc6 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -77,6 +77,10 @@ const SendDetails = () => { const [payjoinUrl, setPayjoinUrl] = useState(null); const [changeAddress, setChangeAddress] = useState(); const [dumb, setDumb] = useState(false); + // if utxo is limited we use it to calculate available balance + const balance = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : wallet?.getBalance(); + const allBalance = formatBalanceWithoutSuffix(balance, BitcoinUnit.BTC, true); + // if cutomFee is not set, we need to choose highest possible fee for wallet balance // if there are no funds for even Slow option, use 1 sat/byte fee const feeRate = useMemo(() => { @@ -1336,11 +1340,6 @@ const SendDetails = () => { ); } - - // if utxo is limited we use it to calculate available balance - const balance = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : wallet.getBalance(); - const allBalance = formatBalanceWithoutSuffix(balance, BitcoinUnit.BTC, true); - return ( setWidth(e.nativeEvent.layout.width)}> From 0e75012045c160a1dc9ee36b040c828a2fe5204c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Mon, 23 Aug 2021 15:54:22 -0400 Subject: [PATCH 4/7] Update TooltipMenu.ios.js --- components/TooltipMenu.ios.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/TooltipMenu.ios.js b/components/TooltipMenu.ios.js index dca051c89..589f9811d 100644 --- a/components/TooltipMenu.ios.js +++ b/components/TooltipMenu.ios.js @@ -12,9 +12,8 @@ const ToolTipMenu = props => { menuOptions: action.menuOptions, menuTitle: action.menuTitle, }; - if (action.menuStateOn) { - item.menuState = action.menuStateOn ? 'on' : 'off'; - } + item.menuState = action.menuStateOn ? 'on' : 'off'; + if (action.disabled) { item.menuAttributes = ['disabled']; } From 842b80b4780b209eaef64d3adb03c14e463a5807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Tue, 24 Aug 2021 01:04:34 -0400 Subject: [PATCH 5/7] Update details.js --- screen/send/details.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen/send/details.js b/screen/send/details.js index ad42d6fc6..e137babc6 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -1436,7 +1436,7 @@ SendDetails.actionIcons = { ImportTransaction: { iconType: 'SYSTEM', iconValue: 'square.and.arrow.down' }, ImportTransactionMultsig: { iconType: 'SYSTEM', iconValue: 'square.and.arrow.down.on.square' }, ImportTransactionQR: { iconType: 'SYSTEM', iconValue: 'qrcode.viewfinder' }, - CoinControl: { iconType: 'SYSTEM', iconValue: 'bitcoinsign.circle' }, + CoinControl: { iconType: 'SYSTEM', iconValue: 'switch.2' }, }; const styles = StyleSheet.create({ From e94594d8bcc151677ddb9902d978fb83522e98ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Tue, 24 Aug 2021 16:19:15 -0400 Subject: [PATCH 6/7] REF: Localization for QR --- loc/en.json | 1 + screen/send/details.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/loc/en.json b/loc/en.json index 00c5cd3cd..4c2b8a402 100644 --- a/loc/en.json +++ b/loc/en.json @@ -181,6 +181,7 @@ "details_adv_full": "Use Full Balance", "details_adv_full_sure": "Are you sure you want to use your wallet’s full balance for this transaction?", "details_adv_import": "Import Transaction", + "details_adv_import_qr": "Import Transaction (QR)", "details_amount_field_is_not_valid": "The amount is not valid.", "details_amount_field_is_less_than_minimum_amount_sat": "The specified amount is too small. Please enter an amount greater than 500 sats.", "details_create": "Create Invoice", diff --git a/screen/send/details.js b/screen/send/details.js index e137babc6..a1d697fa2 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -835,7 +835,7 @@ const SendDetails = () => { }, { id: SendDetails.actionKeys.ImportTransactionQR, - text: loc.send.details_adv_import + ' (QR)', + text: loc.send.details_adv_import_qr, icon: SendDetails.actionIcons.ImportTransactionQR, }, ); @@ -1144,7 +1144,7 @@ const SendDetails = () => { {wallet.type === WatchOnlyWallet.type && wallet.isHd() && ( Date: Thu, 26 Aug 2021 11:21:02 +0000 Subject: [PATCH 7/7] Translate /loc/en.json in es_419 review completed for the source file '/loc/en.json' on the 'es_419' language. --- loc/es_419.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/loc/es_419.json b/loc/es_419.json index 81da47f68..bead735b4 100644 --- a/loc/es_419.json +++ b/loc/es_419.json @@ -16,7 +16,7 @@ "seed": "Semilla", "success": "Éxito", "wallet_key": "Clave de la billetera", - "invalid_animated_qr_code_fragment": "Fragmento inválido de Código QR animado. Por favor intenta de nuevo.", + "invalid_animated_qr_code_fragment" : "Fragmento inválido de Código QR animado. Por favor intenta de nuevo.", "file_saved": "El archivo {filePath} se ha guardado en tu {destination}.", "file_save_title": "Guardar el archivo", "file_save_location": "Selecciona dónde guardar {filePath}", @@ -119,6 +119,7 @@ "lightning_invoice": "Factura Lightning", "has_been_paid": "Esta factura ha sido pagada.", "open_direct_channel": "Abrir un canal directo con este nodo:", + "please_pay_between_and": "Paga entre {min} y {max}", "please_pay": "Pagar por favor", "preimage": "Preimagen", "sats": "sats.", @@ -261,6 +262,7 @@ "default_wallets": "Ver todas las Billeteras", "electrum_connected": "Conectado", "electrum_connected_not": "No Conectado", + "electrum_connnected_not_description": "Se requiere una conexión activa de Electrum para importar una billetera. Puedes verificar si se ha establecido una conexión yendo a la pantalla Red en Configuración.", "electrum_error_connect": "No se puede conectar al servidor Electrum proporcionado", "electrum_host": "Por ej.: {example}", "electrum_offline_mode": "Modo offline", @@ -284,6 +286,7 @@ "electrum_reset_to_default": "¿Estás seguro de querer restablecer la configuración de Electrum a los valores predeterminados?", "electrum_clear": "Limpiar", "tor_supported": "Tor soportado", + "tor_unsupported": "Las conexiones Tor no son compatibles.", "encrypt_decrypt": "Descifrar Almacenamiento", "encrypt_decrypt_q": "¿Estás seguro de que deseas descifrar tu almacenamiento? Esto permitirá acceder a tus billeteras sin una contraseña.", "encrypt_del_uninstall": "Eliminar si BlueWallet es desinstalada", @@ -311,7 +314,7 @@ "network_electrum": "Servidor Electrum", "not_a_valid_uri": "URI inválida", "notifications": "Notificaciones", - "open_link_in_explorer": "Abrir enlace en el explorador", + "open_link_in_explorer" : "Abrir enlace en el explorador", "password": "Contraseña", "password_explain": "Crea la contraseña que usarás para desencriptar el almacenamiento", "passwords_do_not_match": "Las contraseñas no coinciden.", @@ -330,7 +333,7 @@ "selfTest": "Auto-Test", "save": "Guardar", "saved": "Guardado", - "success_transaction_broadcasted": "¡Éxito! ¡Tu transacción ha sido transmitida!", + "success_transaction_broadcasted" : "¡Éxito! ¡Tu transacción ha sido transmitida!", "total_balance": "Balance Total", "total_balance_explanation": "Muestra el saldo total de todas tus billeteras en los widgets de tu pantalla de inicio.", "widgets": "Widgets", @@ -599,4 +602,4 @@ "send_success": "Firma enviada con éxito", "send_error": "Error de envío de firma" } -} +} \ No newline at end of file