From f1013d6d03e8610c775ba9583480e6f524f28394 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 23 Mar 2024 23:48:44 -0400 Subject: [PATCH 1/6] REF: Use container --- App.js | 57 ++++++++++++++++++++----------- components/NavigationProvider.tsx | 47 +++++++++++++++++++++++++ screen/wallets/export.js | 11 +----- 3 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 components/NavigationProvider.tsx diff --git a/App.js b/App.js index 3c5710293..c401dd26e 100644 --- a/App.js +++ b/App.js @@ -33,6 +33,7 @@ import HandoffComponent from './components/handoff'; import triggerHapticFeedback, { HapticFeedbackTypes } from './blue_modules/hapticFeedback'; import MenuElements from './components/MenuElements'; import { updateExchangeRate } from './blue_modules/currency'; +import { NavigationProvider } from './components/NavigationProvider'; const A = require('./blue_modules/analytics'); const eventEmitter = Platform.OS === 'ios' ? new NativeEventEmitter(NativeModules.EventEmitter) : undefined; @@ -99,28 +100,42 @@ const App = () => { } }; - useEffect(() => { - if (walletsInitialized) { - addListeners(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [walletsInitialized]); - const addListeners = () => { - Linking.addEventListener('url', handleOpenURL); - AppState.addEventListener('change', handleAppStateChange); + const urlSubscription = Linking.addEventListener('url', handleOpenURL); + const appStateSubscription = AppState.addEventListener('change', handleAppStateChange); + + // Note: `getMostRecentUserActivity` doesn't create a persistent listener, so no need to unsubscribe EventEmitter?.getMostRecentUserActivity() .then(onUserActivityOpen) .catch(() => console.log('No userActivity object sent')); - handleAppStateChange(undefined); - /* - When a notification on iOS is shown while the app is on foreground; - On willPresent on AppDelegate.m - */ - eventEmitter?.addListener('onNotificationReceived', onNotificationReceived); - eventEmitter?.addListener('onUserActivityOpen', onUserActivityOpen); + + const notificationSubscription = eventEmitter?.addListener('onNotificationReceived', onNotificationReceived); + const activitySubscription = eventEmitter?.addListener('onUserActivityOpen', onUserActivityOpen); + + // Store subscriptions in a ref or state to remove them later + return { + urlSubscription, + appStateSubscription, + notificationSubscription, + activitySubscription, + }; }; + useEffect(() => { + if (walletsInitialized) { + const subscriptions = addListeners(); + + // Cleanup function + return () => { + subscriptions.urlSubscription?.remove(); + subscriptions.appStateSubscription?.remove(); + subscriptions.notificationSubscription?.remove(); + subscriptions.activitySubscription?.remove(); + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [walletsInitialized]); // Re-run when walletsInitialized changes + /** * Processes push notifications stored in AsyncStorage. Might navigate to some screen. * @@ -285,10 +300,12 @@ const App = () => { - - - - + + + + + + diff --git a/components/NavigationProvider.tsx b/components/NavigationProvider.tsx new file mode 100644 index 000000000..744b2a247 --- /dev/null +++ b/components/NavigationProvider.tsx @@ -0,0 +1,47 @@ +import React, { createContext, useContext, useCallback, ReactNode } from 'react'; +import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native'; +import Biometric from '../class/biometrics'; + +interface NavigationContextType { + navigate: (name: string, params?: object) => void; +} + +const NavigationContext = createContext({ + navigate: () => {}, // default empty implementation +}); + +interface NavigationProviderProps { + children: ReactNode; +} + +export const NavigationProvider: React.FC = ({ children }) => { + const navigation = useNavigation>(); + + const customNavigate = useCallback( + (name: string, params: object = {}) => { + if (name === 'WalletExportRoot') { + Biometric.isBiometricUseEnabled().then((isBiometricsEnabled: boolean) => { + if (isBiometricsEnabled) { + Biometric.unlockWithBiometrics().then((isAuthenticated: boolean) => { + if (isAuthenticated) { + navigation.navigate(name, params); + } else { + console.error('Biometric authentication failed'); + } + }); + } else { + console.warn('Biometric authentication is not enabled'); + navigation.navigate(name, params); + } + }); + } else { + navigation.navigate(name, params); + } + }, + [navigation], + ); + + return {children}; +}; + +export const useCustomNavigation = () => useContext(NavigationContext); diff --git a/screen/wallets/export.js b/screen/wallets/export.js index 305cfc0ec..62fd7f531 100644 --- a/screen/wallets/export.js +++ b/screen/wallets/export.js @@ -1,10 +1,8 @@ import React, { useState, useCallback, useContext, useRef, useEffect } from 'react'; import { InteractionManager, ScrollView, ActivityIndicator, View, StyleSheet, AppState } from 'react-native'; import { useNavigation, useFocusEffect, useRoute } from '@react-navigation/native'; - import { BlueSpacing20, BlueText, BlueCopyTextToClipboard, BlueCard } from '../../BlueComponents'; import navigationStyle from '../../components/navigationStyle'; -import Biometric from '../../class/biometrics'; import { LegacyWallet, LightningCustodianWallet, SegwitBech32Wallet, SegwitP2SHWallet, WatchOnlyWallet } from '../../class'; import loc from '../../loc'; import { BlueStorageContext } from '../../blue_modules/storage-context'; @@ -56,13 +54,6 @@ const WalletExport = () => { enableBlur(); const task = InteractionManager.runAfterInteractions(async () => { if (wallet) { - const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); - - if (isBiometricsEnabled) { - if (!(await Biometric.unlockWithBiometrics())) { - return goBack(); - } - } if (!wallet.getUserHasSavedExport()) { wallet.setUserHasSavedExport(true); saveToDisk(); @@ -75,7 +66,7 @@ const WalletExport = () => { disableBlur(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [goBack, saveToDisk, wallet]), + }, [wallet]), ); if (isLoading || !wallet) From b3beb795ddd73174524fb1fe495183ea49b7cc8e Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 24 Mar 2024 10:52:10 -0400 Subject: [PATCH 2/6] ADD: useExtendedNavigation --- App.js | 2 +- components/NavigationProvider.tsx | 42 +------------- hooks/useExtendedNavigation.ts | 57 +++++++++++++++++++ ios/Podfile.lock | 4 +- package-lock.json | 14 ++--- screen/wallets/details.js | 5 +- .../exportMultisigCoordinationSetup.js | 13 +---- screen/wallets/list.js | 5 +- screen/wallets/reorderWallets.js | 4 +- screen/wallets/transactions.js | 5 +- screen/wallets/viewEditMultisigCosigners.tsx | 12 +--- screen/wallets/xpub.tsx | 8 --- 12 files changed, 85 insertions(+), 86 deletions(-) create mode 100644 hooks/useExtendedNavigation.ts diff --git a/App.js b/App.js index c401dd26e..aea7c4197 100644 --- a/App.js +++ b/App.js @@ -305,11 +305,11 @@ const App = () => { + - ); diff --git a/components/NavigationProvider.tsx b/components/NavigationProvider.tsx index 744b2a247..78300109e 100644 --- a/components/NavigationProvider.tsx +++ b/components/NavigationProvider.tsx @@ -1,47 +1,9 @@ -import React, { createContext, useContext, useCallback, ReactNode } from 'react'; -import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native'; -import Biometric from '../class/biometrics'; - -interface NavigationContextType { - navigate: (name: string, params?: object) => void; -} - -const NavigationContext = createContext({ - navigate: () => {}, // default empty implementation -}); +import React, { ReactNode } from 'react'; interface NavigationProviderProps { children: ReactNode; } export const NavigationProvider: React.FC = ({ children }) => { - const navigation = useNavigation>(); - - const customNavigate = useCallback( - (name: string, params: object = {}) => { - if (name === 'WalletExportRoot') { - Biometric.isBiometricUseEnabled().then((isBiometricsEnabled: boolean) => { - if (isBiometricsEnabled) { - Biometric.unlockWithBiometrics().then((isAuthenticated: boolean) => { - if (isAuthenticated) { - navigation.navigate(name, params); - } else { - console.error('Biometric authentication failed'); - } - }); - } else { - console.warn('Biometric authentication is not enabled'); - navigation.navigate(name, params); - } - }); - } else { - navigation.navigate(name, params); - } - }, - [navigation], - ); - - return {children}; + return <>{children}; }; - -export const useCustomNavigation = () => useContext(NavigationContext); diff --git a/hooks/useExtendedNavigation.ts b/hooks/useExtendedNavigation.ts new file mode 100644 index 000000000..e7e4574c4 --- /dev/null +++ b/hooks/useExtendedNavigation.ts @@ -0,0 +1,57 @@ +import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native'; +import Biometric from '../class/biometrics'; +import { navigationRef } from '../NavigationService'; + +export const useExtendedNavigation = (): NavigationProp => { + const originalNavigation = useNavigation>(); + + const enhancedNavigate: any = (screenOrOptions: any, params?: any) => { + let screenName: string; + if (typeof screenOrOptions === 'string') { + screenName = screenOrOptions; + } else if (typeof screenOrOptions === 'object' && 'name' in screenOrOptions) { + screenName = screenOrOptions.name; + } else { + throw new Error('Invalid navigation options'); + } + + const requiresBiometrics = [ + 'WalletExportRoot', + 'WalletXpubRoot', + 'ViewEditMultisigCosignersRoot', + 'ExportMultisigCoordinationSetupRoot', + ].includes(screenName); + + const proceedWithNavigation = () => { + if (navigationRef.current?.isReady()) { + typeof screenOrOptions === 'string' + ? originalNavigation.navigate(screenOrOptions, params) + : originalNavigation.navigate(screenOrOptions); + } + }; + + if (requiresBiometrics) { + Biometric.isBiometricUseEnabled().then(isBiometricsEnabled => { + if (isBiometricsEnabled) { + Biometric.unlockWithBiometrics().then(isAuthenticated => { + if (isAuthenticated) { + proceedWithNavigation(); + } else { + console.error('Biometric authentication failed'); + } + }); + } else { + console.warn('Biometric authentication is not enabled'); + proceedWithNavigation(); + } + }); + } else { + proceedWithNavigation(); + } + }; + + return { + ...originalNavigation, + navigate: enhancedNavigate, + }; +}; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a1f06311c..85ea25edc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -511,7 +511,7 @@ PODS: - RNScreens (3.29.0): - RCT-Folly (= 2021.07.22.00) - React-Core - - RNShare (10.0.2): + - RNShare (10.1.0): - React-Core - RNSVG (13.14.0): - React-Core @@ -861,7 +861,7 @@ SPEC CHECKSUMS: RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 RNReanimated: fc36806836aca984b797f01432abe31689663421 RNScreens: 8ba3eeb8f5cb9f13662df564e785d64ef7214bf2 - RNShare: 859ff710211285676b0bcedd156c12437ea1d564 + RNShare: b674d9f1cb0dc11116983bebd8712908a226a3ee RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 RNVectorIcons: 2b974a961e7ad079fafdd8af68b12210d5b4b28e RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236 diff --git a/package-lock.json b/package-lock.json index 9e3da8cc0..ec4475d74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ "react-native-safe-area-context": "4.9.0", "react-native-screens": "3.29.0", "react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#2076b48", - "react-native-share": "10.0.2", + "react-native-share": "10.1.0", "react-native-svg": "13.14.0", "react-native-tcp-socket": "6.0.6", "react-native-vector-icons": "10.0.3", @@ -19689,9 +19689,9 @@ "license": "ISC" }, "node_modules/react-native-share": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.0.2.tgz", - "integrity": "sha512-EZs4MtsyauAI1zP8xXT1hIFB/pXOZJNDCKcgCpEfTZFXgCUzz8MDVbI1ocP2hA59XHRSkqAQdbJ0BFTpjxOBlg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.1.0.tgz", + "integrity": "sha512-fKKvwaZe5s3HW0tcRKyR5AWvAfXvkk0FgTw1nYWYjI4gNqntXbMjRqWZed8Hmd1MvIJS17K/ELPges0tsxbp3g==", "engines": { "node": ">=16" } @@ -37265,9 +37265,9 @@ "from": "react-native-secure-key-store@https://github.com/BlueWallet/react-native-secure-key-store#2076b48" }, "react-native-share": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.0.2.tgz", - "integrity": "sha512-EZs4MtsyauAI1zP8xXT1hIFB/pXOZJNDCKcgCpEfTZFXgCUzz8MDVbI1ocP2hA59XHRSkqAQdbJ0BFTpjxOBlg==" + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.1.0.tgz", + "integrity": "sha512-fKKvwaZe5s3HW0tcRKyR5AWvAfXvkk0FgTw1nYWYjI4gNqntXbMjRqWZed8Hmd1MvIJS17K/ELPges0tsxbp3g==" }, "react-native-size-matters": { "version": "0.3.1", diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 88414ecb2..973a7d493 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -32,7 +32,7 @@ import { LightningLdkWallet, } from '../../class'; import loc, { formatBalanceWithoutSuffix } from '../../loc'; -import { useRoute, useNavigation } from '@react-navigation/native'; +import { useRoute } from '@react-navigation/native'; import RNFS from 'react-native-fs'; import Share from 'react-native-share'; import { BlueStorageContext } from '../../blue_modules/storage-context'; @@ -48,6 +48,7 @@ import ListItem from '../../components/ListItem'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import Button from '../../components/Button'; import { SecondButton } from '../../components/SecondButton'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; const prompt = require('../../helpers/prompt'); @@ -134,7 +135,7 @@ const WalletDetails = () => { const [isAdvancedModeEnabledRender, setIsAdvancedModeEnabledRender] = useState(false); const [isBIP47Enabled, setIsBIP47Enabled] = useState(wallet.isBIP47Enabled()); const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList()); - const { goBack, navigate, setOptions, popToTop } = useNavigation(); + const { goBack, setOptions, popToTop, navigate } = useExtendedNavigation(); const { colors } = useTheme(); const [masterFingerprint, setMasterFingerprint] = useState(); const walletTransactionsLength = useMemo(() => wallet.getTransactions().length, [wallet]); diff --git a/screen/wallets/exportMultisigCoordinationSetup.js b/screen/wallets/exportMultisigCoordinationSetup.js index af6f38117..fabf5a99c 100644 --- a/screen/wallets/exportMultisigCoordinationSetup.js +++ b/screen/wallets/exportMultisigCoordinationSetup.js @@ -1,10 +1,9 @@ import React, { useCallback, useContext, useRef, useState } from 'react'; import { ActivityIndicator, InteractionManager, ScrollView, StyleSheet, View } from 'react-native'; -import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native'; +import { useFocusEffect, useRoute } from '@react-navigation/native'; import { BlueSpacing20, BlueText } from '../../BlueComponents'; import navigationStyle from '../../components/navigationStyle'; import { DynamicQRCode } from '../../components/DynamicQRCode'; -import Biometric from '../../class/biometrics'; import loc from '../../loc'; import { SquareButton } from '../../components/SquareButton'; import { BlueStorageContext } from '../../blue_modules/storage-context'; @@ -21,7 +20,6 @@ const ExportMultisigCoordinationSetup = () => { const dynamicQRCode = useRef(); const [isLoading, setIsLoading] = useState(true); const [isShareButtonTapped, setIsShareButtonTapped] = useState(false); - const { goBack } = useNavigation(); const { colors } = useTheme(); const { enableBlur, disableBlur } = usePrivacy(); const stylesHook = StyleSheet.create({ @@ -54,13 +52,6 @@ const ExportMultisigCoordinationSetup = () => { enableBlur(); const task = InteractionManager.runAfterInteractions(async () => { if (wallet) { - const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); - - if (isBiometricsEnabled) { - if (!(await Biometric.unlockWithBiometrics())) { - return goBack(); - } - } qrCodeContents.current = Buffer.from(wallet.getXpub(), 'ascii').toString('hex'); setIsLoading(false); } @@ -69,7 +60,7 @@ const ExportMultisigCoordinationSetup = () => { task.cancel(); disableBlur(); }; - }, [disableBlur, enableBlur, goBack, wallet]), + }, [disableBlur, enableBlur, wallet]), ); return isLoading ? ( diff --git a/screen/wallets/list.js b/screen/wallets/list.js index 59e3ea990..0cfbffd64 100644 --- a/screen/wallets/list.js +++ b/screen/wallets/list.js @@ -20,7 +20,7 @@ import DeeplinkSchemaMatch from '../../class/deeplink-schema-match'; import ActionSheet from '../ActionSheet'; import loc from '../../loc'; import { FContainer, FButton } from '../../components/FloatButtons'; -import { useFocusEffect, useIsFocused, useNavigation, useRoute } from '@react-navigation/native'; +import { useFocusEffect, useIsFocused, useRoute } from '@react-navigation/native'; import { BlueStorageContext } from '../../blue_modules/storage-context'; import { isDesktop, isTablet } from '../../blue_modules/environment'; import BlueClipboard from '../../blue_modules/clipboard'; @@ -30,6 +30,7 @@ import { scanQrHelper } from '../../helpers/scan-qr'; import { useTheme } from '../../components/themes'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import presentAlert from '../../components/Alert'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; const A = require('../../blue_modules/analytics'); const fs = require('../../blue_modules/fs'); @@ -49,7 +50,7 @@ const WalletsList = () => { } = useContext(BlueStorageContext); const { width } = useWindowDimensions(); const { colors, scanImage } = useTheme(); - const { navigate, setOptions } = useNavigation(); + const { navigate, setOptions } = useExtendedNavigation(); const isFocused = useIsFocused(); const routeName = useRoute().name; const [isLoading, setIsLoading] = useState(false); diff --git a/screen/wallets/reorderWallets.js b/screen/wallets/reorderWallets.js index 2dce9d1f2..888c3044f 100644 --- a/screen/wallets/reorderWallets.js +++ b/screen/wallets/reorderWallets.js @@ -1,7 +1,6 @@ import React, { useEffect, useRef, useContext, useState } from 'react'; import { StyleSheet, useColorScheme, Platform } from 'react-native'; import DraggableFlatList, { ScaleDecorator } from 'react-native-draggable-flatlist'; -import { useNavigation } from '@react-navigation/native'; import navigationStyle from '../../components/navigationStyle'; import loc from '../../loc'; import { BlueStorageContext } from '../../blue_modules/storage-context'; @@ -9,6 +8,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useTheme } from '../../components/themes'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { WalletCarouselItem } from '../../components/WalletsCarousel'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; const styles = StyleSheet.create({ root: { @@ -24,7 +24,7 @@ const ReorderWallets = () => { const { colors } = useTheme(); const { wallets, setWalletsWithNewOrder } = useContext(BlueStorageContext); const colorScheme = useColorScheme(); - const { navigate, setOptions } = useNavigation(); + const { navigate, setOptions } = useExtendedNavigation(); const [searchQuery, setSearchQuery] = useState(''); const [isSearchFocused, setIsSearchFocused] = useState(false); const [walletData, setWalletData] = useState([]); diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 88be6dadc..d42d9fd05 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -15,7 +15,7 @@ import { findNodeHandle, } from 'react-native'; import { Icon } from 'react-native-elements'; -import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native'; +import { useRoute, useFocusEffect } from '@react-navigation/native'; import { Chain } from '../../models/bitcoinUnits'; import { BlueAlertWalletExportReminder } from '../../BlueComponents'; import WalletGradient from '../../class/wallet-gradient'; @@ -35,6 +35,7 @@ import PropTypes from 'prop-types'; import { scanQrHelper } from '../../helpers/scan-qr'; import { useTheme } from '../../components/themes'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; const fs = require('../../blue_modules/fs'); const BlueElectrum = require('../../blue_modules/BlueElectrum'); @@ -63,7 +64,7 @@ const WalletTransactions = ({ navigation }) => { const [timeElapsed, setTimeElapsed] = useState(0); const [limit, setLimit] = useState(15); const [pageSize, setPageSize] = useState(20); - const { setParams, setOptions, navigate } = useNavigation(); + const { setParams, setOptions, navigate } = useExtendedNavigation(); const { colors } = useTheme(); const [lnNodeInfo, setLnNodeInfo] = useState({ canReceive: 0, canSend: 0 }); const walletActionButtonsRef = useRef(); diff --git a/screen/wallets/viewEditMultisigCosigners.tsx b/screen/wallets/viewEditMultisigCosigners.tsx index 462bcbac3..b6a07f75b 100644 --- a/screen/wallets/viewEditMultisigCosigners.tsx +++ b/screen/wallets/viewEditMultisigCosigners.tsx @@ -1,4 +1,4 @@ -import { useFocusEffect, useNavigation } from '@react-navigation/native'; +import { useFocusEffect } from '@react-navigation/native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { @@ -52,6 +52,7 @@ import usePrivacy from '../../hooks/usePrivacy'; import loc from '../../loc'; import { isDesktop } from '../../blue_modules/environment'; import ActionSheet from '../ActionSheet'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; const fs = require('../../blue_modules/fs'); const prompt = require('../../helpers/prompt'); @@ -61,7 +62,7 @@ const ViewEditMultisigCosigners = ({ route }: Props) => { const hasLoaded = useRef(false); const { colors } = useTheme(); const { wallets, setWalletsWithNewOrder, isElectrumDisabled, isAdvancedModeEnabled } = useContext(BlueStorageContext); - const { navigate, goBack, dispatch, addListener } = useNavigation(); + const { navigate, dispatch, addListener } = useExtendedNavigation(); const openScannerButtonRef = useRef(); const { walletId } = route.params; const w = useRef(wallets.find(wallet => wallet.getID() === walletId)); @@ -214,13 +215,6 @@ const ViewEditMultisigCosigners = ({ route }: Props) => { enableBlur(); const task = InteractionManager.runAfterInteractions(async () => { - const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); - - if (isBiometricsEnabled) { - if (!(await Biometric.unlockWithBiometrics())) { - return goBack(); - } - } if (!w.current) { // lets create fake wallet so renderer wont throw any errors w.current = new MultisigHDWallet(); diff --git a/screen/wallets/xpub.tsx b/screen/wallets/xpub.tsx index ef2e69c2d..0b89cbffa 100644 --- a/screen/wallets/xpub.tsx +++ b/screen/wallets/xpub.tsx @@ -4,7 +4,6 @@ import { ActivityIndicator, InteractionManager, View } from 'react-native'; import Share from 'react-native-share'; import { BlueCopyTextToClipboard, BlueSpacing20, BlueText } from '../../BlueComponents'; import { BlueStorageContext } from '../../blue_modules/storage-context'; -import Biometric from '../../class/biometrics'; import Button from '../../components/Button'; import QRCodeComponent from '../../components/QRCodeComponent'; import SafeArea from '../../components/SafeArea'; @@ -43,13 +42,6 @@ const WalletXpub: React.FC = () => { enableBlur(); const task = InteractionManager.runAfterInteractions(async () => { if (wallet) { - const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); - - if (isBiometricsEnabled) { - if (!(await Biometric.unlockWithBiometrics())) { - return navigation.goBack(); - } - } const walletXpub = wallet.getXpub(); if (xpub !== walletXpub) { navigation.setParams({ xpub: walletXpub || undefined }); From 8cb565f9282cd6e13fc2368163637abe04f92416 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 24 Mar 2024 16:29:58 -0400 Subject: [PATCH 3/6] REF: BlueAlertWalletExportReminder --- BlueComponents.js | 37 ---------------- helpers/presentWalletExportReminder.ts | 16 +++++++ hooks/useExtendedNavigation.ts | 61 +++++++++++++++++++------- screen/receive/details.js | 23 ++-------- screen/wallets/transactions.js | 14 +++--- scripts/find-unused-loc.js | 2 +- 6 files changed, 72 insertions(+), 81 deletions(-) create mode 100644 helpers/presentWalletExportReminder.ts diff --git a/BlueComponents.js b/BlueComponents.js index d9edb8cb8..409cf4ce8 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import { Icon, Text, Header } from 'react-native-elements'; import { ActivityIndicator, - Alert, Animated, Dimensions, Image, @@ -18,7 +17,6 @@ import { View, I18nManager, ImageBackground, - findNodeHandle, } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees'; @@ -27,8 +25,6 @@ import { BlueCurrentTheme, useTheme } from './components/themes'; import PlusIcon from './components/icons/PlusIcon'; import loc, { formatStringAddTwoWhiteSpaces } from './loc'; import SafeArea from './components/SafeArea'; -import { isDesktop } from './blue_modules/environment'; -import ActionSheet from './screen/ActionSheet'; const { height, width } = Dimensions.get('window'); const aspectRatio = height / width; @@ -196,39 +192,6 @@ export const BlueButtonLink = forwardRef((props, ref) => { ); }); -export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure, anchor }) => { - if (isDesktop) { - ActionSheet.showActionSheetWithOptions( - { - title: loc.wallets.details_title, // Changed from loc.send.header to loc.wallets.details_title - message: loc.pleasebackup.ask, - options: [loc.pleasebackup.ask_yes, loc.pleasebackup.ask_no], - anchor: findNodeHandle(anchor), // Kept the same for context - }, - buttonIndex => { - switch (buttonIndex) { - case 0: - onSuccess(); // Assuming the first button (yes) triggers onSuccess - break; - case 1: - onFailure(); // Assuming the second button (no) triggers onFailure - break; - } - }, - ); - } else { - Alert.alert( - loc.wallets.details_title, - loc.pleasebackup.ask, - [ - { text: loc.pleasebackup.ask_yes, onPress: onSuccess, style: 'cancel' }, - { text: loc.pleasebackup.ask_no, onPress: onFailure }, - ], - { cancelable: false }, - ); - } -}; - export const BluePrivateBalance = () => { return ( diff --git a/helpers/presentWalletExportReminder.ts b/helpers/presentWalletExportReminder.ts new file mode 100644 index 000000000..e861ffcaf --- /dev/null +++ b/helpers/presentWalletExportReminder.ts @@ -0,0 +1,16 @@ +import { Alert } from 'react-native'; +import loc from '../loc'; + +export const presentWalletExportReminder = (): Promise => { + return new Promise((resolve, reject) => { + Alert.alert( + loc.wallets.details_title, + loc.pleasebackup.ask, + [ + { text: loc.pleasebackup.ask_yes, onPress: () => resolve(), style: 'default' }, + { text: loc.pleasebackup.ask_no, onPress: () => reject(new Error('User has denied saving the wallet backup.')), style: 'cancel' }, + ], + { cancelable: false }, + ); + }); +}; diff --git a/hooks/useExtendedNavigation.ts b/hooks/useExtendedNavigation.ts index e7e4574c4..84612c9d5 100644 --- a/hooks/useExtendedNavigation.ts +++ b/hooks/useExtendedNavigation.ts @@ -1,16 +1,21 @@ import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native'; import Biometric from '../class/biometrics'; import { navigationRef } from '../NavigationService'; +import { BlueStorageContext } from '../blue_modules/storage-context'; +import { useContext } from 'react'; +import { presentWalletExportReminder } from '../helpers/presentWalletExportReminder'; export const useExtendedNavigation = (): NavigationProp => { const originalNavigation = useNavigation>(); + const { wallets, saveToDisk } = useContext(BlueStorageContext); - const enhancedNavigate: any = (screenOrOptions: any, params?: any) => { + const enhancedNavigate: NavigationProp['navigate'] = (screenOrOptions: any, params?: any) => { let screenName: string; if (typeof screenOrOptions === 'string') { screenName = screenOrOptions; } else if (typeof screenOrOptions === 'object' && 'name' in screenOrOptions) { screenName = screenOrOptions.name; + params = screenOrOptions.params; // Assign params from object if present } else { throw new Error('Invalid navigation options'); } @@ -21,33 +26,55 @@ export const useExtendedNavigation = (): NavigationProp => { 'ViewEditMultisigCosignersRoot', 'ExportMultisigCoordinationSetupRoot', ].includes(screenName); + const requiresWalletExportIsSaved = ['ReceiveDetailsRoot'].includes(screenName); const proceedWithNavigation = () => { if (navigationRef.current?.isReady()) { typeof screenOrOptions === 'string' ? originalNavigation.navigate(screenOrOptions, params) - : originalNavigation.navigate(screenOrOptions); + : originalNavigation.navigate(screenName, params); // Fixed to use screenName and params } }; - if (requiresBiometrics) { - Biometric.isBiometricUseEnabled().then(isBiometricsEnabled => { + (async () => { + if (requiresBiometrics) { + const isBiometricsEnabled = await Biometric.isBiometricUseEnabled(); if (isBiometricsEnabled) { - Biometric.unlockWithBiometrics().then(isAuthenticated => { - if (isAuthenticated) { - proceedWithNavigation(); - } else { - console.error('Biometric authentication failed'); - } - }); - } else { - console.warn('Biometric authentication is not enabled'); - proceedWithNavigation(); + const isAuthenticated = await Biometric.unlockWithBiometrics(); + if (isAuthenticated) { + proceedWithNavigation(); + return; // Ensure the function exits if this path is taken + } else { + console.error('Biometric authentication failed'); + // Decide if navigation should proceed or not after failed authentication + } } - }); - } else { + } + if (requiresWalletExportIsSaved) { + console.log('Checking if wallet export is saved'); + const walletID = params?.params ? params.params.walletID : undefined; + if (!walletID) { + proceedWithNavigation(); + return; + } + const wallet = wallets.find(w => w.getID() === walletID); + if (wallet && !wallet.getUserHasSavedExport()) { + await presentWalletExportReminder() + .then(() => { + wallet.setUserHasSavedExport(true); + saveToDisk().finally(() => proceedWithNavigation()); + }) + .catch(() => { + originalNavigation.navigate('WalletExportRoot', { + screen: 'WalletExport', + params: { walletID }, + }); + }); + return; // Prevent proceeding with the original navigation if the reminder is shown + } + } proceedWithNavigation(); - } + })(); }; return { diff --git a/screen/receive/details.js b/screen/receive/details.js index 90a842574..b436c1426 100644 --- a/screen/receive/details.js +++ b/screen/receive/details.js @@ -10,7 +10,7 @@ import { TextInput, View, } from 'react-native'; -import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native'; +import { useRoute, useFocusEffect } from '@react-navigation/native'; import Share from 'react-native-share'; import QRCodeComponent from '../../components/QRCodeComponent'; import { @@ -19,7 +19,6 @@ import { BlueButtonLink, BlueText, BlueSpacing20, - BlueAlertWalletExportReminder, BlueCard, BlueSpacing40, } from '../../BlueComponents'; @@ -39,6 +38,7 @@ import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { fiatToBTC, satoshiToBTC } from '../../blue_modules/currency'; +import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; const ReceiveDetails = () => { const { walletID, address } = useRoute().params; @@ -53,7 +53,7 @@ const ReceiveDetails = () => { const [showPendingBalance, setShowPendingBalance] = useState(false); const [showConfirmedBalance, setShowConfirmedBalance] = useState(false); const [showAddress, setShowAddress] = useState(false); - const { navigate, goBack, setParams } = useNavigation(); + const { goBack, setParams } = useExtendedNavigation(); const { colors } = useTheme(); const [intervalMs, setIntervalMs] = useState(5000); const [eta, setEta] = useState(''); @@ -341,22 +341,7 @@ const ReceiveDetails = () => { useCallback(() => { const task = InteractionManager.runAfterInteractions(async () => { if (wallet) { - if (!wallet.getUserHasSavedExport()) { - BlueAlertWalletExportReminder({ - onSuccess: obtainWalletAddress, - onFailure: () => { - navigate('WalletExportRoot', { - screen: 'WalletExport', - params: { - walletID: wallet.getID(), - }, - }); - }, - anchor: receiveAddressButton.current, - }); - } else { - obtainWalletAddress(); - } + obtainWalletAddress(); } else if (!wallet && address) { setAddressBIP21Encoded(address); } diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index d42d9fd05..12ae52fdc 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -17,7 +17,6 @@ import { import { Icon } from 'react-native-elements'; import { useRoute, useFocusEffect } from '@react-navigation/native'; import { Chain } from '../../models/bitcoinUnits'; -import { BlueAlertWalletExportReminder } from '../../BlueComponents'; import WalletGradient from '../../class/wallet-gradient'; import navigationStyle from '../../components/navigationStyle'; import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class'; @@ -36,6 +35,7 @@ import { scanQrHelper } from '../../helpers/scan-qr'; import { useTheme } from '../../components/themes'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; +import { presentWalletExportReminder } from '../../helpers/presentWalletExportReminder'; const fs = require('../../blue_modules/fs'); const BlueElectrum = require('../../blue_modules/BlueElectrum'); @@ -519,20 +519,20 @@ const WalletTransactions = ({ navigation }) => { if (wallet.getUserHasSavedExport()) { onManageFundsPressed({ id }); } else { - BlueAlertWalletExportReminder({ - onSuccess: async () => { + presentWalletExportReminder() + .then(async () => { wallet.setUserHasSavedExport(true); await saveToDisk(); onManageFundsPressed({ id }); - }, - onFailure: () => + }) + .catch(() => { navigate('WalletExportRoot', { screen: 'WalletExport', params: { walletID: wallet.getID(), }, - }), - }); + }); + }); } } }} diff --git a/scripts/find-unused-loc.js b/scripts/find-unused-loc.js index b473234f7..4de004f37 100644 --- a/scripts/find-unused-loc.js +++ b/scripts/find-unused-loc.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const mainLocFile = './loc/en.json'; -const dirsToInterate = ['components', 'screen', 'blue_modules', 'class']; +const dirsToInterate = ['components', 'screen', 'blue_modules', 'class', 'hooks', 'helpers']; const addFiles = ['BlueComponents.js', 'App.js', 'BlueApp.ts', 'Navigation.tsx']; const allowedLocPrefixes = ['loc.lnurl_auth', 'loc.units']; From 39cb0123a85f29017dd8aee71dbf41e4b6ebf0bf Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 24 Mar 2024 16:41:16 -0400 Subject: [PATCH 4/6] Update useExtendedNavigation.ts --- hooks/useExtendedNavigation.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/hooks/useExtendedNavigation.ts b/hooks/useExtendedNavigation.ts index 84612c9d5..a6754b3c0 100644 --- a/hooks/useExtendedNavigation.ts +++ b/hooks/useExtendedNavigation.ts @@ -5,6 +5,14 @@ import { BlueStorageContext } from '../blue_modules/storage-context'; import { useContext } from 'react'; import { presentWalletExportReminder } from '../helpers/presentWalletExportReminder'; +// List of screens that require biometrics + +const requiresBiometrics = ['WalletExportRoot', 'WalletXpubRoot', 'ViewEditMultisigCosignersRoot', 'ExportMultisigCoordinationSetupRoot']; + +// List of screens that require wallet export to be saved + +const requiresWalletExportIsSaved = ['ReceiveDetailsRoot', 'WalletAddresses']; + export const useExtendedNavigation = (): NavigationProp => { const originalNavigation = useNavigation>(); const { wallets, saveToDisk } = useContext(BlueStorageContext); @@ -20,15 +28,11 @@ export const useExtendedNavigation = (): NavigationProp => { throw new Error('Invalid navigation options'); } - const requiresBiometrics = [ - 'WalletExportRoot', - 'WalletXpubRoot', - 'ViewEditMultisigCosignersRoot', - 'ExportMultisigCoordinationSetupRoot', - ].includes(screenName); - const requiresWalletExportIsSaved = ['ReceiveDetailsRoot'].includes(screenName); + const isRequiresBiometrics = requiresBiometrics.includes(screenName); + const isRequiresWalletExportIsSaved = requiresWalletExportIsSaved.includes(screenName); const proceedWithNavigation = () => { + console.log('Proceeding with navigation to', screenName); if (navigationRef.current?.isReady()) { typeof screenOrOptions === 'string' ? originalNavigation.navigate(screenOrOptions, params) @@ -37,7 +41,7 @@ export const useExtendedNavigation = (): NavigationProp => { }; (async () => { - if (requiresBiometrics) { + if (isRequiresBiometrics) { const isBiometricsEnabled = await Biometric.isBiometricUseEnabled(); if (isBiometricsEnabled) { const isAuthenticated = await Biometric.unlockWithBiometrics(); @@ -50,9 +54,14 @@ export const useExtendedNavigation = (): NavigationProp => { } } } - if (requiresWalletExportIsSaved) { + if (isRequiresWalletExportIsSaved) { console.log('Checking if wallet export is saved'); - const walletID = params?.params ? params.params.walletID : undefined; + let walletID: string | undefined; + if (params && params.walletID) { + walletID = params.walletID; + } else if (params && params.params && params.params.walletID) { + walletID = params.params.walletID; + } if (!walletID) { proceedWithNavigation(); return; From cf295903561889e26c3f47ba20a363f6a4bc74b0 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 24 Mar 2024 16:43:12 -0400 Subject: [PATCH 5/6] Update useExtendedNavigation.ts --- hooks/useExtendedNavigation.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/hooks/useExtendedNavigation.ts b/hooks/useExtendedNavigation.ts index a6754b3c0..d2d3abe92 100644 --- a/hooks/useExtendedNavigation.ts +++ b/hooks/useExtendedNavigation.ts @@ -68,17 +68,18 @@ export const useExtendedNavigation = (): NavigationProp => { } const wallet = wallets.find(w => w.getID() === walletID); if (wallet && !wallet.getUserHasSavedExport()) { - await presentWalletExportReminder() - .then(() => { - wallet.setUserHasSavedExport(true); - saveToDisk().finally(() => proceedWithNavigation()); - }) - .catch(() => { - originalNavigation.navigate('WalletExportRoot', { - screen: 'WalletExport', - params: { walletID }, - }); + try { + await presentWalletExportReminder(); + wallet.setUserHasSavedExport(true); + await saveToDisk(); // Assuming saveToDisk() returns a Promise. + proceedWithNavigation(); + } catch { + originalNavigation.navigate('WalletExportRoot', { + screen: 'WalletExport', + params: { walletID }, }); + } + return; // Prevent proceeding with the original navigation if the reminder is shown } } From eff16acc30a2bf58542b6e4f2abb2312ffa6eb40 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sun, 24 Mar 2024 16:46:56 -0400 Subject: [PATCH 6/6] Update lndCreateInvoice.js --- screen/lnd/lndCreateInvoice.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/screen/lnd/lndCreateInvoice.js b/screen/lnd/lndCreateInvoice.js index 0d87014e1..68485855a 100644 --- a/screen/lnd/lndCreateInvoice.js +++ b/screen/lnd/lndCreateInvoice.js @@ -16,7 +16,7 @@ import { import { Icon } from 'react-native-elements'; import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native'; -import { BlueAlertWalletExportReminder, BlueDismissKeyboardInputAccessory, BlueLoading } from '../../BlueComponents'; +import { BlueDismissKeyboardInputAccessory, BlueLoading } from '../../BlueComponents'; import navigationStyle from '../../components/navigationStyle'; import AmountInput from '../../components/AmountInput'; import * as NavigationService from '../../NavigationService'; @@ -32,6 +32,7 @@ import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { btcToSatoshi, fiatToBTC, satoshiToBTC } from '../../blue_modules/currency'; +import { presentWalletExportReminder } from '../../helpers/presentWalletExportReminder'; const LNDCreateInvoice = () => { const { wallets, saveToDisk, setSelectedWalletID } = useContext(BlueStorageContext); @@ -117,9 +118,11 @@ const LNDCreateInvoice = () => { if (wallet.current.getUserHasSavedExport()) { renderReceiveDetails(); } else { - BlueAlertWalletExportReminder({ - onSuccess: () => renderReceiveDetails(), - onFailure: () => { + presentWalletExportReminder() + .then(() => { + renderReceiveDetails(); + }) + .catch(() => { getParent().pop(); NavigationService.navigate('WalletExportRoot', { screen: 'WalletExport', @@ -127,8 +130,7 @@ const LNDCreateInvoice = () => { walletID, }, }); - }, - }); + }); } } else { triggerHapticFeedback(HapticFeedbackTypes.NotificationError);