diff --git a/navigation/AddWalletStack.tsx b/navigation/AddWalletStack.tsx index 61d6eda8c..e8d9f736f 100644 --- a/navigation/AddWalletStack.tsx +++ b/navigation/AddWalletStack.tsx @@ -21,9 +21,16 @@ import { export type AddWalletStackParamList = { AddWallet: undefined; ImportWallet: undefined; - ImportWalletDiscovery: undefined; + ImportWalletDiscovery: { + importText: string; + askPassphrase: boolean; + searchAccounts: boolean; + }; ImportSpeed: undefined; - ImportCustomDerivationPath: undefined; + ImportCustomDerivationPath: { + importText: string; + password: string | undefined; + }; PleaseBackup: undefined; PleaseBackupLNDHub: undefined; ProvideEntropy: undefined; diff --git a/navigation/LazyLoadAddWalletStack.tsx b/navigation/LazyLoadAddWalletStack.tsx index 7b5fd5ff3..9b3501158 100644 --- a/navigation/LazyLoadAddWalletStack.tsx +++ b/navigation/LazyLoadAddWalletStack.tsx @@ -5,7 +5,7 @@ import { LazyLoadingIndicator } from './LazyLoadingIndicator'; // Define lazy imports const WalletsAdd = lazy(() => import('../screen/wallets/Add')); const ImportCustomDerivationPath = lazy(() => import('../screen/wallets/importCustomDerivationPath')); -const ImportWalletDiscovery = lazy(() => import('../screen/wallets/importDiscovery')); +const ImportWalletDiscovery = lazy(() => import('../screen/wallets/ImportWalletDiscovery')); const ImportSpeed = lazy(() => import('../screen/wallets/importSpeed')); const ImportWallet = lazy(() => import('../screen/wallets/import')); const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup')); diff --git a/screen/wallets/importDiscovery.js b/screen/wallets/ImportWalletDiscovery.tsx similarity index 51% rename from screen/wallets/importDiscovery.js rename to screen/wallets/ImportWalletDiscovery.tsx index b3cba6c72..a96597da3 100644 --- a/screen/wallets/importDiscovery.js +++ b/screen/wallets/ImportWalletDiscovery.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { useRoute } from '@react-navigation/native'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { RouteProp, useRoute } from '@react-navigation/native'; import { ActivityIndicator, FlatList, LayoutAnimation, StyleSheet, View } from 'react-native'; import IdleTimerManager from 'react-native-idle-timer'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; -import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20 } from '../../BlueComponents'; -import { HDSegwitBech32Wallet } from '../../class'; +import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents'; +import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class'; import startImport from '../../class/wallet-import'; import presentAlert from '../../components/Alert'; import Button from '../../components/Button'; @@ -15,20 +15,44 @@ import prompt from '../../helpers/prompt'; import loc from '../../loc'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { useStorage } from '../../hooks/context/useStorage'; +import { AddWalletStackParamList } from '../../navigation/AddWalletStack'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { THDWalletForWatchOnly, TWallet } from '../../class/wallets/types'; +import { navigate } from '../../NavigationService'; -const ImportWalletDiscovery = () => { - const navigation = useExtendedNavigation(); +type RouteProps = RouteProp; +type NavigationProp = NativeStackNavigationProp; + +type TReturn = { + cancelled?: boolean; + stopped?: boolean; + wallets: TWallet[]; +}; + +type ImportTask = { + promise: Promise; + stop: () => void; +}; + +type WalletEntry = { + wallet: TWallet | THDWalletForWatchOnly; + subtitle: string; + id: string; +}; + +const ImportWalletDiscovery: React.FC = () => { + const navigation = useExtendedNavigation(); const { colors } = useTheme(); - const route = useRoute(); + const route = useRoute(); const { importText, askPassphrase, searchAccounts } = route.params; - const task = useRef(); + const task = useRef(null); const { addAndSaveWallet } = useStorage(); - const [loading, setLoading] = useState(true); - const [wallets, setWallets] = useState([]); - const [password, setPassword] = useState(); - const [selected, setSelected] = useState(0); - const [progress, setProgress] = useState(); - const importing = useRef(false); + const [loading, setLoading] = useState(true); + const [wallets, setWallets] = useState([]); + const [password, setPassword] = useState(); + const [selected, setSelected] = useState(0); + const [progress, setProgress] = useState(); + const importing = useRef(false); const bip39 = useMemo(() => { const hd = new HDSegwitBech32Wallet(); hd.setSecret(importText); @@ -44,32 +68,46 @@ const ImportWalletDiscovery = () => { }, }); - const saveWallet = wallet => { - if (importing.current) return; - importing.current = true; - addAndSaveWallet(wallet); - navigation.getParent().pop(); - }; + const saveWallet = useCallback( + (wallet: TWallet | THDWalletForWatchOnly) => { + if (importing.current) return; + importing.current = true; + addAndSaveWallet(wallet); + navigate('WalletsList'); + }, + [addAndSaveWallet], + ); useEffect(() => { - const onProgress = data => setProgress(data); + const onProgress = (data: string) => setProgress(data); - const onWallet = wallet => { + const onWallet = (wallet: TWallet | THDWalletForWatchOnly) => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); const id = wallet.getID(); - let subtitle; + let subtitle: string | undefined; + try { - subtitle = wallet.getDerivationPath?.(); + // For watch-only wallets, display the descriptor or xpub + if (wallet.type === WatchOnlyWallet.type) { + if (wallet.isHd() && wallet.getSecret()) { + subtitle = wallet.getSecret(); // Display descriptor + } else { + subtitle = wallet.getAddress(); // Display address + } + } else { + subtitle = (wallet as THDWalletForWatchOnly).getDerivationPath?.(); + } } catch (e) {} - setWallets(w => [...w, { wallet, subtitle, id }]); + + setWallets(w => [...w, { wallet, subtitle: subtitle || '', id }]); }; - const onPassword = async (title, subtitle) => { + const onPassword = async (title: string, subtitle: string) => { try { const pass = await prompt(title, subtitle); setPassword(pass); return pass; - } catch (e) { + } catch (e: any) { if (e.message === 'Cancel Pressed') { navigation.goBack(); } @@ -84,7 +122,7 @@ const ImportWalletDiscovery = () => { task.current.promise .then(({ cancelled, wallets: w }) => { if (cancelled) return; - if (w.length === 1) saveWallet(w[0]); // instantly save wallet if only one has been discovered + if (w.length === 1) saveWallet(w[0]); // Instantly save wallet if only one has been discovered if (w.length === 0) { triggerHapticFeedback(HapticFeedbackTypes.ImpactLight); } @@ -99,16 +137,19 @@ const ImportWalletDiscovery = () => { IdleTimerManager.setIdleTimerDisabled(false); }); - return () => task.current.stop(); + return () => { + task.current?.stop(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleCustomDerivation = () => { - task.current.stop(); + task.current?.stop(); + navigation.navigate('ImportCustomDerivationPath', { importText, password }); }; - const renderItem = ({ item, index }) => ( + const renderItem = ({ item, index }: { item: WalletEntry; index: number }) => ( { /> ); - const keyExtractor = w => w.id; + const keyExtractor = (w: WalletEntry) => w.id; + + const ListHeaderComponent = useMemo( + () => ( + <> + {wallets && wallets.length > 0 ? ( + <> + + {loc.wallets.import_discovery_subtitle} + + + ) : null} + + ), + [wallets], + ); + + const ListEmptyComponent = useMemo( + () => ( + + {loc.wallets.import_discovery_no_wallets} + + + ), + [], + ); return ( - - {loc.wallets.import_discovery_subtitle} - - - {!loading && wallets.length === 0 ? ( - - {loc.wallets.import_discovery_no_wallets} - - ) : ( - - )} - + {loading && ( <> @@ -157,9 +221,12 @@ const ImportWalletDiscovery = () => {