mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-23 15:20:55 +01:00
Merge pull request #6584 from BlueWallet/tooltipreff
REF: ToolTipMenu to TSX
This commit is contained in:
commit
72439f36c1
15 changed files with 348 additions and 355 deletions
|
@ -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,22 @@ const actionIcons: { [key: string]: ActionIcons } = {
|
|||
},
|
||||
};
|
||||
|
||||
const actionKeys = {
|
||||
Share: 'share',
|
||||
Copy: 'copy',
|
||||
};
|
||||
|
||||
const menuActions: Action[] =
|
||||
Platform.OS === 'ios' || Platform.OS === 'macos'
|
||||
? [
|
||||
{
|
||||
id: actionKeys.Copy,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: actionIcons.Copy,
|
||||
},
|
||||
]
|
||||
: [{ id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share }];
|
||||
|
||||
const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
||||
value = '',
|
||||
isLogoRendered = true,
|
||||
|
@ -75,23 +76,6 @@ const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
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 = (
|
||||
<QRCode
|
||||
value={value}
|
||||
|
@ -115,7 +99,7 @@ const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
|
|||
accessibilityLabel={loc.receive.qrcode_for_the_address}
|
||||
>
|
||||
{isMenuAvailable ? (
|
||||
<ToolTipMenu actions={menuActions()} onPressMenuItem={onPressMenuItem}>
|
||||
<ToolTipMenu actions={menuActions} onPressMenuItem={onPressMenuItem}>
|
||||
{renderQRCode}
|
||||
</ToolTipMenu>
|
||||
) : (
|
||||
|
|
|
@ -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<ViewStyle>;
|
||||
afterOnPress?: () => void;
|
||||
beforeOnPress?: () => Promise<void>; // Changed this line
|
||||
beforeOnPress?: () => Promise<void>;
|
||||
onMenuWillHide?: () => void;
|
||||
onMenuWillShow?: () => void;
|
||||
}
|
||||
|
@ -26,30 +27,24 @@ const SaveFileButton: React.FC<SaveFileButtonProps> = ({
|
|||
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}
|
||||
<ToolTipMenu
|
||||
onMenuWillHide={onMenuWillHide}
|
||||
onMenuWillShow={onMenuWillShow}
|
||||
|
@ -57,7 +52,7 @@ const SaveFileButton: React.FC<SaveFileButtonProps> = ({
|
|||
isMenuPrimaryAction
|
||||
actions={actions}
|
||||
onPressMenuItem={handlePressMenuItem}
|
||||
buttonStyle={style}
|
||||
buttonStyle={style as ViewStyle} // Type assertion to match ViewStyle
|
||||
>
|
||||
{children}
|
||||
</ToolTipMenu>
|
||||
|
@ -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 },
|
||||
];
|
||||
|
|
|
@ -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 (
|
||||
<Pressable
|
||||
{...(enableAndroidRipple ? { android_ripple: { color: 'lightgrey' } } : {})}
|
||||
ref={menuRef}
|
||||
disabled={disabled}
|
||||
style={buttonStyle}
|
||||
{...(isMenuPrimaryAction ? { onPress: showMenu } : { onPress: props.onPress, onLongPress: showMenu })}
|
||||
>
|
||||
{props.children}
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
76
components/TooltipMenu.android.tsx
Normal file
76
components/TooltipMenu.android.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
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';
|
||||
|
||||
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;
|
|
@ -1,140 +0,0 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import { ContextMenuView, ContextMenuButton } from 'react-native-ios-context-menu';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
|
||||
const BaseToolTipMenu = (props, ref) => {
|
||||
const menuItemMapped = ({ action, menuOptions }) => {
|
||||
const item = {
|
||||
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 = props.actions.map(action => {
|
||||
if (Array.isArray(action)) {
|
||||
const mapped = [];
|
||||
for (const actionToMap of action) {
|
||||
mapped.push(menuItemMapped({ action: actionToMap }));
|
||||
}
|
||||
const submenu = {
|
||||
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;
|
||||
return isButton ? (
|
||||
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" style={buttonStyle}>
|
||||
<ContextMenuButton
|
||||
ref={ref}
|
||||
onMenuWillShow={onMenuWillShow}
|
||||
onMenuWillHide={onMenuWillHide}
|
||||
useActionSheetFallback={false}
|
||||
onPressMenuItem={({ nativeEvent }) => {
|
||||
props.onPressMenuItem(nativeEvent.actionKey);
|
||||
}}
|
||||
isMenuPrimaryAction={isMenuPrimaryAction}
|
||||
menuConfig={{
|
||||
menuTitle,
|
||||
menuItems,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ContextMenuButton>
|
||||
</TouchableOpacity>
|
||||
) : props.onPress ? (
|
||||
<ContextMenuView
|
||||
ref={ref}
|
||||
lazyPreview
|
||||
shouldEnableAggressiveCleanup
|
||||
shouldCleanupOnComponentWillUnmountForMenuPreview
|
||||
internalCleanupMode="automatic"
|
||||
onPressMenuItem={({ nativeEvent }) => {
|
||||
props.onPressMenuItem(nativeEvent.actionKey);
|
||||
}}
|
||||
useActionSheetFallback={false}
|
||||
menuConfig={{
|
||||
menuTitle,
|
||||
menuItems,
|
||||
}}
|
||||
{...(renderPreview
|
||||
? {
|
||||
previewConfig: {
|
||||
previewType: 'CUSTOM',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
renderPreview,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<TouchableOpacity accessibilityRole="button" onPress={props.onPress}>
|
||||
{props.children}
|
||||
</TouchableOpacity>
|
||||
</ContextMenuView>
|
||||
) : (
|
||||
<ContextMenuView
|
||||
ref={ref}
|
||||
internalCleanupMode="viewController"
|
||||
onPressMenuItem={({ nativeEvent }) => {
|
||||
props.onPressMenuItem(nativeEvent.actionKey);
|
||||
}}
|
||||
lazyPreview
|
||||
shouldEnableAggressiveCleanup
|
||||
useActionSheetFallback={false}
|
||||
menuConfig={{
|
||||
menuTitle,
|
||||
menuItems,
|
||||
}}
|
||||
{...(renderPreview
|
||||
? {
|
||||
previewConfig: {
|
||||
previewType: 'CUSTOM',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
renderPreview,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{props.children}
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
122
components/TooltipMenu.ios.tsx
Normal file
122
components/TooltipMenu.ios.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
import React, { forwardRef, Ref, useMemo, useCallback } 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: ToolTipMenuProps, ref: Ref<any>) => {
|
||||
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,
|
||||
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,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</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}>
|
||||
{props.children}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</ContextMenuView>
|
||||
);
|
||||
|
||||
return isMenuPrimaryAction && onPress ? (
|
||||
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" style={buttonStyle}>
|
||||
{renderContextMenuButton()}
|
||||
</TouchableOpacity>
|
||||
) : isButton ? (
|
||||
renderContextMenuButton()
|
||||
) : (
|
||||
renderContextMenuView()
|
||||
);
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
|
@ -1,9 +0,0 @@
|
|||
import { forwardRef } from 'react';
|
||||
|
||||
const BaseToolTipMenu = (props, _ref) => {
|
||||
return props.children;
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
11
components/TooltipMenu.tsx
Normal file
11
components/TooltipMenu.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { forwardRef, Ref } from 'react';
|
||||
import { ToolTipMenuProps } from './types';
|
||||
|
||||
const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
|
||||
console.debug('ToolTipMenu.tsx ref:', ref);
|
||||
return props.children;
|
||||
};
|
||||
|
||||
const ToolTipMenu = forwardRef(BaseToolTipMenu);
|
||||
|
||||
export default ToolTipMenu;
|
|
@ -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<TransactionListItemProps> = 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<TransactionListItemProps> = React.mem
|
|||
icon: actionIcons.Clipboard,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.hash) {
|
||||
actions.push(
|
||||
{
|
||||
|
@ -337,10 +339,9 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
]);
|
||||
}
|
||||
|
||||
return actions;
|
||||
return actions as Action[] | Action[][];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ToolTipMenu ref={menuRef} actions={toolTipActions} onPressMenuItem={onToolTipPress} onPress={onPress}>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { 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';
|
||||
|
@ -16,6 +15,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
|
|||
import QRCodeComponent from '../QRCodeComponent';
|
||||
import confirm from '../../helpers/confirm';
|
||||
import { useBiometrics } from '../../hooks/useBiometrics';
|
||||
import { Action } from '../types';
|
||||
|
||||
interface AddressItemProps {
|
||||
// todo: fix `any` after addresses.js is converted to the church of holy typescript
|
||||
|
@ -80,6 +80,8 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
});
|
||||
};
|
||||
|
||||
const menuActions = useMemo(() => getAvailableActions({ allowSignVerifyMessage }), [allowSignVerifyMessage]);
|
||||
|
||||
const balance = formatBalance(item.balance, balanceUnit, true);
|
||||
|
||||
const handleCopyPress = () => {
|
||||
|
@ -130,39 +132,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 <QRCodeComponent value={item.address} isMenuAvailable={false} />;
|
||||
};
|
||||
|
@ -172,7 +141,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
<TooltipMenu
|
||||
title={item.address}
|
||||
ref={menuRef}
|
||||
actions={getAvailableActions()}
|
||||
actions={menuActions}
|
||||
onPressMenuItem={onToolTipPress}
|
||||
renderPreview={renderPreview}
|
||||
onPress={navigateToReceive}
|
||||
|
@ -247,15 +216,37 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
AddressItem.propTypes = {
|
||||
item: PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
index: PropTypes.number,
|
||||
address: PropTypes.string,
|
||||
isInternal: PropTypes.bool,
|
||||
transactions: PropTypes.number,
|
||||
balance: PropTypes.number,
|
||||
}),
|
||||
balanceUnit: PropTypes.string,
|
||||
const getAvailableActions = ({ allowSignVerifyMessage }: { allowSignVerifyMessage: boolean }): Action[] | Action[][] => {
|
||||
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 };
|
||||
|
|
30
components/types.ts
Normal file
30
components/types.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -47,7 +47,7 @@ export const useExtendedNavigation = (): NavigationProp<ParamListBase> => {
|
|||
const isAuthenticated = await 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
|
||||
|
|
|
@ -23,6 +23,22 @@ interface TransactionDetailsProps {
|
|||
navigation: NativeStackNavigationProp<any>;
|
||||
}
|
||||
|
||||
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<TransactionDetailsProps['route']>().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 = () => {
|
|||
)}
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
actions={[
|
||||
{
|
||||
id: TransactionDetails.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.copy_link,
|
||||
icon: TransactionDetails.actionIcons.Clipboard,
|
||||
},
|
||||
]}
|
||||
actions={toolTipMenuActions}
|
||||
onPressMenuItem={handleCopyPress}
|
||||
onPress={handleOnOpenTransactionOnBlockExplorerTapped}
|
||||
buttonStyle={[styles.greyButton, stylesHooks.greyButton]}
|
||||
buttonStyle={StyleSheet.flatten([styles.greyButton, stylesHooks.greyButton])}
|
||||
>
|
||||
<Text style={[styles.Link, stylesHooks.Link]}>{loc.transactions.details_show_in_block_explorer}</Text>
|
||||
</ToolTipMenu>
|
||||
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = () => {};
|
||||
|
|
Loading…
Add table
Reference in a new issue