Merge pull request #6711 from BlueWallet/pop

REF: Use menu package since showPopmenu is being removed
This commit is contained in:
GLaDOS 2024-06-19 19:43:32 +00:00 committed by GitHub
commit 456582a282
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 351 additions and 459 deletions

View File

@ -1,32 +0,0 @@
// @ts-ignore: Ignore
import type { Element } from 'react';
import { findNodeHandle, Text, TouchableNativeFeedback, TouchableWithoutFeedback, UIManager, View } from 'react-native';
type PopupMenuItem = { id?: any; label: string };
type OnPopupMenuItemSelect = (selectedPopupMenuItem: PopupMenuItem) => void;
type PopupAnchor = Element<typeof Text | typeof TouchableNativeFeedback | typeof TouchableWithoutFeedback | typeof View>;
type PopupMenuOptions = { onCancel?: () => void };
function showPopupMenu(
items: PopupMenuItem[],
onSelect: OnPopupMenuItemSelect,
anchor: PopupAnchor,
{ onCancel }: PopupMenuOptions = {},
): void {
UIManager.showPopupMenu(
// @ts-ignore: Ignore
findNodeHandle(anchor),
items.map(item => item.label),
function () {
if (onCancel) onCancel();
},
function (eventName: 'dismissed' | 'itemSelected', selectedIndex?: number) {
// @ts-ignore: Ignore
if (eventName === 'itemSelected') onSelect(items[selectedIndex]);
else onCancel && onCancel();
},
);
}
export type { OnPopupMenuItemSelect, PopupMenuItem, PopupMenuOptions };
export default showPopupMenu;

View File

@ -1,13 +1,13 @@
import React from 'react';
import { Image, Keyboard, StyleSheet, Text, TextInput, View } from 'react-native';
import React, { useCallback, useMemo } from 'react';
import { Image, Keyboard, Platform, StyleSheet, Text, TextInput, View } from 'react-native';
import { scanQrHelper } from '../helpers/scan-qr';
import loc from '../loc';
import { useTheme } from './themes';
import ToolTipMenu from './TooltipMenu';
import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs';
import Clipboard from '@react-native-clipboard/clipboard';
import presentAlert from './Alert';
import ToolTipMenu from './TooltipMenu';
interface AddressInputProps {
isLoading?: boolean;
@ -69,52 +69,63 @@ const AddressInput = ({
Keyboard.dismiss();
};
const onMenuItemPressed = (action: string) => {
if (onBarScanned === undefined) throw new Error('onBarScanned is required');
switch (action) {
case actionKeys.ScanQR:
scanButtonTapped();
if (launchedBy) {
scanQrHelper(launchedBy)
.then(value => onBarScanned({ data: value }))
const toolTipOnPress = useCallback(async () => {
await scanButtonTapped();
Keyboard.dismiss();
if (launchedBy) scanQrHelper(launchedBy).then(value => onBarScanned({ data: value }));
}, [launchedBy, onBarScanned, scanButtonTapped]);
const onMenuItemPressed = useCallback(
(action: string) => {
if (onBarScanned === undefined) throw new Error('onBarScanned is required');
switch (action) {
case actionKeys.ScanQR:
scanButtonTapped();
if (launchedBy) {
scanQrHelper(launchedBy)
.then(value => onBarScanned({ data: value }))
.catch(error => {
presentAlert({ message: error.message });
});
}
break;
case actionKeys.CopyFromClipboard:
Clipboard.getString()
.then(onChangeText)
.catch(error => {
presentAlert({ message: error.message });
});
}
break;
case actionKeys.ChoosePhoto:
showImagePickerAndReadImage()
.then(value => {
if (value) {
onChangeText(value);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
case actionKeys.ImportFile:
showFilePickerAndReadFile()
.then(value => {
if (value.data) {
onChangeText(value.data);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
}
Keyboard.dismiss();
},
[launchedBy, onBarScanned, onChangeText, scanButtonTapped],
);
break;
case actionKeys.CopyFromClipboard:
Clipboard.getString()
.then(onChangeText)
.catch(error => {
presentAlert({ message: error.message });
});
break;
case actionKeys.ChoosePhoto:
showImagePickerAndReadImage()
.then(value => {
if (value) {
onChangeText(value);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
case actionKeys.ImportFile:
showFilePickerAndReadFile()
.then(value => {
if (value.data) {
onChangeText(value.data);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
}
Keyboard.dismiss();
};
const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]);
return (
<View style={[styles.root, stylesHook.root]}>
@ -141,12 +152,8 @@ const AddressInput = ({
onPressMenuItem={onMenuItemPressed}
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={async () => {
await scanButtonTapped();
Keyboard.dismiss();
if (launchedBy) scanQrHelper(launchedBy).then(value => onBarScanned({ data: value }));
}}
style={[styles.scan, stylesHook.scan]}
onPress={toolTipOnPress}
buttonStyle={buttonStyle}
accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint}
>
@ -202,20 +209,16 @@ const actionKeys = {
const actionIcons = {
ScanQR: {
iconType: 'SYSTEM',
iconValue: 'qrcode',
iconValue: Platform.OS === 'ios' ? 'qrcode' : 'ic_menu_camera',
},
ImportFile: {
iconType: 'SYSTEM',
iconValue: 'doc',
},
ChoosePhoto: {
iconType: 'SYSTEM',
iconValue: 'photo',
iconValue: Platform.OS === 'ios' ? 'photo' : 'ic_menu_gallery',
},
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
iconValue: Platform.OS === 'ios' ? 'doc' : 'ic_menu_file',
},
};

View File

@ -1,5 +1,5 @@
import Clipboard from '@react-native-clipboard/clipboard';
import React, { useRef } from 'react';
import React, { useCallback, useRef } from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import Share from 'react-native-share';
@ -22,11 +22,9 @@ interface QRCodeComponentProps {
const actionIcons: { [key: string]: ActionIcons } = {
Share: {
iconType: 'SYSTEM',
iconValue: 'square.and.arrow.up',
},
Copy: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
};
@ -76,13 +74,13 @@ const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
});
};
const onPressMenuItem = (id: string) => {
const onPressMenuItem = useCallback((id: string) => {
if (id === actionKeys.Share) {
handleShareQRCode();
} else if (id === actionKeys.Copy) {
qrCode.current.toDataURL(Clipboard.setImage);
}
};
}, []);
const renderQRCode = (
<QRCode

View File

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, useCallback } from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import * as fs from '../blue_modules/fs';
@ -28,22 +28,25 @@ const SaveFileButton: React.FC<SaveFileButtonProps> = ({
onMenuWillHide,
onMenuWillShow,
}) => {
const handlePressMenuItem = async (actionId: string) => {
if (beforeOnPress) {
await beforeOnPress();
}
const action = actions.find(a => a.id === actionId);
const handlePressMenuItem = useCallback(
async (actionId: string) => {
if (beforeOnPress) {
await beforeOnPress();
}
const action = actions.find(a => a.id === actionId);
if (action?.id === 'save') {
await fs.writeFileAndExport(fileName, fileContent, false).finally(() => {
afterOnPress?.();
});
} else if (action?.id === 'share') {
await fs.writeFileAndExport(fileName, fileContent, true).finally(() => {
afterOnPress?.();
});
}
};
if (action?.id === 'save') {
await fs.writeFileAndExport(fileName, fileContent, false).finally(() => {
afterOnPress?.();
});
} else if (action?.id === 'share') {
await fs.writeFileAndExport(fileName, fileContent, true).finally(() => {
afterOnPress?.();
});
}
},
[afterOnPress, beforeOnPress, fileContent, fileName],
);
return (
<ToolTipMenu
@ -64,11 +67,9 @@ export default SaveFileButton;
const actionIcons: { [key: string]: ActionIcons } = {
Share: {
iconType: 'SYSTEM',
iconValue: 'square.and.arrow.up',
},
Save: {
iconType: 'SYSTEM',
iconValue: 'square.and.arrow.down',
},
};

View File

@ -1,77 +0,0 @@
import React, { forwardRef, Ref, useCallback, useEffect, useMemo, useRef } from 'react';
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<View>(null);
const {
actions,
children,
onPressMenuItem,
isMenuPrimaryAction = false,
buttonStyle = {},
enableAndroidRipple = true,
disabled = false,
onPress,
...restProps
} = props;
const handleToolTipSelection = useCallback<OnPopupMenuItemSelect>(
(selection: PopupMenuItem) => {
if (selection.id) {
onPressMenuItem(selection.id);
}
},
[onPressMenuItem],
);
useEffect(() => {
// @ts-ignore: fix later
if (ref && ref.current) {
// @ts-ignore: fix later
ref.current.dismissMenu = dismissMenu;
}
}, [ref]);
const menuItems = useMemo(() => {
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 });
}
});
return menu;
}, [actions]);
const showMenu = useCallback(() => {
if (menuRef.current) {
showPopupMenu(menuItems, handleToolTipSelection, menuRef.current);
}
}, [menuItems, handleToolTipSelection]);
return (
<Pressable
{...(enableAndroidRipple ? { android_ripple: { color: 'lightgrey' } } : {})}
ref={menuRef}
disabled={disabled}
style={buttonStyle}
{...(isMenuPrimaryAction ? { onPress: showMenu } : { onPress, onLongPress: showMenu })}
{...restProps}
>
{children}
</Pressable>
);
};
const ToolTipMenu = forwardRef(BaseToolTipMenu);
export default ToolTipMenu;

View File

@ -1,128 +0,0 @@
import React, { forwardRef, Ref, useCallback, useMemo } from 'react';
import { TouchableOpacity } from 'react-native';
import { ContextMenuButton, ContextMenuView, RenderItem } from 'react-native-ios-context-menu';
import { Action, ToolTipMenuProps } from './types';
const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
const {
title = '',
isButton = false,
isMenuPrimaryAction = false,
renderPreview,
disabled = false,
onPress,
onMenuWillShow,
onMenuWillHide,
buttonStyle,
onPressMenuItem,
...restProps
} = props;
const menuItemMapped = useCallback(({ action, menuOptions }: { action: Action; menuOptions?: string[] }) => {
const item: any = {
actionKey: action.id.toString(),
actionTitle: action.text,
icon: action.icon,
menuOptions,
menuTitle: action.menuTitle,
};
item.menuState = action.menuStateOn ? 'on' : 'off';
if (action.disabled) {
item.menuAttributes = ['disabled'];
}
return item;
}, []);
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 handlePressMenuItem = useCallback(
({ nativeEvent }: { nativeEvent: { actionKey: string } }) => {
onPressMenuItem(nativeEvent.actionKey);
},
[onPressMenuItem],
);
const renderContextMenuButton = () => (
<ContextMenuButton
ref={ref}
onMenuWillShow={onMenuWillShow}
onMenuWillHide={onMenuWillHide}
useActionSheetFallback={false}
onPressMenuItem={handlePressMenuItem}
isMenuPrimaryAction={isMenuPrimaryAction}
menuConfig={{
menuTitle: title,
menuItems,
}}
{...restProps}
style={buttonStyle}
>
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" {...restProps}>
{props.children}
</TouchableOpacity>
</ContextMenuButton>
);
const renderContextMenuView = () => (
<ContextMenuView
ref={ref}
lazyPreview
shouldEnableAggressiveCleanup
internalCleanupMode="automatic"
onPressMenuItem={handlePressMenuItem}
useActionSheetFallback={false}
menuConfig={{
menuTitle: title,
menuItems,
}}
{...(renderPreview
? {
previewConfig: {
previewType: 'CUSTOM',
backgroundColor: 'white',
},
renderPreview: renderPreview as RenderItem,
}
: {})}
>
{onPress ? (
<TouchableOpacity accessibilityRole="button" onPress={onPress} {...restProps}>
{props.children}
</TouchableOpacity>
) : (
props.children
)}
</ContextMenuView>
);
return isMenuPrimaryAction && onPress ? (
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" {...restProps}>
{renderContextMenuButton()}
</TouchableOpacity>
) : isButton ? (
renderContextMenuButton()
) : (
renderContextMenuView()
);
};
const ToolTipMenu = forwardRef(BaseToolTipMenu);
export default ToolTipMenu;

View File

@ -1,12 +1,144 @@
import { forwardRef, Ref } from 'react';
import React, { Ref, useCallback, useMemo } from 'react';
import { Platform, Pressable, TouchableOpacity, View } from 'react-native';
import { ContextMenuView, RenderItem, OnPressMenuItemEventObject, MenuState, IconConfig } from 'react-native-ios-context-menu';
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
import { ToolTipMenuProps, Action } from './types';
import { ToolTipMenuProps } from './types';
const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
const {
title = '',
isMenuPrimaryAction = false,
renderPreview,
disabled = false,
onPress,
onMenuWillShow,
onMenuWillHide,
buttonStyle,
onPressMenuItem,
children,
isButton = false,
...restProps
} = props;
const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
console.debug('ToolTipMenu.tsx ref:', ref);
return props.children;
};
const mapMenuItemForContextMenuView = useCallback((action: Action) => {
return {
actionKey: action.id.toString(),
actionTitle: action.text,
icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined,
state: action.menuStateOn ? ('on' as MenuState) : ('off' as MenuState),
attributes: action.disabled ? ['disabled'] : [],
};
}, []);
const ToolTipMenu = forwardRef(BaseToolTipMenu);
const mapMenuItemForMenuView = useCallback((action: Action): MenuAction => {
return {
id: action.id.toString(),
title: action.text,
image: action.menuStateOn && Platform.OS === 'android' ? 'checkbox_on_background' : action.icon?.iconValue || undefined,
state: action.menuStateOn ? ('on' as MenuState) : undefined,
attributes: { disabled: action.disabled },
};
}, []);
const contextMenuItems = useMemo(() => {
const flattenedActions = props.actions.flat();
return flattenedActions.map(mapMenuItemForContextMenuView);
}, [props.actions, mapMenuItemForContextMenuView]);
const menuViewItemsIOS = useMemo(() => {
return props.actions.map(actionGroup => {
if (Array.isArray(actionGroup)) {
return {
id: actionGroup[0].id.toString(),
title: '',
subactions: actionGroup.map(mapMenuItemForMenuView),
displayInline: true,
};
} else {
return mapMenuItemForMenuView(actionGroup);
}
});
}, [props.actions, mapMenuItemForMenuView]);
const menuViewItemsAndroid = useMemo(() => {
const mergedActions = props.actions.flat();
return mergedActions.map(mapMenuItemForMenuView);
}, [props.actions, mapMenuItemForMenuView]);
const handlePressMenuItemForContextMenuView = useCallback(
(event: OnPressMenuItemEventObject) => {
onPressMenuItem(event.nativeEvent.actionKey);
},
[onPressMenuItem],
);
const handlePressMenuItemForMenuView = useCallback(
({ nativeEvent }: NativeActionEvent) => {
onPressMenuItem(nativeEvent.event);
},
[onPressMenuItem],
);
const renderContextMenuView = () => {
console.debug('ToolTipMenu.tsx rendering: renderContextMenuView');
return (
<ContextMenuView
lazyPreview
shouldEnableAggressiveCleanup
internalCleanupMode="automatic"
onPressMenuItem={handlePressMenuItemForContextMenuView}
onMenuWillShow={onMenuWillShow}
onMenuWillHide={onMenuWillHide}
useActionSheetFallback={false}
menuConfig={{
menuTitle: title,
menuItems: contextMenuItems,
}}
{...(renderPreview
? {
previewConfig: {
previewType: 'CUSTOM',
backgroundColor: 'white',
},
renderPreview: renderPreview as RenderItem,
}
: {})}
>
{onPress ? (
<Pressable accessibilityRole="button" onPress={onPress} {...restProps}>
{children}
</Pressable>
) : (
children
)}
</ContextMenuView>
);
};
const renderMenuView = () => {
console.debug('ToolTipMenu.tsx rendering: renderMenuView');
return (
<View>
<MenuView
title={title}
isAnchoredToRight
onPressAction={handlePressMenuItemForMenuView}
actions={Platform.OS === 'ios' ? menuViewItemsIOS : menuViewItemsAndroid}
shouldOpenOnLongPress={!isMenuPrimaryAction}
>
{isMenuPrimaryAction || isButton ? (
<TouchableOpacity style={buttonStyle} disabled={disabled} onPress={onPress} {...restProps}>
{children}
</TouchableOpacity>
) : (
children
)}
</MenuView>
</View>
);
};
return Platform.OS === 'ios' && renderPreview ? renderContextMenuView() : renderMenuView();
});
export default ToolTipMenu;

View File

@ -16,12 +16,12 @@ import { BitcoinUnit } from '../models/bitcoinUnits';
import { useSettings } from '../hooks/context/useSettings';
import ListItem from './ListItem';
import { useTheme } from './themes';
import ToolTipMenu from './TooltipMenu';
import { Action, ToolTipMenuProps } from './types';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { DetailViewStackParamList } from '../navigation/DetailViewStackParamList';
import { useStorage } from '../hooks/context/useStorage';
import ToolTipMenu from './TooltipMenu';
interface TransactionListItemProps {
itemPriceUnit: BitcoinUnit;
@ -339,7 +339,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
return (
<ToolTipMenu ref={menuRef} actions={toolTipActions} onPressMenuItem={onToolTipPress} onPress={onPress}>
<ToolTipMenu isButton actions={toolTipActions} onPressMenuItem={onToolTipPress} onPress={onPress}>
<ListItem
leftAvatar={avatar}
title={title}
@ -367,23 +367,18 @@ const actionKeys = {
const actionIcons = {
Eye: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
EyeSlash: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
Link: {
iconType: 'SYSTEM',
iconValue: 'link',
},
Note: {
iconType: 'SYSTEM',
iconValue: 'note.text',
},
};

View File

@ -1,8 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import { I18nManager, Image, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import WalletGradient from '../class/wallet-gradient';
import { TWallet } from '../class/wallets/types';
@ -11,8 +10,8 @@ import { BitcoinUnit } from '../models/bitcoinUnits';
import { FiatUnit } from '../models/fiatUnit';
import { BlurredBalanceView } from './BlurredBalanceView';
import { useSettings } from '../hooks/context/useSettings';
import ToolTipMenu from './TooltipMenu';
import { ToolTipMenuProps } from './types';
import ToolTipMenu from './TooltipMenu';
interface TransactionsNavigationHeaderProps {
wallet: TWallet;
@ -64,16 +63,16 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
verifyIfWalletAllowsOnchainAddress();
}, [wallet, verifyIfWalletAllowsOnchainAddress]);
const handleCopyPress = () => {
const handleCopyPress = useCallback(() => {
const value = formatBalance(wallet.getBalance(), wallet.getPreferredBalanceUnit());
if (value) {
Clipboard.setString(value);
}
};
}, [wallet]);
const handleBalanceVisibility = () => {
const handleBalanceVisibility = useCallback(() => {
onWalletBalanceVisibilityChange?.(!wallet.hideBalance);
};
}, [onWalletBalanceVisibilityChange, wallet.hideBalance]);
const updateWalletWithNewUnit = (w: TWallet, newPreferredUnit: BitcoinUnit) => {
w.preferredBalanceUnit = newPreferredUnit;
@ -101,19 +100,40 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
onWalletUnitChange?.(updatedWallet);
};
const handleManageFundsPressed = (actionKeyID?: string) => {
if (onManageFundsPressed) {
onManageFundsPressed(actionKeyID);
}
};
const handleManageFundsPressed = useCallback(
(actionKeyID?: string) => {
if (onManageFundsPressed) {
onManageFundsPressed(actionKeyID);
}
},
[onManageFundsPressed],
);
const onPressMenuItem = (id: string) => {
if (id === 'walletBalanceVisibility') {
handleBalanceVisibility();
} else if (id === 'copyToClipboard') {
handleCopyPress();
}
};
const onPressMenuItem = useCallback(
(id: string) => {
if (id === 'walletBalanceVisibility') {
handleBalanceVisibility();
} else if (id === 'copyToClipboard') {
handleCopyPress();
}
},
[handleBalanceVisibility, handleCopyPress],
);
const toolTipActions = useMemo(() => {
return [
{
id: actionKeys.Refill,
text: loc.lnd.refill,
icon: actionIcons.Refill,
},
{
id: actionKeys.RefillWithExternalWallet,
text: loc.lnd.refill_external,
icon: actionIcons.RefillWithExternalWallet,
},
];
}, []);
const balance = useMemo(() => {
const hideBalance = wallet.hideBalance;
@ -126,6 +146,35 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.hideBalance, wallet.getPreferredBalanceUnit()]);
const toolTipWalletBalanceActions = useMemo(() => {
return wallet.hideBalance
? [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
icon: {
iconValue: 'eye',
},
},
]
: [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
icon: {
iconValue: 'eye.slash',
},
},
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
icon: {
iconValue: 'doc.on.doc',
},
},
];
}, [wallet.hideBalance]);
return (
<LinearGradient
colors={WalletGradient.gradientsFor(wallet.type)}
@ -155,40 +204,9 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
isMenuPrimaryAction
isButton
enableAndroidRipple={false}
ref={menuRef}
buttonStyle={styles.walletBalance}
onPressMenuItem={onPressMenuItem}
actions={
wallet.hideBalance
? [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
},
]
: [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
},
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
icon: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
},
]
}
actions={toolTipWalletBalanceActions}
>
<View style={styles.walletBalance}>
{wallet.hideBalance ? (
@ -223,18 +241,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
isMenuPrimaryAction
isButton
onPressMenuItem={handleManageFundsPressed}
actions={[
{
id: actionKeys.Refill,
text: loc.lnd.refill,
icon: actionIcons.Refill,
},
{
id: actionKeys.RefillWithExternalWallet,
text: loc.lnd.refill_external,
icon: actionIcons.RefillWithExternalWallet,
},
]}
actions={toolTipActions}
buttonStyle={styles.manageFundsButton}
>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
@ -333,23 +340,18 @@ export const actionKeys = {
export const actionIcons = {
Eye: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
EyeSlash: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
Refill: {
iconType: 'SYSTEM',
iconValue: 'goforward.plus',
},
RefillWithExternalWallet: {
iconType: 'SYSTEM',
iconValue: 'qrcode',
},
};

View File

@ -1,4 +1,4 @@
import React, { useMemo, useRef } from 'react';
import React, { useMemo } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, View } from 'react-native';
@ -12,12 +12,12 @@ import { BitcoinUnit } from '../../models/bitcoinUnits';
import presentAlert from '../Alert';
import QRCodeComponent from '../QRCodeComponent';
import { useTheme } from '../themes';
import TooltipMenu from '../TooltipMenu';
import { Action, ToolTipMenuProps } from '../types';
import { Action } from '../types';
import { AddressTypeBadge } from './AddressTypeBadge';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { useStorage } from '../../hooks/context/useStorage';
import ToolTipMenu from '../TooltipMenu';
interface AddressItemProps {
// todo: fix `any` after addresses.js is converted to the church of holy typescript
@ -57,16 +57,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
const { navigate } = useNavigation<NavigationProps>();
const menuRef = useRef<ToolTipMenuProps>();
const dismissMenu = () => {
if (menuRef.current?.dismissMenu) {
menuRef.current.dismissMenu();
}
};
const navigateToReceive = () => {
dismissMenu();
navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails',
params: {
@ -77,7 +68,6 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
};
const navigateToSignVerify = () => {
dismissMenu();
navigate('SignVerifyRoot', {
screen: 'SignVerify',
params: {
@ -145,13 +135,13 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
const render = () => {
return (
<TooltipMenu
<ToolTipMenu
title={item.address}
ref={menuRef}
actions={menuActions}
onPressMenuItem={onToolTipPress}
renderPreview={renderPreview}
onPress={navigateToReceive}
isButton
>
<ListItem key={item.key} containerStyle={stylesHook.container}>
<ListItem.Content style={stylesHook.list}>
@ -170,7 +160,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
</Text>
</View>
</ListItem>
</TooltipMenu>
</ToolTipMenu>
);
};
@ -186,19 +176,15 @@ const actionKeys = {
const actionIcons = {
Signature: {
iconType: 'SYSTEM',
iconValue: 'signature',
},
Share: {
iconType: 'SYSTEM',
iconValue: 'square.and.arrow.up',
},
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
ExportPrivateKey: {
iconType: 'SYSTEM',
iconValue: 'key',
},
};

View File

@ -3,13 +3,13 @@ import { AccessibilityRole, ViewStyle } from 'react-native';
export interface Action {
id: string | number;
text: string;
icon: {
iconType: string;
icon?: {
iconValue: string;
};
menuTitle?: string;
menuStateOn?: boolean;
disabled?: boolean;
displayInline?: boolean;
}
export interface ToolTipMenuProps {
@ -30,7 +30,7 @@ export interface ToolTipMenuProps {
style?: ViewStyle | ViewStyle[];
accessibilityLabel?: string;
accessibilityHint?: string;
buttonStyle?: ViewStyle;
buttonStyle?: ViewStyle | ViewStyle[];
onMenuWillShow?: () => void;
onMenuWillHide?: () => void;
}

View File

@ -169,7 +169,7 @@
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
C59F90CE0D04D3E4BB39BC5D /* libPods-BlueWalletUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F02C2F7CA3591E4E0B06EBA /* libPods-BlueWalletUITests.a */; };
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -491,7 +491,7 @@
files = (
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */,
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1156,9 +1156,9 @@
);
mainGroup = 83CBB9F61A601CBA00E9B192;
packageReferences = (
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */,
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */,
B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */,
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */,
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift.git" */,
);
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
@ -1839,7 +1839,7 @@
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
@ -1893,7 +1893,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
@ -2150,7 +2150,7 @@
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@ -2208,7 +2208,7 @@
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -2352,7 +2352,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
};
@ -2399,7 +2399,7 @@
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
};
@ -2547,7 +2547,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
VERSIONING_SYSTEM = "apple-generic";
};
@ -2595,7 +2595,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
VERSIONING_SYSTEM = "apple-generic";
};
@ -2679,7 +2679,7 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */ = {
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/EFPrefix/EFQRCode.git";
requirement = {
@ -2695,7 +2695,7 @@
version = 6.28.1;
};
};
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */ = {
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift.git" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/evgenyneu/keychain-swift.git";
requirement = {
@ -2708,7 +2708,7 @@
/* Begin XCSwiftPackageProductDependency section */
6DFC806F24EA0B6C007B8700 /* EFQRCode */ = {
isa = XCSwiftPackageProductDependency;
package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */;
package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */;
productName = EFQRCode;
};
B41B76842B66B2FF002C48D5 /* Bugsnag */ = {
@ -2723,7 +2723,7 @@
};
B48A6A282C1DF01000030AB9 /* KeychainSwift */ = {
isa = XCSwiftPackageProductDependency;
package = B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */;
package = B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift.git" */;
productName = KeychainSwift;
};
/* End XCSwiftPackageProductDependency section */

View File

@ -339,6 +339,8 @@ PODS:
- React-Core
- react-native-ios-context-menu (1.15.3):
- React-Core
- react-native-menu (1.1.2):
- React
- react-native-qrcode-local-image (1.0.4):
- React
- react-native-randombytes (3.6.1):
@ -551,6 +553,7 @@ DEPENDENCIES:
- react-native-idle-timer (from `../node_modules/react-native-idle-timer`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`)
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
- "react-native-qrcode-local-image (from `../node_modules/@remobile/react-native-qrcode-local-image`)"
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
@ -670,6 +673,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-image-picker"
react-native-ios-context-menu:
:path: "../node_modules/react-native-ios-context-menu"
react-native-menu:
:path: "../node_modules/@react-native-menu/menu"
react-native-qrcode-local-image:
:path: "../node_modules/@remobile/react-native-qrcode-local-image"
react-native-randombytes:
@ -801,6 +806,7 @@ SPEC CHECKSUMS:
react-native-idle-timer: ee2053f2cd458f6fef1db7bebe5098ca281cce07
react-native-image-picker: 1889c342e6a4ba089ff11ae0c3bf5cc30a3134d0
react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5
react-native-menu: d32728a357dfb360cf01cd5979cf7713c5acbb95
react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-safe-area-context: a240ad4b683349e48b1d51fed1611138d1bdad97

15
package-lock.json generated
View File

@ -19,6 +19,7 @@
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-clipboard/clipboard": "1.14.1",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-menu/menu": "1.1.2",
"@react-navigation/drawer": "6.6.15",
"@react-navigation/native": "6.1.17",
"@react-navigation/native-stack": "6.9.26",
@ -5402,6 +5403,15 @@
"react-native": ">=0.58.4"
}
},
"node_modules/@react-native-menu/menu": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@react-native-menu/menu/-/menu-1.1.2.tgz",
"integrity": "sha512-I7JWiwmIwRp+Tee2OJHPwzVKdLjzgUkdChHQ75oyEWo1YcVsXEjLVGmxWCTA6JExzS6SQW/ACmjuXLJFTvu9Pw==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.72.0",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",
@ -26621,6 +26631,11 @@
"invariant": "^2.2.4"
}
},
"@react-native-menu/menu": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@react-native-menu/menu/-/menu-1.1.2.tgz",
"integrity": "sha512-I7JWiwmIwRp+Tee2OJHPwzVKdLjzgUkdChHQ75oyEWo1YcVsXEjLVGmxWCTA6JExzS6SQW/ACmjuXLJFTvu9Pw=="
},
"@react-native/assets-registry": {
"version": "0.72.0",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",

View File

@ -104,6 +104,7 @@
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-clipboard/clipboard": "1.14.1",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-menu/menu": "1.1.2",
"@react-navigation/drawer": "6.6.15",
"@react-navigation/native": "6.1.17",
"@react-navigation/native-stack": "6.9.26",

View File

@ -271,8 +271,6 @@ const ReceiveDetails = () => {
const obtainWalletAddress = useCallback(async () => {
console.log('receive/details - componentDidMount');
wallet.setUserHasSavedExport(true);
await saveToDisk();
let newAddress;
if (address) {
setAddressBIP21Encoded(address);

View File

@ -57,6 +57,7 @@ import { isTablet } from '../../blue_modules/environment';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { ContactList } from '../../class/contact-list';
import { useStorage } from '../../hooks/context/useStorage';
import { Action } from '../../components/types';
interface IPaymentDestinations {
address: string; // btc address or payment code
@ -990,7 +991,7 @@ const SendDetails = () => {
};
const headerRightActions = () => {
const actions = [];
const actions: Action[] & Action[][] = [];
if (isEditable) {
if (wallet?.allowBIP47() && wallet?.isBIP47Enabled()) {
actions.push([
@ -1620,16 +1621,16 @@ SendDetails.actionKeys = {
};
SendDetails.actionIcons = {
InsertContact: { iconType: 'SYSTEM', iconValue: 'at.badge.plus' },
SignPSBT: { iconType: 'SYSTEM', iconValue: 'signature' },
InsertContact: { iconValue: 'at.badge.plus' },
SignPSBT: { iconValue: 'signature' },
SendMax: 'SendMax',
AddRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.plus' },
RemoveRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.minus' },
AddRecipient: { iconValue: 'person.badge.plus' },
RemoveRecipient: { 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: 'switch.2' },
ImportTransaction: { iconValue: 'square.and.arrow.down' },
ImportTransactionMultsig: { iconValue: 'square.and.arrow.down.on.square' },
ImportTransactionQR: { iconValue: 'qrcode.viewfinder' },
CoinControl: { iconValue: 'switch.2' },
};
const styles = StyleSheet.create({

View File

@ -32,11 +32,9 @@ const actionKeys = {
const actionIcons = {
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
GoToWallet: {
iconType: 'SYSTEM',
iconValue: 'wallet.pass',
},
};

View File

@ -42,7 +42,6 @@ const actionKeys: Action[] = [
id: Actions.pay,
text: loc.bip47.pay_this_contact,
icon: {
iconType: 'SYSTEM',
iconValue: 'paperplane',
},
},
@ -50,7 +49,6 @@ const actionKeys: Action[] = [
id: Actions.rename,
text: loc.bip47.rename_contact,
icon: {
iconType: 'SYSTEM',
iconValue: 'pencil',
},
},
@ -58,7 +56,6 @@ const actionKeys: Action[] = [
id: Actions.copyToClipboard,
text: loc.bip47.copy_payment_code,
icon: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
},
@ -66,7 +63,6 @@ const actionKeys: Action[] = [
id: Actions.hide,
text: loc.bip47.hide_contact,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
},

View File

@ -200,6 +200,4 @@ jest.mock('react-native-keychain', () => mockKeychain);
jest.mock('react-native-tcp-socket', () => mockKeychain);
jest.mock('../components/TooltipMenu.ios.tsx', () => require('../components/TooltipMenu.tsx'));
global.alert = () => {};

View File

@ -1,4 +1,3 @@
export interface ActionIcons {
iconType: 'SYSTEM';
iconValue: string;
}