2024-05-31 19:18:01 +02:00
|
|
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
2024-07-03 05:25:15 +02:00
|
|
|
import { useRoute } from '@react-navigation/native';
|
|
|
|
import PropTypes from 'prop-types';
|
2020-10-29 09:32:55 +01:00
|
|
|
import {
|
2020-11-03 09:41:50 +01:00
|
|
|
ActivityIndicator,
|
2020-10-29 09:32:55 +01:00
|
|
|
FlatList,
|
|
|
|
Keyboard,
|
2020-12-12 19:00:12 +01:00
|
|
|
LayoutAnimation,
|
|
|
|
PixelRatio,
|
2024-07-16 18:59:17 +02:00
|
|
|
Platform,
|
2020-10-29 09:32:55 +01:00
|
|
|
StyleSheet,
|
|
|
|
Text,
|
|
|
|
TextInput,
|
|
|
|
TouchableWithoutFeedback,
|
2020-12-12 19:00:12 +01:00
|
|
|
useWindowDimensions,
|
2020-11-03 09:41:50 +01:00
|
|
|
View,
|
2020-10-29 09:32:55 +01:00
|
|
|
} from 'react-native';
|
2024-06-12 18:46:44 +02:00
|
|
|
import { Avatar, Badge, Icon, ListItem as RNElementsListItem } from '@rneui/themed';
|
2021-09-23 15:05:10 +02:00
|
|
|
import * as RNLocalize from 'react-native-localize';
|
|
|
|
import debounce from '../../blue_modules/debounce';
|
2024-05-20 11:54:13 +02:00
|
|
|
import { BlueSpacing10, BlueSpacing20 } from '../../BlueComponents';
|
|
|
|
import BottomModal from '../../components/BottomModal';
|
2023-11-15 09:40:22 +01:00
|
|
|
import Button from '../../components/Button';
|
2024-05-20 11:54:13 +02:00
|
|
|
import { FButton, FContainer } from '../../components/FloatButtons';
|
2023-12-16 22:44:35 +01:00
|
|
|
import ListItem from '../../components/ListItem';
|
2023-12-27 07:52:11 +01:00
|
|
|
import SafeArea from '../../components/SafeArea';
|
2024-05-20 11:54:13 +02:00
|
|
|
import { useTheme } from '../../components/themes';
|
|
|
|
import loc, { formatBalance } from '../../loc';
|
|
|
|
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
2024-05-31 19:18:01 +02:00
|
|
|
import { useStorage } from '../../hooks/context/useStorage';
|
2024-07-03 05:25:15 +02:00
|
|
|
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
2020-11-22 09:15:49 +01:00
|
|
|
|
2020-12-12 19:00:12 +01:00
|
|
|
const FrozenBadge = () => {
|
|
|
|
const { colors } = useTheme();
|
|
|
|
const oStyles = StyleSheet.create({
|
2020-12-13 08:49:07 +01:00
|
|
|
freeze: { backgroundColor: colors.redBG, borderWidth: 0 },
|
2020-12-12 19:00:12 +01:00
|
|
|
freezeText: { color: colors.redText },
|
|
|
|
});
|
2020-12-13 08:49:07 +01:00
|
|
|
return <Badge value={loc.cc.freeze} badgeStyle={oStyles.freeze} textStyle={oStyles.freezeText} />;
|
2020-12-12 19:00:12 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const ChangeBadge = () => {
|
|
|
|
const { colors } = useTheme();
|
|
|
|
const oStyles = StyleSheet.create({
|
2020-12-13 08:49:07 +01:00
|
|
|
change: { backgroundColor: colors.buttonDisabledBackgroundColor, borderWidth: 0 },
|
2020-12-12 19:00:12 +01:00
|
|
|
changeText: { color: colors.alternativeTextColor },
|
|
|
|
});
|
2020-12-13 08:49:07 +01:00
|
|
|
return <Badge value={loc.cc.change} badgeStyle={oStyles.change} textStyle={oStyles.changeText} />;
|
2020-12-12 19:00:12 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const OutputList = ({
|
2021-03-18 09:55:56 +01:00
|
|
|
item: { address, txid, value, vout, confirmations = 0 },
|
2020-12-01 22:38:18 +01:00
|
|
|
balanceUnit = BitcoinUnit.BTC,
|
|
|
|
oMemo,
|
|
|
|
frozen,
|
2020-12-12 19:00:12 +01:00
|
|
|
change,
|
|
|
|
onOpen,
|
|
|
|
selected,
|
|
|
|
selectionStarted,
|
|
|
|
onSelect,
|
|
|
|
onDeSelect,
|
2020-12-01 22:38:18 +01:00
|
|
|
}) => {
|
2020-10-22 14:24:47 +02:00
|
|
|
const { colors } = useTheme();
|
2024-05-31 17:52:29 +02:00
|
|
|
const { txMetadata } = useStorage();
|
2020-10-28 09:46:40 +01:00
|
|
|
const memo = oMemo || txMetadata[txid]?.memo || '';
|
2020-10-22 14:24:47 +02:00
|
|
|
const color = `#${txid.substring(0, 6)}`;
|
2020-12-01 22:38:18 +01:00
|
|
|
const amount = formatBalance(value, balanceUnit, true);
|
2020-10-22 14:24:47 +02:00
|
|
|
|
2020-11-06 11:12:20 +01:00
|
|
|
const oStyles = StyleSheet.create({
|
2020-12-12 19:00:12 +01:00
|
|
|
container: { borderBottomColor: colors.lightBorder, backgroundColor: colors.elevated },
|
|
|
|
containerSelected: {
|
|
|
|
backgroundColor: colors.ballOutgoingExpired,
|
2021-01-24 02:38:14 +01:00
|
|
|
borderBottomColor: 'rgba(0, 0, 0, 0)',
|
2020-12-12 19:00:12 +01:00
|
|
|
},
|
|
|
|
avatar: { borderColor: 'white', borderWidth: 1, backgroundColor: color },
|
|
|
|
amount: { fontWeight: 'bold', color: colors.foregroundColor },
|
|
|
|
memo: { fontSize: 13, marginTop: 3, color: colors.alternativeTextColor },
|
2020-11-06 11:12:20 +01:00
|
|
|
});
|
|
|
|
|
2020-12-12 19:00:12 +01:00
|
|
|
let onPress = onOpen;
|
|
|
|
if (selectionStarted) {
|
|
|
|
onPress = selected ? onDeSelect : onSelect;
|
|
|
|
}
|
|
|
|
|
2020-10-22 14:24:47 +02:00
|
|
|
return (
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem bottomDivider onPress={onPress} containerStyle={selected ? oStyles.containerSelected : oStyles.container}>
|
2024-06-14 23:07:01 +02:00
|
|
|
<RNElementsListItem.CheckBox
|
|
|
|
checkedColor="#0070FF"
|
2024-08-17 17:50:56 +02:00
|
|
|
iconType="font-awesome"
|
|
|
|
checkedIcon="check-square"
|
2024-06-14 23:07:01 +02:00
|
|
|
checked={selected}
|
2020-12-12 19:00:12 +01:00
|
|
|
onPress={selected ? onDeSelect : onSelect}
|
|
|
|
/>
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem.Content>
|
|
|
|
<RNElementsListItem.Title style={oStyles.amount}>{amount}</RNElementsListItem.Title>
|
|
|
|
<RNElementsListItem.Subtitle style={oStyles.memo} numberOfLines={1} ellipsizeMode="middle">
|
2021-04-14 16:30:52 +02:00
|
|
|
{memo || address}
|
2023-12-16 22:44:35 +01:00
|
|
|
</RNElementsListItem.Subtitle>
|
|
|
|
</RNElementsListItem.Content>
|
2020-12-12 19:00:12 +01:00
|
|
|
{change && <ChangeBadge />}
|
|
|
|
{frozen && <FrozenBadge />}
|
2023-12-16 22:44:35 +01:00
|
|
|
</RNElementsListItem>
|
2020-12-12 19:00:12 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
OutputList.propTypes = {
|
|
|
|
item: PropTypes.shape({
|
|
|
|
address: PropTypes.string.isRequired,
|
|
|
|
txid: PropTypes.string.isRequired,
|
|
|
|
value: PropTypes.number.isRequired,
|
|
|
|
vout: PropTypes.number.isRequired,
|
2021-03-18 09:55:56 +01:00
|
|
|
confirmations: PropTypes.number,
|
2020-12-12 19:00:12 +01:00
|
|
|
}),
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2021-03-18 09:55:56 +01:00
|
|
|
const OutputModal = ({ item: { address, txid, value, vout, confirmations = 0 }, balanceUnit = BitcoinUnit.BTC, oMemo }) => {
|
2020-12-12 19:00:12 +01:00
|
|
|
const { colors } = useTheme();
|
2024-05-31 17:52:29 +02:00
|
|
|
const { txMetadata } = useStorage();
|
2020-12-12 19:00:12 +01:00
|
|
|
const memo = oMemo || txMetadata[txid]?.memo || '';
|
|
|
|
const fullId = `${txid}:${vout}`;
|
|
|
|
const color = `#${txid.substring(0, 6)}`;
|
|
|
|
const amount = formatBalance(value, balanceUnit, true);
|
|
|
|
|
|
|
|
const oStyles = StyleSheet.create({
|
2024-06-30 23:38:01 +02:00
|
|
|
container: { paddingHorizontal: 0, borderBottomColor: colors.lightBorder, backgroundColor: 'transparent' },
|
2020-12-12 19:00:12 +01:00
|
|
|
avatar: { borderColor: 'white', borderWidth: 1, backgroundColor: color },
|
|
|
|
amount: { fontWeight: 'bold', color: colors.foregroundColor },
|
|
|
|
tranContainer: { paddingLeft: 20 },
|
|
|
|
tranText: { fontWeight: 'normal', fontSize: 13, color: colors.alternativeTextColor },
|
|
|
|
memo: { fontSize: 13, marginTop: 3, color: colors.alternativeTextColor },
|
2020-11-06 11:12:20 +01:00
|
|
|
});
|
2021-01-30 05:57:52 +01:00
|
|
|
const confirmationsFormatted = new Intl.NumberFormat(RNLocalize.getLocales()[0].languageCode, { maximumSignificantDigits: 3 }).format(
|
|
|
|
confirmations,
|
|
|
|
);
|
2020-11-06 11:12:20 +01:00
|
|
|
|
2020-10-22 14:24:47 +02:00
|
|
|
return (
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem bottomDivider containerStyle={oStyles.container}>
|
2020-12-12 19:00:12 +01:00
|
|
|
<Avatar rounded overlayContainerStyle={oStyles.avatar} />
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem.Content>
|
|
|
|
<RNElementsListItem.Title numberOfLines={1} adjustsFontSizeToFit style={oStyles.amount}>
|
2020-12-12 19:00:12 +01:00
|
|
|
{amount}
|
|
|
|
<View style={oStyles.tranContainer}>
|
2021-01-24 02:38:14 +01:00
|
|
|
<Text style={oStyles.tranText}>{loc.formatString(loc.transactions.list_conf, { number: confirmationsFormatted })}</Text>
|
2020-12-12 19:00:12 +01:00
|
|
|
</View>
|
2023-12-16 22:44:35 +01:00
|
|
|
</RNElementsListItem.Title>
|
2020-12-12 19:00:12 +01:00
|
|
|
{memo ? (
|
2020-10-23 12:27:03 +02:00
|
|
|
<>
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem.Subtitle style={oStyles.memo}>{memo}</RNElementsListItem.Subtitle>
|
2020-10-28 11:00:26 +01:00
|
|
|
<BlueSpacing10 />
|
2020-10-23 12:27:03 +02:00
|
|
|
</>
|
2020-12-12 19:00:12 +01:00
|
|
|
) : null}
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem.Subtitle style={oStyles.memo}>{address}</RNElementsListItem.Subtitle>
|
2020-12-12 19:00:12 +01:00
|
|
|
<BlueSpacing10 />
|
2023-12-16 22:44:35 +01:00
|
|
|
<RNElementsListItem.Subtitle style={oStyles.memo}>{fullId}</RNElementsListItem.Subtitle>
|
|
|
|
</RNElementsListItem.Content>
|
|
|
|
</RNElementsListItem>
|
2020-10-22 14:24:47 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-12-12 19:00:12 +01:00
|
|
|
OutputModal.propTypes = {
|
2020-10-22 14:24:47 +02:00
|
|
|
item: PropTypes.shape({
|
2020-10-28 09:46:40 +01:00
|
|
|
address: PropTypes.string.isRequired,
|
2020-10-22 14:24:47 +02:00
|
|
|
txid: PropTypes.string.isRequired,
|
2020-10-22 15:30:58 +02:00
|
|
|
value: PropTypes.number.isRequired,
|
2020-10-23 12:27:03 +02:00
|
|
|
vout: PropTypes.number.isRequired,
|
2021-03-18 09:55:56 +01:00
|
|
|
confirmations: PropTypes.number,
|
2020-10-22 14:24:47 +02:00
|
|
|
}),
|
2020-12-01 22:38:18 +01:00
|
|
|
balanceUnit: PropTypes.string,
|
2020-10-25 10:04:04 +01:00
|
|
|
oMemo: PropTypes.string,
|
2020-10-22 14:24:47 +02:00
|
|
|
};
|
|
|
|
|
2020-10-25 11:17:37 +01:00
|
|
|
const mStyles = StyleSheet.create({
|
|
|
|
memoTextInput: {
|
|
|
|
flexDirection: 'row',
|
|
|
|
borderWidth: 1,
|
|
|
|
borderBottomWidth: 0.5,
|
|
|
|
minHeight: 44,
|
|
|
|
height: 44,
|
|
|
|
alignItems: 'center',
|
|
|
|
marginVertical: 8,
|
|
|
|
borderRadius: 4,
|
|
|
|
paddingHorizontal: 8,
|
|
|
|
color: '#81868e',
|
|
|
|
},
|
2020-11-06 07:29:03 +01:00
|
|
|
buttonContainer: {
|
|
|
|
height: 45,
|
2024-07-16 18:59:17 +02:00
|
|
|
marginBottom: 36,
|
|
|
|
marginHorizontal: 24,
|
2020-11-06 07:29:03 +01:00
|
|
|
},
|
2020-10-25 11:17:37 +01:00
|
|
|
});
|
|
|
|
|
2024-06-30 23:38:01 +02:00
|
|
|
const transparentBackground = { backgroundColor: 'transparent' };
|
2020-12-12 19:00:12 +01:00
|
|
|
const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) => {
|
2020-10-25 11:17:37 +01:00
|
|
|
const { colors } = useTheme();
|
2024-05-31 17:52:29 +02:00
|
|
|
const { txMetadata, saveToDisk } = useStorage();
|
2020-10-25 11:17:37 +01:00
|
|
|
const [memo, setMemo] = useState(wallet.getUTXOMetadata(output.txid, output.vout).memo || txMetadata[output.txid]?.memo || '');
|
2020-10-26 12:00:14 +01:00
|
|
|
const onMemoChange = value => setMemo(value);
|
2020-10-29 20:48:27 +01:00
|
|
|
const switchValue = useMemo(() => ({ value: frozen, onValueChange: value => setFrozen(value) }), [frozen, setFrozen]);
|
2020-10-25 11:17:37 +01:00
|
|
|
|
2020-10-28 09:10:12 +01:00
|
|
|
// save on form change. Because effect called on each event, debounce it.
|
2020-12-12 19:00:12 +01:00
|
|
|
const debouncedSaveMemo = useRef(
|
2023-07-25 15:50:04 +02:00
|
|
|
debounce(async m => {
|
|
|
|
wallet.setUTXOMetadata(output.txid, output.vout, { memo: m });
|
2020-10-28 09:10:12 +01:00
|
|
|
await saveToDisk();
|
|
|
|
}, 500),
|
|
|
|
);
|
2020-10-25 11:17:37 +01:00
|
|
|
useEffect(() => {
|
2020-12-12 19:00:12 +01:00
|
|
|
debouncedSaveMemo.current(memo);
|
|
|
|
}, [memo]);
|
2020-10-25 11:17:37 +01:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2020-12-12 19:00:12 +01:00
|
|
|
<OutputModal item={output} balanceUnit={wallet.getPreferredBalanceUnit()} />
|
2020-10-25 11:17:37 +01:00
|
|
|
<BlueSpacing20 />
|
|
|
|
<TextInput
|
2020-11-03 14:18:46 +01:00
|
|
|
testID="OutputMemo"
|
2020-10-25 11:17:37 +01:00
|
|
|
placeholder={loc.send.details_note_placeholder}
|
|
|
|
value={memo}
|
|
|
|
placeholderTextColor="#81868e"
|
|
|
|
style={[
|
|
|
|
mStyles.memoTextInput,
|
|
|
|
{
|
|
|
|
borderColor: colors.formBorder,
|
|
|
|
borderBottomColor: colors.formBorder,
|
|
|
|
backgroundColor: colors.inputBackgroundColor,
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
onChangeText={onMemoChange}
|
|
|
|
/>
|
2024-06-30 23:38:01 +02:00
|
|
|
<ListItem
|
|
|
|
title={loc.cc.freezeLabel}
|
|
|
|
containerStyle={transparentBackground}
|
|
|
|
Component={TouchableWithoutFeedback}
|
|
|
|
switch={switchValue}
|
|
|
|
/>
|
2020-10-25 11:17:37 +01:00
|
|
|
<BlueSpacing20 />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
OutputModalContent.propTypes = {
|
|
|
|
output: PropTypes.object,
|
|
|
|
wallet: PropTypes.object,
|
2020-10-26 18:50:03 +01:00
|
|
|
onUseCoin: PropTypes.func.isRequired,
|
2020-12-12 19:00:12 +01:00
|
|
|
frozen: PropTypes.bool.isRequired,
|
|
|
|
setFrozen: PropTypes.func.isRequired,
|
2020-10-25 11:17:37 +01:00
|
|
|
};
|
|
|
|
|
2020-10-22 14:24:47 +02:00
|
|
|
const CoinControl = () => {
|
2020-10-22 15:30:58 +02:00
|
|
|
const { colors } = useTheme();
|
2024-07-03 05:25:15 +02:00
|
|
|
const navigation = useExtendedNavigation();
|
2020-12-12 19:00:12 +01:00
|
|
|
const { width } = useWindowDimensions();
|
2024-06-30 19:17:55 +02:00
|
|
|
const bottomModalRef = useRef(null);
|
2021-03-09 13:59:36 +01:00
|
|
|
const { walletID, onUTXOChoose } = useRoute().params;
|
2024-05-31 17:52:29 +02:00
|
|
|
const { wallets, saveToDisk, sleep } = useStorage();
|
2021-03-09 13:59:36 +01:00
|
|
|
const wallet = wallets.find(w => w.getID() === walletID);
|
2020-11-03 09:41:50 +01:00
|
|
|
// sort by height ascending, txid , vout ascending
|
2020-11-22 08:43:11 +01:00
|
|
|
const utxo = wallet.getUtxo(true).sort((a, b) => a.height - b.height || a.txid.localeCompare(b.txid) || a.vout - b.vout);
|
2020-10-22 15:30:58 +02:00
|
|
|
const [output, setOutput] = useState();
|
2020-11-03 09:41:50 +01:00
|
|
|
const [loading, setLoading] = useState(true);
|
2020-12-12 19:00:12 +01:00
|
|
|
const [selected, setSelected] = useState([]);
|
|
|
|
const [frozen, setFrozen] = useState(
|
2023-07-25 15:50:04 +02:00
|
|
|
utxo.filter(out => wallet.getUTXOMetadata(out.txid, out.vout).frozen).map(({ txid, vout }) => `${txid}:${vout}`),
|
2020-12-12 19:00:12 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
// save frozen status. Because effect called on each event, debounce it.
|
|
|
|
const debouncedSaveFronen = useRef(
|
2023-07-25 15:50:04 +02:00
|
|
|
debounce(async frzn => {
|
2020-12-12 19:00:12 +01:00
|
|
|
utxo.forEach(({ txid, vout }) => {
|
2023-07-25 15:50:04 +02:00
|
|
|
wallet.setUTXOMetadata(txid, vout, { frozen: frzn.includes(`${txid}:${vout}`) });
|
2020-12-12 19:00:12 +01:00
|
|
|
});
|
|
|
|
await saveToDisk();
|
|
|
|
}, 500),
|
|
|
|
);
|
|
|
|
useEffect(() => {
|
|
|
|
debouncedSaveFronen.current(frozen);
|
|
|
|
}, [frozen]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2021-01-13 09:51:46 +01:00
|
|
|
(async () => {
|
|
|
|
try {
|
|
|
|
await Promise.race([wallet.fetchUtxo(), sleep(10000)]);
|
|
|
|
} catch (e) {
|
|
|
|
console.log('coincontrol wallet.fetchUtxo() failed'); // either sleep expired or fetchUtxo threw an exception
|
|
|
|
}
|
2020-12-12 19:00:12 +01:00
|
|
|
const freshUtxo = wallet.getUtxo(true);
|
2023-07-25 15:50:04 +02:00
|
|
|
setFrozen(freshUtxo.filter(out => wallet.getUTXOMetadata(out.txid, out.vout).frozen).map(({ txid, vout }) => `${txid}:${vout}`));
|
2020-12-12 19:00:12 +01:00
|
|
|
setLoading(false);
|
2021-01-13 09:51:46 +01:00
|
|
|
})();
|
|
|
|
}, [wallet, setLoading, sleep]);
|
2020-11-03 09:41:50 +01:00
|
|
|
|
2020-11-19 14:32:00 +01:00
|
|
|
const stylesHook = StyleSheet.create({
|
|
|
|
tip: {
|
|
|
|
backgroundColor: colors.ballOutgoingExpired,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2020-11-19 16:40:26 +01:00
|
|
|
const tipCoins = () => {
|
2022-01-24 11:28:03 +01:00
|
|
|
if (utxo.length === 0) return null;
|
|
|
|
|
|
|
|
let text = loc.cc.tip;
|
|
|
|
if (selected.length > 0) {
|
|
|
|
// show summ of coins if any selected
|
|
|
|
const summ = selected.reduce((prev, curr) => {
|
|
|
|
return prev + utxo.find(({ txid, vout }) => `${txid}:${vout}` === curr).value;
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
const value = formatBalance(summ, wallet.getPreferredBalanceUnit(), true);
|
|
|
|
text = loc.formatString(loc.cc.selected_summ, { value });
|
|
|
|
}
|
|
|
|
|
2020-11-22 08:01:54 +01:00
|
|
|
return (
|
2022-01-24 11:28:03 +01:00
|
|
|
<View style={[styles.tip, stylesHook.tip]}>
|
|
|
|
<Text style={{ color: colors.foregroundColor }}>{text}</Text>
|
|
|
|
</View>
|
2020-11-19 14:32:00 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-10-22 15:30:58 +02:00
|
|
|
const handleChoose = item => setOutput(item);
|
2020-10-26 18:50:03 +01:00
|
|
|
|
2024-07-24 16:56:10 +02:00
|
|
|
const handleUseCoin = async u => {
|
2024-07-24 18:10:08 +02:00
|
|
|
setOutput(null);
|
2020-10-26 18:50:03 +01:00
|
|
|
navigation.pop();
|
2023-07-25 15:50:04 +02:00
|
|
|
onUTXOChoose(u);
|
2020-10-26 18:50:03 +01:00
|
|
|
};
|
|
|
|
|
2020-12-12 19:00:12 +01:00
|
|
|
const handleMassFreeze = () => {
|
|
|
|
if (allFrozen) {
|
|
|
|
setFrozen(f => f.filter(i => !selected.includes(i))); // unfreeze
|
|
|
|
} else {
|
|
|
|
setFrozen(f => [...new Set([...f, ...selected])]); // freeze
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleMassUse = () => {
|
|
|
|
const fUtxo = utxo.filter(({ txid, vout }) => selected.includes(`${txid}:${vout}`));
|
|
|
|
handleUseCoin(fUtxo);
|
|
|
|
};
|
|
|
|
|
|
|
|
// check if any outputs are selected
|
|
|
|
const selectionStarted = selected.length > 0;
|
|
|
|
// check if all selected items are frozen
|
|
|
|
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);
|
|
|
|
|
2020-10-25 10:04:04 +01:00
|
|
|
const renderItem = p => {
|
2020-12-12 19:00:12 +01:00
|
|
|
const { memo } = wallet.getUTXOMetadata(p.item.txid, p.item.vout);
|
2020-10-29 19:42:40 +01:00
|
|
|
const change = wallet.addressIsChange(p.item.address);
|
2020-12-12 19:00:12 +01:00
|
|
|
const oFrozen = frozen.includes(`${p.item.txid}:${p.item.vout}`);
|
2020-12-01 22:38:18 +01:00
|
|
|
return (
|
2020-12-12 19:00:12 +01:00
|
|
|
<OutputList
|
2020-12-01 22:38:18 +01:00
|
|
|
balanceUnit={wallet.getPreferredBalanceUnit()}
|
|
|
|
item={p.item}
|
|
|
|
oMemo={memo}
|
2020-12-12 19:00:12 +01:00
|
|
|
frozen={oFrozen}
|
2020-12-01 22:38:18 +01:00
|
|
|
change={change}
|
2020-12-12 19:00:12 +01:00
|
|
|
onOpen={() => handleChoose(p.item)}
|
|
|
|
selected={selected.includes(`${p.item.txid}:${p.item.vout}`)}
|
|
|
|
selectionStarted={selectionStarted}
|
|
|
|
onSelect={() => {
|
|
|
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show
|
2023-07-25 15:50:04 +02:00
|
|
|
setSelected(s => [...s, `${p.item.txid}:${p.item.vout}`]);
|
2020-12-12 19:00:12 +01:00
|
|
|
}}
|
2021-01-24 02:38:14 +01:00
|
|
|
onDeSelect={() => {
|
|
|
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show
|
2023-07-25 15:50:04 +02:00
|
|
|
setSelected(s => s.filter(i => i !== `${p.item.txid}:${p.item.vout}`));
|
2021-01-24 02:38:14 +01:00
|
|
|
}}
|
2020-12-01 22:38:18 +01:00
|
|
|
/>
|
|
|
|
);
|
2020-10-25 10:04:04 +01:00
|
|
|
};
|
2020-10-22 14:24:47 +02:00
|
|
|
|
2020-12-12 19:00:12 +01:00
|
|
|
const renderOutputModalContent = () => {
|
|
|
|
const oFrozen = frozen.includes(`${output.txid}:${output.vout}`);
|
|
|
|
const setOFrozen = value => {
|
|
|
|
if (value) {
|
|
|
|
setFrozen(f => [...f, `${output.txid}:${output.vout}`]);
|
|
|
|
} else {
|
|
|
|
setFrozen(f => f.filter(i => i !== `${output.txid}:${output.vout}`));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return <OutputModalContent output={output} wallet={wallet} onUseCoin={handleUseCoin} frozen={oFrozen} setFrozen={setOFrozen} />;
|
|
|
|
};
|
|
|
|
|
2024-06-30 19:17:55 +02:00
|
|
|
useEffect(() => {
|
|
|
|
if (output) {
|
|
|
|
bottomModalRef.current?.present();
|
|
|
|
}
|
|
|
|
}, [output]);
|
|
|
|
|
2020-11-03 09:41:50 +01:00
|
|
|
if (loading) {
|
|
|
|
return (
|
2023-12-27 07:52:11 +01:00
|
|
|
<SafeArea style={[styles.center, { backgroundColor: colors.elevated }]}>
|
2020-11-03 09:41:50 +01:00
|
|
|
<ActivityIndicator testID="Loading" />
|
2023-12-27 07:52:11 +01:00
|
|
|
</SafeArea>
|
2020-11-03 09:41:50 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-22 14:24:47 +02:00
|
|
|
return (
|
2020-12-12 19:00:12 +01:00
|
|
|
<View style={[styles.root, { backgroundColor: colors.elevated }]}>
|
2020-10-29 09:32:55 +01:00
|
|
|
{utxo.length === 0 && (
|
|
|
|
<View style={styles.empty}>
|
|
|
|
<Text style={{ color: colors.foregroundColor }}>{loc.cc.empty}</Text>
|
|
|
|
</View>
|
|
|
|
)}
|
2020-11-19 14:32:00 +01:00
|
|
|
|
2020-11-22 09:54:58 +01:00
|
|
|
<BottomModal
|
2024-06-30 19:17:55 +02:00
|
|
|
ref={bottomModalRef}
|
2020-11-22 09:54:58 +01:00
|
|
|
onClose={() => {
|
2020-11-03 14:18:46 +01:00
|
|
|
Keyboard.dismiss();
|
|
|
|
setOutput(false);
|
|
|
|
}}
|
2024-06-30 23:38:01 +02:00
|
|
|
backgroundColor={colors.elevated}
|
2024-07-14 22:38:14 +02:00
|
|
|
footer={
|
|
|
|
<View style={mStyles.buttonContainer}>
|
2024-09-06 02:12:29 +02:00
|
|
|
<Button
|
|
|
|
testID="UseCoin"
|
|
|
|
title={loc.cc.use_coin}
|
|
|
|
onPress={async () => {
|
|
|
|
await bottomModalRef.current?.dismiss();
|
|
|
|
handleUseCoin([output]);
|
|
|
|
}}
|
|
|
|
/>
|
2024-07-14 22:38:14 +02:00
|
|
|
</View>
|
|
|
|
}
|
2024-07-26 00:54:38 +02:00
|
|
|
footerDefaultMargins
|
2024-07-16 18:59:17 +02:00
|
|
|
contentContainerStyle={[styles.modalContent, styles.modalMinHeight]}
|
2020-10-22 15:30:58 +02:00
|
|
|
>
|
2024-06-30 23:38:01 +02:00
|
|
|
{output && renderOutputModalContent()}
|
2020-11-22 09:54:58 +01:00
|
|
|
</BottomModal>
|
2020-12-12 19:00:12 +01:00
|
|
|
<FlatList
|
|
|
|
ListHeaderComponent={tipCoins}
|
|
|
|
data={utxo}
|
|
|
|
renderItem={renderItem}
|
|
|
|
keyExtractor={item => `${item.txid}:${item.vout}`}
|
|
|
|
contentInset={{ top: 0, left: 0, bottom: 70, right: 0 }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
{selectionStarted && (
|
|
|
|
<FContainer>
|
|
|
|
<FButton
|
|
|
|
onPress={handleMassFreeze}
|
|
|
|
text={allFrozen ? loc.cc.freezeLabel_un : loc.cc.freezeLabel}
|
|
|
|
icon={<Icon name="snowflake" size={buttonFontSize} type="font-awesome-5" color={colors.buttonAlternativeTextColor} />}
|
|
|
|
/>
|
|
|
|
<FButton
|
|
|
|
onPress={handleMassUse}
|
|
|
|
text={selected.length > 1 ? loc.cc.use_coins : loc.cc.use_coin}
|
|
|
|
icon={
|
|
|
|
<View style={styles.sendIcon}>
|
|
|
|
<Icon name="arrow-down" size={buttonFontSize} type="font-awesome" color={colors.buttonAlternativeTextColor} />
|
|
|
|
</View>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</FContainer>
|
|
|
|
)}
|
|
|
|
</View>
|
2020-10-22 14:24:47 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-10-22 15:30:58 +02:00
|
|
|
const styles = StyleSheet.create({
|
2020-10-28 11:00:26 +01:00
|
|
|
root: {
|
|
|
|
flex: 1,
|
|
|
|
},
|
2020-11-03 09:41:50 +01:00
|
|
|
center: {
|
|
|
|
justifyContent: 'center',
|
|
|
|
alignItems: 'center',
|
|
|
|
},
|
2020-10-22 15:30:58 +02:00
|
|
|
modalContent: {
|
|
|
|
padding: 22,
|
|
|
|
},
|
2024-07-26 02:18:01 +02:00
|
|
|
modalMinHeight: Platform.OS === 'android' ? { minHeight: 500 } : {},
|
2020-10-29 09:32:55 +01:00
|
|
|
empty: {
|
|
|
|
flex: 1,
|
|
|
|
justifyContent: 'center',
|
|
|
|
alignItems: 'center',
|
2020-11-19 14:32:00 +01:00
|
|
|
padding: 24,
|
|
|
|
},
|
|
|
|
tip: {
|
|
|
|
marginHorizontal: 16,
|
|
|
|
borderRadius: 12,
|
|
|
|
padding: 16,
|
|
|
|
marginVertical: 24,
|
2020-10-29 09:32:55 +01:00
|
|
|
},
|
2020-12-12 19:00:12 +01:00
|
|
|
sendIcon: {
|
|
|
|
transform: [{ rotate: '225deg' }],
|
|
|
|
},
|
2020-10-22 15:30:58 +02:00
|
|
|
});
|
|
|
|
|
2020-10-22 14:24:47 +02:00
|
|
|
export default CoinControl;
|