Merge pull request #7282 from BlueWallet/cc-ts

fix: CoinControl Typescript
This commit is contained in:
GLaDOS 2024-11-10 04:40:02 +00:00 committed by GitHub
commit e183a40792
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 257 additions and 118 deletions

View file

@ -89,7 +89,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
...CommonToolTipActions.ExportPrivateKey, ...CommonToolTipActions.ExportPrivateKey,
hidden: !allowSignVerifyMessage, hidden: !allowSignVerifyMessage,
}, },
].filter(action => !action.hidden), ].filter(action => 'hidden' in action && !action.hidden),
[allowSignVerifyMessage], [allowSignVerifyMessage],
); );

View file

@ -592,7 +592,13 @@
"header": "Coin Control", "header": "Coin Control",
"use_coin": "Use Coin", "use_coin": "Use Coin",
"use_coins": "Use Coins", "use_coins": "Use Coins",
"tip": "This feature allows you to see, label, freeze or select coins for improved wallet management. You can select multiple coins by tapping on the colored circles." "tip": "This feature allows you to see, label, freeze or select coins for improved wallet management. You can select multiple coins by tapping on the colored circles.",
"sort_asc": "Ascending",
"sort_desc": "Descending",
"sort_height": "by Height",
"sort_value": "by Value",
"sort_label": "by Label",
"sort_status": "by Status"
}, },
"units": { "units": {
"BTC": "BTC", "BTC": "BTC",

View file

@ -10,7 +10,7 @@ const PsbtMultisig = lazy(() => import('../screen/send/psbtMultisig'));
const PsbtMultisigQRCode = lazy(() => import('../screen/send/psbtMultisigQRCode')); const PsbtMultisigQRCode = lazy(() => import('../screen/send/psbtMultisigQRCode'));
const Success = lazy(() => import('../screen/send/success')); const Success = lazy(() => import('../screen/send/success'));
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet')); const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
const CoinControl = lazy(() => import('../screen/send/coinControl')); const CoinControl = lazy(() => import('../screen/send/CoinControl'));
const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList')); const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList'));
// Export each component with its lazy loader and optional configurations // Export each component with its lazy loader and optional configurations

View file

@ -1,6 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useRoute } from '@react-navigation/native'; import { RouteProp, useRoute } from '@react-navigation/native';
import PropTypes from 'prop-types'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { Avatar, Badge, Icon, ListItem as RNElementsListItem } from '@rneui/themed';
import { import {
ActivityIndicator, ActivityIndicator,
FlatList, FlatList,
@ -15,41 +16,62 @@ import {
useWindowDimensions, useWindowDimensions,
View, View,
} from 'react-native'; } from 'react-native';
import { Avatar, Badge, Icon, ListItem as RNElementsListItem } from '@rneui/themed';
import * as RNLocalize from 'react-native-localize'; import * as RNLocalize from 'react-native-localize';
import debounce from '../../blue_modules/debounce'; import debounce from '../../blue_modules/debounce';
import { BlueSpacing10, BlueSpacing20 } from '../../BlueComponents'; import { BlueSpacing10, BlueSpacing20 } from '../../BlueComponents';
import BottomModal from '../../components/BottomModal'; import { TWallet, Utxo } from '../../class/wallets/types';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
import Button from '../../components/Button'; import Button from '../../components/Button';
import { FButton, FContainer } from '../../components/FloatButtons'; import { FButton, FContainer } from '../../components/FloatButtons';
import HeaderMenuButton from '../../components/HeaderMenuButton';
import ListItem from '../../components/ListItem'; import ListItem from '../../components/ListItem';
import SafeArea from '../../components/SafeArea'; import SafeArea from '../../components/SafeArea';
import { useTheme } from '../../components/themes'; import { useTheme } from '../../components/themes';
import loc, { formatBalance } from '../../loc'; import { Action } from '../../components/types';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { useStorage } from '../../hooks/context/useStorage'; import { useStorage } from '../../hooks/context/useStorage';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc, { formatBalance } from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
const FrozenBadge = () => { type NavigationProps = NativeStackNavigationProp<SendDetailsStackParamList, 'CoinControl'>;
type RouteProps = RouteProp<SendDetailsStackParamList, 'CoinControl'>;
const FrozenBadge: React.FC = () => {
const { colors } = useTheme(); const { colors } = useTheme();
const oStyles = StyleSheet.create({ const oStyles = StyleSheet.create({
freeze: { backgroundColor: colors.redBG, borderWidth: 0 }, freeze: { backgroundColor: colors.redBG, borderWidth: 0, marginLeft: 4 },
freezeText: { color: colors.redText }, freezeText: { color: colors.redText, marginTop: -1 },
}); });
return <Badge value={loc.cc.freeze} badgeStyle={oStyles.freeze} textStyle={oStyles.freezeText} />; return <Badge value={loc.cc.freeze} badgeStyle={oStyles.freeze} textStyle={oStyles.freezeText} />;
}; };
const ChangeBadge = () => { const ChangeBadge: React.FC = () => {
const { colors } = useTheme(); const { colors } = useTheme();
const oStyles = StyleSheet.create({ const oStyles = StyleSheet.create({
change: { backgroundColor: colors.buttonDisabledBackgroundColor, borderWidth: 0 }, change: { backgroundColor: colors.buttonDisabledBackgroundColor, borderWidth: 0, marginLeft: 4 },
changeText: { color: colors.alternativeTextColor }, changeText: { color: colors.alternativeTextColor, marginTop: -1 },
}); });
return <Badge value={loc.cc.change} badgeStyle={oStyles.change} textStyle={oStyles.changeText} />; return <Badge value={loc.cc.change} badgeStyle={oStyles.change} textStyle={oStyles.changeText} />;
}; };
const OutputList = ({ type TOutputListProps = {
item: { address, txid, value, vout, confirmations = 0 }, item: Utxo;
balanceUnit: string;
oMemo?: string;
frozen: boolean;
change: boolean;
onOpen: () => void;
selected: boolean;
selectionStarted: boolean;
onSelect: () => void;
onDeSelect: () => void;
};
const OutputList: React.FC<TOutputListProps> = ({
item: { address, txid, value },
balanceUnit = BitcoinUnit.BTC, balanceUnit = BitcoinUnit.BTC,
oMemo, oMemo,
frozen, frozen,
@ -59,7 +81,7 @@ const OutputList = ({
selectionStarted, selectionStarted,
onSelect, onSelect,
onDeSelect, onDeSelect,
}) => { }: TOutputListProps) => {
const { colors } = useTheme(); const { colors } = useTheme();
const { txMetadata } = useStorage(); const { txMetadata } = useStorage();
const memo = oMemo || txMetadata[txid]?.memo || ''; const memo = oMemo || txMetadata[txid]?.memo || '';
@ -84,12 +106,12 @@ const OutputList = ({
return ( return (
<RNElementsListItem bottomDivider onPress={onPress} containerStyle={selected ? oStyles.containerSelected : oStyles.container}> <RNElementsListItem bottomDivider onPress={onPress} containerStyle={selected ? oStyles.containerSelected : oStyles.container}>
<RNElementsListItem.CheckBox <Avatar
checkedColor="#0070FF" rounded
iconType="font-awesome" size={40}
checkedIcon="check-square" containerStyle={oStyles.avatar}
checked={selected}
onPress={selected ? onDeSelect : onSelect} onPress={selected ? onDeSelect : onSelect}
icon={selected ? { name: 'check', type: 'font-awesome-6' } : undefined}
/> />
<RNElementsListItem.Content> <RNElementsListItem.Content>
<RNElementsListItem.Title style={oStyles.amount}>{amount}</RNElementsListItem.Title> <RNElementsListItem.Title style={oStyles.amount}>{amount}</RNElementsListItem.Title>
@ -97,32 +119,25 @@ const OutputList = ({
{memo || address} {memo || address}
</RNElementsListItem.Subtitle> </RNElementsListItem.Subtitle>
</RNElementsListItem.Content> </RNElementsListItem.Content>
{change && <ChangeBadge />} <View style={styles.badges}>
{frozen && <FrozenBadge />} {frozen && <FrozenBadge />}
{change && <ChangeBadge />}
</View>
</RNElementsListItem> </RNElementsListItem>
); );
}; };
OutputList.propTypes = { type TOutputModalProps = {
item: PropTypes.shape({ item: Utxo;
address: PropTypes.string.isRequired, balanceUnit: string;
txid: PropTypes.string.isRequired, oMemo?: string;
value: PropTypes.number.isRequired,
vout: PropTypes.number.isRequired,
confirmations: PropTypes.number,
}),
balanceUnit: PropTypes.string,
oMemo: PropTypes.string,
frozen: PropTypes.bool,
change: PropTypes.bool,
onOpen: PropTypes.func,
selected: PropTypes.bool,
selectionStarted: PropTypes.bool,
onSelect: PropTypes.func,
onDeSelect: PropTypes.func,
}; };
const OutputModal = ({ item: { address, txid, value, vout, confirmations = 0 }, balanceUnit = BitcoinUnit.BTC, oMemo }) => { const OutputModal: React.FC<TOutputModalProps> = ({
item: { address, txid, value, vout, confirmations = 0 },
balanceUnit = BitcoinUnit.BTC,
oMemo,
}) => {
const { colors } = useTheme(); const { colors } = useTheme();
const { txMetadata } = useStorage(); const { txMetadata } = useStorage();
const memo = oMemo || txMetadata[txid]?.memo || ''; const memo = oMemo || txMetadata[txid]?.memo || '';
@ -144,7 +159,7 @@ const OutputModal = ({ item: { address, txid, value, vout, confirmations = 0 },
return ( return (
<RNElementsListItem bottomDivider containerStyle={oStyles.container}> <RNElementsListItem bottomDivider containerStyle={oStyles.container}>
<Avatar rounded overlayContainerStyle={oStyles.avatar} /> <Avatar rounded size={40} containerStyle={oStyles.avatar} />
<RNElementsListItem.Content> <RNElementsListItem.Content>
<RNElementsListItem.Title numberOfLines={1} adjustsFontSizeToFit style={oStyles.amount}> <RNElementsListItem.Title numberOfLines={1} adjustsFontSizeToFit style={oStyles.amount}>
{amount} {amount}
@ -166,18 +181,6 @@ const OutputModal = ({ item: { address, txid, value, vout, confirmations = 0 },
); );
}; };
OutputModal.propTypes = {
item: PropTypes.shape({
address: PropTypes.string.isRequired,
txid: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
vout: PropTypes.number.isRequired,
confirmations: PropTypes.number,
}),
balanceUnit: PropTypes.string,
oMemo: PropTypes.string,
};
const mStyles = StyleSheet.create({ const mStyles = StyleSheet.create({
memoTextInput: { memoTextInput: {
flexDirection: 'row', flexDirection: 'row',
@ -198,13 +201,22 @@ const mStyles = StyleSheet.create({
}, },
}); });
type TOutputModalContentProps = {
output: Utxo;
wallet: TWallet;
onUseCoin: (u: Utxo[]) => void;
frozen: boolean;
setFrozen: (value: boolean) => void;
};
const transparentBackground = { backgroundColor: 'transparent' }; const transparentBackground = { backgroundColor: 'transparent' };
const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) => { const OutputModalContent: React.FC<TOutputModalContentProps> = ({ output, wallet, onUseCoin, frozen, setFrozen }) => {
const { colors } = useTheme(); const { colors } = useTheme();
const { txMetadata, saveToDisk } = useStorage(); const { txMetadata, saveToDisk } = useStorage();
const [memo, setMemo] = useState(wallet.getUTXOMetadata(output.txid, output.vout).memo || txMetadata[output.txid]?.memo || ''); const [memo, setMemo] = useState<string>(wallet.getUTXOMetadata(output.txid, output.vout).memo || txMetadata[output.txid]?.memo || '');
const onMemoChange = value => setMemo(value); const switchValue = useMemo(() => ({ value: frozen, onValueChange: (value: boolean) => setFrozen(value) }), [frozen, setFrozen]);
const switchValue = useMemo(() => ({ value: frozen, onValueChange: value => setFrozen(value) }), [frozen, setFrozen]);
const onMemoChange = (value: string) => setMemo(value);
// save on form change. Because effect called on each event, debounce it. // save on form change. Because effect called on each event, debounce it.
const debouncedSaveMemo = useRef( const debouncedSaveMemo = useRef(
@ -218,7 +230,7 @@ const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) =>
}, [memo]); }, [memo]);
return ( return (
<View style={styles.padding}> <View>
<OutputModal item={output} balanceUnit={wallet.getPreferredBalanceUnit()} /> <OutputModal item={output} balanceUnit={wallet.getPreferredBalanceUnit()} />
<BlueSpacing20 /> <BlueSpacing20 />
<TextInput <TextInput
@ -247,35 +259,66 @@ const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) =>
); );
}; };
OutputModalContent.propTypes = { enum ESortDirections {
output: PropTypes.object, asc = 'asc',
wallet: PropTypes.object, desc = 'desc',
onUseCoin: PropTypes.func.isRequired, }
frozen: PropTypes.bool.isRequired,
setFrozen: PropTypes.func.isRequired,
};
const CoinControl = () => { enum ESortTypes {
height = 'height',
label = 'label',
value = 'value',
frozen = 'frozen',
}
const CoinControl: React.FC = () => {
const { colors } = useTheme(); const { colors } = useTheme();
const navigation = useExtendedNavigation(); const navigation = useExtendedNavigation<NavigationProps>();
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const bottomModalRef = useRef(null); const bottomModalRef = useRef<BottomModalHandle | null>(null);
const { walletID } = useRoute().params; const { walletID } = useRoute<RouteProps>().params;
const { wallets, saveToDisk, sleep } = useStorage(); const { wallets, saveToDisk, sleep } = useStorage();
const wallet = wallets.find(w => w.getID() === walletID); const [sortDirection, setSortDirection] = useState<ESortDirections>(ESortDirections.asc);
// sort by height ascending, txid , vout ascending const [sortType, setSortType] = useState<ESortTypes>(ESortTypes.height);
const utxo = wallet.getUtxo(true).sort((a, b) => a.height - b.height || a.txid.localeCompare(b.txid) || a.vout - b.vout); const wallet = useMemo(() => wallets.find(w => w.getID() === walletID) as TWallet, [walletID, wallets]);
const [output, setOutput] = useState(); const [frozen, setFrozen] = useState<string[]>(
const [loading, setLoading] = useState(true); wallet
const [selected, setSelected] = useState([]); .getUtxo(true)
const [frozen, setFrozen] = useState( .filter(out => wallet.getUTXOMetadata(out.txid, out.vout).frozen)
utxo.filter(out => wallet.getUTXOMetadata(out.txid, out.vout).frozen).map(({ txid, vout }) => `${txid}:${vout}`), .map(({ txid, vout }) => `${txid}:${vout}`),
); );
const utxos: Utxo[] = useMemo(() => {
const res = wallet.getUtxo(true).sort((a, b) => {
switch (sortType) {
case ESortTypes.height:
return a.height - b.height || a.txid.localeCompare(b.txid) || a.vout - b.vout;
case ESortTypes.value:
return a.value - b.value || a.txid.localeCompare(b.txid) || a.vout - b.vout;
case ESortTypes.label: {
const aMemo = wallet.getUTXOMetadata(a.txid, a.vout).memo || '';
const bMemo = wallet.getUTXOMetadata(b.txid, b.vout).memo || '';
return aMemo.localeCompare(bMemo) || a.txid.localeCompare(b.txid) || a.vout - b.vout;
}
case ESortTypes.frozen: {
const aF = frozen.includes(`${a.txid}:${a.vout}`);
const bF = frozen.includes(`${b.txid}:${b.vout}`);
return aF !== bF ? (aF ? -1 : 1) : a.txid.localeCompare(b.txid) || a.vout - b.vout;
}
default:
return 0;
}
});
// invert if descending
return sortDirection === ESortDirections.desc ? res.reverse() : res;
}, [sortDirection, sortType, wallet, frozen]);
const [output, setOutput] = useState<Utxo | undefined>();
const [loading, setLoading] = useState<boolean>(true);
const [selected, setSelected] = useState<string[]>([]);
// save frozen status. Because effect called on each event, debounce it. // save frozen status. Because effect called on each event, debounce it.
const debouncedSaveFronen = useRef( const debouncedSaveFronen = useRef(
debounce(async frzn => { debounce(async frzn => {
utxo.forEach(({ txid, vout }) => { utxos.forEach(({ txid, vout }) => {
wallet.setUTXOMetadata(txid, vout, { frozen: frzn.includes(`${txid}:${vout}`) }); wallet.setUTXOMetadata(txid, vout, { frozen: frzn.includes(`${txid}:${vout}`) });
}); });
await saveToDisk(); await saveToDisk();
@ -305,13 +348,13 @@ const CoinControl = () => {
}); });
const tipCoins = () => { const tipCoins = () => {
if (utxo.length === 0) return null; if (utxos.length === 0) return null;
let text = loc.cc.tip; let text = loc.cc.tip;
if (selected.length > 0) { if (selected.length > 0) {
// show summ of coins if any selected // show summ of coins if any selected
const summ = selected.reduce((prev, curr) => { const summ = selected.reduce((prev, curr) => {
return prev + utxo.find(({ txid, vout }) => `${txid}:${vout}` === curr).value; return prev + (utxos.find(({ txid, vout }) => `${txid}:${vout}` === curr) as Utxo).value;
}, 0); }, 0);
const value = formatBalance(summ, wallet.getPreferredBalanceUnit(), true); const value = formatBalance(summ, wallet.getPreferredBalanceUnit(), true);
@ -325,10 +368,11 @@ const CoinControl = () => {
); );
}; };
const handleChoose = item => setOutput(item); const handleChoose = (item: Utxo) => setOutput(item);
const handleUseCoin = async u => { const handleUseCoin = async (u: Utxo[]) => {
setOutput(null); setOutput(undefined);
// @ts-ignore navigation WTF
navigation.navigate('SendDetailsRoot', { navigation.navigate('SendDetailsRoot', {
screen: 'SendDetails', screen: 'SendDetails',
params: { params: {
@ -347,7 +391,7 @@ const CoinControl = () => {
}; };
const handleMassUse = () => { const handleMassUse = () => {
const fUtxo = utxo.filter(({ txid, vout }) => selected.includes(`${txid}:${vout}`)); const fUtxo = utxos.filter(({ txid, vout }) => selected.includes(`${txid}:${vout}`));
handleUseCoin(fUtxo); handleUseCoin(fUtxo);
}; };
@ -357,7 +401,7 @@ const CoinControl = () => {
const allFrozen = selectionStarted && selected.reduce((prev, curr) => (prev ? frozen.includes(curr) : false), true); const allFrozen = selectionStarted && selected.reduce((prev, curr) => (prev ? frozen.includes(curr) : false), true);
const buttonFontSize = PixelRatio.roundToNearestPixel(width / 26) > 22 ? 22 : PixelRatio.roundToNearestPixel(width / 26); const buttonFontSize = PixelRatio.roundToNearestPixel(width / 26) > 22 ? 22 : PixelRatio.roundToNearestPixel(width / 26);
const renderItem = p => { const renderItem = (p: { item: Utxo }) => {
const { memo } = wallet.getUTXOMetadata(p.item.txid, p.item.vout); const { memo } = wallet.getUTXOMetadata(p.item.txid, p.item.vout);
const change = wallet.addressIsChange(p.item.address); const change = wallet.addressIsChange(p.item.address);
const oFrozen = frozen.includes(`${p.item.txid}:${p.item.vout}`); const oFrozen = frozen.includes(`${p.item.txid}:${p.item.vout}`);
@ -372,27 +416,39 @@ const CoinControl = () => {
selected={selected.includes(`${p.item.txid}:${p.item.vout}`)} selected={selected.includes(`${p.item.txid}:${p.item.vout}`)}
selectionStarted={selectionStarted} selectionStarted={selectionStarted}
onSelect={() => { onSelect={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show setSelected(s => {
setSelected(s => [...s, `${p.item.txid}:${p.item.vout}`]); if (s.length === 0) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show
}
return [...s, `${p.item.txid}:${p.item.vout}`];
});
}} }}
onDeSelect={() => { onDeSelect={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show setSelected(s => {
setSelected(s => s.filter(i => i !== `${p.item.txid}:${p.item.vout}`)); const newValue = s.filter(i => i !== `${p.item.txid}:${p.item.vout}`);
if (newValue.length === 0) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show
}
return newValue;
});
}} }}
/> />
); );
}; };
const renderOutputModalContent = () => { const renderOutputModalContent = (o: Utxo | undefined) => {
const oFrozen = frozen.includes(`${output.txid}:${output.vout}`); if (!o) {
const setOFrozen = value => { return null;
}
const oFrozen = frozen.includes(`${o.txid}:${o.vout}`);
const setOFrozen = (value: boolean) => {
if (value) { if (value) {
setFrozen(f => [...f, `${output.txid}:${output.vout}`]); setFrozen(f => [...f, `${o.txid}:${o.vout}`]);
} else { } else {
setFrozen(f => f.filter(i => i !== `${output.txid}:${output.vout}`)); setFrozen(f => f.filter(i => i !== `${o.txid}:${o.vout}`));
} }
}; };
return <OutputModalContent output={output} wallet={wallet} onUseCoin={handleUseCoin} frozen={oFrozen} setFrozen={setOFrozen} />; return <OutputModalContent output={o} wallet={wallet} onUseCoin={handleUseCoin} frozen={oFrozen} setFrozen={setOFrozen} />;
}; };
useEffect(() => { useEffect(() => {
@ -401,6 +457,45 @@ const CoinControl = () => {
} }
}, [output]); }, [output]);
const toolTipActions = useMemo((): Action[] => {
return [
sortDirection === ESortDirections.asc ? CommonToolTipActions.SortASC : CommonToolTipActions.SortDESC,
{ ...CommonToolTipActions.SortHeight, menuState: sortType === ESortTypes.height },
{ ...CommonToolTipActions.SortValue, menuState: sortType === ESortTypes.value },
{ ...CommonToolTipActions.SortLabel, menuState: sortType === ESortTypes.label },
{ ...CommonToolTipActions.SortStatus, menuState: sortType === ESortTypes.frozen },
];
}, [sortDirection, sortType]);
const toolTipOnPressMenuItem = useCallback((menuItem: string) => {
Keyboard.dismiss();
if (menuItem === CommonToolTipActions.SortASC.id) {
setSortDirection(ESortDirections.desc);
} else if (menuItem === CommonToolTipActions.SortDESC.id) {
setSortDirection(ESortDirections.asc);
} else if (menuItem === CommonToolTipActions.SortHeight.id) {
setSortType(ESortTypes.height);
} else if (menuItem === CommonToolTipActions.SortValue.id) {
setSortType(ESortTypes.value);
} else if (menuItem === CommonToolTipActions.SortLabel.id) {
setSortType(ESortTypes.label);
} else if (menuItem === CommonToolTipActions.SortStatus.id) {
setSortType(ESortTypes.frozen);
}
}, []);
const HeaderRight = useMemo(
() => <HeaderMenuButton onPressMenuItem={toolTipOnPressMenuItem} actions={toolTipActions} />,
[toolTipOnPressMenuItem, toolTipActions],
);
// Adding the ToolTipMenu to the header
useEffect(() => {
navigation.setOptions({
headerRight: () => HeaderRight,
});
}, [HeaderRight, navigation]);
if (loading) { if (loading) {
return ( return (
<SafeArea style={[styles.center, { backgroundColor: colors.elevated }]}> <SafeArea style={[styles.center, { backgroundColor: colors.elevated }]}>
@ -411,7 +506,7 @@ const CoinControl = () => {
return ( return (
<View style={[styles.root, { backgroundColor: colors.elevated }]}> <View style={[styles.root, { backgroundColor: colors.elevated }]}>
{utxo.length === 0 && ( {utxos.length === 0 && (
<View style={styles.empty}> <View style={styles.empty}>
<Text style={{ color: colors.foregroundColor }}>{loc.cc.empty}</Text> <Text style={{ color: colors.foregroundColor }}>{loc.cc.empty}</Text>
</View> </View>
@ -421,31 +516,32 @@ const CoinControl = () => {
ref={bottomModalRef} ref={bottomModalRef}
onClose={() => { onClose={() => {
Keyboard.dismiss(); Keyboard.dismiss();
setOutput(false); setOutput(undefined);
}} }}
backgroundColor={colors.elevated} backgroundColor={colors.elevated}
contentContainerStyle={styles.modalMinHeight}
footer={ footer={
<View style={mStyles.buttonContainer}> <View style={mStyles.buttonContainer}>
<Button <Button
testID="UseCoin" testID="UseCoin"
title={loc.cc.use_coin} title={loc.cc.use_coin}
onPress={async () => { onPress={async () => {
if (!output) throw new Error('output is not set');
await bottomModalRef.current?.dismiss(); await bottomModalRef.current?.dismiss();
handleUseCoin([output]); handleUseCoin([output]);
}} }}
/> />
</View> </View>
} }
contentContainerStyle={styles.modalMinHeight}
> >
{output && renderOutputModalContent()} {renderOutputModalContent(output)}
</BottomModal> </BottomModal>
<FlatList <FlatList
ListHeaderComponent={tipCoins} ListHeaderComponent={tipCoins}
data={utxo} data={utxos}
renderItem={renderItem} renderItem={renderItem}
keyExtractor={item => `${item.txid}:${item.vout}`} keyExtractor={item => `${item.txid}:${item.vout}`}
contentInset={{ top: 0, left: 0, bottom: 70, right: 0 }} contentInset={styles.listContent}
/> />
{selectionStarted && ( {selectionStarted && (
@ -478,9 +574,6 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
padding: {
padding: 16,
},
modalMinHeight: Platform.OS === 'android' ? { minHeight: 530 } : {}, modalMinHeight: Platform.OS === 'android' ? { minHeight: 530 } : {},
empty: { empty: {
flex: 1, flex: 1,
@ -497,6 +590,15 @@ const styles = StyleSheet.create({
sendIcon: { sendIcon: {
transform: [{ rotate: '225deg' }], transform: [{ rotate: '225deg' }],
}, },
badges: {
flexDirection: 'row',
},
listContent: {
top: 0,
left: 0,
bottom: 70,
right: 0,
},
}); });
export default CoinControl; export default CoinControl;

View file

@ -509,9 +509,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
const hideShareModal = () => {}; const hideShareModal = () => {};
const toolTipActions = useMemo(() => { const toolTipActions = useMemo(() => {
const passphrase = CommonToolTipActions.Passphrase; return [{ ...CommonToolTipActions.Passphrase, menuState: askPassphrase }];
passphrase.menuState = askPassphrase;
return [passphrase];
}, [askPassphrase]); }, [askPassphrase]);
const renderProvideMnemonicsModal = () => { const renderProvideMnemonicsModal = () => {

View file

@ -1,6 +1,5 @@
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import loc from '../loc'; import loc from '../loc';
import { Action } from '../components/types';
const keys = { const keys = {
CopyTXID: 'copyTX_ID', CopyTXID: 'copyTX_ID',
@ -45,9 +44,15 @@ const keys = {
ExportPrivateKey: 'exportPrivateKey', ExportPrivateKey: 'exportPrivateKey',
PasteFromClipboard: 'pasteFromClipboard', PasteFromClipboard: 'pasteFromClipboard',
Hide: 'hide', Hide: 'hide',
}; SortASC: 'sortASC',
SortDESC: 'sortDESC',
SortHeight: 'sortHeight',
SortValue: 'sortValue',
SortLabel: 'sortLabel',
SortStatus: 'sortStatus',
} as const;
const icons: { [key: string]: { iconValue: string } } = { const icons = {
Eye: { iconValue: 'eye' }, Eye: { iconValue: 'eye' },
EyeSlash: { iconValue: 'eye.slash' }, EyeSlash: { iconValue: 'eye.slash' },
Link: { iconValue: 'link' }, Link: { iconValue: 'link' },
@ -87,9 +92,11 @@ const icons: { [key: string]: { iconValue: string } } = {
ImportFile: { iconValue: 'document.viewfinder' }, ImportFile: { iconValue: 'document.viewfinder' },
Hide: { iconValue: 'eye.slash' }, Hide: { iconValue: 'eye.slash' },
ClearClipboard: { iconValue: 'clipboard' }, ClearClipboard: { iconValue: 'clipboard' },
}; SortASC: { iconValue: 'arrow.down.to.line' },
SortDESC: { iconValue: 'arrow.up.to.line' },
} as const;
export const CommonToolTipActions: { [key: string]: Action } = { export const CommonToolTipActions = {
CopyTXID: { CopyTXID: {
id: keys.CopyTXID, id: keys.CopyTXID,
text: loc.transactions.details_copy_txid, text: loc.transactions.details_copy_txid,
@ -318,4 +325,30 @@ export const CommonToolTipActions: { [key: string]: Action } = {
icon: icons.Clipboard, icon: icons.Clipboard,
menuState: true, menuState: true,
}, },
}; SortASC: {
id: keys.SortASC,
text: loc.cc.sort_asc,
icon: icons.SortASC,
},
SortDESC: {
id: keys.SortDESC,
text: loc.cc.sort_desc,
icon: icons.SortDESC,
},
SortHeight: {
id: keys.SortHeight,
text: loc.cc.sort_height,
},
SortValue: {
id: keys.SortValue,
text: loc.cc.sort_value,
},
SortLabel: {
id: keys.SortLabel,
text: loc.cc.sort_label,
},
SortStatus: {
id: keys.SortStatus,
text: loc.cc.sort_status,
},
} as const;