Improve address display (#1010)

* adding social providers

* add translation

* improving invoice info container
This commit is contained in:
Blake Kaufman
2026-07-19 09:54:20 -04:00
committed by GitHub
parent af330364d8
commit a29490dc67
22 changed files with 268 additions and 55 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -1,6 +1,8 @@
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useMemo } from 'react';
import { Image } from 'expo-image';
import { ThemeText } from '../../../../../functions/CustomElements';
import { CENTER } from '../../../../../constants';
import { CENTER, COLORS, FONT, ICONS, SIZES } from '../../../../../constants';
import GetThemeColors from '../../../../../hooks/themeColors';
import formatSparkPaymentAddress from '../functions/formatSparkPaymentAddress';
import { useNavigation } from '@react-navigation/native';
@@ -8,6 +10,25 @@ import { InputTypes } from 'bitcoin-address-parser';
import ContactProfileImage from '../../contacts/internalComponents/profileImage';
import normalizeLNURLAddress from '../../../../../functions/lnurl/normalizeLNURLAddress';
import ProfileImageRow from '../../contacts/internalComponents/profileImageRow';
import ThemeIcon from '../../../../../functions/CustomElements/themeIcon';
import { HIDDEN_OPACITY } from '../../../../../constants/theme';
import { useTranslation } from 'react-i18next';
// LNURL/lightning-address domain → ICONS key for the provider brand logo.
const LNURL_PROVIDER_ICONS = {
'aqua.net': 'aqua',
'blink.sv': 'blink',
'breez.tips': 'breez',
'cake.cash': 'cake',
'coinos.io': 'coinos',
'mannabitcoin.com': 'mannabitcoin',
'cluborange.org': 'cluborange',
'strike.me': 'strike',
'tether.me': 'tether',
'walletofsatoshi.com': 'walletofsatoshi',
'zeuspay.com': 'zeuspay',
};
export default function InvoiceInfo({
paymentInfo,
fromPage,
@@ -23,11 +44,131 @@ export default function InvoiceInfo({
undefined,
true,
);
const { t } = useTranslation();
const { backgroundOffset, backgroundColor } = GetThemeColors();
const navigate = useNavigation();
const splitContacts = splitRecipients?.map(({ contact }) => contact);
const isLNURLPay = paymentInfo?.type === InputTypes.LNURL_PAY;
const paymentType = formmateedSparkPaymentInfo.paymentType;
// The bitcoin/spark/lrc20 branch renders the full address on screen, so
// tapping to "reveal the full address" adds nothing. Only single-row label
// variants (branta/contact/LNURL/lightning) stay clickable + get a chevron.
const showsFullAddress =
!isUsingBranta &&
!isSplitPayment &&
fromPage !== 'contacts' &&
!isLNURLPay &&
paymentType !== 'lightning';
const isClickable = !isSplitPayment && !showsFullAddress;
// LNURL: resolve the human-readable "user@host", match the host to a provider
// logo, and drop "@host" when we have a logo (the logo conveys the provider).
const normalizedLNURL = isLNURLPay
? normalizeLNURLAddress(paymentInfo?.data?.address) ??
paymentInfo?.data?.address ??
''
: '';
const lnurlDomain = normalizedLNURL.includes('@')
? normalizedLNURL.split('@')[1]?.toLowerCase()
: '';
const providerIconKey = LNURL_PROVIDER_ICONS[lnurlDomain];
const lnurlDisplayText = providerIconKey
? normalizedLNURL.split('@')[0]
: normalizedLNURL;
// On-chain / spark addresses: 4-char groups with alternating weight for
// easy visual validation (mirrors depositQRView).
const addressSegments = useMemo(() => {
const addr = formmateedSparkPaymentInfo.address || '';
return (addr.match(/.{1,4}/g) || []).map((group, i, all) => (
<Text
key={i}
style={{
fontFamily: i % 2 === 0 ? FONT.Title_SemiBold : FONT.Title_Regular,
}}
>
{group}
{i < all.length - 1 ? ' ' : ''}
</Text>
));
}, [formmateedSparkPaymentInfo.address]);
let paymentContent;
if (isLNURLPay) {
paymentContent = (
<View style={styles.contactRow}>
<View
style={[
styles.profileImage,
providerIconKey
? styles.providerLogoCircle
: { backgroundColor: backgroundColor },
]}
>
{providerIconKey ? (
<Image
style={styles.providerLogo}
source={ICONS[providerIconKey]}
contentFit="contain"
/>
) : (
<ContactProfileImage
updated={undefined}
uri={undefined}
darkModeType={darkModeType}
theme={theme}
/>
)}
</View>
<ThemeText
styles={styles.addressText}
CustomNumberOfLines={1}
content={lnurlDisplayText}
/>
</View>
);
} else if (paymentType === 'lightning') {
paymentContent = (
<View style={styles.contactRow}>
<View
style={[styles.profileImage, { backgroundColor: backgroundColor }]}
>
<Image
style={[
styles.lightningIcon,
{
tintColor:
theme && darkModeType ? COLORS.darkModeText : COLORS.primary,
},
]}
source={ICONS.lightningReceiveIcon}
contentFit="contain"
/>
</View>
<ThemeText
styles={styles.addressText}
CustomNumberOfLines={1}
content={t('wallet.sendPages.sendPaymentScreen.lightningPayment')}
/>
</View>
);
} else {
// bitcoin / spark / lrc20
paymentContent = (
<ThemeText
styles={styles.segmentText}
content={addressSegments}
CustomNumberOfLines={4}
/>
);
}
const Container = isClickable ? TouchableOpacity : View;
return (
<TouchableOpacity
<Container
onPress={() => {
navigate.navigate('ErrorScreen', {
errorMessage: formmateedSparkPaymentInfo.address,
@@ -35,11 +176,12 @@ export default function InvoiceInfo({
}}
style={[
styles.invoiceContainer,
isClickable && styles.clickableContainer,
{
backgroundColor: backgroundOffset,
},
]}
disabled={isSplitPayment}
disabled={!isClickable}
>
{isUsingBranta ? (
<View style={styles.contactRow}>
@@ -94,18 +236,12 @@ export default function InvoiceInfo({
/>
</View>
) : (
<ThemeText
styles={{ includeFontPadding: false }}
CustomNumberOfLines={2}
content={
paymentInfo?.type === InputTypes.LNURL_PAY
? normalizeLNURLAddress(paymentInfo.data.address) ??
paymentInfo.data.address
: formmateedSparkPaymentInfo.address
}
/>
paymentContent
)}
</TouchableOpacity>
{isClickable && (
<ThemeIcon iconName="ChevronRight" size={20} styles={styles.chevron} />
)}
</Container>
);
}
@@ -114,15 +250,23 @@ const styles = StyleSheet.create({
width: '80%',
alignItems: 'center',
justifyContent: 'center',
padding: 8,
borderRadius: 8,
padding: 12,
borderRadius: 16,
...CENTER,
marginTop: 30,
},
clickableContainer: {
flexDirection: 'row',
},
contactRow: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
},
chevron: {
opacity: 0.8,
marginLeft: 8,
},
profileImage: {
width: 40,
@@ -133,8 +277,28 @@ const styles = StyleSheet.create({
overflow: 'hidden',
marginRight: 10,
},
providerLogoCircle: {
backgroundColor: COLORS.white,
borderWidth: StyleSheet.hairlineWidth,
borderColor: COLORS.gray,
},
providerLogo: {
width: '100%',
height: '100%',
},
lightningIcon: {
width: 24,
height: 24,
},
addressText: {
includeFontPadding: false,
flexShrink: 1,
},
segmentText: {
fontSize: SIZES.small,
lineHeight: 24,
includeFontPadding: false,
textAlign: 'center',
width: '100%',
},
});
@@ -64,6 +64,7 @@ import {
resolveUsdFiatStats,
} from '../../../../functions/displayCurrency';
import CurrencySwitchButton from '../../../../functions/CustomElements/currencySwitchButton';
import ThemeIcon from '../../../../functions/CustomElements/themeIcon';
import { Image } from 'expo-image';
const QUOTE_TTL_MS = 115_000;
@@ -747,35 +748,42 @@ export default function StablecoinSendScreen() {
}
style={[styles.destinationBox, { backgroundColor: rowBg }]}
>
<View style={styles.confirmIconWrapper}>
<View
style={[
styles.confirmChainCircle,
{ backgroundColor: backgroundOffset },
]}
>
<Image
style={styles.confirmChainIcon}
source={ICONS[`chain_${chainLabel?.toLowerCase()}`]}
contentFit="contain"
/>
</View>
<View
style={[
styles.confirmCurrencyBadge,
{ borderColor: backgroundColor },
]}
>
<Image
style={styles.confirmCurrencyIcon}
source={ICONS[`${asset?.toLowerCase()}Logo`]}
contentFit="contain"
/>
<View style={styles.destinationContent}>
<View style={styles.confirmIconWrapper}>
<View
style={[
styles.confirmChainCircle,
{ backgroundColor: backgroundOffset },
]}
>
<Image
style={styles.confirmChainIcon}
source={ICONS[`chain_${chainLabel?.toLowerCase()}`]}
contentFit="contain"
/>
</View>
<View
style={[
styles.confirmCurrencyBadge,
{ borderColor: backgroundColor },
]}
>
<Image
style={styles.confirmCurrencyIcon}
source={ICONS[`${asset?.toLowerCase()}Logo`]}
contentFit="contain"
/>
</View>
</View>
<ThemeText
styles={styles.quoteValue}
content={`${truncateAddress(address)}`}
/>
</View>
<ThemeText
styles={styles.quoteValue}
content={`${truncateAddress(address)}`}
<ThemeIcon
iconName="ChevronRight"
size={20}
styles={styles.destinationChevron}
/>
</TouchableOpacity>
)}
@@ -918,12 +926,21 @@ const styles = StyleSheet.create({
width: '80%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 8,
borderRadius: 8,
justifyContent: 'space-between',
padding: 12,
borderRadius: 16,
...CENTER,
marginTop: 30,
},
destinationContent: {
flexShrink: 1,
flexDirection: 'row',
alignItems: 'center',
},
destinationChevron: {
opacity: 0.8,
marginLeft: 8,
},
receiveAmount: {
opacity: HIDDEN_OPACITY,
...CENTER,
+24
View File
@@ -39,6 +39,17 @@ import ios_maps_dark from '../assets/apple_maps_dark.png';
import android_maps_light from '../assets/google_maps_light.png';
import android_maps_dark from '../assets/google_maps_dark.png';
import bankIcon from '../assets/icons/bank.png';
import aqua from '../assets/social_logos/aqua.png';
import blink from '../assets/social_logos/blink.png';
import breez from '../assets/social_logos/breez.png';
import cake from '../assets/social_logos/cake.png';
import coinos from '../assets/social_logos/coinos.png';
import mannabitcoin from '../assets/social_logos/manna.png';
import cluborange from '../assets/social_logos/orangePillApp.png';
import strike from '../assets/social_logos/strike.png';
import tether from '../assets/social_logos/tether.png';
import walletofsatoshi from '../assets/social_logos/walletofsatoshi.png';
import zeuspay from '../assets/social_logos/zeus.png';
export default {
logoIcon,
@@ -100,4 +111,17 @@ export default {
android_maps_dark,
bankIcon,
// LNURL Providers
aqua,
blink,
breez,
cake,
coinos,
mannabitcoin,
cluborange,
strike,
tether,
walletofsatoshi,
zeuspay,
};
+2 -1
View File
@@ -1228,7 +1228,8 @@
"swapRatesChangedBody": "Der Wechselkurs für diesen Swap hat sich während Ihrer Prüfung geändert. Bitte geben Sie den Betrag erneut ein, um fortzufahren.",
"swapRatesChangedButton": "Betrag erneut eingeben",
"feeEstimateError": "Gebühr konnte nicht geschätzt werden, bitte versuchen Sie es erneut",
"brantaVerification": "Diese Zahlung ist von Branta verifiziert."
"brantaVerification": "Diese Zahlung ist von Branta verifiziert.",
"lightningPayment": "Lightning-Zahlung"
},
"selectPaymentMethod": {
"header": "Wie möchten Sie diese Zahlung senden (BTC oder USD)?"
+2 -1
View File
@@ -1228,7 +1228,8 @@
"swapRatesChangedBody": "The exchange rate for this swap changed while you were reviewing. Please re-enter your amount to continue.",
"swapRatesChangedButton": "Re-enter Amount",
"feeEstimateError": "Unable to estimate fee, please try again",
"brantaVerification": "This payment has been verified by Branta"
"brantaVerification": "This payment has been verified by Branta",
"lightningPayment": "Lightning payment"
},
"selectPaymentMethod": {
"header": "How do you want to fund your payment?"
+2 -1
View File
@@ -1228,7 +1228,8 @@
"swapRatesChangedBody": "El tipo de cambio para este intercambio cambió mientras lo estabas revisando. Vuelve a ingresar el monto para continuar.",
"swapRatesChangedButton": "Volver a ingresar el monto",
"feeEstimateError": "No se pudo estimar la comisión, inténtalo de nuevo",
"brantaVerification": "Este pago ha sido verificado por Branta"
"brantaVerification": "Este pago ha sido verificado por Branta",
"lightningPayment": "Pago Lightning"
},
"selectPaymentMethod": {
"header": "Selecciona cómo deseas financiar tu pago."
+2 -1
View File
@@ -1228,7 +1228,8 @@
"swapRatesChangedBody": "Le taux de change pour cet échange a changé pendant votre vérification. Veuillez saisir à nouveau le montant pour continuer.",
"swapRatesChangedButton": "Ressaisir le montant",
"feeEstimateError": "Impossible destimer les frais, veuillez réessayer",
"brantaVerification": "Ce paiement a été vérifié par Branta"
"brantaVerification": "Ce paiement a été vérifié par Branta",
"lightningPayment": "Paiement Lightning"
},
"selectPaymentMethod": {
"header": "Veuillez sélectionner comment vous souhaitez financer votre paiement."
+2 -1
View File
@@ -1228,7 +1228,8 @@
"swapRatesChangedBody": "Il tasso di cambio per questo scambio è cambiato durante la revisione. Inserisci nuovamente limporto per continuare.",
"swapRatesChangedButton": "Reinserisci importo",
"feeEstimateError": "Impossibile stimare la commissione, riprova",
"brantaVerification": "Questo pagamento è stato verificato da Branta"
"brantaVerification": "Questo pagamento è stato verificato da Branta",
"lightningPayment": "Pagamento Lightning"
},
"selectPaymentMethod": {
"header": "Seleziona come desideri finanziare il pagamento."
+2 -1
View File
@@ -1227,7 +1227,8 @@
"swapRatesChangedBody": "A cotação para esta troca mudou enquanto você estava revisando. Insira o valor novamente para continuar.",
"swapRatesChangedButton": "Inserir valor novamente",
"feeEstimateError": "Não foi possível estimar a taxa, tente novamente",
"brantaVerification": "Este pagamento foi verificado pela Branta"
"brantaVerification": "Este pagamento foi verificado pela Branta",
"lightningPayment": "Pagamento Lightning"
},
"selectPaymentMethod": {
"header": "Escolha o saldo para realizar esse pagamento"
+2 -1
View File
@@ -1229,7 +1229,8 @@
"swapRatesChangedBody": "Курс обмена для этой операции изменился во время проверки. Пожалуйста, введите сумму заново, чтобы продолжить.",
"swapRatesChangedButton": "Ввести сумму снова",
"feeEstimateError": "Не удалось оценить комиссию, попробуйте снова",
"brantaVerification": "Этот платёж был подтверждён Branta"
"brantaVerification": "Этот платёж был подтверждён Branta",
"lightningPayment": "Платёж Lightning"
},
"selectPaymentMethod": {
"header": "Выберите источник средств."
+2 -1
View File
@@ -1228,7 +1228,8 @@
"swapRatesChangedBody": "Växelkursen för denna växling ändrades medan du granskade den. Ange beloppet igen för att fortsätta.",
"swapRatesChangedButton": "Ange belopp igen",
"feeEstimateError": "Kunde inte uppskatta avgiften, försök igen",
"brantaVerification": "Denna betalning har verifierats av Branta"
"brantaVerification": "Denna betalning har verifierats av Branta",
"lightningPayment": "Lightning-betalning"
},
"selectPaymentMethod": {
"header": "Välj hur du vill finansiera din betalning."