BlueWallet/components/TransactionsNavigationHeader.tsx
Nuno 01a11bc8dd
FIX: text size on main app views (#8689)
* fix: text size on wallet view

* fix big font sizes

* fix lint

* fix Glados comments

* fix: run prettier

---------

Co-authored-by: Ivan Vershigora <ivan.vershigora@gmail.com>
2026-06-22 15:27:48 +02:00

369 lines
11 KiB
TypeScript

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import { Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { useTheme } from './themes';
import { LightningArkWallet } from '../class/wallets/lightning-ark-wallet';
import { LightningCustodianWallet } from '../class/wallets/lightning-custodian-wallet';
import { MultisigHDWallet } from '../class/wallets/multisig-hd-wallet';
import WalletGradient from '../class/wallet-gradient';
import { TWallet } from '../class/wallets/types';
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../loc';
import { BitcoinUnit } from '../models/bitcoinUnits';
import { FiatUnit } from '../models/fiatUnit';
import { BlurredBalanceView } from './BlurredBalanceView';
import { useSettings } from '../hooks/context/useSettings';
import ToolTipMenu from './TooltipMenu';
import { useLocale } from '@react-navigation/native';
import ActionSheet from '../screen/ActionSheet';
const HERO_BASE_BODY_MIN_HEIGHT = 120;
const HERO_MIN_BODY_HEIGHT = Math.round(HERO_BASE_BODY_MIN_HEIGHT * 1.2);
const HERO_BOTTOM_PADDING = 32;
const WALLET_LABEL_TOP_GAP = 32;
interface TransactionsNavigationHeaderProps {
wallet: TWallet;
unit: BitcoinUnit;
headerOverlayHeight: number;
onWalletUnitChange: (unit: BitcoinUnit) => void;
onManageFundsPressed?: (id?: string) => void;
onWalletBalanceVisibilityChange?: (shouldHideBalance: boolean) => void;
unitSwitching?: boolean;
}
const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps> = ({
wallet,
headerOverlayHeight,
onWalletUnitChange,
onManageFundsPressed,
onWalletBalanceVisibilityChange,
unit = BitcoinUnit.BTC,
unitSwitching = false,
}) => {
const { colors } = useTheme();
const { hideBalance } = wallet;
const isLightningWallet = wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type;
const [allowOnchainAddress, setAllowOnchainAddress] = useState(isLightningWallet);
const { preferredFiatCurrency } = useSettings();
const { direction } = useLocale();
const verifyIfWalletAllowsOnchainAddress = useCallback(() => {
if (isLightningWallet) {
wallet
.allowOnchainAddress()
.then((value: boolean) => setAllowOnchainAddress(value))
.catch(() => {
console.error('This LNDhub wallet does not have an onchain address API.');
setAllowOnchainAddress(false);
});
}
}, [isLightningWallet, wallet]);
useEffect(() => {
setAllowOnchainAddress(isLightningWallet);
}, [isLightningWallet]);
useEffect(() => {
verifyIfWalletAllowsOnchainAddress();
}, [wallet, verifyIfWalletAllowsOnchainAddress]);
const handleCopyPress = useCallback(() => {
const value = formatBalance(wallet.getBalance(), unit);
if (value) {
Clipboard.setString(value);
}
}, [unit, wallet]);
const handleBalanceVisibility = useCallback(() => {
onWalletBalanceVisibilityChange?.(!hideBalance);
}, [hideBalance, onWalletBalanceVisibilityChange]);
const changeWalletBalanceUnit = () => {
if (hideBalance) {
return;
}
let newWalletPreferredUnit = wallet.getPreferredBalanceUnit();
if (newWalletPreferredUnit === BitcoinUnit.BTC) {
newWalletPreferredUnit = BitcoinUnit.SATS;
} else if (newWalletPreferredUnit === BitcoinUnit.SATS) {
newWalletPreferredUnit = BitcoinUnit.LOCAL_CURRENCY;
} else {
newWalletPreferredUnit = BitcoinUnit.BTC;
}
onWalletUnitChange(newWalletPreferredUnit);
};
const handleManageFundsPressed = useCallback(
(actionKeyID?: string) => {
if (onManageFundsPressed) {
onManageFundsPressed(actionKeyID);
}
},
[onManageFundsPressed],
);
const onPressMenuItem = useCallback(
(id: string) => {
if (id === actionKeys.WalletBalanceVisibility) {
handleBalanceVisibility();
} else if (id === actionKeys.CopyToClipboard) {
handleCopyPress();
}
},
[handleBalanceVisibility, handleCopyPress],
);
// The Manage Funds menu is presented via a JS ActionSheet rather than the
// native context menu (ToolTipMenu): react-native-context-menu-view is
// Paper-only and, routed through Fabric's legacy interop on the New
// Architecture, its host view gets mispositioned to the header origin —
// overlapping the wallet label. A plain TouchableOpacity + ActionSheet lays
// out correctly (same pattern as the Multisig button below).
const showManageFundsActionSheet = useCallback(() => {
ActionSheet.showActionSheetWithOptions(
{
title: loc.lnd.title,
options: [loc._.cancel, loc.lnd.refill, loc.lnd.refill_external],
cancelButtonIndex: 0,
},
buttonIndex => {
if (buttonIndex === 1) handleManageFundsPressed(actionKeys.Refill);
else if (buttonIndex === 2) handleManageFundsPressed(actionKeys.RefillWithExternalWallet);
},
);
}, [handleManageFundsPressed]);
const currentBalance = wallet ? wallet.getBalance() : 0;
const formattedBalance = useMemo(() => {
return unit === BitcoinUnit.LOCAL_CURRENCY
? formatBalance(currentBalance, unit, true)
: formatBalanceWithoutSuffix(currentBalance, unit, true);
}, [unit, currentBalance]);
const balance = !wallet.hideBalance && formattedBalance;
const toolTipWalletBalanceActions = useMemo(() => {
return hideBalance
? [
{
id: actionKeys.WalletBalanceVisibility,
text: loc.transactions.details_balance_show,
icon: actionIcons.Eye,
},
]
: [
{
id: actionKeys.WalletBalanceVisibility,
text: loc.transactions.details_balance_hide,
icon: actionIcons.EyeSlash,
},
{
id: actionKeys.CopyToClipboard,
text: loc.transactions.details_copy,
icon: actionIcons.Clipboard,
},
];
}, [hideBalance]);
return (
<View
style={[
styles.lineaderGradient,
{
paddingTop: headerOverlayHeight,
minHeight: headerOverlayHeight + HERO_MIN_BODY_HEIGHT,
backgroundColor: WalletGradient.headerColorFor(wallet.type),
},
]}
>
<LinearGradient colors={WalletGradient.gradientsFor(wallet.type)} style={StyleSheet.absoluteFill} />
<View style={styles.contentContainer}>
<Text testID="WalletLabel" numberOfLines={1} style={[styles.walletLabel, { writingDirection: direction }]}>
{wallet.getLabel()}
</Text>
<View style={styles.balanceSection}>
<View style={styles.walletBalanceAndUnitContainer}>
<ToolTipMenu
shouldOpenOnLongPress
isButton
enableAndroidRipple={false}
buttonStyle={styles.walletBalance}
onPressMenuItem={onPressMenuItem}
actions={toolTipWalletBalanceActions}
>
<View style={styles.walletBalance}>
{hideBalance ? (
<BlurredBalanceView />
) : (
<Text
testID="WalletBalance"
numberOfLines={1}
minimumFontScale={0.5}
adjustsFontSizeToFit
style={styles.walletBalanceText}
>
{balance}
</Text>
)}
</View>
</ToolTipMenu>
{!hideBalance && (
<TouchableOpacity style={styles.walletPreferredUnitView} onPress={changeWalletBalanceUnit} disabled={unitSwitching}>
<Text style={styles.walletPreferredUnitText}>
{unit === BitcoinUnit.LOCAL_CURRENCY ? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD) : unit}
</Text>
</TouchableOpacity>
)}
</View>
{(wallet.type === LightningCustodianWallet.type || wallet.type === LightningArkWallet.type) && allowOnchainAddress && (
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={showManageFundsActionSheet}>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</TouchableOpacity>
)}
</View>
{wallet.type === MultisigHDWallet.type && (
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={() => handleManageFundsPressed()}>
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
</TouchableOpacity>
)}
</View>
<View style={styles.bottomBarSpacer}>
<View
style={[
styles.bottomBar,
{
backgroundColor: colors.background,
...Platform.select({
ios: { shadowColor: colors.shadowColor },
android: {},
}),
},
]}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
lineaderGradient: {
justifyContent: 'flex-start',
position: 'relative',
},
contentContainer: {
flex: 1,
paddingTop: WALLET_LABEL_TOP_GAP,
paddingHorizontal: 16,
paddingBottom: HERO_BOTTOM_PADDING,
},
bottomBarSpacer: {
position: 'relative',
height: 12,
marginBottom: 0,
},
bottomBar: {
position: 'absolute',
left: 0,
right: 0,
bottom: -1,
height: 13,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
...Platform.select({
ios: {
shadowOffset: { width: 0, height: -8 },
shadowOpacity: 0.1,
shadowRadius: 6,
},
android: {
elevation: 0.5,
},
}),
},
walletLabel: {
backgroundColor: 'transparent',
fontSize: 19,
color: 'rgba(255, 255, 255, 0.7)',
marginBottom: 4,
},
walletBalance: {
flexShrink: 1,
marginRight: 6,
minHeight: 39,
justifyContent: 'center',
},
balanceSection: {
flexDirection: 'column',
alignItems: 'flex-start',
},
manageFundsButton: {
marginTop: 14,
marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9,
minHeight: 39,
alignSelf: 'flex-start',
justifyContent: 'center',
alignItems: 'center',
},
manageFundsButtonText: {
fontWeight: '500',
fontSize: 14,
color: '#FFFFFF',
padding: 12,
},
walletBalanceAndUnitContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingRight: 10,
},
walletBalanceText: {
color: '#fff',
fontWeight: 'bold',
fontSize: 36,
flexShrink: 1,
},
walletPreferredUnitView: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderRadius: 8,
minHeight: 35,
minWidth: 65,
},
walletPreferredUnitText: {
color: '#fff',
fontWeight: '600',
},
});
export const actionKeys = {
CopyToClipboard: 'copyToClipboard',
WalletBalanceVisibility: 'walletBalanceVisibility',
Refill: 'refill',
RefillWithExternalWallet: 'refillWithExternalWallet',
};
export const actionIcons = {
Eye: {
iconValue: 'eye',
},
EyeSlash: {
iconValue: 'eye.slash',
},
Clipboard: {
iconValue: 'doc.on.doc',
},
Refill: {
iconValue: 'goforward.plus',
},
RefillWithExternalWallet: {
iconValue: 'qrcode',
},
};
export default TransactionsNavigationHeader;