diff --git a/src/assets/icons/wallet.ts b/src/assets/icons/wallet.ts index 8dbf94d8e..dc6b13028 100644 --- a/src/assets/icons/wallet.ts +++ b/src/assets/icons/wallet.ts @@ -182,9 +182,6 @@ export const usersIcon = (color = 'white'): string => export const userIcon = (color = 'white'): string => ``; -export const userRectangleIcon = (color = 'white'): string => - ``; - export const speedFastIcon = (color = 'white'): string => ``; diff --git a/src/screens/Settings/BackupSettings/index.tsx b/src/screens/Settings/BackupSettings/index.tsx index 3d2259311..6a9e716d5 100644 --- a/src/screens/Settings/BackupSettings/index.tsx +++ b/src/screens/Settings/BackupSettings/index.tsx @@ -1,46 +1,47 @@ import React, { ReactElement, ReactNode, memo, useMemo } from 'react'; -import { StyleSheet, View } from 'react-native'; import { useTranslation } from 'react-i18next'; +import { StyleSheet, View } from 'react-native'; import { EItemType, IListData } from '../../../components/List'; -import SettingsView from '../SettingsView'; import { useAppDispatch, useAppSelector } from '../../../hooks/redux'; -import { showBottomSheet } from '../../../store/utils/ui'; import { SettingsScreenProps } from '../../../navigation/types'; -import { Caption13M, Caption13Up, Text01M } from '../../../styles/text'; +import { backupSelector } from '../../../store/reselect/backup'; +import { lightningBackupSelector } from '../../../store/reselect/lightning'; +import { forceBackup } from '../../../store/slices/backup'; +import { TBackupItem } from '../../../store/types/backup'; +import { EBackupCategories } from '../../../store/utils/backup'; +import { showBottomSheet } from '../../../store/utils/ui'; import { ScrollView, - TouchableOpacity, View as ThemedView, + TouchableOpacity, } from '../../../styles/components'; -import { backupSelector } from '../../../store/reselect/backup'; import { ArrowClockwise, - // LightningHollow, + LightningHollow, NoteIcon, RectanglesTwo, SettingsIcon, TagIcon, - // TransferIcon, UsersIcon, - // UserRectangleIcon, } from '../../../styles/icons'; -import { i18nTime } from '../../../utils/i18n'; -import { EBackupCategories } from '../../../store/utils/backup'; -import { forceBackup } from '../../../store/slices/backup'; +import { Caption13M, Caption13Up, Text01M } from '../../../styles/text'; import { IThemeColors } from '../../../styles/themes'; -import { TBackupItem } from '../../../store/types/backup'; +import { i18nTime } from '../../../utils/i18n'; +import SettingsView from '../SettingsView'; const Status = ({ Icon, title, status, category, + disableRetry, }: { Icon: React.FunctionComponent; title: ReactNode; status: TBackupItem; - category: EBackupCategories; + category?: EBackupCategories; + disableRetry?: boolean; }): ReactElement => { const { t } = useTranslation('settings'); const { t: tTime } = useTranslation('intl', { i18n: i18nTime }); @@ -93,7 +94,9 @@ const Status = ({ } const retry = (): void => { - // setHideRetry(true); + if (!category) { + return; + } dispatch(forceBackup({ category })); }; @@ -108,7 +111,7 @@ const Status = ({ {title} {subtitle} - {showRetry && ( + {!disableRetry && showRetry && ( @@ -117,21 +120,36 @@ const Status = ({ ); }; +type TBackupCategory = { + Icon: React.FunctionComponent; + title: string; + category?: EBackupCategories; + status: TBackupItem; + disableRetry?: boolean; +}; + const BackupSettings = ({ navigation, }: SettingsScreenProps<'BackupSettings'>): ReactElement => { const { t } = useTranslation('settings'); const pin = useAppSelector((state) => state.settings.pin); const backup = useAppSelector(backupSelector); + const lightningBackup = useAppSelector(lightningBackupSelector); - const categories = [ - // { - // Icon: LightningHollow, - // title: t('backup.category_connections'), - // isSyncedKey: 'remoteLdkBackupSynced', - // lastSync: backup.remoteLdkBackupLastSync, - // syncRequired: backup.remoteLdkBackupLastSyncRequired, - // }, + // find lightning latest backup item to show + const lightning = useMemo(() => { + const channels = Object.entries(lightningBackup).filter(([key]) => + key.startsWith('channel_'), + ); + if (channels.length === 0) { + return; + } + return channels.reduce((acc, [, value]) => { + return value.lastQueued > acc.lastQueued ? value : acc; + }, channels[0][1]); + }, [lightningBackup]); + + const categories: Array = [ { Icon: NoteIcon, title: t('backup.category_connection_receipts'), @@ -177,6 +195,19 @@ const BackupSettings = ({ }, ]; + if (lightning) { + categories.unshift({ + Icon: LightningHollow, + title: t('backup.category_connections'), + status: { + running: false, + synced: lightning.lastPersisted ?? 0, + required: lightning.lastQueued, + }, + disableRetry: true, + }); + } + const settingsListData: IListData[] = useMemo( () => [ { diff --git a/src/store/migrations/index.ts b/src/store/migrations/index.ts index d51b9fc64..6b6cc4ec2 100644 --- a/src/store/migrations/index.ts +++ b/src/store/migrations/index.ts @@ -364,12 +364,25 @@ const migrations = { }; }, 34: (state): PersistedState => { + const newNodes = { ...state.lightning.nodes }; + // Loop through all nodes + for (const walletName in newNodes) { + newNodes[walletName] = { + ...newNodes[walletName], + backup: getNetworkContent({}), + }; + } + return { ...state, backup: { ...initialBackupState, ...state.backup, }, + lightning: { + ...state.lightning, + nodes: newNodes, + }, }; }, }; diff --git a/src/store/reselect/lightning.ts b/src/store/reselect/lightning.ts index 422eaf2e7..58eaf77a9 100644 --- a/src/store/reselect/lightning.ts +++ b/src/store/reselect/lightning.ts @@ -219,3 +219,11 @@ export const lightningBalanceSelector = createSelector( }; }, ); + +export const lightningBackupSelector = createSelector( + [lightningState, selectedWalletSelector, selectedNetworkSelector], + (lightning, selectedWallet, selectedNetwork) => { + const node = lightning.nodes[selectedWallet]; + return node?.backup[selectedNetwork] ?? {}; + }, +); diff --git a/src/store/shapes/lightning.ts b/src/store/shapes/lightning.ts index bd23617a7..ab9239ad5 100644 --- a/src/store/shapes/lightning.ts +++ b/src/store/shapes/lightning.ts @@ -8,6 +8,7 @@ export const defaultLightningShape: TNode = { openChannelIds: getNetworkContent([]), peers: getNetworkContent([]), claimableBalances: getNetworkContent([]), + backup: getNetworkContent({}), }; export const initialLightningState: TLightningState = { diff --git a/src/store/slices/lightning.ts b/src/store/slices/lightning.ts index 2c234cfb0..c951a0f40 100644 --- a/src/store/slices/lightning.ts +++ b/src/store/slices/lightning.ts @@ -1,5 +1,8 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { TClaimableBalance } from '@synonymdev/react-native-ldk'; +import { + TBackupStateUpdate, + TClaimableBalance, +} from '@synonymdev/react-native-ldk'; import { initialLightningState } from '../shapes/lightning'; import { EAvailableNetwork } from '../../utils/networks'; @@ -87,6 +90,17 @@ export const lightningSlice = createSlice({ state.nodes[selectedWallet].claimableBalances[selectedNetwork] = claimableBalances; }, + updateBackupState: ( + state, + action: PayloadAction<{ + backup: TBackupStateUpdate; + selectedWallet: TWalletName; + selectedNetwork: EAvailableNetwork; + }>, + ) => { + const { backup, selectedWallet, selectedNetwork } = action.payload; + state.nodes[selectedWallet].backup[selectedNetwork] = backup; + }, updateLdkAccountVersion: ( state, action: PayloadAction, @@ -106,6 +120,7 @@ export const { saveLightningPeer, removeLightningPeer, updateClaimableBalances, + updateBackupState, updateLdkAccountVersion, resetLightningState, } = actions; diff --git a/src/store/types/lightning.ts b/src/store/types/lightning.ts index 989211611..e27a0d0f2 100644 --- a/src/store/types/lightning.ts +++ b/src/store/types/lightning.ts @@ -1,4 +1,5 @@ import { + TBackupStateUpdate, TChannel, TClaimableBalance, TCreatePaymentReq, @@ -27,6 +28,7 @@ export type TNode = { info: IWalletItem<{}>; peers: IWalletItem; claimableBalances: IWalletItem; + backup: IWalletItem; }; export type TNodes = { diff --git a/src/styles/icons.ts b/src/styles/icons.ts index 4d525fc7d..df38cf5ec 100644 --- a/src/styles/icons.ts +++ b/src/styles/icons.ts @@ -27,7 +27,6 @@ import { clipboardTextIcon, usersIcon, userIcon, - userRectangleIcon, speedFastIcon, speedNormalIcon, speedSlowIcon, @@ -282,16 +281,6 @@ export const UserIcon = styled(SvgXml).attrs((props) => ({ color: props.color ? props.theme.colors[props.color] : 'white', })); -export const UserRectangleIcon = styled(SvgXml).attrs((props) => ({ - xml: userRectangleIcon( - props.color ? props.theme.colors[props.color] : 'white', - ), - height: props.height ?? '32px', - width: props.width ?? '32px', -}))((props) => ({ - color: props.color ? props.theme.colors[props.color] : 'white', -})); - export const SpeedFastIcon = styled(SvgXml).attrs((props) => ({ xml: speedFastIcon(props.color ? props.theme.colors[props.color] : 'white'), height: props.height ?? '32px', diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index 0aba4387c..d9fd7008f 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -23,6 +23,7 @@ import lm, { TPaymentReq, TTransactionData, TTransactionPosition, + TBackupStateUpdate, } from '@synonymdev/react-native-ldk'; import { @@ -50,6 +51,7 @@ import { } from '../../store/helpers'; import { defaultHeader } from '../../store/shapes/wallet'; import { + updateBackupState, updateLdkAccountVersion, updateLightningNodeId, } from '../../store/slices/lightning'; @@ -111,6 +113,7 @@ export const FALLBACK_BLOCKTANK_PEERS: IWalletItem = { let paymentSubscription: EmitterSubscription | undefined; let onChannelSubscription: EmitterSubscription | undefined; let onSpendableOutputsSubscription: EmitterSubscription | undefined; +let onBackupStateUpdate: EmitterSubscription | undefined; /** * Wipes LDK data from storage @@ -467,12 +470,34 @@ export const subscribeToLightningPayments = ({ () => {}, ); } + if (!onBackupStateUpdate) { + onBackupStateUpdate = ldk.onEvent( + EEventTypes.backup_state_update, + (res: TBackupStateUpdate) => { + if (!selectedWallet) { + selectedWallet = getSelectedWallet(); + } + if (!selectedNetwork) { + selectedNetwork = getSelectedNetwork(); + } + + dispatch( + updateBackupState({ + backup: res, + selectedWallet, + selectedNetwork, + }), + ); + }, + ); + } }; export const unsubscribeFromLightningSubscriptions = (): void => { paymentSubscription?.remove(); onChannelSubscription?.remove(); onSpendableOutputsSubscription?.remove(); + onBackupStateUpdate?.remove(); }; let isRefreshing = false;