From 43bb5726292976df663b820a0346168d06b5f454 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 12:48:03 -0400 Subject: [PATCH 1/8] REF: ToolTipMenu to TSX --- components/QRCodeComponent.tsx | 51 ++++------- components/SaveFileButton.tsx | 23 +++-- components/TooltipMenu.android.js | 64 -------------- components/TooltipMenu.android.tsx | 67 ++++++++++++++ ...TooltipMenu.ios.js => TooltipMenu.ios.tsx} | 65 ++++++-------- components/TooltipMenu.js | 9 -- components/TooltipMenu.tsx | 10 +++ components/TransactionListItem.tsx | 9 +- components/addresses/AddressItem.tsx | 87 +++++++++---------- components/types.ts | 30 +++++++ hooks/useExtendedNavigation.ts | 2 +- screen/transactions/details.tsx | 66 +++++++------- screen/wallets/PaymentCodesList.tsx | 14 +-- 13 files changed, 240 insertions(+), 257 deletions(-) delete mode 100644 components/TooltipMenu.android.js create mode 100644 components/TooltipMenu.android.tsx rename components/{TooltipMenu.ios.js => TooltipMenu.ios.tsx} (62%) delete mode 100644 components/TooltipMenu.js create mode 100644 components/TooltipMenu.tsx create mode 100644 components/types.ts diff --git a/components/QRCodeComponent.tsx b/components/QRCodeComponent.tsx index abbc5c1cd..d887fdee7 100644 --- a/components/QRCodeComponent.tsx +++ b/components/QRCodeComponent.tsx @@ -7,6 +7,7 @@ import loc from '../loc'; import Clipboard from '@react-native-clipboard/clipboard'; import { useTheme } from './themes'; import { ActionIcons } from '../typings/ActionIcons'; +import { Action } from './types'; interface QRCodeComponentProps { value: string; @@ -18,22 +19,6 @@ interface QRCodeComponentProps { onError?: () => void; } -interface ActionType { - Share: 'share'; - Copy: 'copy'; -} - -interface Action { - id: string; - text: string; - icon: ActionIcons; -} - -const actionKeys: ActionType = { - Share: 'share', - Copy: 'copy', -}; - const actionIcons: { [key: string]: ActionIcons } = { Share: { iconType: 'SYSTEM', @@ -45,6 +30,17 @@ const actionIcons: { [key: string]: ActionIcons } = { }, }; +const menuActions: Action[] = + Platform.OS === 'ios' || Platform.OS === 'macos' + ? [ + { + id: 'copy', + text: loc.transactions.details_copy, + icon: actionIcons.Copy, + }, + ] + : [{ id: 'share', text: loc.receive.details_share, icon: actionIcons.Share }]; + const QRCodeComponent: React.FC = ({ value = '', isLogoRendered = true, @@ -68,30 +64,13 @@ const QRCodeComponent: React.FC = ({ }; const onPressMenuItem = (id: string) => { - if (id === actionKeys.Share) { + if (id === 'share') { handleShareQRCode(); - } else if (id === actionKeys.Copy) { + } else if (id === 'copy') { qrCode.current.toDataURL(Clipboard.setImage); } }; - const menuActions = (): Action[] => { - const actions: Action[] = []; - if (Platform.OS === 'ios' || Platform.OS === 'macos') { - actions.push({ - id: actionKeys.Copy, - text: loc.transactions.details_copy, - icon: actionIcons.Copy, - }); - } - actions.push({ - id: actionKeys.Share, - text: loc.receive.details_share, - icon: actionIcons.Share, - }); - return actions; - }; - const renderQRCode = ( = ({ accessibilityLabel={loc.receive.qrcode_for_the_address} > {isMenuAvailable ? ( - + {renderQRCode} ) : ( diff --git a/components/SaveFileButton.tsx b/components/SaveFileButton.tsx index f417f6344..98607b0f1 100644 --- a/components/SaveFileButton.tsx +++ b/components/SaveFileButton.tsx @@ -1,9 +1,10 @@ import React, { ReactNode } from 'react'; import { StyleProp, ViewStyle } from 'react-native'; -import ToolTipMenu from './TooltipMenu'; import loc from '../loc'; import { ActionIcons } from '../typings/ActionIcons'; import * as fs from '../blue_modules/fs'; +import { Action } from './types'; +import ToolTipMenu from './TooltipMenu'; interface SaveFileButtonProps { fileName: string; @@ -11,7 +12,7 @@ interface SaveFileButtonProps { children?: ReactNode; style?: StyleProp; afterOnPress?: () => void; - beforeOnPress?: () => Promise; // Changed this line + beforeOnPress?: () => Promise; onMenuWillHide?: () => void; onMenuWillShow?: () => void; } @@ -26,30 +27,24 @@ const SaveFileButton: React.FC = ({ onMenuWillHide, onMenuWillShow, }) => { - const actions = [ - { id: 'save', text: loc._.save, icon: actionIcons.Save }, - { id: 'share', text: loc.receive.details_share, icon: actionIcons.Share }, - ]; - const handlePressMenuItem = async (actionId: string) => { if (beforeOnPress) { - await beforeOnPress(); // Now properly awaiting a function that returns a promise + await beforeOnPress(); } const action = actions.find(a => a.id === actionId); if (action?.id === 'save') { await fs.writeFileAndExport(fileName, fileContent, false).finally(() => { - afterOnPress?.(); // Safely call afterOnPress if it exists + afterOnPress?.(); }); } else if (action?.id === 'share') { await fs.writeFileAndExport(fileName, fileContent, true).finally(() => { - afterOnPress?.(); // Safely call afterOnPress if it exists + afterOnPress?.(); }); } }; return ( - // @ts-ignore: Tooltip must be refactored to use TSX} = ({ isMenuPrimaryAction actions={actions} onPressMenuItem={handlePressMenuItem} - buttonStyle={style} + buttonStyle={style as ViewStyle} // Type assertion to match ViewStyle > {children} @@ -76,3 +71,7 @@ const actionIcons: { [key: string]: ActionIcons } = { iconValue: 'square.and.arrow.down', }, }; +const actions: Action[] = [ + { id: 'save', text: loc._.save, icon: actionIcons.Save }, + { id: 'share', text: loc.receive.details_share, icon: actionIcons.Share }, +]; diff --git a/components/TooltipMenu.android.js b/components/TooltipMenu.android.js deleted file mode 100644 index c9a1d3190..000000000 --- a/components/TooltipMenu.android.js +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useRef, useEffect, forwardRef } from 'react'; -import PropTypes from 'prop-types'; -import { Pressable } from 'react-native'; -import showPopupMenu from '../blue_modules/showPopupMenu'; - -const BaseToolTipMenu = (props, ref) => { - const menuRef = useRef(); - const disabled = props.disabled ?? false; - const isMenuPrimaryAction = props.isMenuPrimaryAction ?? false; - const enableAndroidRipple = props.enableAndroidRipple ?? true; - const buttonStyle = props.buttonStyle ?? {}; - const handleToolTipSelection = selection => { - props.onPressMenuItem(selection.id); - }; - - useEffect(() => { - if (ref && ref.current) { - ref.current.dismissMenu = dismissMenu; - } - }, [ref]); - - const dismissMenu = () => { - console.log('dismissMenu Not implemented'); - }; - - const showMenu = () => { - const menu = []; - for (const actions of props.actions) { - if (Array.isArray(actions)) { - for (const actionToMap of actions) { - menu.push({ id: actionToMap.id, label: actionToMap.text }); - } - } else { - menu.push({ id: actions.id, label: actions.text }); - } - } - - showPopupMenu(menu, handleToolTipSelection, menuRef.current); - }; - - return ( - - {props.children} - - ); -}; - -const ToolTipMenu = forwardRef(BaseToolTipMenu); - -export default ToolTipMenu; -ToolTipMenu.propTypes = { - actions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired, - children: PropTypes.node, - onPressMenuItem: PropTypes.func.isRequired, - isMenuPrimaryAction: PropTypes.bool, - onPress: PropTypes.func, - disabled: PropTypes.bool, -}; diff --git a/components/TooltipMenu.android.tsx b/components/TooltipMenu.android.tsx new file mode 100644 index 000000000..fe8aeb732 --- /dev/null +++ b/components/TooltipMenu.android.tsx @@ -0,0 +1,67 @@ +import React, { useRef, useEffect, forwardRef, Ref } from 'react'; +import { Pressable, View } from 'react-native'; +import showPopupMenu, { OnPopupMenuItemSelect, PopupMenuItem } from '../blue_modules/showPopupMenu.android'; +import { ToolTipMenuProps } from './types'; + +const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => void }>) => { + const menuRef = useRef(null); + const { + actions, + children, + onPressMenuItem, + isMenuPrimaryAction = false, + buttonStyle = {}, + enableAndroidRipple = true, + disabled = false, + onPress, + ...restProps + } = props; + + const handleToolTipSelection: OnPopupMenuItemSelect = (selection: PopupMenuItem) => { + if (selection.id) { + onPressMenuItem(selection.id); + } + }; + + useEffect(() => { + if (ref && menuRef.current) { + (ref as React.MutableRefObject<{ dismissMenu?: () => void }>).current.dismissMenu = dismissMenu; + } + }, [ref]); + + const dismissMenu = () => { + console.log('dismissMenu Not implemented'); + }; + + const showMenu = () => { + const menu: { id: string; label: string }[] = []; + actions.forEach(action => { + if (Array.isArray(action)) { + action.forEach(actionToMap => { + menu.push({ id: actionToMap.id.toString(), label: actionToMap.text }); + }); + } else { + menu.push({ id: action.id.toString(), label: action.text }); + } + }); + + showPopupMenu(menu, handleToolTipSelection, menuRef.current); + }; + + return ( + + {children} + + ); +}; + +const ToolTipMenu = forwardRef(BaseToolTipMenu); + +export default ToolTipMenu; diff --git a/components/TooltipMenu.ios.js b/components/TooltipMenu.ios.tsx similarity index 62% rename from components/TooltipMenu.ios.js rename to components/TooltipMenu.ios.tsx index 29f982516..03c2f4aae 100644 --- a/components/TooltipMenu.ios.js +++ b/components/TooltipMenu.ios.tsx @@ -1,11 +1,12 @@ -import React, { forwardRef } from 'react'; -import { ContextMenuView, ContextMenuButton } from 'react-native-ios-context-menu'; -import PropTypes from 'prop-types'; +// ToolTipMenu.ios.tsx +import React, { forwardRef, Ref } from 'react'; +import { ContextMenuView, ContextMenuButton, RenderItem } from 'react-native-ios-context-menu'; import { TouchableOpacity } from 'react-native'; +import { ToolTipMenuProps, Action } from './types'; -const BaseToolTipMenu = (props, ref) => { - const menuItemMapped = ({ action, menuOptions }) => { - const item = { +const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { + const menuItemMapped = ({ action, menuOptions }: { action: Action; menuOptions?: string[] }) => { + const item: any = { actionKey: action.id.toString(), actionTitle: action.text, icon: action.icon, @@ -22,30 +23,29 @@ const BaseToolTipMenu = (props, ref) => { const menuItems = props.actions.map(action => { if (Array.isArray(action)) { - const mapped = []; - for (const actionToMap of action) { - mapped.push(menuItemMapped({ action: actionToMap })); - } - const submenu = { + const mapped = action.map(actionToMap => menuItemMapped({ action: actionToMap })); + return { menuOptions: ['displayInline'], menuItems: mapped, menuTitle: '', }; - return submenu; } else { return menuItemMapped({ action }); } }); - const menuTitle = props.title ?? ''; - const isButton = !!props.isButton; - const isMenuPrimaryAction = props.isMenuPrimaryAction ? props.isMenuPrimaryAction : false; - const renderPreview = props.renderPreview ?? undefined; - const disabled = props.disabled ?? false; - const onPress = props.onPress ?? undefined; - const onMenuWillShow = props.onMenuWillShow ?? undefined; - const onMenuWillHide = props.onMenuWillHide ?? undefined; - const buttonStyle = props.buttonStyle; + const { + title = '', + isButton = false, + isMenuPrimaryAction = false, + renderPreview, + disabled = false, + onPress, + onMenuWillShow, + onMenuWillHide, + buttonStyle, + } = props; + return isButton ? ( { }} isMenuPrimaryAction={isMenuPrimaryAction} menuConfig={{ - menuTitle, + menuTitle: title, menuItems, }} > @@ -70,14 +70,13 @@ const BaseToolTipMenu = (props, ref) => { ref={ref} lazyPreview shouldEnableAggressiveCleanup - shouldCleanupOnComponentWillUnmountForMenuPreview internalCleanupMode="automatic" onPressMenuItem={({ nativeEvent }) => { props.onPressMenuItem(nativeEvent.actionKey); }} useActionSheetFallback={false} menuConfig={{ - menuTitle, + menuTitle: title, menuItems, }} {...(renderPreview @@ -86,7 +85,7 @@ const BaseToolTipMenu = (props, ref) => { previewType: 'CUSTOM', backgroundColor: 'white', }, - renderPreview, + renderPreview: renderPreview as RenderItem, } : {})} > @@ -105,7 +104,7 @@ const BaseToolTipMenu = (props, ref) => { shouldEnableAggressiveCleanup useActionSheetFallback={false} menuConfig={{ - menuTitle, + menuTitle: title, menuItems, }} {...(renderPreview @@ -114,7 +113,7 @@ const BaseToolTipMenu = (props, ref) => { previewType: 'CUSTOM', backgroundColor: 'white', }, - renderPreview, + renderPreview: renderPreview as RenderItem, } : {})} > @@ -126,15 +125,3 @@ const BaseToolTipMenu = (props, ref) => { const ToolTipMenu = forwardRef(BaseToolTipMenu); export default ToolTipMenu; -ToolTipMenu.propTypes = { - actions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired, - title: PropTypes.string, - children: PropTypes.node, - onPressMenuItem: PropTypes.func.isRequired, - isMenuPrimaryAction: PropTypes.bool, - isButton: PropTypes.bool, - renderPreview: PropTypes.func, - onPress: PropTypes.func, - previewValue: PropTypes.string, - disabled: PropTypes.bool, -}; diff --git a/components/TooltipMenu.js b/components/TooltipMenu.js deleted file mode 100644 index ca6307f1b..000000000 --- a/components/TooltipMenu.js +++ /dev/null @@ -1,9 +0,0 @@ -import { forwardRef } from 'react'; - -const BaseToolTipMenu = (props, _ref) => { - return props.children; -}; - -const ToolTipMenu = forwardRef(BaseToolTipMenu); - -export default ToolTipMenu; diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx new file mode 100644 index 000000000..34952011c --- /dev/null +++ b/components/TooltipMenu.tsx @@ -0,0 +1,10 @@ +import { forwardRef, Ref } from 'react'; +import { ToolTipMenuProps } from './types'; + +const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { + return props.children; +}; + +const ToolTipMenu = forwardRef(BaseToolTipMenu); + +export default ToolTipMenu; diff --git a/components/TransactionListItem.tsx b/components/TransactionListItem.tsx index db42bca60..b22cfee8d 100644 --- a/components/TransactionListItem.tsx +++ b/components/TransactionListItem.tsx @@ -20,6 +20,7 @@ import { useTheme } from './themes'; import ListItem from './ListItem'; import { useSettings } from './Context/SettingsContext'; import { LightningTransaction, Transaction } from '../class/wallets/types'; +import { Action } from './types'; interface TransactionListItemProps { itemPriceUnit: BitcoinUnit; @@ -287,9 +288,9 @@ export const TransactionListItem: React.FC = React.mem handleOnViewOnBlockExplorer, ], ); + const toolTipActions = useMemo((): Action[] | Action[][] => { + const actions: (Action | Action[])[] = []; - const toolTipActions = useMemo(() => { - const actions = []; if (rowTitle !== loc.lnd.expired) { actions.push({ id: actionKeys.CopyAmount, @@ -305,6 +306,7 @@ export const TransactionListItem: React.FC = React.mem icon: actionIcons.Clipboard, }); } + if (item.hash) { actions.push( { @@ -337,10 +339,9 @@ export const TransactionListItem: React.FC = React.mem ]); } - return actions; + return actions as Action[] | Action[][]; // eslint-disable-next-line react-hooks/exhaustive-deps }, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]); - return ( diff --git a/components/addresses/AddressItem.tsx b/components/addresses/AddressItem.tsx index a6ce981f7..382052551 100644 --- a/components/addresses/AddressItem.tsx +++ b/components/addresses/AddressItem.tsx @@ -1,8 +1,7 @@ -import React, { useContext, useRef } from 'react'; +import React, { useMemo, useRef } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { ListItem } from 'react-native-elements'; -import PropTypes from 'prop-types'; import { AddressTypeBadge } from './AddressTypeBadge'; import loc, { formatBalance } from '../../loc'; import TooltipMenu from '../TooltipMenu'; @@ -10,12 +9,13 @@ import Clipboard from '@react-native-clipboard/clipboard'; import Share from 'react-native-share'; import { useTheme } from '../themes'; import { BitcoinUnit } from '../../models/bitcoinUnits'; -import { BlueStorageContext } from '../../blue_modules/storage-context'; +import { useStorage } from '../../blue_modules/storage-context'; import Biometric from '../../class/biometrics'; import presentAlert from '../Alert'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import QRCodeComponent from '../QRCodeComponent'; import confirm from '../../helpers/confirm'; +import { Action } from '../types'; interface AddressItemProps { // todo: fix `any` after addresses.js is converted to the church of holy typescript @@ -26,7 +26,7 @@ interface AddressItemProps { } const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: AddressItemProps) => { - const { wallets } = useContext(BlueStorageContext); + const { wallets } = useStorage(); const { colors } = useTheme(); const hasTransactions = item.transactions > 0; @@ -79,6 +79,8 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad }); }; + const menuActions = useMemo(() => getAvailableActions({ allowSignVerifyMessage }), [allowSignVerifyMessage]); + const balance = formatBalance(item.balance, balanceUnit, true); const handleCopyPress = () => { @@ -129,39 +131,6 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad } }; - const getAvailableActions = () => { - const actions = [ - { - id: AddressItem.actionKeys.CopyToClipboard, - text: loc.transactions.details_copy, - icon: AddressItem.actionIcons.Clipboard, - }, - { - id: AddressItem.actionKeys.Share, - text: loc.receive.details_share, - icon: AddressItem.actionIcons.Share, - }, - ]; - - if (allowSignVerifyMessage) { - actions.push({ - id: AddressItem.actionKeys.SignVerify, - text: loc.addresses.sign_title, - icon: AddressItem.actionIcons.Signature, - }); - } - - if (allowSignVerifyMessage) { - actions.push({ - id: AddressItem.actionKeys.ExportPrivateKey, - text: loc.addresses.copy_private_key, - icon: AddressItem.actionIcons.ExportPrivateKey, - }); - } - - return actions; - }; - const renderPreview = () => { return ; }; @@ -171,7 +140,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad { + const actions = [ + { + id: AddressItem.actionKeys.CopyToClipboard, + text: loc.transactions.details_copy, + icon: AddressItem.actionIcons.Clipboard, + }, + { + id: AddressItem.actionKeys.Share, + text: loc.receive.details_share, + icon: AddressItem.actionIcons.Share, + }, + ]; + + if (allowSignVerifyMessage) { + actions.push({ + id: AddressItem.actionKeys.SignVerify, + text: loc.addresses.sign_title, + icon: AddressItem.actionIcons.Signature, + }); + } + + if (allowSignVerifyMessage) { + actions.push({ + id: AddressItem.actionKeys.ExportPrivateKey, + text: loc.addresses.copy_private_key, + icon: AddressItem.actionIcons.ExportPrivateKey, + }); + } + + return actions; }; + export { AddressItem }; diff --git a/components/types.ts b/components/types.ts new file mode 100644 index 000000000..d4a7c623f --- /dev/null +++ b/components/types.ts @@ -0,0 +1,30 @@ +import { ViewStyle } from 'react-native'; + +export interface Action { + id: string | number; + text: string; + icon: { + iconType: string; + iconValue: string; + }; + menuTitle?: string; + menuStateOn?: boolean; + disabled?: boolean; +} + +export interface ToolTipMenuProps { + actions: Action[] | Action[][]; + children: React.ReactNode; + enableAndroidRipple?: boolean; + onPressMenuItem: (id: string) => void; + title?: string; + isMenuPrimaryAction?: boolean; + isButton?: boolean; + renderPreview?: () => React.ReactNode; + onPress?: () => void; + previewValue?: string; + disabled?: boolean; + buttonStyle?: ViewStyle; + onMenuWillShow?: () => void; + onMenuWillHide?: () => void; +} diff --git a/hooks/useExtendedNavigation.ts b/hooks/useExtendedNavigation.ts index 6eb612c8d..a5346900c 100644 --- a/hooks/useExtendedNavigation.ts +++ b/hooks/useExtendedNavigation.ts @@ -47,7 +47,7 @@ export const useExtendedNavigation = (): NavigationProp => { const isAuthenticated = await Biometric.unlockWithBiometrics(); if (isAuthenticated) { proceedWithNavigation(); - return; // Ensure the function exits if this path is taken + return; } else { console.error('Biometric authentication failed'); // Decide if navigation should proceed or not after failed authentication diff --git a/screen/transactions/details.tsx b/screen/transactions/details.tsx index ee0076993..b40867fd8 100644 --- a/screen/transactions/details.tsx +++ b/screen/transactions/details.tsx @@ -23,6 +23,22 @@ interface TransactionDetailsProps { navigation: NativeStackNavigationProp; } +const actionKeys = { + CopyToClipboard: 'copyToClipboard', + GoToWallet: 'goToWallet', +}; + +const actionIcons = { + Clipboard: { + iconType: 'SYSTEM', + iconValue: 'doc.on.doc', + }, + GoToWallet: { + iconType: 'SYSTEM', + iconValue: 'wallet.pass', + }, +}; + function onlyUnique(value: any, index: number, self: any[]) { return self.indexOf(value) === index; } @@ -37,6 +53,14 @@ function arrDiff(a1: any[], a2: any[]) { return ret; } +const toolTipMenuActions = [ + { + id: actionKeys.CopyToClipboard, + text: loc.transactions.copy_link, + icon: actionIcons.Clipboard, + }, +]; + const TransactionDetails = () => { const { setOptions, navigate } = useNavigation(); const { hash, walletID } = useRoute().params; @@ -162,9 +186,7 @@ const TransactionDetails = () => { }; const handleCopyPress = (stringToCopy: string) => { - Clipboard.setString( - stringToCopy !== TransactionDetails.actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`, - ); + Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`); }; if (isLoading || !tx) { @@ -189,9 +211,9 @@ const TransactionDetails = () => { }; const onPressMenuItem = (key: string) => { - if (key === TransactionDetails.actionKeys.CopyToClipboard) { + if (key === actionKeys.CopyToClipboard) { handleCopyPress(key); - } else if (key === TransactionDetails.actionKeys.GoToWallet) { + } else if (key === actionKeys.GoToWallet) { const wallet = weOwnAddress(key); if (wallet) { navigateToWallet(wallet); @@ -205,16 +227,16 @@ const TransactionDetails = () => { for (const [index, address] of array.entries()) { const actions = []; actions.push({ - id: TransactionDetails.actionKeys.CopyToClipboard, + id: actionKeys.CopyToClipboard, text: loc.transactions.details_copy, - icon: TransactionDetails.actionIcons.Clipboard, + icon: actionIcons.Clipboard, }); const isWeOwnAddress = weOwnAddress(address); if (isWeOwnAddress) { actions.push({ - id: TransactionDetails.actionKeys.GoToWallet, + id: actionKeys.GoToWallet, text: loc.formatString(loc.transactions.view_wallet, { walletLabel: isWeOwnAddress.getLabel() }), - icon: TransactionDetails.actionIcons.GoToWallet, + icon: actionIcons.GoToWallet, }); } @@ -320,16 +342,10 @@ const TransactionDetails = () => { )} {loc.transactions.details_show_in_block_explorer} @@ -338,22 +354,6 @@ const TransactionDetails = () => { ); }; -TransactionDetails.actionKeys = { - CopyToClipboard: 'copyToClipboard', - GoToWallet: 'goToWallet', -}; - -TransactionDetails.actionIcons = { - Clipboard: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, - GoToWallet: { - iconType: 'SYSTEM', - iconValue: 'wallet.pass', - }, -}; - const styles = StyleSheet.create({ scroll: { flex: 1, diff --git a/screen/wallets/PaymentCodesList.tsx b/screen/wallets/PaymentCodesList.tsx index a3c255884..1f23aa85b 100644 --- a/screen/wallets/PaymentCodesList.tsx +++ b/screen/wallets/PaymentCodesList.tsx @@ -21,25 +21,19 @@ import { satoshiToLocalCurrency } from '../../blue_modules/currency'; import { BlueLoading } from '../../BlueComponents'; import { PaymentCodeStackParamList } from '../../navigation/PaymentCodeStack'; import presentAlert from '../../components/Alert'; +import { Action } from '../../components/types'; interface DataSection { title: string; data: string[]; } - -interface IActionKey { - id: Actions; - text: string; - icon: any; -} - enum Actions { pay, rename, copyToClipboard, } -const actionKeys: IActionKey[] = [ +const actionKeys: Action[] = [ { id: Actions.pay, text: loc.bip47.pay_this_contact, @@ -97,9 +91,7 @@ export default function PaymentCodesList() { setData(newData); }, [walletID, wallets, reload]); - const toolTipActions = useMemo(() => { - return actionKeys; - }, []); + const toolTipActions = useMemo(() => actionKeys, []); const shortenContactName = (name: string): string => { if (name.length < 20) return name; From f48653309526f06c4ac099c67b6a8bae49a39f75 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 12:56:57 -0400 Subject: [PATCH 2/8] REF: useMemo --- components/TooltipMenu.android.tsx | 29 +++--- components/TooltipMenu.ios.tsx | 143 ++++++++++++++--------------- 2 files changed, 85 insertions(+), 87 deletions(-) diff --git a/components/TooltipMenu.android.tsx b/components/TooltipMenu.android.tsx index fe8aeb732..225c9d658 100644 --- a/components/TooltipMenu.android.tsx +++ b/components/TooltipMenu.android.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, forwardRef, Ref } from 'react'; +import React, { useRef, useEffect, forwardRef, Ref, useMemo, useCallback } from 'react'; import { Pressable, View } from 'react-native'; import showPopupMenu, { OnPopupMenuItemSelect, PopupMenuItem } from '../blue_modules/showPopupMenu.android'; import { ToolTipMenuProps } from './types'; @@ -17,23 +17,27 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => ...restProps } = props; - const handleToolTipSelection: OnPopupMenuItemSelect = (selection: PopupMenuItem) => { - if (selection.id) { - onPressMenuItem(selection.id); - } - }; + const handleToolTipSelection = useCallback( + (selection: PopupMenuItem) => { + if (selection.id) { + onPressMenuItem(selection.id); + } + }, + [onPressMenuItem], + ); useEffect(() => { if (ref && menuRef.current) { (ref as React.MutableRefObject<{ dismissMenu?: () => void }>).current.dismissMenu = dismissMenu; } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ref]); - const dismissMenu = () => { + const dismissMenu = useCallback(() => { console.log('dismissMenu Not implemented'); - }; + }, []); - const showMenu = () => { + const menuItems = useMemo(() => { const menu: { id: string; label: string }[] = []; actions.forEach(action => { if (Array.isArray(action)) { @@ -44,9 +48,12 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => menu.push({ id: action.id.toString(), label: action.text }); } }); + return menu; + }, [actions]); - showPopupMenu(menu, handleToolTipSelection, menuRef.current); - }; + const showMenu = useCallback(() => { + showPopupMenu(menuItems, handleToolTipSelection, menuRef.current); + }, [menuItems, handleToolTipSelection]); return ( ) => { - const menuItemMapped = ({ action, menuOptions }: { action: Action; menuOptions?: string[] }) => { + const { + title = '', + isButton = false, + isMenuPrimaryAction = false, + renderPreview, + disabled = false, + onPress, + onMenuWillShow, + onMenuWillHide, + buttonStyle, + onPressMenuItem, + } = props; + + const menuItemMapped = useCallback(({ action, menuOptions }: { action: Action; menuOptions?: string[] }) => { const item: any = { actionKey: action.id.toString(), actionTitle: action.text, @@ -19,61 +31,56 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { item.menuAttributes = ['disabled']; } return item; - }; + }, []); - const menuItems = props.actions.map(action => { - if (Array.isArray(action)) { - const mapped = action.map(actionToMap => menuItemMapped({ action: actionToMap })); - return { - menuOptions: ['displayInline'], - menuItems: mapped, - menuTitle: '', - }; - } else { - return menuItemMapped({ action }); - } - }); + const menuItems = useMemo( + () => + props.actions.map(action => { + if (Array.isArray(action)) { + const mapped = action.map(actionToMap => menuItemMapped({ action: actionToMap })); + return { + menuOptions: ['displayInline'], + menuItems: mapped, + menuTitle: '', + }; + } else { + return menuItemMapped({ action }); + } + }), + [props.actions, menuItemMapped], + ); - const { - title = '', - isButton = false, - isMenuPrimaryAction = false, - renderPreview, - disabled = false, - onPress, - onMenuWillShow, - onMenuWillHide, - buttonStyle, - } = props; + const handlePressMenuItem = useCallback( + ({ nativeEvent }: { nativeEvent: { actionKey: string } }) => { + onPressMenuItem(nativeEvent.actionKey); + }, + [onPressMenuItem], + ); - return isButton ? ( - - { - props.onPressMenuItem(nativeEvent.actionKey); - }} - isMenuPrimaryAction={isMenuPrimaryAction} - menuConfig={{ - menuTitle: title, - menuItems, - }} - > - {props.children} - - - ) : props.onPress ? ( + const renderContextMenuButton = () => ( + + {props.children} + + ); + + const renderContextMenuView = () => ( { - props.onPressMenuItem(nativeEvent.actionKey); - }} + onPressMenuItem={handlePressMenuItem} useActionSheetFallback={false} menuConfig={{ menuTitle: title, @@ -89,36 +96,20 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { } : {})} > - + {props.children} + ); + + return isButton ? ( + + {renderContextMenuButton()} + + ) : props.onPress ? ( + renderContextMenuView() ) : ( - { - props.onPressMenuItem(nativeEvent.actionKey); - }} - lazyPreview - shouldEnableAggressiveCleanup - useActionSheetFallback={false} - menuConfig={{ - menuTitle: title, - menuItems, - }} - {...(renderPreview - ? { - previewConfig: { - previewType: 'CUSTOM', - backgroundColor: 'white', - }, - renderPreview: renderPreview as RenderItem, - } - : {})} - > - {props.children} - + renderContextMenuView() ); }; From a642667ef3d885f07e0f4f6d68fe22304ee649cc Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 13:50:50 -0400 Subject: [PATCH 3/8] FIX: Tests --- components/TooltipMenu.ios.tsx | 2 +- components/TooltipMenu.tsx | 2 +- tests/setup.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/TooltipMenu.ios.tsx b/components/TooltipMenu.ios.tsx index 6d985d351..355eb28b1 100644 --- a/components/TooltipMenu.ios.tsx +++ b/components/TooltipMenu.ios.tsx @@ -109,7 +109,7 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { ) : props.onPress ? ( renderContextMenuView() ) : ( - renderContextMenuView() + renderContextMenuButton() ); }; diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx index 34952011c..3028662c1 100644 --- a/components/TooltipMenu.tsx +++ b/components/TooltipMenu.tsx @@ -1,7 +1,7 @@ import { forwardRef, Ref } from 'react'; import { ToolTipMenuProps } from './types'; -const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { +const BaseToolTipMenu = (props: ToolTipMenuProps, _ref: Ref) => { return props.children; }; diff --git a/tests/setup.js b/tests/setup.js index fc9b4c650..2f1c44a5a 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -206,6 +206,6 @@ jest.mock('react-native-keychain', () => mockKeychain); jest.mock('react-native-tcp-socket', () => mockKeychain); -jest.mock('../components/TooltipMenu.ios.js', () => require('../components/TooltipMenu.js')); +jest.mock('../components/TooltipMenu.ios.tsx', () => require('../components/TooltipMenu.tsx')); global.alert = () => {}; From 127d3be270a47077f5e9905e53045f0217e96491 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 14:28:24 -0400 Subject: [PATCH 4/8] Update TooltipMenu.tsx --- components/TooltipMenu.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx index 3028662c1..77e0f542b 100644 --- a/components/TooltipMenu.tsx +++ b/components/TooltipMenu.tsx @@ -1,10 +1,7 @@ -import { forwardRef, Ref } from 'react'; import { ToolTipMenuProps } from './types'; -const BaseToolTipMenu = (props: ToolTipMenuProps, _ref: Ref) => { +const ToolTipMenu = (props: ToolTipMenuProps, _ref: any) => { return props.children; }; -const ToolTipMenu = forwardRef(BaseToolTipMenu); - export default ToolTipMenu; From a4a8d58db3b2549fc35bee1267441b37933b5075 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 14:34:54 -0400 Subject: [PATCH 5/8] REF: use consts in QRCodeCOmppnent --- components/QRCodeComponent.tsx | 13 +++++++++---- components/TooltipMenu.tsx | 6 +++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/components/QRCodeComponent.tsx b/components/QRCodeComponent.tsx index d887fdee7..c11b55ba7 100644 --- a/components/QRCodeComponent.tsx +++ b/components/QRCodeComponent.tsx @@ -30,16 +30,21 @@ const actionIcons: { [key: string]: ActionIcons } = { }, }; +const actionKeys = { + Share: 'share', + Copy: 'copy', +}; + const menuActions: Action[] = Platform.OS === 'ios' || Platform.OS === 'macos' ? [ { - id: 'copy', + id: actionKeys.Copy, text: loc.transactions.details_copy, icon: actionIcons.Copy, }, ] - : [{ id: 'share', text: loc.receive.details_share, icon: actionIcons.Share }]; + : [{ id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share }]; const QRCodeComponent: React.FC = ({ value = '', @@ -64,9 +69,9 @@ const QRCodeComponent: React.FC = ({ }; const onPressMenuItem = (id: string) => { - if (id === 'share') { + if (id === actionKeys.Share) { handleShareQRCode(); - } else if (id === 'copy') { + } else if (id === actionKeys.Copy) { qrCode.current.toDataURL(Clipboard.setImage); } }; diff --git a/components/TooltipMenu.tsx b/components/TooltipMenu.tsx index 77e0f542b..26180bfac 100644 --- a/components/TooltipMenu.tsx +++ b/components/TooltipMenu.tsx @@ -1,7 +1,11 @@ +import { forwardRef, Ref } from 'react'; import { ToolTipMenuProps } from './types'; -const ToolTipMenu = (props: ToolTipMenuProps, _ref: any) => { +const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { + console.debug('ToolTipMenu.tsx ref:', ref); return props.children; }; +const ToolTipMenu = forwardRef(BaseToolTipMenu); + export default ToolTipMenu; From 5d4e1112d8577b16141b5b9dde38d2daa8be9f4a Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 14:36:13 -0400 Subject: [PATCH 6/8] Update MasterView.tsx --- navigation/MasterView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/navigation/MasterView.tsx b/navigation/MasterView.tsx index 31a1fe5c8..9713fa418 100644 --- a/navigation/MasterView.tsx +++ b/navigation/MasterView.tsx @@ -2,7 +2,6 @@ import 'react-native-gesture-handler'; // should be on top import React, { Suspense, lazy } from 'react'; import MainRoot from '../navigation'; import { useStorage } from '../blue_modules/storage-context'; -import Biometric from '../class/biometrics'; const CompanionDelegates = lazy(() => import('../components/CompanionDelegates')); const MasterView = () => { @@ -10,7 +9,6 @@ const MasterView = () => { return ( <> - {walletsInitialized && ( From 5b1122ea6864ce2373e625e0fa43f29995c03eff Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 21:25:31 -0400 Subject: [PATCH 7/8] Update TooltipMenu.ios.tsx --- components/TooltipMenu.ios.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/TooltipMenu.ios.tsx b/components/TooltipMenu.ios.tsx index 355eb28b1..89ed6bc1b 100644 --- a/components/TooltipMenu.ios.tsx +++ b/components/TooltipMenu.ios.tsx @@ -96,9 +96,13 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { } : {})} > - - {props.children} - + {onPress ? ( + + {props.children} + + ) : ( + props.children + )} ); @@ -109,7 +113,7 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { ) : props.onPress ? ( renderContextMenuView() ) : ( - renderContextMenuButton() + renderContextMenuView() ); }; From 4efbd71ef0fb468a9cc2b05262f8d313cf0c7a77 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 18 May 2024 22:04:52 -0400 Subject: [PATCH 8/8] Fixes --- components/TooltipMenu.android.tsx | 18 ++++++++++-------- components/TooltipMenu.ios.tsx | 6 +++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/components/TooltipMenu.android.tsx b/components/TooltipMenu.android.tsx index 225c9d658..00ab41999 100644 --- a/components/TooltipMenu.android.tsx +++ b/components/TooltipMenu.android.tsx @@ -3,6 +3,9 @@ import { Pressable, View } from 'react-native'; import showPopupMenu, { OnPopupMenuItemSelect, PopupMenuItem } from '../blue_modules/showPopupMenu.android'; import { ToolTipMenuProps } from './types'; +const dismissMenu = () => { + console.log('dismissMenu Not implemented'); +}; const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => void }>) => { const menuRef = useRef(null); const { @@ -27,16 +30,13 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => ); useEffect(() => { - if (ref && menuRef.current) { - (ref as React.MutableRefObject<{ dismissMenu?: () => void }>).current.dismissMenu = dismissMenu; + // @ts-ignore: fix later + if (ref && ref.current) { + // @ts-ignore: fix later + ref.current.dismissMenu = dismissMenu; } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ref]); - const dismissMenu = useCallback(() => { - console.log('dismissMenu Not implemented'); - }, []); - const menuItems = useMemo(() => { const menu: { id: string; label: string }[] = []; actions.forEach(action => { @@ -52,7 +52,9 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<{ dismissMenu?: () => }, [actions]); const showMenu = useCallback(() => { - showPopupMenu(menuItems, handleToolTipSelection, menuRef.current); + if (menuRef.current) { + showPopupMenu(menuItems, handleToolTipSelection, menuRef.current); + } }, [menuItems, handleToolTipSelection]); return ( diff --git a/components/TooltipMenu.ios.tsx b/components/TooltipMenu.ios.tsx index 89ed6bc1b..44eacedae 100644 --- a/components/TooltipMenu.ios.tsx +++ b/components/TooltipMenu.ios.tsx @@ -106,12 +106,12 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref) => { ); - return isButton ? ( + return isMenuPrimaryAction && onPress ? ( {renderContextMenuButton()} - ) : props.onPress ? ( - renderContextMenuView() + ) : isButton ? ( + renderContextMenuButton() ) : ( renderContextMenuView() );