import React, { useMemo, useState, useContext, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { Avatar, Badge, Icon, ListItem as RNElementsListItem } from 'react-native-elements'; import { ActivityIndicator, FlatList, Keyboard, KeyboardAvoidingView, LayoutAnimation, PixelRatio, Platform, StyleSheet, Text, TextInput, TouchableWithoutFeedback, useWindowDimensions, View, } from 'react-native'; import { useRoute, useNavigation } from '@react-navigation/native'; import * as RNLocalize from 'react-native-localize'; import loc, { formatBalance } from '../../loc'; import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BlueSpacing10, BlueSpacing20 } from '../../BlueComponents'; import navigationStyle from '../../components/navigationStyle'; import BottomModal from '../../components/BottomModal'; import { FContainer, FButton } from '../../components/FloatButtons'; import debounce from '../../blue_modules/debounce'; import { BlueStorageContext } from '../../blue_modules/storage-context'; import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import ListItem from '../../components/ListItem'; import SafeArea from '../../components/SafeArea'; const FrozenBadge = () => { const { colors } = useTheme(); const oStyles = StyleSheet.create({ freeze: { backgroundColor: colors.redBG, borderWidth: 0 }, freezeText: { color: colors.redText }, }); return ; }; const ChangeBadge = () => { const { colors } = useTheme(); const oStyles = StyleSheet.create({ change: { backgroundColor: colors.buttonDisabledBackgroundColor, borderWidth: 0 }, changeText: { color: colors.alternativeTextColor }, }); return ; }; const OutputList = ({ item: { address, txid, value, vout, confirmations = 0 }, balanceUnit = BitcoinUnit.BTC, oMemo, frozen, change, onOpen, selected, selectionStarted, onSelect, onDeSelect, }) => { const { colors } = useTheme(); const { txMetadata } = useContext(BlueStorageContext); const memo = oMemo || txMetadata[txid]?.memo || ''; const color = `#${txid.substring(0, 6)}`; const amount = formatBalance(value, balanceUnit, true); const oStyles = StyleSheet.create({ container: { borderBottomColor: colors.lightBorder, backgroundColor: colors.elevated }, containerSelected: { backgroundColor: colors.ballOutgoingExpired, borderBottomColor: 'rgba(0, 0, 0, 0)', }, avatar: { borderColor: 'white', borderWidth: 1, backgroundColor: color }, amount: { fontWeight: 'bold', color: colors.foregroundColor }, memo: { fontSize: 13, marginTop: 3, color: colors.alternativeTextColor }, }); let onPress = onOpen; if (selectionStarted) { onPress = selected ? onDeSelect : onSelect; } return ( {amount} {memo || address} {change && } {frozen && } ); }; OutputList.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, 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 { colors } = useTheme(); const { txMetadata } = useContext(BlueStorageContext); 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({ container: { paddingHorizontal: 0, borderBottomColor: colors.lightBorder, backgroundColor: colors.elevated }, 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 }, }); const confirmationsFormatted = new Intl.NumberFormat(RNLocalize.getLocales()[0].languageCode, { maximumSignificantDigits: 3 }).format( confirmations, ); return ( {amount} {loc.formatString(loc.transactions.list_conf, { number: confirmationsFormatted })} {memo ? ( <> {memo} ) : null} {address} {fullId} ); }; 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({ memoTextInput: { flexDirection: 'row', borderWidth: 1, borderBottomWidth: 0.5, minHeight: 44, height: 44, alignItems: 'center', marginVertical: 8, borderRadius: 4, paddingHorizontal: 8, color: '#81868e', }, buttonContainer: { height: 45, }, }); const OutputModalContent = ({ output, wallet, onUseCoin, frozen, setFrozen }) => { const { colors } = useTheme(); const { txMetadata, saveToDisk } = useContext(BlueStorageContext); const [memo, setMemo] = useState(wallet.getUTXOMetadata(output.txid, output.vout).memo || txMetadata[output.txid]?.memo || ''); const onMemoChange = value => setMemo(value); const switchValue = useMemo(() => ({ value: frozen, onValueChange: value => setFrozen(value) }), [frozen, setFrozen]); // save on form change. Because effect called on each event, debounce it. const debouncedSaveMemo = useRef( debounce(async m => { wallet.setUTXOMetadata(output.txid, output.vout, { memo: m }); await saveToDisk(); }, 500), ); useEffect(() => { debouncedSaveMemo.current(memo); }, [memo]); return ( <>