Merge branch 'master' into contr

This commit is contained in:
Marcos Rodriguez Velez 2025-02-20 18:06:15 -04:00
commit 4b93827b7f
11 changed files with 130 additions and 122 deletions

View file

@ -457,7 +457,6 @@ export const StorageProvider = ({ children }: { children: React.ReactNode }) =>
refreshAllWalletTransactions,
resetWallets,
walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus,
handleWalletDeletion,
],
);

View file

@ -7,18 +7,24 @@ import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency';
import { BlueApp } from '../class';
import { HandOffComponentProps } from './types';
const HandOffComponent: React.FC<HandOffComponentProps> = props => {
const { isHandOffUseEnabled } = useSettings();
if (!props || !props.type || !props.userInfo || Object.keys(props.userInfo).length === 0) {
console.debug('HandOffComponent: Missing required type or userInfo data');
return null;
}
const userInfo = JSON.stringify(props.userInfo);
console.debug(`HandOffComponent is rendering. Type: ${props.type}, UserInfo: ${userInfo}...`);
return isHandOffUseEnabled ? <Handoff {...props} /> : null;
};
const HandOffComponent: React.FC<HandOffComponentProps> = React.memo(
props => {
const { isHandOffUseEnabled } = useSettings();
const MemoizedHandOffComponent = React.memo(HandOffComponent);
if (!props || !props.type || !props.userInfo || Object.keys(props.userInfo).length === 0) {
return null;
}
return isHandOffUseEnabled ? <Handoff {...props} /> : null;
},
(prevProps, nextProps) => {
return (
prevProps.type === nextProps.type &&
JSON.stringify(prevProps.userInfo) === JSON.stringify(nextProps.userInfo) &&
prevProps.title === nextProps.title
);
},
);
export const setIsHandOffUseEnabled = async (value: boolean) => {
try {
@ -44,4 +50,4 @@ export const getIsHandOffUseEnabled = async (): Promise<boolean> => {
}
};
export default MemoizedHandOffComponent;
export default HandOffComponent;

View file

@ -98,8 +98,12 @@ interface WalletCarouselItemProps {
isSelectedWallet?: boolean;
customStyle?: ViewStyle;
horizontal?: boolean;
isPlaceHolder?: boolean;
searchQuery?: string;
renderHighlightedText?: (text: string, query: string) => JSX.Element;
animationsEnabled?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
}
const iStyles = StyleSheet.create({
@ -161,21 +165,6 @@ const iStyles = StyleSheet.create({
},
});
interface WalletCarouselItemProps {
item: TWallet;
onPress: (item: TWallet) => void;
handleLongPress?: () => void;
isSelectedWallet?: boolean;
customStyle?: ViewStyle;
horizontal?: boolean;
isPlaceHolder?: boolean;
searchQuery?: string;
renderHighlightedText?: (text: string, query: string) => JSX.Element;
animationsEnabled?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
}
export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
({
item,
@ -203,7 +192,6 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
Animated.spring(scaleValue, {
toValue: 0.95,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start();
}
@ -215,7 +203,6 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
Animated.spring(scaleValue, {
toValue: 1.0,
useNativeDriver: true,
friction: 3,
tension: 100,
}).start();
}
@ -362,6 +349,10 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
renderHighlightedText,
isFlatList = true,
} = props;
const { width } = useWindowDimensions();
const itemWidth = React.useMemo(() => (width * 0.82 > 375 ? 375 : width * 0.82), [width]);
const renderItem = useCallback(
({ item, index }: ListRenderItemInfo<TWallet>) =>
item ? (
@ -379,7 +370,6 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
);
const flatListRef = useRef<FlatList<any>>(null);
useImperativeHandle(ref, (): any => {
return {
scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params),
@ -401,10 +391,8 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(),
};
}, []);
const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => {
console.debug('onScrollToIndexFailed');
console.debug(error);
console.debug('onScrollToIndexFailed', error);
flatListRef.current?.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true });
setTimeout(() => {
if (data.length !== 0 && flatListRef.current !== null) {
@ -413,16 +401,16 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
}, 100);
};
const { width } = useWindowDimensions();
const sliderHeight = 195;
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
const keyExtractor = useCallback((item: TWallet, index: number) => (item?.getID ? item.getID() : index.toString()), []);
return isFlatList ? (
<FlatList
ref={flatListRef}
renderItem={renderItem}
extraData={data}
keyExtractor={(_, index) => index.toString()}
keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false}
pagingEnabled={horizontal}
disableIntervalMomentum={horizontal}

View file

@ -59,7 +59,6 @@ const navigationStyle = (
{
closeButtonPosition,
onCloseButtonPressed,
headerBackVisible = Platform.OS === 'ios' || !closeButtonPosition,
...opts
}: NativeStackNavigationOptions & {
closeButtonPosition?: CloseButtonPosition;
@ -103,19 +102,24 @@ const navigationStyle = (
</TouchableOpacity>
);
}
let options: NativeStackNavigationOptions = {
const baseHeaderStyle = {
headerShadowVisible: false,
headerTitleStyle: {
fontWeight: '600',
fontWeight: '600' as const,
color: theme.colors.foregroundColor,
},
headerBackVisible,
headerBackTitle: undefined,
headerBackButtonDisplayMode: 'minimal',
headerTintColor: theme.colors.foregroundColor,
headerBackButtonDisplayMode: 'minimal',
};
const isLeftCloseButtonAndroid = closeButton === CloseButtonPosition.Left && Platform.OS === 'android';
const leftCloseButtonStyle = isLeftCloseButtonAndroid ? { headerBackImageSource: theme.closeImage } : { headerLeft };
let options: NativeStackNavigationOptions = {
...baseHeaderStyle,
...leftCloseButtonStyle,
headerBackButtonDisplayMode: 'minimal',
headerRight,
headerLeft,
...opts,
};

View file

@ -1819,7 +1819,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (4.6.0):
- RNScreens (4.7.0):
- DoubleConversion
- glog
- hermes-engine
@ -2312,7 +2312,7 @@ SPEC CHECKSUMS:
RNRate: 7641919330e0d6688ad885a985b4bd697ed7d14c
RNReactNativeHapticFeedback: 00ba111b82aa266bb3ee1aa576831c2ea9a9dfad
RNReanimated: 66cf0f600a26d2b5e74c6e0b1c77c1ab1f62fc05
RNScreens: b05d3b8e716e68d9e2f1364d440d23de5b6885f1
RNScreens: 9a7346d6ce564a948e9d61cf9ec10950093e34df
RNShare: 6204e6a1987ba3e7c47071ef703e5449a0e3548a
RNSVG: 86fecdfc637614ba9def63f7f3f2e7795e018356
RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28

View file

@ -324,11 +324,7 @@ const DetailViewStackScreensStack = () => {
component={SettingsPrivacy}
options={navigationStyle({ title: loc.settings.privacy })(theme)}
/>
<DetailViewStack.Screen
name="AddWalletRoot"
component={AddWalletStack}
options={navigationStyle({ closeButtonPosition: CloseButtonPosition.Left, ...NavigationDefaultOptions })(theme)}
/>
<DetailViewStack.Screen name="AddWalletRoot" component={AddWalletStack} options={NavigationDefaultOptions} />
<DetailViewStack.Screen name="SendDetailsRoot" component={SendDetailsStack} options={NavigationDefaultOptions} />
<DetailViewStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={NavigationDefaultOptions} />
@ -379,6 +375,7 @@ const DetailViewStackScreensStack = () => {
options={navigationStyle({
headerShown: false,
statusBarHidden: true,
autoHideHomeIndicator: true,
presentation: 'fullScreenModal',
headerShadowVisible: false,
})(theme)}

View file

@ -1,7 +1,6 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import navigationStyle from '../components/navigationStyle';
import navigationStyle, { CloseButtonPosition } from '../components/navigationStyle';
import { useTheme } from '../components/themes';
import loc from '../loc';
import { ReceiveDetailsComponent } from './LazyLoadReceiveDetailsStack';
@ -16,7 +15,11 @@ const ReceiveDetailsStackRoot = () => {
<Stack.Screen
name="ReceiveDetails"
component={ReceiveDetailsComponent}
options={navigationStyle({ headerBackVisible: false, title: loc.receive.header, statusBarStyle: 'light' })(theme)}
options={navigationStyle({
closeButtonPosition: CloseButtonPosition.Left,
title: loc.receive.header,
statusBarStyle: 'light',
})(theme)}
/>
</Stack.Navigator>
);

11
package-lock.json generated
View file

@ -26,7 +26,6 @@
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#14bab79",
"@react-native/gradle-plugin": "0.76.7",
"@react-native/metro-config": "0.76.7",
"@react-navigation/devtools": "7.0.15",
"@react-navigation/drawer": "7.1.1",
"@react-navigation/native": "7.0.14",
"@react-navigation/native-stack": "7.2.0",
@ -92,7 +91,7 @@
"react-native-reanimated": "3.16.7",
"react-native-safe-area-context": "5.2.0",
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "4.6.0",
"react-native-screens": "4.7.0",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
"react-native-share": "11.1.0",
"react-native-svg": "15.11.1",
@ -121,6 +120,7 @@
"@react-native/js-polyfills": "^0.76.7",
"@react-native/metro-babel-transformer": "^0.76.7",
"@react-native/typescript-config": "0.76.7",
"@react-navigation/devtools": "7.0.15",
"@testing-library/react-native": "^13.0.1",
"@types/bip38": "^3.1.2",
"@types/bs58check": "^2.1.0",
@ -6280,6 +6280,7 @@
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@react-navigation/devtools/-/devtools-7.0.15.tgz",
"integrity": "sha512-pxEBVtd6e5ocT7bs6k6ghOJNyb9Fzxm+EYHemHQ53GEin1sQKYpsSHWZEJdFj1cxYp+/+KCT+TueuNDFkJOr4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@ -22101,9 +22102,9 @@
}
},
"node_modules/react-native-screens": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.6.0.tgz",
"integrity": "sha512-PqGtR/moJLiTMSavhfo5spKXNHZrlxffq3g5UUVPmyuu7MmazFlPvYqiAYnR2iB9tkJYgvZO6sbjYAE7619M0A==",
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.7.0.tgz",
"integrity": "sha512-PKBwBIKasBuaR6otU7GsUb9t5pb2eG1G9uHMHOivst/Iw1tXK+DDz1HSDQFjwcj2pUjrKSkXmwUtbY/oAvsCUA==",
"license": "MIT",
"dependencies": {
"react-freeze": "^1.0.0",

View file

@ -14,8 +14,10 @@
"@react-native/babel-preset": "0.76.7",
"@react-native/eslint-config": "^0.76.7",
"@react-native/js-polyfills": "^0.76.7",
"@react-navigation/devtools": "7.0.15",
"@react-native/metro-babel-transformer": "^0.76.7",
"@react-native/typescript-config": "0.76.7",
"@react-navigation/devtools": "7.0.15",
"@testing-library/react-native": "^13.0.1",
"@types/bip38": "^3.1.2",
"@types/bs58check": "^2.1.0",
@ -94,7 +96,6 @@
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#14bab79",
"@react-native/gradle-plugin": "0.76.7",
"@react-native/metro-config": "0.76.7",
"@react-navigation/devtools": "7.0.15",
"@react-navigation/drawer": "7.1.1",
"@react-navigation/native": "7.0.14",
"@react-navigation/native-stack": "7.2.0",
@ -160,7 +161,7 @@
"react-native-reanimated": "3.16.7",
"react-native-safe-area-context": "5.2.0",
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "4.6.0",
"react-native-screens": "4.7.0",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
"react-native-share": "11.1.0",
"react-native-svg": "15.11.1",

View file

@ -1,17 +1,6 @@
import { useFocusEffect, useRoute } from '@react-navigation/native';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
BackHandler,
Image,
InteractionManager,
LayoutAnimation,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { BackHandler, InteractionManager, LayoutAnimation, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
import Share from 'react-native-share';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
@ -42,6 +31,37 @@ import TipBox from '../../components/TipBox';
const segmentControlValues = [loc.wallets.details_address, loc.bip47.payment_code];
const TabContent = React.memo(
({ currentTab, bip21encoded, wallet, address, showAddress, isCustom, handleShareButtonPressed, renderReceiveDetails }) => {
const qrValue = useMemo(
() => (currentTab === segmentControlValues[0] ? bip21encoded : wallet?.getBIP47PaymentCode()),
[currentTab, bip21encoded, wallet],
);
if (currentTab === segmentControlValues[0]) {
return <View style={styles.container}>{address && renderReceiveDetails()}</View>;
} else {
return (
<View style={styles.container}>
{!qrValue && <Text>{loc.bip47.not_found}</Text>}
{qrValue && (
<>
<TipBox description={loc.receive.bip47_explanation} containerStyle={styles.tip} />
<QRCodeComponent value={qrValue} />
<CopyTextToClipboard text={qrValue} truncated={false} />
</>
)}
</View>
);
}
},
);
const MemoizedHandoffComponent = React.memo(
({ address }) => <HandOffComponent title={loc.send.details_address} type={HandOffActivityType.ReceiveOnchain} userInfo={{ address }} />,
(prevProps, nextProps) => prevProps.address === nextProps.address,
);
const ReceiveDetails = () => {
const { walletID, address } = useRoute().params;
const { wallets, saveToDisk, sleep, fetchAndSaveWalletTransactions } = useStorage();
@ -61,7 +81,7 @@ const ReceiveDetails = () => {
const [currentTab, setCurrentTab] = useState(segmentControlValues[0]);
const { goBack, setParams, setOptions } = useExtendedNavigation();
const bottomModalRef = useRef(null);
const { colors, closeImage } = useTheme();
const { colors } = useTheme();
const [intervalMs, setIntervalMs] = useState(5000);
const [eta, setEta] = useState('');
const [initialConfirmed, setInitialConfirmed] = useState(0);
@ -181,32 +201,12 @@ const ReceiveDetails = () => {
[onPressMenuItem, toolTipActions],
);
const handleClose = useCallback(() => {
goBack();
}, [goBack]);
const HeaderLeft = useMemo(
() => (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.close}
style={styles.button}
onPress={handleClose}
testID="NavigationCloseButton"
>
<Image source={closeImage} />
</TouchableOpacity>
),
[closeImage, handleClose],
);
useEffect(() => {
wallet?.allowBIP47() &&
setOptions({
headerLeft: () => HeaderLeft,
headerRight: () => HeaderRight,
});
}, [HeaderLeft, HeaderRight, colors.foregroundColor, setOptions, wallet]);
}, [HeaderRight, colors.foregroundColor, setOptions, wallet]);
// re-fetching address balance periodically
useEffect(() => {
@ -459,25 +459,37 @@ const ReceiveDetails = () => {
}
};
const renderTabContent = () => {
const qrValue = currentTab === segmentControlValues[0] ? bip21encoded : wallet.getBIP47PaymentCode();
const [isLoading, setIsLoading] = useState(false);
if (currentTab === segmentControlValues[0]) {
return <View style={styles.container}>{address && renderReceiveDetails()}</View>;
} else {
return (
<View style={styles.container}>
{!qrValue && <Text>{loc.bip47.not_found}</Text>}
{qrValue && (
<>
<TipBox description={loc.receive.bip47_explanation} containerStyle={styles.tip} />
<QRCodeComponent value={qrValue} />
<CopyTextToClipboard text={qrValue} truncated={false} />
</>
)}
</View>
);
const handleTabChange = useCallback(index => {
setIsLoading(true);
// Use requestAnimationFrame to prevent UI blocking (better than InteractionManager)
requestAnimationFrame(() => {
setCurrentTab(segmentControlValues[index]);
// Add a small delay to allow the UI to update (sadly needed hack)
setTimeout(() => {
setIsLoading(false);
}, 100);
});
}, []);
const renderTabContent = () => {
if (isLoading) {
return <BlueLoading />;
}
return (
<TabContent
currentTab={currentTab}
bip21encoded={bip21encoded}
wallet={wallet}
address={address}
showAddress={showAddress}
isCustom={isCustom}
handleShareButtonPressed={handleShareButtonPressed}
renderReceiveDetails={renderReceiveDetails}
/>
);
};
return (
@ -492,16 +504,12 @@ const ReceiveDetails = () => {
<SegmentedControl
values={segmentControlValues}
selectedIndex={segmentControlValues.findIndex(tab => tab === currentTab)}
onChange={index => {
setCurrentTab(segmentControlValues[index]);
}}
onChange={handleTabChange}
/>
</View>
)}
{showAddress && renderTabContent()}
{address !== undefined && showAddress && (
<HandOffComponent title={loc.send.details_address} type={HandOffActivityType.ReceiveOnchain} userInfo={{ address }} />
)}
{address !== undefined && showAddress && <MemoizedHandoffComponent address={address} />}
{showConfirmedBalance ? renderConfirmedBalance() : null}
{showPendingBalance ? renderPendingBalance() : null}
{!showAddress && !showPendingBalance && !showConfirmedBalance ? <BlueLoading /> : null}

View file

@ -29,6 +29,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignContent: 'center',
alignItems: 'center',
height: '100%',
},
backdoorButton: {
width: 60,
@ -70,7 +71,7 @@ const ScanQRCode = () => {
const [backdoorText, setBackdoorText] = useState('');
const [backdoorVisible, setBackdoorVisible] = useState(false);
const [animatedQRCodeData, setAnimatedQRCodeData] = useState({});
const [cameraStatusGranted, setCameraStatusGranted] = useState(false);
const [cameraStatusGranted, setCameraStatusGranted] = useState(undefined);
const stylesHook = StyleSheet.create({
openSettingsContainer: {
backgroundColor: colors.brandingColor,
@ -280,7 +281,7 @@ const ScanQRCode = () => {
<BlueLoading />
) : (
<View>
{!cameraStatusGranted ? (
{cameraStatusGranted === false ? (
<View style={[styles.openSettingsContainer, stylesHook.openSettingsContainer]}>
<BlueText>{loc.send.permission_camera_message}</BlueText>
<BlueSpacing40 />
@ -292,7 +293,7 @@ const ScanQRCode = () => {
<BlueSpacing40 />
<Button title={loc._.cancel} onPress={dismiss} />
</View>
) : isFocused ? (
) : isFocused && cameraStatusGranted ? (
<CameraScreen
onReadCode={handleReadCode}
showFrame={false}