BlueWallet/screen/send/CoinControl.tsx

605 lines
20 KiB
TypeScript
Raw Normal View History

2024-11-09 19:28:47 +00:00
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2024-11-09 15:14:13 +00:00
import { RouteProp, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { Avatar, Badge, Icon, ListItem as RNElementsListItem } from '@rneui/themed';
2020-10-29 11:32:55 +03:00
import {
ActivityIndicator,
2020-10-29 11:32:55 +03:00
FlatList,
Keyboard,
2020-12-12 21:00:12 +03:00
LayoutAnimation,
PixelRatio,
2024-07-16 12:59:17 -04:00
Platform,
2020-10-29 11:32:55 +03:00
StyleSheet,
Text,
TextInput,
TouchableWithoutFeedback,
2020-12-12 21:00:12 +03:00
useWindowDimensions,
View,
2020-10-29 11:32:55 +03:00
} from 'react-native';
import * as RNLocalize from 'react-native-localize';
2024-11-09 15:14:13 +00:00
import debounce from '../../blue_modules/debounce';
2024-05-20 10:54:13 +01:00
import { BlueSpacing10, BlueSpacing20 } from '../../BlueComponents';
2024-11-09 15:14:13 +00:00
import { TWallet, Utxo } from '../../class/wallets/types';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
2023-11-15 04:40:22 -04:00
import Button from '../../components/Button';
2024-05-20 10:54:13 +01:00
import { FButton, FContainer } from '../../components/FloatButtons';
2024-11-09 19:28:47 +00:00
import HeaderMenuButton from '../../components/HeaderMenuButton';
2023-12-16 17:44:35 -04:00
import ListItem from '../../components/ListItem';
import SafeArea from '../../components/SafeArea';
2024-05-20 10:54:13 +01:00
import { useTheme } from '../../components/themes';
2024-11-09 19:28:47 +00:00
import { Action } from '../../components/types';
2024-05-31 13:18:01 -04:00
import { useStorage } from '../../hooks/context/useStorage';
2024-07-02 23:25:15 -04:00
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
2024-11-09 15:14:13 +00:00
import loc, { formatBalance } from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
2024-11-09 19:28:47 +00:00
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
2024-11-09 15:14:13 +00:00
type NavigationProps = NativeStackNavigationProp<SendDetailsStackParamList, 'CoinControl'>;
type RouteProps = RouteProp<SendDetailsStackParamList, 'CoinControl'>;
2024-11-09 15:14:13 +00:00
const FrozenBadge: React.FC = () => {
2020-12-12 21:00:12 +03:00
const { colors } = useTheme();
const oStyles = StyleSheet.create({
2024-11-09 15:14:13 +00:00
freeze: { backgroundColor: colors.redBG, borderWidth: 0, marginLeft: 4 },
freezeText: { color: colors.redText, marginTop: -1 },
2020-12-12 21:00:12 +03:00
});
2020-12-13 10:49:07 +03:00
return <Badge value={loc.cc.freeze} badgeStyle={oStyles.freeze} textStyle={oStyles.freezeText} />;
2020-12-12 21:00:12 +03:00
};
2024-11-09 15:14:13 +00:00
const ChangeBadge: React.FC = () => {
2020-12-12 21:00:12 +03:00
const { colors } = useTheme();
const oStyles = StyleSheet.create({
2024-11-09 15:14:13 +00:00
change: { backgroundColor: colors.buttonDisabledBackgroundColor, borderWidth: 0, marginLeft: 4 },
changeText: { color: colors.alternativeTextColor, marginTop: -1 },
2020-12-12 21:00:12 +03:00
});
2020-12-13 10:49:07 +03:00
return <Badge value={loc.cc.change} badgeStyle={oStyles.change} textStyle={oStyles.changeText} />;
2020-12-12 21:00:12 +03:00
};
2024-11-09 15:14:13 +00:00
type TOutputListProps = {
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,
oMemo,
frozen,
2020-12-12 21:00:12 +03:00
change,
onOpen,
selected,
selectionStarted,
onSelect,
onDeSelect,
2024-11-09 15:14:13 +00:00
}: TOutputListProps) => {
2020-10-22 15:24:47 +03:00
const { colors } = useTheme();
const { txMetadata } = useStorage();
2020-10-28 11:46:40 +03:00
const memo = oMemo || txMetadata[txid]?.memo || '';
2020-10-22 15:24:47 +03:00
const color = `#${txid.substring(0, 6)}`;
const amount = formatBalance(value, balanceUnit, true);
2020-10-22 15:24:47 +03:00
const oStyles = StyleSheet.create({
2020-12-12 21:00:12 +03:00
container: { borderBottomColor: colors.lightBorder, backgroundColor: colors.elevated },
containerSelected: {
backgroundColor: colors.ballOutgoingExpired,
borderBottomColor: 'rgba(0, 0, 0, 0)',
2020-12-12 21:00:12 +03:00
},
avatar: { borderColor: 'white', borderWidth: 1, backgroundColor: color },
amount: { fontWeight: 'bold', color: colors.foregroundColor },
memo: { fontSize: 13, marginTop: 3, color: colors.alternativeTextColor },
});
2020-12-12 21:00:12 +03:00
let onPress = onOpen;
if (selectionStarted) {
onPress = selected ? onDeSelect : onSelect;
}
2020-10-22 15:24:47 +03:00
return (
2023-12-16 17:44:35 -04:00
<RNElementsListItem bottomDivider onPress={onPress} containerStyle={selected ? oStyles.containerSelected : oStyles.container}>
2024-11-09 15:14:13 +00:00
<Avatar
rounded
size={40}
containerStyle={oStyles.avatar}
2020-12-12 21:00:12 +03:00
onPress={selected ? onDeSelect : onSelect}
2024-11-09 15:14:13 +00:00
icon={selected ? { name: 'check', type: 'font-awesome-6' } : undefined}
2020-12-12 21:00:12 +03:00
/>
2023-12-16 17:44:35 -04:00
<RNElementsListItem.Content>
<RNElementsListItem.Title style={oStyles.amount}>{amount}</RNElementsListItem.Title>
<RNElementsListItem.Subtitle style={oStyles.memo} numberOfLines={1} ellipsizeMode="middle">
{memo || address}
2023-12-16 17:44:35 -04:00
</RNElementsListItem.Subtitle>
</RNElementsListItem.Content>
2024-11-09 15:14:13 +00:00
<View style={styles.badges}>
{frozen && <FrozenBadge />}
{change && <ChangeBadge />}
</View>
2023-12-16 17:44:35 -04:00
</RNElementsListItem>
2020-12-12 21:00:12 +03:00
);
};
2024-11-09 15:14:13 +00:00
type TOutputModalProps = {
item: Utxo;
balanceUnit: string;
oMemo?: string;
2020-12-12 21:00:12 +03:00
};
2024-11-09 15:14:13 +00:00
const OutputModal: React.FC<TOutputModalProps> = ({
item: { address, txid, value, vout, confirmations = 0 },
balanceUnit = BitcoinUnit.BTC,
oMemo,
}) => {
2020-12-12 21:00:12 +03:00
const { colors } = useTheme();
const { txMetadata } = useStorage();
2020-12-12 21:00:12 +03: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 17:38:01 -04:00
container: { paddingHorizontal: 0, borderBottomColor: colors.lightBorder, backgroundColor: 'transparent' },
2020-12-12 21:00:12 +03: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 },
});
2021-01-29 23:57:52 -05:00
const confirmationsFormatted = new Intl.NumberFormat(RNLocalize.getLocales()[0].languageCode, { maximumSignificantDigits: 3 }).format(
confirmations,
);
2020-10-22 15:24:47 +03:00
return (
2023-12-16 17:44:35 -04:00
<RNElementsListItem bottomDivider containerStyle={oStyles.container}>
2024-11-09 15:14:13 +00:00
<Avatar rounded size={40} containerStyle={oStyles.avatar} />
2023-12-16 17:44:35 -04:00
<RNElementsListItem.Content>
<RNElementsListItem.Title numberOfLines={1} adjustsFontSizeToFit style={oStyles.amount}>
2020-12-12 21:00:12 +03:00
{amount}
<View style={oStyles.tranContainer}>
<Text style={oStyles.tranText}>{loc.formatString(loc.transactions.list_conf, { number: confirmationsFormatted })}</Text>
2020-12-12 21:00:12 +03:00
</View>
2023-12-16 17:44:35 -04:00
</RNElementsListItem.Title>
2020-12-12 21:00:12 +03:00
{memo ? (
2020-10-23 13:27:03 +03:00
<>
2023-12-16 17:44:35 -04:00
<RNElementsListItem.Subtitle style={oStyles.memo}>{memo}</RNElementsListItem.Subtitle>
2020-10-28 13:00:26 +03:00
<BlueSpacing10 />
2020-10-23 13:27:03 +03:00
</>
2020-12-12 21:00:12 +03:00
) : null}
2023-12-16 17:44:35 -04:00
<RNElementsListItem.Subtitle style={oStyles.memo}>{address}</RNElementsListItem.Subtitle>
2020-12-12 21:00:12 +03:00
<BlueSpacing10 />
2023-12-16 17:44:35 -04:00
<RNElementsListItem.Subtitle style={oStyles.memo}>{fullId}</RNElementsListItem.Subtitle>
</RNElementsListItem.Content>
</RNElementsListItem>
2020-10-22 15:24:47 +03:00
);
};
2020-10-25 13:17:37 +03: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 09:29:03 +03:00
buttonContainer: {
height: 45,
2024-07-16 12:59:17 -04:00
marginBottom: 36,
marginHorizontal: 24,
2020-11-06 09:29:03 +03:00
},
2020-10-25 13:17:37 +03:00
});
2024-11-09 15:14:13 +00:00
type TOutputModalContentProps = {
output: Utxo;
wallet: TWallet;
onUseCoin: (u: Utxo[]) => void;
frozen: boolean;
setFrozen: (value: boolean) => void;
};
2024-06-30 17:38:01 -04:00
const transparentBackground = { backgroundColor: 'transparent' };
2024-11-09 15:14:13 +00:00
const OutputModalContent: React.FC<TOutputModalContentProps> = ({ output, wallet, onUseCoin, frozen, setFrozen }) => {
2020-10-25 13:17:37 +03:00
const { colors } = useTheme();
const { txMetadata, saveToDisk } = useStorage();
2024-11-09 15:14:13 +00:00
const [memo, setMemo] = useState<string>(wallet.getUTXOMetadata(output.txid, output.vout).memo || txMetadata[output.txid]?.memo || '');
const switchValue = useMemo(() => ({ value: frozen, onValueChange: (value: boolean) => setFrozen(value) }), [frozen, setFrozen]);
const onMemoChange = (value: string) => setMemo(value);
2020-10-25 13:17:37 +03:00
2020-10-28 11:10:12 +03:00
// save on form change. Because effect called on each event, debounce it.
2020-12-12 21:00:12 +03:00
const debouncedSaveMemo = useRef(
debounce(async m => {
wallet.setUTXOMetadata(output.txid, output.vout, { memo: m });
2020-10-28 11:10:12 +03:00
await saveToDisk();
}, 500),
);
2020-10-25 13:17:37 +03:00
useEffect(() => {
2020-12-12 21:00:12 +03:00
debouncedSaveMemo.current(memo);
}, [memo]);
2020-10-25 13:17:37 +03:00
return (
2024-11-09 15:14:13 +00:00
<View>
2020-12-12 21:00:12 +03:00
<OutputModal item={output} balanceUnit={wallet.getPreferredBalanceUnit()} />
2020-10-25 13:17:37 +03:00
<BlueSpacing20 />
<TextInput
2020-11-03 16:18:46 +03:00
testID="OutputMemo"
2020-10-25 13:17:37 +03: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 17:38:01 -04:00
<ListItem
title={loc.cc.freezeLabel}
containerStyle={transparentBackground}
Component={TouchableWithoutFeedback}
switch={switchValue}
/>
2020-10-25 13:17:37 +03:00
<BlueSpacing20 />
2024-08-24 13:35:13 -04:00
</View>
2020-10-25 13:17:37 +03:00
);
};
2024-11-09 19:28:47 +00:00
enum ESortDirections {
asc = 'asc',
desc = 'desc',
}
enum ESortTypes {
height = 'height',
label = 'label',
value = 'value',
frozen = 'frozen',
}
2024-11-09 15:14:13 +00:00
const CoinControl: React.FC = () => {
2020-10-22 16:30:58 +03:00
const { colors } = useTheme();
2024-11-09 15:14:13 +00:00
const navigation = useExtendedNavigation<NavigationProps>();
2020-12-12 21:00:12 +03:00
const { width } = useWindowDimensions();
2024-11-09 15:14:13 +00:00
const bottomModalRef = useRef<BottomModalHandle | null>(null);
const { walletID } = useRoute<RouteProps>().params;
const { wallets, saveToDisk, sleep } = useStorage();
2024-11-09 19:28:47 +00:00
const [sortDirection, setSortDirection] = useState<ESortDirections>(ESortDirections.asc);
const [sortType, setSortType] = useState<ESortTypes>(ESortTypes.height);
const wallet = useMemo(() => wallets.find(w => w.getID() === walletID) as TWallet, [walletID, wallets]);
const [frozen, setFrozen] = useState<string[]>(
wallet
.getUtxo(true)
.filter(out => wallet.getUTXOMetadata(out.txid, out.vout).frozen)
.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]);
2024-11-09 15:14:13 +00:00
const [output, setOutput] = useState<Utxo | undefined>();
const [loading, setLoading] = useState<boolean>(true);
const [selected, setSelected] = useState<string[]>([]);
2020-12-12 21:00:12 +03:00
// save frozen status. Because effect called on each event, debounce it.
const debouncedSaveFronen = useRef(
debounce(async frzn => {
2024-11-09 15:14:13 +00:00
utxos.forEach(({ txid, vout }) => {
wallet.setUTXOMetadata(txid, vout, { frozen: frzn.includes(`${txid}:${vout}`) });
2020-12-12 21:00:12 +03:00
});
await saveToDisk();
}, 500),
);
useEffect(() => {
debouncedSaveFronen.current(frozen);
}, [frozen]);
useEffect(() => {
(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 21:00:12 +03:00
const freshUtxo = wallet.getUtxo(true);
setFrozen(freshUtxo.filter(out => wallet.getUTXOMetadata(out.txid, out.vout).frozen).map(({ txid, vout }) => `${txid}:${vout}`));
2020-12-12 21:00:12 +03:00
setLoading(false);
})();
}, [wallet, setLoading, sleep]);
const stylesHook = StyleSheet.create({
tip: {
backgroundColor: colors.ballOutgoingExpired,
},
});
2020-11-19 16:40:26 +01:00
const tipCoins = () => {
2024-11-09 15:14:13 +00:00
if (utxos.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) => {
2024-11-09 15:14:13 +00:00
return prev + (utxos.find(({ txid, vout }) => `${txid}:${vout}` === curr) as Utxo).value;
}, 0);
const value = formatBalance(summ, wallet.getPreferredBalanceUnit(), true);
text = loc.formatString(loc.cc.selected_summ, { value });
}
2020-11-22 10:01:54 +03:00
return (
<View style={[styles.tip, stylesHook.tip]}>
<Text style={{ color: colors.foregroundColor }}>{text}</Text>
</View>
);
};
2024-11-09 15:14:13 +00:00
const handleChoose = (item: Utxo) => setOutput(item);
2020-10-26 20:50:03 +03:00
2024-11-09 15:14:13 +00:00
const handleUseCoin = async (u: Utxo[]) => {
setOutput(undefined);
// @ts-ignore navigation WTF
navigation.navigate('SendDetailsRoot', {
screen: 'SendDetails',
params: {
utxos: u,
},
merge: true,
});
2020-10-26 20:50:03 +03:00
};
2020-12-12 21:00:12 +03:00
const handleMassFreeze = () => {
if (allFrozen) {
setFrozen(f => f.filter(i => !selected.includes(i))); // unfreeze
} else {
setFrozen(f => [...new Set([...f, ...selected])]); // freeze
}
};
const handleMassUse = () => {
2024-11-09 15:14:13 +00:00
const fUtxo = utxos.filter(({ txid, vout }) => selected.includes(`${txid}:${vout}`));
2020-12-12 21:00:12 +03:00
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);
2024-11-09 15:14:13 +00:00
const renderItem = (p: { item: Utxo }) => {
2020-12-12 21:00:12 +03:00
const { memo } = wallet.getUTXOMetadata(p.item.txid, p.item.vout);
2020-10-29 21:42:40 +03:00
const change = wallet.addressIsChange(p.item.address);
2020-12-12 21:00:12 +03:00
const oFrozen = frozen.includes(`${p.item.txid}:${p.item.vout}`);
return (
2020-12-12 21:00:12 +03:00
<OutputList
balanceUnit={wallet.getPreferredBalanceUnit()}
item={p.item}
oMemo={memo}
2020-12-12 21:00:12 +03:00
frozen={oFrozen}
change={change}
2020-12-12 21:00:12 +03:00
onOpen={() => handleChoose(p.item)}
selected={selected.includes(`${p.item.txid}:${p.item.vout}`)}
selectionStarted={selectionStarted}
onSelect={() => {
2024-11-09 15:14:13 +00:00
setSelected(s => {
if (s.length === 0) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // animate buttons show
}
return [...s, `${p.item.txid}:${p.item.vout}`];
});
2020-12-12 21:00:12 +03:00
}}
onDeSelect={() => {
2024-11-09 15:14:13 +00:00
setSelected(s => {
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;
});
}}
/>
);
};
2020-10-22 15:24:47 +03:00
2024-11-09 15:14:13 +00:00
const renderOutputModalContent = (o: Utxo | undefined) => {
if (!o) {
return null;
}
const oFrozen = frozen.includes(`${o.txid}:${o.vout}`);
const setOFrozen = (value: boolean) => {
2020-12-12 21:00:12 +03:00
if (value) {
2024-11-09 15:14:13 +00:00
setFrozen(f => [...f, `${o.txid}:${o.vout}`]);
2020-12-12 21:00:12 +03:00
} else {
2024-11-09 15:14:13 +00:00
setFrozen(f => f.filter(i => i !== `${o.txid}:${o.vout}`));
2020-12-12 21:00:12 +03:00
}
};
2024-11-09 15:14:13 +00:00
return <OutputModalContent output={o} wallet={wallet} onUseCoin={handleUseCoin} frozen={oFrozen} setFrozen={setOFrozen} />;
2020-12-12 21:00:12 +03:00
};
2024-06-30 13:17:55 -04:00
useEffect(() => {
if (output) {
bottomModalRef.current?.present();
}
}, [output]);
2024-11-09 19:28:47 +00:00
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) {
return (
<SafeArea style={[styles.center, { backgroundColor: colors.elevated }]}>
<ActivityIndicator testID="Loading" />
</SafeArea>
);
}
2020-10-22 15:24:47 +03:00
return (
2020-12-12 21:00:12 +03:00
<View style={[styles.root, { backgroundColor: colors.elevated }]}>
2024-11-09 15:14:13 +00:00
{utxos.length === 0 && (
2020-10-29 11:32:55 +03:00
<View style={styles.empty}>
<Text style={{ color: colors.foregroundColor }}>{loc.cc.empty}</Text>
</View>
)}
<BottomModal
2024-06-30 13:17:55 -04:00
ref={bottomModalRef}
onClose={() => {
2020-11-03 16:18:46 +03:00
Keyboard.dismiss();
2024-11-09 15:14:13 +00:00
setOutput(undefined);
2020-11-03 16:18:46 +03:00
}}
2024-06-30 17:38:01 -04:00
backgroundColor={colors.elevated}
2024-11-09 19:28:47 +00:00
contentContainerStyle={styles.modalMinHeight}
2024-07-14 16:38:14 -04:00
footer={
<View style={mStyles.buttonContainer}>
<Button
testID="UseCoin"
title={loc.cc.use_coin}
onPress={async () => {
2024-11-09 15:14:13 +00:00
if (!output) throw new Error('output is not set');
await bottomModalRef.current?.dismiss();
handleUseCoin([output]);
}}
/>
2024-07-14 16:38:14 -04:00
</View>
}
2020-10-22 16:30:58 +03:00
>
2024-11-09 15:14:13 +00:00
{renderOutputModalContent(output)}
</BottomModal>
2020-12-12 21:00:12 +03:00
<FlatList
ListHeaderComponent={tipCoins}
2024-11-09 15:14:13 +00:00
data={utxos}
2020-12-12 21:00:12 +03:00
renderItem={renderItem}
keyExtractor={item => `${item.txid}:${item.vout}`}
2024-11-09 19:28:47 +00:00
contentInset={styles.listContent}
2020-12-12 21:00:12 +03:00
/>
{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 15:24:47 +03:00
);
};
2020-10-22 16:30:58 +03:00
const styles = StyleSheet.create({
2020-10-28 13:00:26 +03:00
root: {
flex: 1,
},
center: {
justifyContent: 'center',
alignItems: 'center',
},
modalMinHeight: Platform.OS === 'android' ? { minHeight: 530 } : {},
2020-10-29 11:32:55 +03:00
empty: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 24,
},
tip: {
marginHorizontal: 16,
borderRadius: 12,
padding: 16,
marginVertical: 24,
2020-10-29 11:32:55 +03:00
},
2020-12-12 21:00:12 +03:00
sendIcon: {
transform: [{ rotate: '225deg' }],
},
2024-11-09 15:14:13 +00:00
badges: {
flexDirection: 'row',
},
2024-11-09 19:28:47 +00:00
listContent: {
top: 0,
left: 0,
bottom: 70,
right: 0,
},
2020-10-22 16:30:58 +03:00
});
2020-10-22 15:24:47 +03:00
export default CoinControl;