From 147a1c17a0d338c3bb72be3ecb9c838fd6e5e491 Mon Sep 17 00:00:00 2001 From: Evan Kaloudis Date: Tue, 28 May 2024 13:11:42 -0400 Subject: [PATCH] Recovery status --- App.tsx | 5 ++ backends/EmbeddedLND.ts | 2 + locales/en.json | 4 ++ stores/SyncStore.ts | 41 +++++++++++++ utils/BackendUtils.ts | 1 + views/SyncRecovery.tsx | 92 +++++++++++++++++++++++++++++ views/Wallet/BalancePane.tsx | 109 ++++++++++++++++++++++++++++++++++- views/Wallet/Wallet.tsx | 12 ++++ 8 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 views/SyncRecovery.tsx diff --git a/App.tsx b/App.tsx index 135edb775..a340aaef4 100644 --- a/App.tsx +++ b/App.tsx @@ -139,6 +139,7 @@ import EditFee from './views/EditFee'; import Seed from './views/Settings/Seed'; import SeedRecovery from './views/Settings/SeedRecovery'; import Sync from './views/Sync'; +import SyncRecovery from './views/SyncRecovery'; import LspExplanationFees from './views/Explanations/LspExplanationFees'; import LspExplanationRouting from './views/Explanations/LspExplanationRouting'; import LspExplanationWrappedInvoices from './views/Explanations/LspExplanationWrappedInvoices'; @@ -603,6 +604,10 @@ export default class App extends React.PureComponent { name="Sync" component={Sync} /> + await subscribeCustomMessages(); getMyNodeInfo = async () => await getInfo(); getNetworkInfo = async () => await getNetworkInfo(); + getRecoveryInfo = async () => await getRecoveryInfo(); getInvoices = async () => await listInvoices(); createInvoice = async (data: any) => await addInvoice({ diff --git a/locales/en.json b/locales/en.json index 00a47c1ec..2e18f1ae8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -305,6 +305,9 @@ "views.OpenChannel.removeAdditionalChannel": "Remove additional channel", "views.Wallet.BalancePane.sync.title": "Finishing sync", "views.Wallet.BalancePane.sync.text": "Hang on tight! You will be ready to use Zeus soon.", + "views.Wallet.BalancePane.recovery.title": "Recovery mode", + "views.Wallet.BalancePane.recovery.text": "Please leave ZEUS open until the process completes.", + "views.Wallet.BalancePane.recovery.textAlt": "Leave ZEUS open until completion.", "views.Wallet.BalancePane.backup.title": "Back up your funds", "views.Wallet.BalancePane.backup.text": "Create a backup to never lose access to your bitcoin.", "views.Wallet.BalancePane.backup.action": "Start backup ->", @@ -949,6 +952,7 @@ "views.Sync.tip": "Tip", "views.Sync.numBlocksUntilSynced": "Number of blocks until synced", "views.LSPS1.pubkeyAndHostNotFound": "Node pubkey and host are not set", + "views.SyncRecovery.title": "Recovering wallet", "views.LSPS1.timeoutError": "Did not receive response from server", "views.LSPS1.channelExpiryBlocks": "Channel Expiry Blocks", "views.LSPS1.maxChannelExpiryBlocks": "Max channel Expiry Blocks", diff --git a/stores/SyncStore.ts b/stores/SyncStore.ts index d751cb725..f62da1b0f 100644 --- a/stores/SyncStore.ts +++ b/stores/SyncStore.ts @@ -10,6 +10,8 @@ import SettingsStore from './SettingsStore'; export default class SyncStore { @observable public isSyncing: boolean = false; + @observable public isRecovering: boolean = false; + @observable public recoveryProgress: number | null; @observable public isInExpressGraphSync: boolean = false; @observable public bestBlockHeight: number; @observable public currentBlockHeight: number; @@ -26,6 +28,7 @@ export default class SyncStore { @action public reset = () => { this.isSyncing = false; + this.isRecovering = false; this.isInExpressGraphSync = false; this.error = false; }; @@ -137,4 +140,42 @@ export default class SyncStore { if (queryMempool) this.getBestBlockHeight(); } }; + + @action + public checkRecoveryStatus = () => { + BackendUtils.getRecoveryInfo().then((data: any) => { + if (data.recovery_mode && !data.recovery_finished) { + this.startRecovering(); + } + }); + }; + + @action + public getRecoveryStatus = async () => { + await BackendUtils.getRecoveryInfo().then((data: any) => { + if (data.recovery_mode) { + if (data.progress) { + this.recoveryProgress = data.progress; + } + if (data.recovery_finished) { + this.isRecovering = false; + this.recoveryProgress = null; + } + } else { + this.isRecovering = false; + this.recoveryProgress = null; + } + return data; + }); + }; + + @action + public startRecovering = async () => { + this.isRecovering = true; + + while (this.isRecovering) { + await sleep(2000); + await this.getRecoveryStatus(); + } + }; } diff --git a/utils/BackendUtils.ts b/utils/BackendUtils.ts index cd9f217f0..dae5ea476 100644 --- a/utils/BackendUtils.ts +++ b/utils/BackendUtils.ts @@ -75,6 +75,7 @@ class BackendUtils { this.call('subscribeCustomMessages', args); getMyNodeInfo = (...args: any[]) => this.call('getMyNodeInfo', args); getNetworkInfo = (...args: any[]) => this.call('getNetworkInfo', args); + getRecoveryInfo = (...args: any[]) => this.call('getRecoveryInfo', args); getInvoices = (...args: any[]) => this.call('getInvoices', args); createInvoice = (...args: any[]) => this.call('createInvoice', args); getPayments = (...args: any[]) => this.call('getPayments', args); diff --git a/views/SyncRecovery.tsx b/views/SyncRecovery.tsx new file mode 100644 index 000000000..6ef6b5590 --- /dev/null +++ b/views/SyncRecovery.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Dimensions, View } from 'react-native'; +import { inject, observer } from 'mobx-react'; +import CircularProgress from 'react-native-circular-progress-indicator'; +import { StackNavigationProp } from '@react-navigation/stack'; + +import Button from '../components/Button'; +import Screen from '../components/Screen'; +import Header from '../components/Header'; + +import SyncStore from '../stores/SyncStore'; + +import { localeString } from '../utils/LocaleUtils'; +import { themeColor } from '../utils/ThemeUtils'; + +interface SyncRecoveryProps { + navigation: StackNavigationProp; + SyncStore: SyncStore; +} + +@inject('SyncStore') +@observer +export default class SyncRecovery extends React.PureComponent< + SyncRecoveryProps, + {} +> { + render() { + const { navigation, SyncStore } = this.props; + const { recoveryProgress } = SyncStore; + + const { width } = Dimensions.get('window'); + + return ( + +
+ + + { + 'worklet'; + return value.toFixed && value.toFixed(1); // 1 decimal place + }} + valueSuffix="%" + /> + + + +