* feat: redesign transaction detail screen with unified layout and Lottie pending animation * ADD: decode OP_RETURN payload as UTF-8 text in transaction detail Co-authored-by: Cursor <cursoragent@cursor.com> * REF: transaction detail redesign (themes, pending icon, loc) Co-authored-by: Cursor <cursoragent@cursor.com> * REF: remove deprecated TransactionDetails, TransactionStatus and getTransactionStatusOptions Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: resolve lint errors (unused vars, styles, loc keys, no-bitwise, inline styles) Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: remove redundant !tx check in transaction detail guard Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: show transaction not available when tx not found after load Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: remove unused transaction prop type from TransactionDetail Co-authored-by: Cursor <cursoragent@cursor.com> * TST: update UTXO note E2E to use new transaction detail note prompt UI Co-authored-by: Cursor <cursoragent@cursor.com> * simplify changes on the PR for review * remove unused loc * remove unchanged colors * better offline support for tx details * remove unused key loc * fix code review issues * fix balance * fix tests * REF: address PR #8289 review feedback * redesigned wallets details * fix lint * fix lint * fix bip84 test * fix test * fix tests * fix tests * fix: truncation and sendTo logic display * fix: new arch fixes * fix: lint * fix: crash on status update * fix: lint and tests * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * Potential fix for pull request finding 'Identical operands' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * fix style * fix merge master * Merge branch 'wallet-details' of https://github.com/BlueWallet/BlueWallet into wallet-details * fix loc * fix loc * fix style * improve coin control from wallet details * fix: e2e * fix: WalletDetails * fix: flat * fix: e2e * fix: e2e * Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * fix: remove notifications dialogs * fix: second button title --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Ivan Vershigora <ivan.vershigora@gmail.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Co-authored-by: Overtorment <overtorment@gmail.com>
227 lines
7.8 KiB
TypeScript
227 lines
7.8 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { RouteProp, useRoute } from '@react-navigation/native';
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import { StyleSheet, Text, TextInput, View } from 'react-native';
|
|
import debounce from '../../blue_modules/debounce';
|
|
import Avatar from '../../components/Avatar';
|
|
import ListItem from '../../components/ListItem';
|
|
import { BlueSpacing10 } from '../../components/BlueSpacing';
|
|
import Button from '../../components/Button';
|
|
import { useTheme } from '../../components/themes';
|
|
import loc, { formatBalance } from '../../loc';
|
|
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
|
import { goFromCoinControlToSendDetails } from '../../navigation/goFromCoinControlToSendDetails';
|
|
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
|
|
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
|
import { useStorage } from '../../hooks/context/useStorage';
|
|
import * as RNLocalize from 'react-native-localize';
|
|
import { useKeyboard } from '../../hooks/useKeyboard';
|
|
import HeaderRightButton from '../../components/HeaderRightButton';
|
|
|
|
type RouteProps = RouteProp<SendDetailsStackParamList, 'CoinControlOutput'>;
|
|
type NavigationProps = NativeStackNavigationProp<SendDetailsStackParamList, 'CoinControlOutput'>;
|
|
|
|
const CoinControlOutputSheet: React.FC = () => {
|
|
const navigation = useExtendedNavigation<NavigationProps>();
|
|
const route = useRoute<RouteProps>();
|
|
const { walletID, utxo } = route.params;
|
|
const { wallets, txMetadata, saveToDisk } = useStorage();
|
|
const wallet = useMemo(() => wallets.find(w => w.getID() === walletID), [walletID, wallets]);
|
|
const { colors } = useTheme();
|
|
const { isVisible } = useKeyboard();
|
|
|
|
const [memo, setMemo] = useState<string>('');
|
|
const [frozen, setFrozen] = useState<boolean>(false);
|
|
const [loading, setLoading] = useState<boolean>(true);
|
|
|
|
useEffect(() => {
|
|
if (!wallet) return;
|
|
const meta = wallet.getUTXOMetadata(utxo.txid, utxo.vout);
|
|
setMemo(meta.memo || txMetadata[utxo.txid]?.memo || '');
|
|
setFrozen(Boolean(meta.frozen));
|
|
setLoading(false);
|
|
}, [txMetadata, utxo.txid, utxo.vout, wallet]);
|
|
|
|
const switchValue = useMemo(
|
|
() => ({
|
|
value: frozen,
|
|
testID: 'FreezeSwitch',
|
|
onValueChange: async (value: boolean) => {
|
|
if (!wallet) return;
|
|
setFrozen(value);
|
|
wallet.setUTXOMetadata(utxo.txid, utxo.vout, { frozen: value });
|
|
await saveToDisk();
|
|
},
|
|
}),
|
|
[frozen, saveToDisk, utxo.txid, utxo.vout, wallet],
|
|
);
|
|
|
|
const onMemoChange = (value: string) => setMemo(value);
|
|
|
|
const debouncedSaveMemo = useRef(
|
|
debounce(async m => {
|
|
if (!wallet) return;
|
|
wallet.setUTXOMetadata(utxo.txid, utxo.vout, { memo: m });
|
|
await saveToDisk();
|
|
}, 500),
|
|
);
|
|
|
|
useEffect(() => {
|
|
debouncedSaveMemo.current(memo);
|
|
}, [memo]);
|
|
|
|
const amount = formatBalance(utxo.value, wallet?.getPreferredBalanceUnit?.() ?? BitcoinUnit.BTC, true);
|
|
const color = `#${utxo.txid.substring(0, 6)}`;
|
|
const confirmationsFormatted = useMemo(
|
|
() => new Intl.NumberFormat(RNLocalize.getLocales()[0].languageCode, { maximumSignificantDigits: 3 }).format(utxo.confirmations ?? 0),
|
|
[utxo.confirmations],
|
|
);
|
|
|
|
const handleUseCoin = useCallback(async () => {
|
|
if (!wallet) return;
|
|
debouncedSaveMemo.current.cancel();
|
|
wallet.setUTXOMetadata(utxo.txid, utxo.vout, { memo });
|
|
await saveToDisk();
|
|
goFromCoinControlToSendDetails(navigation, walletID, [utxo]);
|
|
}, [memo, navigation, saveToDisk, utxo, wallet, walletID]);
|
|
|
|
const applyChangesAndClose = useCallback(async () => {
|
|
if (!wallet) return;
|
|
debouncedSaveMemo.current.cancel();
|
|
wallet.setUTXOMetadata(utxo.txid, utxo.vout, { memo });
|
|
await saveToDisk();
|
|
navigation.goBack();
|
|
}, [memo, navigation, saveToDisk, utxo.txid, utxo.vout, wallet]);
|
|
|
|
if (!wallet) {
|
|
return (
|
|
<View style={[styles.center, { backgroundColor: colors.elevated }]}>
|
|
<Text style={{ color: colors.foregroundColor }}>{loc.wallets.import_discovery_no_wallets}</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={[styles.root, { backgroundColor: colors.elevated }]}>
|
|
<View style={styles.floatingDoneButtonContainer}>
|
|
<HeaderRightButton testID="CoinControlOutputDone" title={loc.send.input_done} onPress={applyChangesAndClose} disabled={loading} />
|
|
</View>
|
|
<View style={styles.flex}>
|
|
<View style={styles.headerContainer}>
|
|
<View style={styles.rowContent}>
|
|
<Avatar rounded size={40} containerStyle={[styles.avatar, { backgroundColor: color }]} />
|
|
<View style={styles.listContent}>
|
|
<Text numberOfLines={1} style={[styles.amount, { color: colors.foregroundColor }]}>
|
|
{amount}
|
|
</Text>
|
|
<View style={styles.tranContainer}>
|
|
<Text style={[styles.tranText, { color: colors.alternativeTextColor }]}>
|
|
{loc.formatString(loc.transactions.list_conf, { number: confirmationsFormatted })}
|
|
</Text>
|
|
</View>
|
|
{memo ? (
|
|
<>
|
|
<Text style={[styles.memo, { color: colors.alternativeTextColor }]}>{memo}</Text>
|
|
<BlueSpacing10 />
|
|
</>
|
|
) : null}
|
|
<Text style={[styles.memo, { color: colors.alternativeTextColor }]}>{utxo.address}</Text>
|
|
<BlueSpacing10 />
|
|
<Text style={[styles.memo, { color: colors.alternativeTextColor }]}>{`${utxo.txid}:${utxo.vout}`}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.content}>
|
|
<TextInput
|
|
testID="OutputMemo"
|
|
placeholder={loc.send.details_note_placeholder}
|
|
value={memo}
|
|
placeholderTextColor="#81868e"
|
|
editable={!loading}
|
|
style={[
|
|
styles.memoTextInput,
|
|
{
|
|
borderColor: colors.formBorder,
|
|
borderBottomColor: colors.formBorder,
|
|
backgroundColor: colors.inputBackgroundColor,
|
|
color: colors.foregroundColor,
|
|
},
|
|
]}
|
|
onChangeText={onMemoChange}
|
|
/>
|
|
<ListItem title={loc.cc.freezeLabel} switch={switchValue} bottomDivider={false} />
|
|
</View>
|
|
|
|
<View style={styles.buttonContainer}>
|
|
{!isVisible && <Button testID="UseCoin" title={loc.cc.use_coin} onPress={handleUseCoin} disabled={loading} />}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
root: {
|
|
flex: 1,
|
|
paddingHorizontal: 24,
|
|
},
|
|
flex: {
|
|
flex: 1,
|
|
},
|
|
center: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
headerContainer: {
|
|
paddingHorizontal: 0,
|
|
borderBottomColor: 'transparent',
|
|
backgroundColor: 'transparent',
|
|
},
|
|
rowContent: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 12,
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
borderBottomColor: 'transparent',
|
|
gap: 10,
|
|
},
|
|
listContent: {
|
|
flex: 1,
|
|
},
|
|
avatar: { borderColor: 'white', borderWidth: 1 },
|
|
amount: { fontWeight: 'bold' },
|
|
tranContainer: { paddingLeft: 20 },
|
|
tranText: { fontWeight: 'normal', fontSize: 13 },
|
|
memo: { fontSize: 13, marginTop: 3 },
|
|
content: {
|
|
paddingTop: 12,
|
|
flex: 1,
|
|
},
|
|
memoTextInput: {
|
|
flexDirection: 'row',
|
|
borderWidth: 1,
|
|
borderBottomWidth: 0.5,
|
|
minHeight: 44,
|
|
height: 44,
|
|
alignItems: 'center',
|
|
marginVertical: 8,
|
|
borderRadius: 4,
|
|
paddingHorizontal: 8,
|
|
},
|
|
buttonContainer: {
|
|
height: 45,
|
|
marginBottom: 36,
|
|
},
|
|
floatingDoneButtonContainer: {
|
|
position: 'absolute',
|
|
top: 8,
|
|
right: 0,
|
|
zIndex: 10,
|
|
elevation: 10,
|
|
},
|
|
});
|
|
|
|
export default CoinControlOutputSheet;
|