Merge branch 'master' into PSBT---File-can-only-be-saved-on-APP,-not-on-Internal/External-Storage-#6220

This commit is contained in:
Marcos Rodriguez Velez 2024-04-02 08:12:35 -04:00
commit 8a12b2a349
No known key found for this signature in database
GPG Key ID: 6030B2F48CCE86D7
11 changed files with 169 additions and 114 deletions

View File

@ -15,7 +15,6 @@ import {
TouchableOpacity,
View,
I18nManager,
ImageBackground,
} from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
@ -191,18 +190,6 @@ export const BlueButtonLink = forwardRef((props, ref) => {
);
});
export const BluePrivateBalance = () => {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 13, borderRadius: 9 }}>
<ImageBackground
blurRadius={6}
style={{ backgroundColor: '#FFFFFF', opacity: 0.5, height: 30, width: 110, marginRight: 8, borderRadius: 9 }}
/>
<Icon name="eye-slash" type="font-awesome" color="#FFFFFF" />
</View>
);
};
export const BlueCard = props => {
return <View {...props} style={{ padding: 20 }} />;
};

View File

@ -0,0 +1,18 @@
import React from 'react';
import { ImageBackground, StyleSheet, View } from 'react-native';
import { Icon } from 'react-native-elements';
export const BlurredBalanceView = () => {
return (
<View style={styles.container}>
{/* @ts-ignore: We just want the blur effect. No source prop needed */}
<ImageBackground blurRadius={6} style={styles.background} />
<Icon name="eye-slash" type="font-awesome" color="#FFFFFF" />
</View>
);
};
const styles = StyleSheet.create({
container: { flexDirection: 'row', alignItems: 'center', borderRadius: 9 },
background: { backgroundColor: '#FFFFFF', opacity: 0.5, height: 30, width: 110, marginRight: 8, borderRadius: 9 },
});

View File

@ -41,6 +41,7 @@ const ToolTipMenu = (props, ref) => {
const isMenuPrimaryAction = props.isMenuPrimaryAction ? props.isMenuPrimaryAction : false;
const renderPreview = props.renderPreview ?? undefined;
const disabled = props.disabled ?? false;
const onPress = props.onPress ?? undefined;
const buttonStyle = props.buttonStyle;
return isButton ? (
@ -55,14 +56,11 @@ const ToolTipMenu = (props, ref) => {
menuTitle,
menuItems,
}}
style={buttonStyle}
onPress={onPress}
accessibilityRole="button"
>
{props.onPress ? (
<TouchableOpacity accessibilityRole="button" style={buttonStyle} onPress={props.onPress}>
{props.children}
</TouchableOpacity>
) : (
props.children
)}
{props.children}
</ContextMenuButton>
) : props.onPress ? (
<TouchableOpacity accessibilityRole="button" onPress={props.onPress}>

View File

@ -1,17 +1,16 @@
import React, { useState, useEffect, useRef, useContext, useCallback, useMemo } from 'react';
import { Image, Text, TouchableOpacity, View, I18nManager, StyleSheet } from 'react-native';
import { Image, Text, TouchableOpacity, View, I18nManager, StyleSheet, LayoutAnimation } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import { HDSegwitBech32Wallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import { BitcoinUnit } from '../models/bitcoinUnits';
import WalletGradient from '../class/wallet-gradient';
import Biometric from '../class/biometrics';
import loc, { formatBalance } from '../loc';
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../loc';
import { BlueStorageContext } from '../blue_modules/storage-context';
import ToolTipMenu from './TooltipMenu';
import { BluePrivateBalance } from '../BlueComponents';
import { FiatUnit } from '../models/fiatUnit';
import { TWallet } from '../class/wallets/types';
import { BlurredBalanceView } from './BlurredBalanceView';
interface TransactionsNavigationHeaderProps {
wallet: TWallet;
@ -20,7 +19,8 @@ interface TransactionsNavigationHeaderProps {
navigate: (route: string, params?: any) => void;
goBack: () => void;
};
onManageFundsPressed?: (id: string) => void; // Add a type definition for this prop
onManageFundsPressed?: (id: string) => void;
onWalletBalanceVisibilityChange?: (isShouldBeVisible: boolean) => void;
actionKeys: {
CopyToClipboard: 'copyToClipboard';
WalletBalanceVisibility: 'walletBalanceVisibility';
@ -38,11 +38,13 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
navigation,
// @ts-ignore: Ugh
onManageFundsPressed,
// @ts-ignore: Ugh
onWalletBalanceVisibilityChange,
}) => {
const [wallet, setWallet] = useState(initialWallet);
const [allowOnchainAddress, setAllowOnchainAddress] = useState(false);
const { preferredFiatCurrency, saveToDisk } = useContext(BlueStorageContext);
const { preferredFiatCurrency } = useContext(BlueStorageContext);
const menuRef = useRef(null);
const verifyIfWalletAllowsOnchainAddress = useCallback(() => {
@ -72,23 +74,8 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
}
};
const updateWalletVisibility = (w: TWallet, newHideBalance: boolean) => {
w.hideBalance = newHideBalance;
return w;
};
const handleBalanceVisibility = async () => {
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled && wallet.hideBalance) {
if (!(await Biometric.unlockWithBiometrics())) {
return navigation.goBack();
}
}
const updatedWallet = updateWalletVisibility(wallet, !wallet.hideBalance);
setWallet(updatedWallet);
saveToDisk();
const handleBalanceVisibility = () => {
onWalletBalanceVisibilityChange?.(!wallet.hideBalance);
};
const updateWalletWithNewUnit = (w: TWallet, newPreferredUnit: BitcoinUnit) => {
@ -109,6 +96,8 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
newWalletPreferredUnit = BitcoinUnit.BTC;
}
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
const updatedWallet = updateWalletWithNewUnit(wallet, newWalletPreferredUnit);
setWallet(updatedWallet);
onWalletUnitChange?.(updatedWallet);
@ -136,8 +125,11 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
const balance = useMemo(() => {
const hideBalance = wallet.hideBalance;
const balanceUnit = wallet.getPreferredBalanceUnit();
const balanceFormatted = formatBalance(wallet.getBalance(), balanceUnit, true);
return !hideBalance && balanceFormatted?.toString();
const balanceFormatted =
balanceUnit === BitcoinUnit.LOCAL_CURRENCY
? formatBalance(wallet.getBalance(), balanceUnit, true)
: formatBalanceWithoutSuffix(wallet.getBalance(), balanceUnit, true);
return !hideBalance && balanceFormatted;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.hideBalance, wallet.getPreferredBalanceUnit()]);
@ -163,68 +155,82 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
style={styles.chainIcon}
/>
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel}>
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel} selectable>
{wallet.getLabel()}
</Text>
<ToolTipMenu
enableAndroidRipple={false}
onPress={changeWalletBalanceUnit}
ref={menuRef}
title={`${loc.wallets.balance} (${
wallet.getPreferredBalanceUnit() === BitcoinUnit.LOCAL_CURRENCY
? preferredFiatCurrency?.endPointKey ?? FiatUnit.USD
: wallet.getPreferredBalanceUnit()
})`}
onPressMenuItem={onPressMenuItem}
actions={
wallet.hideBalance
? [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye',
<View style={styles.walletBalanceAndUnitContainer}>
<ToolTipMenu
isMenuPrimaryAction
isButton
enableAndroidRipple={false}
ref={menuRef}
buttonStyle={styles.walletBalance}
title={`${loc.wallets.balance} (${
wallet.getPreferredBalanceUnit() === BitcoinUnit.LOCAL_CURRENCY
? preferredFiatCurrency?.endPointKey ?? FiatUnit.USD
: wallet.getPreferredBalanceUnit()
})`}
onPressMenuItem={onPressMenuItem}
actions={
wallet.hideBalance
? [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
},
},
]
: [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
]
: [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
},
},
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
icon: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
icon: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
},
},
]
}
>
<View style={styles.walletBalance}>
{wallet.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
testID="WalletBalance"
// @ts-ignore: Ugh
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
numberOfLines={1}
adjustsFontSizeToFit
style={styles.walletBalance}
>
{balance}
</Text>
)}
</View>
</ToolTipMenu>
]
}
>
<View style={styles.walletBalance}>
{wallet.hideBalance ? (
<BlurredBalanceView />
) : (
<TouchableOpacity>
<Text
testID="WalletBalance"
// @ts-ignore: Ugh
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
numberOfLines={1}
adjustsFontSizeToFit
style={styles.walletBalanceText}
ellipsizeMode="middle"
>
{balance}
</Text>
</TouchableOpacity>
)}
</View>
</ToolTipMenu>
<TouchableOpacity style={styles.walletPreferredUnitView} onPress={changeWalletBalanceUnit}>
<Text style={styles.walletPreferredUnitText}>
{wallet.getPreferredBalanceUnit() === BitcoinUnit.LOCAL_CURRENCY
? preferredFiatCurrency?.endPointKey ?? FiatUnit.USD
: wallet.getPreferredBalanceUnit()}
</Text>
</TouchableOpacity>
</View>
{wallet.type === LightningCustodianWallet.type && allowOnchainAddress && (
<ToolTipMenu
isMenuPrimaryAction
@ -291,13 +297,11 @@ const styles = StyleSheet.create({
fontSize: 19,
color: '#fff',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
marginBottom: 10,
},
walletBalance: {
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
flexShrink: 1,
marginRight: 6,
},
manageFundsButton: {
marginTop: 14,
@ -315,6 +319,31 @@ const styles = StyleSheet.create({
color: '#FFFFFF',
padding: 12,
},
walletBalanceAndUnitContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingRight: 10, // Ensure there's some padding to the right
},
walletBalanceText: {
color: '#fff',
fontWeight: 'bold',
fontSize: 36,
flexShrink: 1, // Allow the text to shrink if there's not enough space
},
walletPreferredUnitView: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderRadius: 8,
paddingVertical: 5,
minHeight: 35,
minWidth: 65,
padding: 10, // Adjust padding as needed to fit the content
},
walletPreferredUnitText: {
color: '#fff',
fontWeight: '600',
},
});
export const actionKeys = {

View File

@ -19,10 +19,11 @@ import LinearGradient from 'react-native-linear-gradient';
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import WalletGradient from '../class/wallet-gradient';
import { BluePrivateBalance } from '../BlueComponents';
import { BlueSpacing10 } from '../BlueComponents';
import { BlueStorageContext, WalletTransactionsStatus } from '../blue_modules/storage-context';
import { isTablet, isDesktop } from '../blue_modules/environment';
import { useTheme } from './themes';
import { BlurredBalanceView } from './BlurredBalanceView';
const nStyles = StyleSheet.create({
container: {
@ -218,7 +219,10 @@ export const WalletCarouselItem = ({ item, _, onPress, handleLongPress, isSelect
{item.getLabel()}
</Text>
{item.hideBalance ? (
<BluePrivateBalance />
<>
<BlueSpacing10 />
<BlurredBalanceView />
</>
) : (
<Text
numberOfLines={1}

View File

@ -12,6 +12,7 @@ import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
import { BitcoinUnit } from '../../models/bitcoinUnits';
const dayjs = require('dayjs');
function onlyUnique(value, index, self) {
@ -262,7 +263,7 @@ const TransactionsDetails = () => {
{tx.fee && (
<>
<BlueText style={styles.rowCaption}>{loc.send.create_fee}</BlueText>
<BlueText style={styles.rowValue}>{tx.fee + ' sats'}</BlueText>
<BlueText style={styles.rowValue}>{tx.fee + ` ${BitcoinUnit.SATS}`}</BlueText>
<View style={styles.marginBottom18} />
</>
)}

View File

@ -271,6 +271,7 @@ const styles = StyleSheet.create({
},
borderRadius6: {
borderRadius: 6,
minHeight: 54,
},
buttonContainer: {
padding: 24,

View File

@ -3,7 +3,7 @@ import { View, ActivityIndicator, Image, Text, TouchableOpacity, I18nManager, Fl
import LinearGradient from 'react-native-linear-gradient';
import { useRoute, useNavigation, useNavigationState } from '@react-navigation/native';
import { BlueText, BlueSpacing20, BluePrivateBalance } from '../../BlueComponents';
import { BlueText, BlueSpacing20 } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import WalletGradient from '../../class/wallet-gradient';
import loc, { formatBalance, transactionTimeToReadable } from '../../loc';
@ -13,6 +13,7 @@ import { useTheme } from '../../components/themes';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import SafeArea from '../../components/SafeArea';
import { Chain } from '../../models/bitcoinUnits';
import { BlurredBalanceView } from '../../components/BlurredBalanceView';
const SelectWallet = () => {
const { chainType, onWalletSelect, availableWallets, noWalletExplanationText, onChainRequireSend = false } = useRoute().params;
@ -167,7 +168,7 @@ const SelectWallet = () => {
{item.getLabel()}
</Text>
{item.hideBalance ? (
<BluePrivateBalance />
<BlurredBalanceView />
) : (
<Text numberOfLines={1} adjustsFontSizeToFit style={styles.balance}>
{formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}

View File

@ -15,6 +15,7 @@ import {
TouchableOpacity,
View,
findNodeHandle,
LayoutAnimation,
} from 'react-native';
import { Icon } from 'react-native-elements';
@ -38,6 +39,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc from '../../loc';
import { Chain } from '../../models/bitcoinUnits';
import ActionSheet from '../ActionSheet';
import Biometric from '../../class/biometrics';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
@ -511,6 +513,20 @@ const WalletTransactions = ({ navigation }) => {
saveToDisk();
})
}
onWalletBalanceVisibilityChange={async isShouldBeVisible => {
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (wallet.hideBalance && isBiometricsEnabled) {
const unlocked = await Biometric.unlockWithBiometrics();
if (!unlocked) {
throw new Error('Biometrics failed');
}
}
wallet.hideBalance = isShouldBeVisible;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
await saveToDisk();
}}
onManageFundsPressed={id => {
if (wallet.type === MultisigHDWallet.type) {
navigateToViewEditCosigners();

View File

@ -18,7 +18,7 @@ beforeAll(async () => {
await device.launchApp({ delete: true });
console.log('before all - importing bip48...');
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'HDsegwitBech32', 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526 BTC');
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'HDsegwitBech32', 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526');
console.log('...imported!');
await device.pressBack();
await sleep(15000);

View File

@ -26,7 +26,7 @@ describe('BlueWallet UI Tests - import Watch-only wallet (zpub)', () => {
'zpub6s2EvLxwvDpaHNVP5vfordTyi8cH1fR8usmEjz7RsSQjfTTGU2qA5VEcEyYYBxpZAyBarJoTraB4VRJKVz97Au9jRNYfLAeeHC5UnRZbz8Y',
'watchOnly',
'Imported Watch-only',
'0.0001 BTC',
'0.0001',
);
await sleep(15000);