mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-24 07:28:07 +01:00
Merge pull request #7282 from BlueWallet/cc-ts
fix: CoinControl Typescript
This commit is contained in:
commit
e183a40792
6 changed files with 257 additions and 118 deletions
|
@ -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],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue