mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 06:52:41 +01:00
FIX: Accessibility for Transaction rows
This commit is contained in:
parent
aeb4db00eb
commit
61ee3dfaba
4 changed files with 100 additions and 83 deletions
|
@ -1,5 +1,5 @@
|
|||
import React, { Ref, useCallback, useMemo } from 'react';
|
||||
import { Platform, Pressable, TouchableOpacity, View } from 'react-native';
|
||||
import { Platform, Pressable, TouchableOpacity } from 'react-native';
|
||||
import {
|
||||
ContextMenuView,
|
||||
RenderItem,
|
||||
|
@ -10,6 +10,7 @@ import {
|
|||
} from 'react-native-ios-context-menu';
|
||||
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
|
||||
import { ToolTipMenuProps, Action } from './types';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
|
||||
const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
||||
const {
|
||||
|
@ -27,6 +28,8 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
...restProps
|
||||
} = props;
|
||||
|
||||
const { language } = useSettings();
|
||||
|
||||
const mapMenuItemForContextMenuView = useCallback((action: Action) => {
|
||||
if (!action.id) return null;
|
||||
return {
|
||||
|
@ -99,6 +102,11 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
return (
|
||||
<ContextMenuView
|
||||
lazyPreview
|
||||
accessibilityLabel={props.accessibilityLabel}
|
||||
accessibilityHint={props.accessibilityHint}
|
||||
accessibilityRole={props.accessibilityRole}
|
||||
accessibilityState={props.accessibilityState}
|
||||
accessibilityLanguage={language}
|
||||
shouldEnableAggressiveCleanup
|
||||
internalCleanupMode="automatic"
|
||||
onPressMenuItem={handlePressMenuItemForContextMenuView}
|
||||
|
@ -133,23 +141,26 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||
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>
|
||||
<MenuView
|
||||
title={title}
|
||||
isAnchoredToRight
|
||||
onPressAction={handlePressMenuItemForMenuView}
|
||||
actions={Platform.OS === 'ios' ? menuViewItemsIOS : menuViewItemsAndroid}
|
||||
shouldOpenOnLongPress={!isMenuPrimaryAction}
|
||||
// @ts-ignore: its not in the types but it works
|
||||
accessibilityLabel={props.accessibilityLabel}
|
||||
accessibilityHint={props.accessibilityHint}
|
||||
accessibilityRole={props.accessibilityRole}
|
||||
accessibilityLanguage={language}
|
||||
>
|
||||
{isMenuPrimaryAction || isButton ? (
|
||||
<TouchableOpacity style={buttonStyle} disabled={disabled} onPress={onPress} {...restProps}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</MenuView>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { Linking, StyleSheet, View } from 'react-native';
|
||||
import { Linking, View } from 'react-native';
|
||||
import Lnurl from '../class/lnurl';
|
||||
import { LightningTransaction, Transaction } from '../class/wallets/types';
|
||||
import TransactionExpiredIcon from '../components/icons/TransactionExpiredIcon';
|
||||
|
@ -37,7 +37,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
const { navigate } = useExtendedNavigation<NavigationProps>();
|
||||
const menuRef = useRef<ToolTipMenuProps>();
|
||||
const { txMetadata, counterpartyMetadata, wallets } = useStorage();
|
||||
const { preferredFiatCurrency, language } = useSettings();
|
||||
const { language } = useSettings();
|
||||
const containerStyle = useMemo(
|
||||
() => ({
|
||||
backgroundColor: 'transparent',
|
||||
|
@ -94,8 +94,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
} else {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, itemPriceUnit, preferredFiatCurrency]);
|
||||
}, [item, itemPriceUnit]);
|
||||
|
||||
const rowTitleStyle = useMemo(() => {
|
||||
let color = colors.successColor;
|
||||
|
@ -126,73 +125,70 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
};
|
||||
}, [item, colors.foregroundColor, colors.successColor]);
|
||||
|
||||
const avatar = useMemo(() => {
|
||||
// is it lightning refill tx?
|
||||
const determineTransactionTypeAndAvatar = () => {
|
||||
if (item.category === 'receive' && item.confirmations! < 3) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.pending_transaction,
|
||||
icon: <TransactionPendingIcon />,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type && item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.onchain,
|
||||
icon: <TransactionOnchainIcon />,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.offchain,
|
||||
icon: <TransactionOffchainIcon />,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (!item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise
|
||||
const invoiceExpiration = item.timestamp! + item.expire_time!;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise
|
||||
const invoiceExpiration = item.timestamp! + item.expire_time!;
|
||||
if (!item.ispaid && invoiceExpiration < now) {
|
||||
return {
|
||||
label: loc.transactions.expired_transaction,
|
||||
icon: <TransactionExpiredIcon />,
|
||||
};
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.incoming_transaction,
|
||||
icon: <TransactionOffchainIncomingIcon />,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.confirmations) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.pending_transaction,
|
||||
icon: <TransactionPendingIcon />,
|
||||
};
|
||||
} else if (item.value! < 0) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.outgoing_transaction,
|
||||
icon: <TransactionOutgoingIcon />,
|
||||
};
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<TransactionIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
return {
|
||||
label: loc.transactions.incoming_transaction,
|
||||
icon: <TransactionIncomingIcon />,
|
||||
};
|
||||
}
|
||||
}, [item]);
|
||||
};
|
||||
|
||||
const { label: transactionTypeLabel, icon: avatar } = determineTransactionTypeAndAvatar();
|
||||
|
||||
const amountWithUnit = useMemo(() => {
|
||||
const amount = formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
const unit = itemPriceUnit === BitcoinUnit.BTC || itemPriceUnit === BitcoinUnit.SATS ? ` ${itemPriceUnit}` : ' ';
|
||||
return `${amount}${unit}`;
|
||||
}, [item.value, itemPriceUnit]);
|
||||
|
||||
useEffect(() => {
|
||||
setSubtitleNumberOfLines(1);
|
||||
|
@ -234,13 +230,11 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
});
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, wallets]);
|
||||
}, [item, wallets, navigate, walletID]);
|
||||
|
||||
const handleOnExpandNote = useCallback(() => {
|
||||
setSubtitleNumberOfLines(0);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subtitle]);
|
||||
}, []);
|
||||
|
||||
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
|
||||
|
||||
|
@ -336,10 +330,18 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
|||
}
|
||||
|
||||
return actions as Action[] | Action[][];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines]);
|
||||
|
||||
return (
|
||||
<ToolTipMenu isButton actions={toolTipActions} onPressMenuItem={onToolTipPress} onPress={onPress}>
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
actions={toolTipActions}
|
||||
onPressMenuItem={onToolTipPress}
|
||||
onPress={onPress}
|
||||
accessibilityLabel={`${transactionTypeLabel}, ${amountWithUnit}, ${subtitle ?? title}`}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ expanded: subtitleNumberOfLines === 0 }}
|
||||
>
|
||||
<ListItem
|
||||
leftAvatar={avatar}
|
||||
title={title}
|
||||
|
@ -382,7 +384,3 @@ const actionIcons = {
|
|||
iconValue: 'note.text',
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
iconWidth: { width: 25 },
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ export interface ToolTipMenuProps {
|
|||
style?: ViewStyle | ViewStyle[];
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
accessibilityState?: object;
|
||||
buttonStyle?: ViewStyle | ViewStyle[];
|
||||
onMenuWillShow?: () => void;
|
||||
onMenuWillHide?: () => void;
|
||||
|
|
|
@ -362,6 +362,12 @@
|
|||
"transaction_saved": "Saved",
|
||||
"details_show_in_block_explorer": "View in Block Explorer",
|
||||
"details_title": "Transaction",
|
||||
"incoming_transaction": "Incoming Transaction",
|
||||
"outgoing_transaction": "Outgoing Transaction",
|
||||
"expired_transaction": "Expired Transaction",
|
||||
"pending_transaction": "Pending Transaction",
|
||||
"offchain": "Offchain",
|
||||
"onchain": "Onchain",
|
||||
"details_to": "Output",
|
||||
"enable_offline_signing": "This wallet is not being used in conjunction with an offline signing. Would you wish to enable it now?",
|
||||
"list_conf": "Conf: {number}",
|
||||
|
@ -373,6 +379,7 @@
|
|||
"eta_1d": "ETA: In ~1 day",
|
||||
"view_wallet": "View {walletLabel}",
|
||||
"list_title": "Transactions",
|
||||
"transaction": "Transaction",
|
||||
"open_url_error": "Unable to open the link with the default browser. Please change your default browser and try again.",
|
||||
"rbf_explain": "We will replace this transaction with one with a higher fee so that it will be mined faster. This is called RBF—Replace by Fee.",
|
||||
"rbf_title": "Bump Fee (RBF)",
|
||||
|
|
Loading…
Add table
Reference in a new issue