Merge pull request #8362 from BlueWallet/enhance-address

This commit is contained in:
Marcos Rodriguez Vélez 2026-03-03 12:31:06 -05:00 committed by GitHub
commit 3e8906e31b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 124 additions and 52 deletions

View File

@ -1,16 +1,131 @@
import Clipboard from '@react-native-clipboard/clipboard';
import React, { forwardRef, useEffect, useState } from 'react';
import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Animated, StyleSheet, TouchableOpacity, View, Text } from 'react-native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
import loc from '../loc';
import { useTheme } from './themes';
type CopyTextToClipboardProps = {
text: string;
truncated?: boolean;
isAddress?: boolean;
};
const styleCopyTextToClipboard = StyleSheet.create({
const CopyTextToClipboard = forwardRef<React.ElementRef<typeof TouchableOpacity>, CopyTextToClipboardProps>(
({ text, truncated, isAddress }, ref) => {
const [hasTappedText, setHasTappedText] = useState(false);
const [address, setAddress] = useState(text);
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
addressSection: {
color: colors.newBlue,
fontWeight: '500',
},
});
useEffect(() => {
if (!hasTappedText) {
setAddress(text);
}
}, [text, hasTappedText]);
const copyToClipboard = () => {
setHasTappedText(true);
Clipboard.setString(text);
triggerHapticFeedback(HapticFeedbackTypes.Selection);
setAddress(loc.wallets.xpub_copiedToClipboard);
setTimeout(() => {
setHasTappedText(false);
setAddress(text);
}, 1000);
};
const renderHighlightedAddress = () => {
if (address.includes(loc.wallets.xpub_copiedToClipboard)) {
return (
<Animated.Text
style={styles.address}
{...(truncated ? { numberOfLines: 1, ellipsizeMode: 'middle' } : { numberOfLines: 0 })}
testID="AddressValue"
>
{address}
</Animated.Text>
);
}
if (address.toLocaleLowerCase().startsWith('bitcoin:')) {
const prefix = address.slice(0, 8); // "bitcoin:"
const afterPrefix = address.slice(8);
const qIndex = afterPrefix.indexOf('?');
const addrPart = qIndex === -1 ? afterPrefix : afterPrefix.slice(0, qIndex);
const queryPart = qIndex === -1 ? '' : afterPrefix.slice(qIndex);
const start = addrPart.slice(0, 6);
const middle = addrPart.slice(6, -6);
const end = addrPart.slice(-6);
return (
<Animated.Text style={styles.address} numberOfLines={truncated ? 1 : 0} ellipsizeMode="middle" testID="AddressValue">
<Text>{prefix}</Text>
<Text style={stylesHook.addressSection}>{start}</Text>
<Text>{middle}</Text>
<Text style={stylesHook.addressSection}>{end}</Text>
<Text>{queryPart}</Text>
</Animated.Text>
);
}
return (
<Animated.Text
style={styles.address}
{...(truncated ? { numberOfLines: 1, ellipsizeMode: 'middle' } : { numberOfLines: 0 })}
testID="AddressValue"
>
<Text style={stylesHook.addressSection}>{address.slice(0, 6)}</Text>
<Text>{address.slice(6, -6)}</Text>
<Text style={stylesHook.addressSection}>{address.slice(-6)}</Text>
</Animated.Text>
);
};
return (
<View style={styles.container}>
<TouchableOpacity
ref={ref}
accessibilityRole="button"
onPress={copyToClipboard}
disabled={hasTappedText}
testID="CopyTextToClipboard"
>
{isAddress ? (
<>{renderHighlightedAddress()}</>
) : (
<Animated.Text
style={styles.address}
{...(truncated ? { numberOfLines: 1, ellipsizeMode: 'middle' } : { numberOfLines: 0 })}
testID="AddressValue"
>
{address}
</Animated.Text>
)}
</TouchableOpacity>
</View>
);
},
);
export default CopyTextToClipboard;
const styles = StyleSheet.create({
container: { justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 },
address: {
marginVertical: 32,
fontSize: 15,
@ -18,51 +133,3 @@ const styleCopyTextToClipboard = StyleSheet.create({
textAlign: 'center',
},
});
const CopyTextToClipboard = forwardRef<React.ElementRef<typeof TouchableOpacity>, CopyTextToClipboardProps>(({ text, truncated }, ref) => {
const [hasTappedText, setHasTappedText] = useState(false);
const [address, setAddress] = useState(text);
useEffect(() => {
if (!hasTappedText) {
setAddress(text);
}
}, [text, hasTappedText]);
const copyToClipboard = () => {
setHasTappedText(true);
Clipboard.setString(text);
triggerHapticFeedback(HapticFeedbackTypes.Selection);
setAddress(loc.wallets.xpub_copiedToClipboard); // Adjust according to your localization logic
setTimeout(() => {
setHasTappedText(false);
setAddress(text);
}, 1000);
};
return (
<View style={styles.container}>
<TouchableOpacity
ref={ref}
accessibilityRole="button"
onPress={copyToClipboard}
disabled={hasTappedText}
testID="CopyTextToClipboard"
>
<Animated.Text
style={styleCopyTextToClipboard.address}
{...(truncated ? { numberOfLines: 1, ellipsizeMode: 'middle' } : { numberOfLines: 0 })}
testID="AddressValue"
>
{address}
</Animated.Text>
</TouchableOpacity>
</View>
);
});
export default CopyTextToClipboard;
const styles = StyleSheet.create({
container: { justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 },
});

View File

@ -401,7 +401,7 @@ const ReceiveDetails = () => {
<View style={styles.qrCodeContainer}>
<QRCodeComponent value={bip21encoded} size={qrCodeSize} />
</View>
<CopyTextToClipboard text={isCustom ? bip21encoded : address} />
<CopyTextToClipboard text={isCustom ? bip21encoded : address} isAddress={true} />
</View>
)}
</View>

View File

@ -111,6 +111,9 @@ const Confirm: React.FC = () => {
payjoinWrapper: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
addressSection: {
color: colors.newBlue,
},
});
const HeaderRightButton = useMemo(
@ -290,7 +293,9 @@ const Confirm: React.FC = () => {
<BlueCard>
<Text style={[styles.transactionDetailsTitle, stylesHook.transactionDetailsTitle]}>{loc.send.create_to}</Text>
<Text testID="TransactionAddress" style={[styles.transactionDetailsSubtitle, stylesHook.transactionDetailsSubtitle]}>
{item.address}
<Text style={stylesHook.addressSection}>{item?.address?.slice(0, 6)}</Text>
<Text>{item?.address?.slice(6, -6)}</Text>
<Text style={stylesHook.addressSection}>{item?.address?.slice(-6)}</Text>
</Text>
{contact ? <Text style={[styles.transactionDetailsSubtitle, stylesHook.transactionDetailsSubtitle]}>[{contact}]</Text> : null}
</BlueCard>