diff --git a/e2e/backup.e2e.js b/e2e/backup.e2e.js
index 7c8645e53..e9af07f59 100644
--- a/e2e/backup.e2e.js
+++ b/e2e/backup.e2e.js
@@ -84,7 +84,8 @@ d('Backup', () => {
// change currency to GBP
await element(by.id('TotalBalance')).tap(); // switch to local currency
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('CurrenciesSettings')).tap();
await element(by.text('GBP (£)')).tap();
diff --git a/e2e/boost.e2e.js b/e2e/boost.e2e.js
index 1a23f1fd4..0b3374577 100644
--- a/e2e/boost.e2e.js
+++ b/e2e/boost.e2e.js
@@ -51,7 +51,8 @@ d('Boost', () => {
}
// switch off RBF mode
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
if (!__DEV__) {
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
}
diff --git a/e2e/helpers.js b/e2e/helpers.js
index 498cf1681..dd0db1221 100644
--- a/e2e/helpers.js
+++ b/e2e/helpers.js
@@ -185,7 +185,8 @@ export const waitForActiveChannel = async (lnd, nodeId, maxRetries = 20) => {
};
export const getSeed = async () => {
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('BackupSettings')).tap();
await element(by.id('BackupWallet')).tap();
// animation
@@ -247,7 +248,8 @@ export const restoreWallet = async (seed, passphrase) => {
};
export const waitForBackup = async () => {
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('BackupSettings')).tap();
await waitFor(element(by.id('AllSynced')))
.toBeVisible()
diff --git a/e2e/lightning.e2e.js b/e2e/lightning.e2e.js
index bb0c344f5..6bc9b5176 100644
--- a/e2e/lightning.e2e.js
+++ b/e2e/lightning.e2e.js
@@ -71,7 +71,8 @@ d('Lightning', () => {
const { identityPubkey: lndNodeID } = await lnd.getInfo();
// get LDK Node id
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
// wait for LDK to start
await sleep(5000);
@@ -112,7 +113,8 @@ d('Lightning', () => {
await waitForActiveChannel(lnd, ldkNodeId);
// check channel status
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).atIndex(0).tap();
await element(by.id('Channels')).tap();
await element(by.id('Channel')).atIndex(0).tap();
@@ -331,7 +333,8 @@ d('Lightning', () => {
await element(by.id('NavigationClose')).tap();
// check channel status
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await sleep(100);
await element(by.id('Channels')).tap();
diff --git a/e2e/lnurl.e2e.js b/e2e/lnurl.e2e.js
index 4b6e9c477..699f19cc4 100644
--- a/e2e/lnurl.e2e.js
+++ b/e2e/lnurl.e2e.js
@@ -90,7 +90,8 @@ d('LNURL', () => {
}
// get LDK Node id
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
if (!__DEV__) {
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
}
diff --git a/e2e/numberpad.e2e.js b/e2e/numberpad.e2e.js
index 9275d002b..5a56572e3 100644
--- a/e2e/numberpad.e2e.js
+++ b/e2e/numberpad.e2e.js
@@ -85,7 +85,8 @@ d('NumberPad', () => {
}
// switch to classic denomination
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('UnitSettings')).tap();
await element(by.id('DenominationClassic')).tap();
diff --git a/e2e/onchain.e2e.js b/e2e/onchain.e2e.js
index 0a32d7931..d739b868f 100644
--- a/e2e/onchain.e2e.js
+++ b/e2e/onchain.e2e.js
@@ -246,7 +246,8 @@ d('Onchain', () => {
const coreAddress = await rpc.getNewAddress();
// enable warning for sending over 100$ to test multiple warning dialogs
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
await element(by.id('SendAmountWarning')).tap();
await element(by.id('NavigationClose')).atIndex(0).tap();
diff --git a/e2e/security.e2e.js b/e2e/security.e2e.js
index 682997ae9..a96830b09 100644
--- a/e2e/security.e2e.js
+++ b/e2e/security.e2e.js
@@ -71,7 +71,8 @@ d('Settings Security And Privacy', () => {
await device.setBiometricEnrollment(true);
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
await element(by.id('PINCode')).tap();
await element(by.id('SecureWallet-button-continue')).tap();
@@ -164,7 +165,8 @@ d('Settings Security And Privacy', () => {
await element(by.id('Close')).tap();
// test PIN on idle and disable it after
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
// FIXME: this fails too often
@@ -223,7 +225,8 @@ d('Settings Security And Privacy', () => {
await element(by.id('Close')).tap();
// disable PIN, restart the app, it should not ask for it
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
await element(by.id('PINCode')).tap();
await element(by.id('DisablePin')).tap();
@@ -235,7 +238,8 @@ d('Settings Security And Privacy', () => {
.withTimeout(10000);
// enable PIN for last test
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
await element(by.id('PINCode')).tap();
await element(by.id('SecureWallet-button-continue')).tap();
diff --git a/e2e/send.e2e.js b/e2e/send.e2e.js
index f11ca8a9e..f7e6d54ef 100644
--- a/e2e/send.e2e.js
+++ b/e2e/send.e2e.js
@@ -185,7 +185,8 @@ d('Send', () => {
const { identityPubkey: lndNodeID } = await lnd.getInfo();
// get LDK Node id
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
// wait for LDK to start
await sleep(5000);
@@ -226,7 +227,8 @@ d('Send', () => {
await waitForActiveChannel(lnd, ldkNodeId);
// check channel status
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).atIndex(0).tap();
await element(by.id('Channels')).tap();
await element(by.id('Channel')).atIndex(0).tap();
@@ -455,7 +457,8 @@ d('Send', () => {
const { paymentRequest: invoice7 } = await lnd.addInvoice({ value: 1000 });
// enable quickpay
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('QuickpaySettings')).tap();
await element(by.id('QuickpayIntro-button')).tap();
diff --git a/e2e/settings.e2e.js b/e2e/settings.e2e.js
index 1fef292ef..d99623c9e 100644
--- a/e2e/settings.e2e.js
+++ b/e2e/settings.e2e.js
@@ -58,7 +58,8 @@ d('Settings', () => {
).toHaveText('$');
// switch to GBP
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('CurrenciesSettings')).tap();
await element(by.text('GBP (£)')).tap();
@@ -72,7 +73,8 @@ d('Settings', () => {
await element(by.id('TotalBalance')).tap();
// switch to USD
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('CurrenciesSettings')).tap();
await element(by.text('USD ($)')).tap();
@@ -95,7 +97,8 @@ d('Settings', () => {
by.id('Value').withAncestor(by.id('UnitSettings')),
);
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
// check default unit
await expect(unitRow).toHaveText('Bitcoin');
@@ -110,7 +113,8 @@ d('Settings', () => {
await expect(balance).toHaveText('0.00');
// switch back to BTC
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('UnitSettings')).tap();
await element(by.id('Bitcoin')).tap();
@@ -120,7 +124,8 @@ d('Settings', () => {
await expect(balance).toHaveText('0');
// switch to classic denomination
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('UnitSettings')).tap();
await element(by.id('DenominationClassic')).tap();
@@ -137,7 +142,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
// switch to Fast
@@ -172,7 +178,8 @@ d('Settings', () => {
}
// no tags, menu entry should be hidden
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await expect(element(by.id('TagsSettings'))).not.toBeVisible();
await element(by.id('NavigationClose')).atIndex(0).tap();
@@ -191,7 +198,8 @@ d('Settings', () => {
await sleep(1000);
// open tag manager, delete tag
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('TagsSettings')).tap();
await expect(element(by.text(tag))).toBeVisible();
@@ -214,7 +222,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('About')).tap();
await expect(element(by.id('AboutLogo'))).toBeVisible();
@@ -242,7 +251,8 @@ d('Settings', () => {
await expect(element(by.id('ShowBalance'))).toBeVisible();
// Disable 'swipe to hide balance'
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
await element(by.id('SwipeBalanceToHide')).tap();
await element(by.id('NavigationClose')).atIndex(0).tap();
@@ -255,7 +265,8 @@ d('Settings', () => {
await expect(element(by.id('ShowBalance'))).not.toBeVisible();
// Enable 'hide balance on open'
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('SecuritySettings')).tap();
await element(by.id('SwipeBalanceToHide')).tap();
await element(by.id('HideBalanceOnOpen')).tap();
@@ -275,7 +286,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('BackupSettings')).tap();
await element(by.id('ResetAndRestore')).tap(); // just check if this screen can be opened
await element(by.id('NavigationBack')).atIndex(0).tap();
@@ -327,7 +339,8 @@ d('Settings', () => {
await sleep(1000);
// check same address in Address Viewer
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('WebRelay')).swipe('up');
await element(by.id('AddressViewer')).tap();
@@ -389,7 +402,8 @@ d('Settings', () => {
await sleep(1000);
// switch back to Native segwit
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('AddressTypePreference')).tap();
await element(by.id('p2wpkh')).tap();
@@ -403,7 +417,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
if (!__DEV__) {
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
}
@@ -438,7 +453,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('ElectrumConfig')).tap();
@@ -527,7 +543,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('WebRelay')).tap();
@@ -561,7 +578,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('RGSServer')).tap();
@@ -610,7 +628,8 @@ d('Settings', () => {
await expect(element(by.id('Suggestion-lightning'))).not.toBeVisible();
// reset suggestions
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('WebRelay')).swipe('up');
await element(by.id('ResetSuggestions')).tap();
@@ -628,7 +647,8 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
if (!__DEV__) {
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
}
@@ -653,15 +673,16 @@ d('Settings', () => {
return;
}
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('Support')).tap();
await element(by.id('AppStatus')).tap();
await expect(element(by.id('Status-internet'))).toBeVisible();
- await expect(element(by.id('Status-bitcoin_node'))).toBeVisible();
+ await expect(element(by.id('Status-electrum'))).toBeVisible();
await expect(element(by.id('Status-lightning_node'))).toBeVisible();
await expect(element(by.id('Status-lightning_connection'))).toBeVisible();
- await expect(element(by.id('Status-full_backup'))).toBeVisible();
+ await expect(element(by.id('Status-backup'))).toBeVisible();
await element(by.id('NavigationClose')).atIndex(0).tap();
diff --git a/e2e/slashtags.e2e.js b/e2e/slashtags.e2e.js
index da57e755b..5b9a739dd 100644
--- a/e2e/slashtags.e2e.js
+++ b/e2e/slashtags.e2e.js
@@ -119,7 +119,8 @@ d('Profile and Contacts', () => {
await element(by.id('NavigationClose')).tap();
// ADD CONTACTS
- await element(by.id('HeaderContactsButton')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerContacts')).tap();
await element(by.id('ContactsOnboarding-button')).tap();
// self
@@ -162,7 +163,8 @@ d('Profile and Contacts', () => {
await element(by.id('NavigationClose')).tap();
// FILTER CONTACTS
- await element(by.id('HeaderContactsButton')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerContacts')).tap();
await expect(element(by.text(satoshi.name))).toBeVisible();
await expect(element(by.text(hal.name2))).toBeVisible();
await element(by.id('ContactsSearchInput')).typeText('Satoshi\n');
@@ -180,7 +182,8 @@ d('Profile and Contacts', () => {
.toBeVisible()
.withTimeout(60000);
- await element(by.id('HeaderContactsButton')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerContacts')).tap();
// check un-edited contact
await expect(element(by.text(satoshi.name))).toBeVisible();
// check edited contact retains new name
@@ -231,7 +234,8 @@ d('Profile and Contacts', () => {
.toBeVisible()
.withTimeout(60000);
- await element(by.id('HeaderContactsButton')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerContacts')).tap();
await expect(element(by.text(satoshi.name))).toBeVisible();
await expect(element(by.text(hal.name1))).not.toBeVisible();
await expect(element(by.text(hal.name2))).not.toBeVisible();
diff --git a/e2e/transfer.e2e.js b/e2e/transfer.e2e.js
index f034e2aec..df6f53706 100644
--- a/e2e/transfer.e2e.js
+++ b/e2e/transfer.e2e.js
@@ -81,7 +81,8 @@ d('Transfer', () => {
await element(by.id('NewTxPrompt')).swipe('down'); // close Receive screen
// switch to USD
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('GeneralSettings')).tap();
await element(by.id('CurrenciesSettings')).tap();
await element(by.text('EUR (€)')).tap();
@@ -196,7 +197,8 @@ d('Transfer', () => {
// check channel status
await element(by.id('NavigationClose')).tap();
await sleep(1000);
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).atIndex(0).tap();
await element(by.id('Channels')).tap();
await element(by.id('Channel')).atIndex(0).tap();
@@ -265,7 +267,8 @@ d('Transfer', () => {
await element(by.id('NewTxPrompt')).swipe('down');
// Get LDK node id
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
// wait for LDK to start
await sleep(5000);
@@ -281,7 +284,8 @@ d('Transfer', () => {
const { identityPubkey: lndNodeId } = await lnd.getInfo();
// Connect to LND
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).tap();
await element(by.id('Channels')).tap();
await element(by.id('NavigationAction')).tap();
@@ -348,7 +352,8 @@ d('Transfer', () => {
).not.toBeVisible();
// check channel status
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).atIndex(0).tap();
await element(by.id('Channels')).tap();
await element(by.id('Channel')).atIndex(0).tap();
@@ -397,7 +402,8 @@ d('Transfer', () => {
await element(by.id('TransferSuccess-button')).tap();
// check channel is closed
- await element(by.id('Settings')).tap();
+ await element(by.id('HeaderMenu')).tap();
+ await element(by.id('DrawerSettings')).tap();
await element(by.id('AdvancedSettings')).atIndex(0).tap();
await element(by.id('Channels')).tap();
await expect(element(by.text('Connection 1'))).not.toBeVisible();
diff --git a/index.js b/index.js
index 6327cc15b..2d6c0da74 100644
--- a/index.js
+++ b/index.js
@@ -1,12 +1,10 @@
// NOTE: import order matters
-
+import 'react-native-gesture-handler' // must be first
import './shim';
import './src/utils/fetch';
import './src/utils/ignoreLogs';
import { AppRegistry, Text, TextInput } from 'react-native';
-import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
-
import Root from './src/Root';
import { name as appName } from './app.json';
import './src/utils/fetch-polyfill';
@@ -22,4 +20,4 @@ if (__DEV__ && !__JEST__ && !__E2E__) {
require('./ReactotronConfig');
}
-AppRegistry.registerComponent(appName, () => gestureHandlerRootHOC(Root));
+AppRegistry.registerComponent(appName, () => Root);
diff --git a/package.json b/package.json
index 84ddb8d51..cad645bac 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"@react-native-clipboard/clipboard": "1.16.1",
"@react-native-community/blur": "4.4.1",
"@react-native-community/netinfo": "11.4.1",
+ "@react-navigation/drawer": "^7.1.1",
"@react-navigation/native": "7.0.14",
"@react-navigation/native-stack": "7.2.0",
"@reduxjs/toolkit": "2.2.6",
diff --git a/src/App.tsx b/src/App.tsx
index 72bfb1ae5..457d43465 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -31,7 +31,7 @@ const RecoveryNavigator = lazy(
() => import('./screens/Recovery/RecoveryNavigator'),
);
const OnboardingNavigator = lazy(
- () => import('./navigation/onboarding/OnboardingNavigator'),
+ () => import('./navigation/OnboardingNavigator'),
);
const App = (): ReactElement => {
diff --git a/src/AppOnboarded.tsx b/src/AppOnboarded.tsx
index ecda94f2f..f91e4cd65 100644
--- a/src/AppOnboarded.tsx
+++ b/src/AppOnboarded.tsx
@@ -1,124 +1,21 @@
-import NetInfo from '@react-native-community/netinfo';
-import React, { memo, ReactElement, useEffect, useRef } from 'react';
-import { useTranslation } from 'react-i18next';
-import { AppState } from 'react-native';
-
+import React, { memo, ReactElement } from 'react';
import InactivityTracker from './components/InactivityTracker';
-import { useAppSelector } from './hooks/redux';
-import RootNavigator from './navigation/root/RootNavigator';
-import { dispatch } from './store/helpers';
-import {
- hideBalanceOnOpenSelector,
- pinOnLaunchSelector,
- pinSelector,
-} from './store/reselect/settings';
-import { isOnlineSelector } from './store/reselect/ui';
-import {
- selectedNetworkSelector,
- selectedWalletSelector,
-} from './store/reselect/wallet';
-import { updateSettings } from './store/slices/settings';
-import { updateUi } from './store/slices/ui';
-import { unsubscribeFromLightningSubscriptions } from './utils/lightning';
-import { showToast } from './utils/notifications';
-import { startWalletServices } from './utils/startup';
-import { getOnChainWalletElectrumAsync } from './utils/wallet';
-// import { updateExchangeRates } from './store/actions/wallet';
+import { useAppStateHandler } from './hooks/useAppStateHandler';
+import { useNetworkConnectivity } from './hooks/useNetworkConnectivity';
+import { useWalletStartup } from './hooks/useWalletStartup';
+import DrawerNavigator from './navigation/root/DrawerNavigator';
+import RootNavigationContainer from './navigation/root/RootNavigationContainer';
const AppOnboarded = (): ReactElement => {
- const { t } = useTranslation('other');
- const appState = useRef(AppState.currentState);
- const selectedWallet = useAppSelector(selectedWalletSelector);
- const selectedNetwork = useAppSelector(selectedNetworkSelector);
- const hideBalanceOnOpen = useAppSelector(hideBalanceOnOpenSelector);
- const pin = useAppSelector(pinSelector);
- const pinOnLaunch = useAppSelector(pinOnLaunchSelector);
- const isOnline = useAppSelector(isOnlineSelector);
-
- // on App start
- // biome-ignore lint/correctness/useExhaustiveDependencies: onMount
- useEffect(() => {
- startWalletServices({ selectedNetwork, selectedWallet });
-
- const needsAuth = pin && pinOnLaunch;
- dispatch(updateUi({ isAuthenticated: !needsAuth }));
-
- if (hideBalanceOnOpen) {
- dispatch(updateSettings({ hideBalance: true }));
- }
-
- return (): void => {
- unsubscribeFromLightningSubscriptions();
- };
- }, []);
-
- // biome-ignore lint/correctness/useExhaustiveDependencies: onMount
- useEffect(() => {
- // on AppState change
- const appStateSubscription = AppState.addEventListener(
- 'change',
- async (nextAppState) => {
- dispatch(updateUi({ appState: nextAppState }));
- const electrum = await getOnChainWalletElectrumAsync();
- // on App to foreground
- if (
- appState.current.match(/inactive|background/) &&
- nextAppState === 'active'
- ) {
- // resubscribe to electrum connection changes
- electrum.startConnectionPolling();
- }
-
- // on App to background
- if (
- appState.current.match(/active|inactive/) &&
- nextAppState === 'background'
- ) {
- electrum.stopConnectionPolling();
- }
-
- appState.current = nextAppState;
- },
- );
-
- return (): void => {
- appStateSubscription.remove();
- };
- }, [selectedNetwork]);
-
- useEffect(() => {
- // subscribe to connection information
- const unsubscribeNetInfo = NetInfo.addEventListener(({ isConnected }) => {
- if (isConnected) {
- // prevent toast from showing on startup
- if (isOnline !== isConnected) {
- showToast({
- type: 'success',
- title: t('connection_back_title'),
- description: t('connection_back_msg'),
- });
- }
- dispatch(updateUi({ isOnline: true }));
- // FIXME: this runs too often
- // updateExchangeRates();
- } else {
- showToast({
- type: 'warning',
- title: t('connection_issue'),
- description: t('connection_issue_explain'),
- });
- dispatch(updateUi({ isOnline: false }));
- }
- });
-
- return (): void => {
- unsubscribeNetInfo();
- };
- }, [isOnline, t]);
+ useWalletStartup();
+ useAppStateHandler();
+ useNetworkConnectivity();
return (
-
+
+
+
);
};
diff --git a/src/Root.tsx b/src/Root.tsx
index 8d0cb70fb..afdaccca5 100644
--- a/src/Root.tsx
+++ b/src/Root.tsx
@@ -1,6 +1,7 @@
import { EventEmitter } from 'events';
import React, { ReactElement } from 'react';
import { StyleSheet, View } from 'react-native';
+import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { enableFreeze, enableScreens } from 'react-native-screens';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
@@ -17,13 +18,15 @@ enableFreeze(true);
const Root = (): ReactElement => {
return (
-
- }
- persistor={persistor}>
-
-
-
+
+
+ }
+ persistor={persistor}>
+
+
+
+
);
};
diff --git a/src/assets/icons/header.ts b/src/assets/icons/header.ts
deleted file mode 100644
index 33cd148a8..000000000
--- a/src/assets/icons/header.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const settings = (color = 'white'): string =>
- ``;
-
-export const profileIcon = (color = 'white'): string =>
- ``;
diff --git a/src/assets/icons/settings.ts b/src/assets/icons/settings.ts
index b5f91d892..e79a4f1c9 100644
--- a/src/assets/icons/settings.ts
+++ b/src/assets/icons/settings.ts
@@ -1,58 +1,108 @@
-export const chevronRightIcon = (
- color = 'white',
-): string => `
-`;
-
-export const rightArrowIcon = (color = 'white'): string =>
- ``;
-
-export const upArrowIcon = (color = 'white'): string =>
- ``;
-
-export const downArrowIcon = (color = 'white'): string =>
- ``;
-
-export const arrowCounterClock = (color = 'white'): string =>
- ``;
-export const broadcastIcon = (color = 'white'): string =>
- `
+export const broadcastIcon = (color = 'white'): string => `
+
@@ -62,69 +112,115 @@ export const broadcastIcon = (color = 'white'): string =>
`;
-export const cloudCheckIcon = (color = 'white'): string =>
- `
+export const cloudCheckIcon = (color = 'white'): string => `
+
`;
-export const mediumIcon = (color = 'white'): string =>
- ``;
+export const mediumIcon = (color = 'white'): string => `
+
+
+`;
-export const twitterIcon = (color = 'white'): string =>
- ``;
+export const twitterIcon = (color = 'white'): string => `
+
+
+`;
-export const discordIcon = (color = 'white'): string =>
- `
+export const discordIcon = (color = 'white'): string => `
+
`;
-export const telegramIcon = (color = 'white'): string =>
- ``;
-
-export const listIcon = (color = 'white'): string =>
- ``;
-
-export const sortAscendingIcon = (color = 'white'): string =>
- ``;
-
-export const leftSign = (color = 'white'): string =>
- ``;
-
-export const rightSign = (color = 'white'): string =>
- ``;
-
-export const arrowClockwise = (color = 'white'): string =>
- ``;
-
-export const rectanglesTwo = (color = 'white'): string =>
- ``;
-
-export const lightningHollow = (color = 'white'): string =>
- ``;
-
-export const generalSettingsIcon = (color = 'white'): string =>
- `
+export const telegramIcon = (color = 'white'): string => `
+
+
+`;
+
+export const listIcon = (color = 'white'): string => `
+
+
+
+
+`;
+
+export const sortAscendingIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+`;
+
+export const leftSign = (color = 'white'): string => `
+
+
+`;
+
+export const rightSign = (color = 'white'): string => `
+
+
+`;
+
+export const arrowClockwise = (color = 'white'): string => `
+
+
+
+
+
+`;
+
+export const arrowsClockwise = (color = 'white'): string => `
+
+
+
+
+`;
+
+export const rectanglesTwo = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+`;
+
+export const lightningHollow = (color = 'white'): string => `
+
+
+
+
+
+`;
+
+export const generalSettingsIcon = (color = 'white'): string => `
+
-
-`;
+`;
export const securityIcon = (color = 'white'): string => `
-
+
-
-`;
+`;
export const backupIcon = (color = 'white'): string => `
-
+
@@ -136,7 +232,7 @@ export const backupIcon = (color = 'white'): string => `
`;
export const advancedIcon = (color = 'white'): string => `
-
+
@@ -153,29 +249,26 @@ export const advancedIcon = (color = 'white'): string => `
`;
export const supportIcon = (color = 'white'): string => `
-
+
-
-`;
+`;
export const aboutIcon = (color = 'white'): string => `
-
+
-
-`;
+`;
-export const devSettingsIcon = (color = 'white'): string =>
- `
+export const devSettingsIcon = (color = 'white'): string => `
+
-
-`;
+`;
diff --git a/src/assets/icons/wallet.ts b/src/assets/icons/wallet.ts
index 057f6b90d..70ddfefa6 100644
--- a/src/assets/icons/wallet.ts
+++ b/src/assets/icons/wallet.ts
@@ -1,219 +1,325 @@
-export const transferIcon = (color = 'white'): string => `
-
-
-
-
-
-
-`;
-
-export const unitBitcoinIcon = (color = 'white'): string => `
-
-
-
-
-`;
+export const arrowLNfunds = (color = 'white'): string => `
+
+
+`;
-export const unitSatoshiIcon = (
- color = 'white',
-): string => `
-
-
+export const backIcon = (color = 'white'): string => `
+
+
`;
-export const unitFiatIcon = (color = 'white'): string =>
- `
-
-
-
-
-
-
-
-
- `;
+export const backspaceIcon = (color = 'white'): string => `
+
+
+
+`;
-export const bitcoinIcon = (
- color = 'white',
-): string => `
+export const bitcoinIcon = (color = 'white'): string => `
+
`;
-export const bitcoinSlantedIcon = (
- color = 'white',
-): string => `
-
+export const bitcoinSlantedIcon = (color = 'white'): string => `
+
+
`;
-export const bitcoinCircleIcon = (color: string): string =>
- `
-
-
-
-
-
-
-
-
-
- `;
-
-export const lightningIcon = (
- color = 'white',
-): string => `
-
- `;
-
-export const lightningCircleIcon = (): string => `
-
-
-
-
-`;
+export const bitcoinCircleIcon = (color: string): string => `
+
+
+
+`;
-export const unifiedCircleIcon = (): string => `
-
-
-
-
-
-`;
+export const burgerIcon = (color = 'white'): string => `
+
+
+
+
+`;
-export const lockIcon = (
- color = 'white',
-): string => `
-
-
-
-
-`;
+export const calendarIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
-export const sentIcon = (
- color = 'white',
-): string => `
-
+export const cameraIcon = (color = 'white'): string => `
+
+
+
+
`;
-export const receivedIcon = (
- color = 'white',
-): string => `
-
+export const checkCircleIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
`;
-export const switchIcon = (
- color = 'white',
-): string => `
-
+export const clipboardTextIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+
`;
-export const pasteIcon = (
- color = 'white',
-): string => `
-
-
+export const clockIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
`;
-export const backIcon = (
- color = 'white',
-): string => `
-
+export const coinsIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
-export const coinsIcon = (
- color = 'white',
-): string => `
-
+export const cornersOut = (color = 'white'): string => `
+
+
+
+
+
`;
-export const userPlusIcon = (color = 'white'): string =>
- ``;
+export const eyeIcon = (color = 'white'): string => `
+
+
+
+
+`;
-export const userMinusIcon = (color = 'white'): string =>
- ``;
+export const exclamationIcon = (color = 'white'): string => `
+
+
+
+`;
-export const gitBranchIcon = (color = 'white'): string =>
- ``;
+export const fingerPrintIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
-export const noteIcon = (color = 'white'): string =>
- ``;
+export const flashlightIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
-export const calendarIcon = (color = 'white'): string =>
- `
-
-
-
-
-
-
- `;
-
-export const checkCircleIcon = (color = 'white'): string =>
- ``;
-
-export const clockIcon = (color = 'white'): string =>
- ``;
-
-export const hourglassIcon = (color = 'white'): string =>
- `
-
-
-
-
-
- `;
-
-export const hourglassSimpleIcon = (color = 'white'): string =>
- `
-
-
-
- `;
+export const gitBranchIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+`;
-export const timerIcon = (color = 'white'): string =>
- ``;
+export const heartbeatIcon = (color = 'white'): string => `
+
+
+`;
-export const timerIconAlt = (color = 'white'): string =>
- ``;
+export const hourglassIcon = (color = 'white'): string => `
+
+
+
+
+
+
+ `;
+
+export const hourglassSimpleIcon = (color = 'white'): string => `
+
+
+
+
+ `;
+
+export const lightningIcon = (color = 'white'): string => `
+
+
+`;
-export const timerSpeedIcon = (color = 'white'): string =>
- ``;
+export const lightningCircleIcon = (color = 'white'): string => `
+
+
+
+
+ `;
+
+export const magnifyingGlassIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+`;
-export const magnifyingGlassIcon = (color = 'white'): string =>
- ``;
+export const minusCircledIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
-export const clipboardTextIcon = (color = 'white'): string =>
- ``;
+export const noteIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+
+
+`;
-export const usersIcon = (color = 'white'): string =>
- ``;
+export const pasteIcon = (color = 'white'): string => `
+
+
+
+`;
-export const userIcon = (color = 'white'): string =>
- ``;
+export const pencilIcon = (color = 'white'): string => `
+
+
+
+
+`;
-export const speedFastIcon = (color = 'white'): string =>
- ``;
+export const pictureIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
-export const speedNormalIcon = (color = 'white'): string =>
- ``;
+export const plusIcon = (color = 'white'): string => `
+
+
+
+`;
-export const speedSlowIcon = (color = 'white'): string =>
- ``;
+export const plusCircledIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
-export const xIcon = (color = 'white'): string =>
- ``;
+export const powerIcon = (color = 'white'): string => `
+
+
+
+`;
-export const tagIcon = (color = 'white'): string =>
- ``;
+export const qrIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
-export const shareIosIcon = (color = 'white'): string =>
- ``;
+export const receivedIcon = (color = 'white'): string => `
+
+
+`;
-export const shareAndroidIcon = (color = 'white'): string =>
- `
+export const scanIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
+
+export const sentIcon = (color = 'white'): string => `
+
+
+`;
+
+export const settingsIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+`;
+
+export const shareIosIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
+
+export const shareAndroidIcon = (color = 'white'): string => `
+
@@ -221,211 +327,204 @@ export const shareAndroidIcon = (color = 'white'): string =>
-
- `;
+`;
-export const penIcon = (color = 'white'): string =>
- ``;
+export const speedFastIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
-export const pencilIcon = (color = 'white'): string =>
- ``;
+export const speedNormalIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
-export const infoIcon = (color = 'white'): string =>
- ``;
+export const speedSlowIcon = (color = 'white'): string => `
+
+
+
+
+
+
+`;
-export const scanIcon = (color = 'white'): string => `
-
-
-
-
-
-
-`;
+export const stackIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
-export const savingsIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
- `;
-
-export const cameraIcon = (color = 'white'): string =>
- ``;
-
-export const trashIcon = (color = 'white'): string =>
- ``;
-
-export const plusIcon = (color = 'white'): string =>
- ``;
-
-export const cornersOut = (color = 'white'): string =>
- ``;
-
-export const pictureIcon = (
- color = 'white',
-): string => `
-
-
-
-
- `;
+export const switchIcon = (color = 'white'): string => `
+
+
+`;
-export const flashlightIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
- `;
-
-export const brokenLinkIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
-
-
-
- `;
-
-export const eyeIcon = (
- color = 'white',
-): string => `
-
-
-
- `;
-
-export const heartbeatIcon = (
- color = 'white',
-): string => `
-
-
- `;
-
-export const chartLineIcon = (
- color = 'white',
-): string => `
-
-
-
- `;
-
-export const newspaperIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
-
-
- `;
-
-export const cubeIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
- `;
-
-export const lightbulbIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
- `;
-
-export const minusCircledIcon = (
- color = 'white',
-): string => `
-
-
+export const tagIcon = (color = 'white'): string => `
+
+
+
+
+
-
- `;
+`;
-export const plusCircledIcon = (
- color = 'white',
-): string => `
-
-
+export const timerIcon = (color = 'white'): string => `
+
+
+
+
+`;
+
+export const timerIconAlt = (color = 'white'): string => `
+
+
+`;
+
+export const timerSpeedIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
-
-
- `;
+
+`;
-export const qrIcon = (
- color = 'white',
-): string => `
-
-
-
-
-
-
-
-
-
-
-
-`;
+export const transferIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
+
+export const trashIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+`;
-export const questionMarkIcon =
- (): string => `
-
-
-
-
-
+export const unifiedCircleIcon = (): string => `
+
+
+
+
`;
-export const keyIcon = (color = 'white'): string =>
- ``;
+export const unitBitcoinIcon = (color = 'white'): string => `
+
+
+
+`;
+
+export const unitSatoshiIcon = (color = 'white'): string => `
+
+
+
+`;
+
+export const unitFiatIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+`;
+
+export const userIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+`;
-export const backspaceIcon = (color = 'white'): string =>
- ``;
+export const userMinusIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+`;
-export const exclamationIcon = (color = 'white'): string =>
- ``;
+export const userPlusIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+
+`;
-export const fingerPrintIcon = (color = 'white'): string =>
- ``;
+export const usersIcon = (color = 'white'): string => `
+
+
+
+
+
+
+
+
+
+
+`;
-export const mapTrifoldIcon = (color = 'white'): string =>
- `
-
-
-
-
+export const userSquareIcon = (color = 'white'): string => `
+
+
+
+
+
`;
-export const mapPinLineIcon = (color = 'white'): string =>
- `
-
-
-
-
+export const warningIcon = (color = 'white'): string => `
+
+
+
+
+
`;
-export const arrowLNfunds = (color = 'white'): string =>
- `
-
-
-`;
+export const xIcon = (color = 'white'): string => `
+
+
+
+
+
+`;
diff --git a/src/components/AppStatus.tsx b/src/components/AppStatus.tsx
new file mode 100644
index 000000000..4f2db9043
--- /dev/null
+++ b/src/components/AppStatus.tsx
@@ -0,0 +1,135 @@
+import { ReactNode, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PressableProps, StyleSheet } from 'react-native';
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withRepeat,
+ withSequence,
+ withTiming,
+ Easing,
+} from 'react-native-reanimated';
+
+import { __E2E__ } from '../constants/env';
+import { useAppStatus } from '../hooks/useAppStatus';
+import { Pressable } from '../styles/components';
+import { ArrowsClockwiseIcon, PowerIcon, WarningIcon } from '../styles/icons';
+import { BodyMSB } from '../styles/text';
+import { IThemeColors } from '../styles/themes';
+
+type Props = PressableProps & { showText?: boolean; showReady?: boolean };
+
+const AppStatus = ({
+ showText = false,
+ showReady = false,
+ style,
+ testID,
+ onPress,
+}: Props): ReactNode => {
+ const appStatus = useAppStatus();
+ const rotation = useSharedValue(0);
+ const opacity = useSharedValue(1);
+ const { t } = useTranslation('wallet');
+
+ useEffect(() => {
+ if (__E2E__) {
+ return;
+ }
+
+ if (appStatus === 'pending') {
+ rotation.value = withRepeat(
+ withSequence(
+ // First half turn with easing
+ withTiming(0.5, {
+ duration: 800,
+ easing: Easing.bezier(0.4, 0, 0.2, 1),
+ }),
+ // Second half turn with different easing
+ withTiming(1, {
+ duration: 1200,
+ easing: Easing.bezier(0.4, 0, 0.2, 1),
+ }),
+ ),
+ -1,
+ false,
+ );
+ } else {
+ rotation.value = 0;
+ }
+
+ if (appStatus === 'error') {
+ opacity.value = withRepeat(
+ withSequence(
+ withTiming(0.3, {
+ duration: 600,
+ easing: Easing.ease,
+ }),
+ withTiming(1, {
+ duration: 600,
+ easing: Easing.ease,
+ }),
+ ),
+ -1,
+ true,
+ );
+ } else {
+ opacity.value = 1;
+ }
+ }, [appStatus, rotation, opacity]);
+
+ const spinStyle = useAnimatedStyle(() => {
+ return {
+ transform: [{ rotate: `${rotation.value * 360}deg` }],
+ };
+ });
+
+ const fadeStyle = useAnimatedStyle(() => {
+ return {
+ opacity: opacity.value,
+ };
+ });
+
+ const appStatusColor = (): keyof IThemeColors => {
+ if (appStatus === 'ready') {
+ return 'green';
+ }
+ if (appStatus === 'pending') {
+ return 'yellow';
+ }
+ return 'red';
+ };
+
+ const color = appStatusColor();
+
+ if (appStatus === 'ready' && !showReady) {
+ return null;
+ }
+
+ return (
+
+ {appStatus === 'ready' && (
+
+ )}
+ {appStatus === 'pending' && (
+
+
+
+ )}
+ {appStatus === 'error' && (
+
+
+
+ )}
+ {showText && {t('drawer.status')}}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ root: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+});
+
+export default AppStatus;
diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx
index 828b81cb5..0cc6b47dd 100644
--- a/src/components/TabBar.tsx
+++ b/src/components/TabBar.tsx
@@ -1,3 +1,4 @@
+import { useNavigation } from '@react-navigation/native';
import React, { ReactElement, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -13,7 +14,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { receiveIcon, sendIcon } from '../assets/icons/tabs';
import useColors from '../hooks/colors';
import { useAppSelector } from '../hooks/redux';
-import { rootNavigation } from '../navigation/root/RootNavigator';
+import { rootNavigation } from '../navigation/root/RootNavigationContainer';
import type { RootNavigationProp } from '../navigation/types';
import { resetSendTransaction } from '../store/actions/wallet';
import { spendingOnboardingSelector } from '../store/reselect/aggregations';
@@ -23,14 +24,11 @@ import { toggleBottomSheet } from '../store/utils/ui';
import { ScanIcon } from '../styles/icons';
import ButtonBlur from './buttons/ButtonBlur';
-const TabBar = ({
- navigation,
-}: {
- navigation: RootNavigationProp;
-}): ReactElement => {
+const TabBar = (): ReactElement => {
const { white10 } = useColors();
const insets = useSafeAreaInsets();
const { t } = useTranslation('wallet');
+ const navigation = useNavigation();
const viewControllers = useAppSelector(viewControllersSelector);
const isSpendingOnboarding = useAppSelector(spendingOnboardingSelector);
@@ -47,7 +45,7 @@ const TabBar = ({
}, [viewControllers]);
const onReceivePress = (): void => {
- const currentRoute = rootNavigation.getCurrenRoute();
+ const currentRoute = rootNavigation.getCurrentRoute();
// if we are on the spending screen and the user has not yet received funds
if (currentRoute === 'ActivitySpending' && isSpendingOnboarding) {
diff --git a/src/components/Widgets.tsx b/src/components/Widgets.tsx
index 23265a9a7..d87a97bc2 100644
--- a/src/components/Widgets.tsx
+++ b/src/components/Widgets.tsx
@@ -1,4 +1,5 @@
import { useFocusEffect } from '@react-navigation/native';
+import { useNavigation } from '@react-navigation/native';
import React, {
ReactElement,
memo,
@@ -14,7 +15,7 @@ import DraggableFlatList, {
} from 'react-native-draggable-flatlist';
import { useAppDispatch, useAppSelector } from '../hooks/redux';
-import { rootNavigation } from '../navigation/root/RootNavigator';
+import { RootNavigationProp } from '../navigation/types';
import {
onboardedWidgetsSelector,
widgetsOrderSelector,
@@ -42,6 +43,7 @@ import WeatherWidget from './widgets/WeatherWidget';
const Widgets = (): ReactElement => {
const { t } = useTranslation('widgets');
const dispatch = useAppDispatch();
+ const navigation = useNavigation();
const widgets = useAppSelector(widgetsSelector);
const sortOrder = useAppSelector(widgetsOrderSelector);
@@ -74,7 +76,7 @@ const Widgets = (): ReactElement => {
const screen = onboardedWidgets
? 'WidgetsSuggestions'
: 'WidgetsOnboarding';
- rootNavigation.navigate(screen);
+ navigation.navigate(screen);
};
const renderItem = useCallback(
diff --git a/src/hooks/useAppStateHandler.ts b/src/hooks/useAppStateHandler.ts
new file mode 100644
index 000000000..5cd77c75b
--- /dev/null
+++ b/src/hooks/useAppStateHandler.ts
@@ -0,0 +1,41 @@
+import { useEffect, useRef } from 'react';
+import { AppState } from 'react-native';
+import { dispatch } from '../store/helpers';
+import { updateUi } from '../store/slices/ui';
+import { getOnChainWalletElectrumAsync } from '../utils/wallet';
+
+export const useAppStateHandler = (): void => {
+ const appState = useRef(AppState.currentState);
+
+ useEffect(() => {
+ const appStateSubscription = AppState.addEventListener(
+ 'change',
+ async (nextAppState) => {
+ dispatch(updateUi({ appState: nextAppState }));
+ const electrum = await getOnChainWalletElectrumAsync();
+ // App to foreground
+ if (
+ appState.current.match(/inactive|background/) &&
+ nextAppState === 'active'
+ ) {
+ // resubscribe to electrum connection changes
+ electrum.startConnectionPolling();
+ }
+
+ // App to background
+ if (
+ appState.current.match(/active|inactive/) &&
+ nextAppState === 'background'
+ ) {
+ electrum.stopConnectionPolling();
+ }
+
+ appState.current = nextAppState;
+ },
+ );
+
+ return (): void => {
+ appStateSubscription.remove();
+ };
+ }, []);
+};
diff --git a/src/hooks/useAppStatus.ts b/src/hooks/useAppStatus.ts
new file mode 100644
index 000000000..6365938d2
--- /dev/null
+++ b/src/hooks/useAppStatus.ts
@@ -0,0 +1,27 @@
+import { useEffect, useState } from 'react';
+import { appStatusSelector } from '../store/reselect/ui';
+import { THealthState } from '../store/types/ui';
+import { useAppSelector } from './redux';
+
+// Give the app some time to initialize before showing the status
+const INIT_DELAY = 5000;
+
+export const useAppStatus = (): THealthState => {
+ const [showStatus, setShowStatus] = useState(false);
+ const appStatus = useAppSelector(appStatusSelector);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setShowStatus(true);
+ }, INIT_DELAY);
+
+ return () => clearTimeout(timer);
+ }, []);
+
+ // During initialization, return 'ready' instead of error
+ if (!showStatus) {
+ return 'ready';
+ }
+
+ return appStatus;
+};
diff --git a/src/hooks/useNetworkConnectivity.ts b/src/hooks/useNetworkConnectivity.ts
new file mode 100644
index 000000000..aceb16e3a
--- /dev/null
+++ b/src/hooks/useNetworkConnectivity.ts
@@ -0,0 +1,42 @@
+import NetInfo from '@react-native-community/netinfo';
+import { useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { dispatch } from '../store/helpers';
+import { isOnlineSelector } from '../store/reselect/ui';
+import { updateUi } from '../store/slices/ui';
+import { showToast } from '../utils/notifications';
+import { useAppSelector } from './redux';
+
+export const useNetworkConnectivity = (): void => {
+ const { t } = useTranslation('other');
+ const isOnline = useAppSelector(isOnlineSelector);
+
+ useEffect(() => {
+ const unsubscribeNetInfo = NetInfo.addEventListener(({ isConnected }) => {
+ if (isConnected) {
+ // prevent toast from showing on startup
+ if (isOnline !== isConnected) {
+ showToast({
+ type: 'success',
+ title: t('connection_back_title'),
+ description: t('connection_back_msg'),
+ });
+ }
+ dispatch(updateUi({ isOnline: true }));
+ // FIXME: this runs too often
+ // updateExchangeRates();
+ } else {
+ showToast({
+ type: 'warning',
+ title: t('connection_issue'),
+ description: t('connection_issue_explain'),
+ });
+ dispatch(updateUi({ isOnline: false }));
+ }
+ });
+
+ return (): void => {
+ unsubscribeNetInfo();
+ };
+ }, [isOnline, t]);
+};
diff --git a/src/hooks/useWalletStartup.ts b/src/hooks/useWalletStartup.ts
new file mode 100644
index 000000000..49266764d
--- /dev/null
+++ b/src/hooks/useWalletStartup.ts
@@ -0,0 +1,34 @@
+import { useEffect } from 'react';
+import { dispatch } from '../store/helpers';
+import {
+ hideBalanceOnOpenSelector,
+ pinOnLaunchSelector,
+ pinSelector,
+} from '../store/reselect/settings';
+import { updateSettings } from '../store/slices/settings';
+import { updateUi } from '../store/slices/ui';
+import { unsubscribeFromLightningSubscriptions } from '../utils/lightning';
+import { startWalletServices } from '../utils/startup';
+import { useAppSelector } from './redux';
+
+export const useWalletStartup = (): void => {
+ const hideBalanceOnOpen = useAppSelector(hideBalanceOnOpenSelector);
+ const pinEnabled = useAppSelector(pinSelector);
+ const pinOnLaunch = useAppSelector(pinOnLaunchSelector);
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: onMount
+ useEffect(() => {
+ startWalletServices();
+
+ const needsAuth = pinEnabled && pinOnLaunch;
+ dispatch(updateUi({ isAuthenticated: !needsAuth }));
+
+ if (hideBalanceOnOpen) {
+ dispatch(updateSettings({ hideBalance: true }));
+ }
+
+ return () => {
+ unsubscribeFromLightningSubscriptions();
+ };
+ }, []);
+};
diff --git a/src/navigation/onboarding/OnboardingNavigator.tsx b/src/navigation/OnboardingNavigator.tsx
similarity index 73%
rename from src/navigation/onboarding/OnboardingNavigator.tsx
rename to src/navigation/OnboardingNavigator.tsx
index bf74b5d60..140d099a5 100644
--- a/src/navigation/onboarding/OnboardingNavigator.tsx
+++ b/src/navigation/OnboardingNavigator.tsx
@@ -1,19 +1,19 @@
+import { DarkTheme, NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React, { ReactElement } from 'react';
-import { useAppSelector } from '../../hooks/redux';
+import { useAppSelector } from '../hooks/redux';
import CreateWallet, {
TCreateWalletParams,
-} from '../../screens/Onboarding/CreateWallet';
-import MultipleDevices from '../../screens/Onboarding/MultipleDevices';
-import Passphrase from '../../screens/Onboarding/Passphrase';
-import RestoreFromSeed from '../../screens/Onboarding/RestoreFromSeed';
-import SlideshowScreen from '../../screens/Onboarding/Slideshow';
-import TermsOfUse from '../../screens/Onboarding/TermsOfUse';
-import WelcomeScreen from '../../screens/Onboarding/Welcome';
-import { requiresRemoteRestoreSelector } from '../../store/reselect/user';
-import { walletExistsSelector } from '../../store/reselect/wallet';
-import { NavigationContainer } from '../../styles/components';
+} from '../screens/Onboarding/CreateWallet';
+import MultipleDevices from '../screens/Onboarding/MultipleDevices';
+import Passphrase from '../screens/Onboarding/Passphrase';
+import RestoreFromSeed from '../screens/Onboarding/RestoreFromSeed';
+import SlideshowScreen from '../screens/Onboarding/Slideshow';
+import TermsOfUse from '../screens/Onboarding/TermsOfUse';
+import WelcomeScreen from '../screens/Onboarding/Welcome';
+import { requiresRemoteRestoreSelector } from '../store/reselect/user';
+import { walletExistsSelector } from '../store/reselect/wallet';
export type OnboardingStackParamList = {
TermsOfUse: undefined;
@@ -46,7 +46,7 @@ const OnboardingNavigator = (): ReactElement => {
walletExists && requiresRemoteRestore ? 'CreateWallet' : 'TermsOfUse';
return (
-
+
;
@@ -86,11 +86,11 @@ export type SettingsStackParamList = {
AddressTypePreference: undefined;
DevSettings: undefined;
LdkDebug: undefined;
- ExportToPhone: undefined;
+ // ExportToPhone: undefined;
ResetAndRestore: undefined;
BitcoinNetworkSelection: undefined;
LightningNodeInfo: undefined;
- Channels?: { showClosed: boolean };
+ Channels: { showClosed: boolean } | undefined;
ChannelDetails: { channel: TChannel };
CloseConnection: { channelId: string };
TagsSettings: undefined;
@@ -155,7 +155,7 @@ const SettingsNavigator = (): ReactElement => {
-
+ {/* */}
;
diff --git a/src/navigation/wallet/WalletNavigator.tsx b/src/navigation/WalletNavigator.tsx
similarity index 66%
rename from src/navigation/wallet/WalletNavigator.tsx
rename to src/navigation/WalletNavigator.tsx
index 85d968c35..82e3f5708 100644
--- a/src/navigation/wallet/WalletNavigator.tsx
+++ b/src/navigation/WalletNavigator.tsx
@@ -5,13 +5,12 @@ import {
} from '@react-navigation/native-stack';
import React, { ReactElement } from 'react';
-import TabBar from '../../components/TabBar';
-import { __E2E__ } from '../../constants/env';
-import ActivityFiltered from '../../screens/Activity/ActivityFiltered';
-import ActivitySavings from '../../screens/Activity/ActivitySavings';
-import ActivitySpending from '../../screens/Activity/ActivitySpending';
-import Home from '../../screens/Wallets/Home';
-import type { RootStackScreenProps } from '../types';
+import TabBar from '../components/TabBar';
+import { __E2E__ } from '../constants/env';
+import ActivityFiltered from '../screens/Activity/ActivityFiltered';
+import ActivitySavings from '../screens/Activity/ActivitySavings';
+import ActivitySpending from '../screens/Activity/ActivitySpending';
+import Home from '../screens/Wallets/Home';
export type WalletStackParamList = {
Home: undefined;
@@ -29,9 +28,7 @@ const screenOptions: NativeStackNavigationOptions = {
animation: __E2E__ ? 'none' : 'default',
};
-const WalletStack = ({
- navigation,
-}: RootStackScreenProps<'Wallet'>): ReactElement => {
+const WalletStack = (): ReactElement => {
return (
<>
@@ -42,7 +39,7 @@ const WalletStack = ({
{/* TabBar should be visible on all of the above screens */}
-
+
>
);
};
diff --git a/src/navigation/bottom-sheet/BackupNavigation.tsx b/src/navigation/bottom-sheet/BackupNavigation.tsx
index db0e65faa..19d809ab2 100644
--- a/src/navigation/bottom-sheet/BackupNavigation.tsx
+++ b/src/navigation/bottom-sheet/BackupNavigation.tsx
@@ -19,7 +19,7 @@ import ShowPassphrase from '../../screens/Settings/Backup/ShowPassphrase';
import Success from '../../screens/Settings/Backup/Success';
import Warning from '../../screens/Settings/Backup/Warning';
import { viewControllerIsOpenSelector } from '../../store/reselect/ui';
-import { NavigationContainer } from '../../styles/components';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type BackupNavigationProp =
NativeStackNavigationProp;
@@ -51,7 +51,7 @@ const BackupNavigation = (): ReactElement => {
return (
-
+
@@ -65,7 +65,7 @@ const BackupNavigation = (): ReactElement => {
-
+
);
diff --git a/src/navigation/bottom-sheet/BottomSheetNavigationContainer.tsx b/src/navigation/bottom-sheet/BottomSheetNavigationContainer.tsx
new file mode 100644
index 000000000..5e613df62
--- /dev/null
+++ b/src/navigation/bottom-sheet/BottomSheetNavigationContainer.tsx
@@ -0,0 +1,24 @@
+import {
+ DarkTheme,
+ NavigationContainer,
+ NavigationContainerProps,
+ NavigationContainerRef,
+} from '@react-navigation/native';
+import React, { forwardRef, ReactElement } from 'react';
+
+const theme = {
+ ...DarkTheme,
+ colors: {
+ ...DarkTheme.colors,
+ background: 'transparent',
+ },
+};
+
+const BottomSheetNavigationContainer = forwardRef<
+ NavigationContainerRef,
+ NavigationContainerProps
+>((props, ref): ReactElement => {
+ return ;
+});
+
+export default BottomSheetNavigationContainer;
diff --git a/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx b/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx
index 6a138a4b7..54c726c8d 100644
--- a/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx
+++ b/src/navigation/bottom-sheet/LNURLWithdrawNavigation.tsx
@@ -17,7 +17,7 @@ import { useAppSelector } from '../../hooks/redux';
import Amount from '../../screens/Wallets/LNURLWithdraw/Amount';
import Confirm from '../../screens/Wallets/LNURLWithdraw/Confirm';
import { viewControllerSelector } from '../../store/reselect/ui';
-import { NavigationContainer } from '../../styles/components';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type LNURLWithdrawNavigationProp =
NativeStackNavigationProp;
@@ -53,7 +53,7 @@ const LNURLWithdrawNavigation = (): ReactElement => {
return (
-
+
@@ -68,7 +68,7 @@ const LNURLWithdrawNavigation = (): ReactElement => {
initialParams={{ wParams, amount: wParams.minWithdrawable }}
/>
-
+
);
diff --git a/src/navigation/bottom-sheet/OrangeTicketNavigation.tsx b/src/navigation/bottom-sheet/OrangeTicketNavigation.tsx
index 033bf4b5d..f429a8927 100644
--- a/src/navigation/bottom-sheet/OrangeTicketNavigation.tsx
+++ b/src/navigation/bottom-sheet/OrangeTicketNavigation.tsx
@@ -13,11 +13,6 @@ import React, {
useState,
} from 'react';
-import ErrorScreen from '../../screens/OrangeTicket/Error';
-import Prize from '../../screens/OrangeTicket/Prize';
-import UsedCard from '../../screens/OrangeTicket/UsedCard';
-import { NavigationContainer } from '../../styles/components';
-
import BottomSheetWrapper from '../../components/BottomSheetWrapper';
import { __TREASURE_HUNT_HOST__ } from '../../constants/env';
import {
@@ -25,9 +20,13 @@ import {
useSnapPoints,
} from '../../hooks/bottomSheet';
import { useAppSelector } from '../../hooks/redux';
+import ErrorScreen from '../../screens/OrangeTicket/Error';
+import Prize from '../../screens/OrangeTicket/Prize';
+import UsedCard from '../../screens/OrangeTicket/UsedCard';
import { viewControllerSelector } from '../../store/reselect/ui';
import { getNodeId, waitForLdk } from '../../utils/lightning';
import { showToast } from '../../utils/notifications';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type OrangeTicketNavigationProp =
NativeStackNavigationProp;
@@ -158,7 +157,7 @@ const OrangeTicket = (): ReactElement => {
return (
-
+
@@ -178,7 +177,7 @@ const OrangeTicket = (): ReactElement => {
initialParams={{ errorCode }}
/>
-
+
);
diff --git a/src/navigation/bottom-sheet/PINNavigation.tsx b/src/navigation/bottom-sheet/PINNavigation.tsx
index 35e9429bb..80d4e57db 100644
--- a/src/navigation/bottom-sheet/PINNavigation.tsx
+++ b/src/navigation/bottom-sheet/PINNavigation.tsx
@@ -6,17 +6,17 @@ import {
} from '@react-navigation/native-stack';
import React, { ReactElement, memo } from 'react';
import { BiometryType } from 'react-native-biometrics';
-import { useAppSelector } from '../../hooks/redux';
import BottomSheetWrapper from '../../components/BottomSheetWrapper';
import { __E2E__ } from '../../constants/env';
import { useSnapPoints } from '../../hooks/bottomSheet';
+import { useAppSelector } from '../../hooks/redux';
import AskForBiometrics from '../../screens/Settings/PIN/AskForBiometrics';
import ChoosePIN from '../../screens/Settings/PIN/ChoosePIN';
import PINPrompt from '../../screens/Settings/PIN/PINPrompt';
import Result from '../../screens/Settings/PIN/Result';
import { viewControllerIsOpenSelector } from '../../store/reselect/ui';
-import { NavigationContainer } from '../../styles/components';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type PinNavigationProp = NativeStackNavigationProp;
@@ -43,7 +43,7 @@ const PINNavigation = (): ReactElement => {
return (
-
+
@@ -53,7 +53,7 @@ const PINNavigation = (): ReactElement => {
/>
-
+
);
diff --git a/src/navigation/bottom-sheet/ProfileLinkNavigation.tsx b/src/navigation/bottom-sheet/ProfileLinkNavigation.tsx
index 108a80f7f..2f9b94296 100644
--- a/src/navigation/bottom-sheet/ProfileLinkNavigation.tsx
+++ b/src/navigation/bottom-sheet/ProfileLinkNavigation.tsx
@@ -13,7 +13,7 @@ import { useAppSelector } from '../../hooks/redux';
import ProfileLink from '../../screens/Profile/ProfileLink';
import ProfileLinkSuggestions from '../../screens/Profile/ProfileLinkSuggestions';
import { viewControllerIsOpenSelector } from '../../store/reselect/ui';
-import { NavigationContainer } from '../../styles/components';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type ProfileLinkNavigationProp =
NativeStackNavigationProp;
@@ -39,7 +39,7 @@ const ProfileLinkNavigation = (): ReactElement => {
return (
-
+
{
component={ProfileLinkSuggestions}
/>
-
+
);
diff --git a/src/navigation/bottom-sheet/ReceiveNavigation.tsx b/src/navigation/bottom-sheet/ReceiveNavigation.tsx
index c3e5b1a86..2312bc585 100644
--- a/src/navigation/bottom-sheet/ReceiveNavigation.tsx
+++ b/src/navigation/bottom-sheet/ReceiveNavigation.tsx
@@ -19,7 +19,7 @@ import ReceiveQR from '../../screens/Wallets/Receive/ReceiveQR';
import Tags from '../../screens/Wallets/Receive/Tags';
import { viewControllerSelector } from '../../store/reselect/ui';
import { resetInvoice } from '../../store/slices/receive';
-import { NavigationContainer } from '../../styles/components';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type ReceiveNavigationProp =
NativeStackNavigationProp;
@@ -69,7 +69,7 @@ const ReceiveNavigation = (): ReactElement => {
onOpen={reset}
onClose={reset}>
-
+
@@ -84,7 +84,7 @@ const ReceiveNavigation = (): ReactElement => {
-
+
);
diff --git a/src/navigation/bottom-sheet/SendNavigation.tsx b/src/navigation/bottom-sheet/SendNavigation.tsx
index d22425dc1..6d234f2e3 100644
--- a/src/navigation/bottom-sheet/SendNavigation.tsx
+++ b/src/navigation/bottom-sheet/SendNavigation.tsx
@@ -45,8 +45,8 @@ import {
} from '../../store/reselect/wallet';
import { EActivityType } from '../../store/types/activity';
import { updateOnchainFeeEstimates } from '../../store/utils/fees';
-import { NavigationContainer } from '../../styles/components';
import { refreshLdk } from '../../utils/lightning';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type SendNavigationProp = NativeStackNavigationProp;
@@ -143,7 +143,9 @@ const SendNavigation = (): ReactElement => {
testID="SendSheet"
onOpen={onOpen}>
-
+
@@ -178,7 +180,7 @@ const SendNavigation = (): ReactElement => {
initialParams={{ pParams, url, amount }}
/>
-
+
);
diff --git a/src/navigation/bottom-sheet/TreasureHuntNavigation.tsx b/src/navigation/bottom-sheet/TreasureHuntNavigation.tsx
index 6c0dc88b3..00c14ce48 100644
--- a/src/navigation/bottom-sheet/TreasureHuntNavigation.tsx
+++ b/src/navigation/bottom-sheet/TreasureHuntNavigation.tsx
@@ -24,7 +24,7 @@ import Loading from '../../screens/TreasureHunt/Loading';
import Prize from '../../screens/TreasureHunt/Prize';
import { viewControllerSelector } from '../../store/reselect/ui';
import { addTreasureChest } from '../../store/slices/settings';
-import { NavigationContainer } from '../../styles/components';
+import BottomSheetNavigationContainer from './BottomSheetNavigationContainer';
export type TreasureHuntNavigationProp =
NativeStackNavigationProp;
@@ -130,7 +130,7 @@ const TreasureHuntNavigation = (): ReactElement => {
return (
-
+
@@ -152,7 +152,7 @@ const TreasureHuntNavigation = (): ReactElement => {
/>
-
+
);
diff --git a/src/navigation/root/DrawerContent.tsx b/src/navigation/root/DrawerContent.tsx
new file mode 100644
index 000000000..45b86e357
--- /dev/null
+++ b/src/navigation/root/DrawerContent.tsx
@@ -0,0 +1,147 @@
+import {
+ DrawerContentComponentProps,
+ DrawerContentScrollView,
+} from '@react-navigation/drawer';
+import { useNavigation } from '@react-navigation/native';
+import React, { ReactElement } from 'react';
+import { useTranslation } from 'react-i18next';
+import { StyleSheet, View } from 'react-native';
+
+import AppStatus from '../../components/AppStatus';
+import GradientView from '../../components/GradientView';
+import colors from '../../styles/colors';
+import { Pressable } from '../../styles/components';
+import {
+ CoinsIcon,
+ HeartbeatIcon,
+ SettingsIcon,
+ StackIcon,
+ UserSquareIcon,
+ UsersIcon,
+} from '../../styles/icons';
+import { DrawerText } from '../../styles/text';
+import { DrawerStackNavigationProp } from './DrawerNavigator';
+
+type DrawerItemProps = {
+ icon: ReactElement;
+ label: string;
+ testID?: string;
+ onPress: () => void;
+};
+
+const DrawerItem = ({
+ icon,
+ label,
+ testID,
+ onPress,
+}: DrawerItemProps): ReactElement => (
+
+ {icon}
+ {label}
+
+);
+
+const DrawerContent = (props: DrawerContentComponentProps): ReactElement => {
+ const { t } = useTranslation('wallet');
+ const navigation = useNavigation();
+
+ return (
+
+
+ }
+ label={t('drawer.wallet')}
+ testID="DrawerWallet"
+ onPress={() => navigation.navigate('Wallet')}
+ />
+ }
+ label={t('drawer.activity')}
+ testID="DrawerActivity"
+ onPress={() => {
+ navigation.navigate('Wallet', { screen: 'ActivityFiltered' });
+ }}
+ />
+ }
+ label={t('drawer.contacts')}
+ testID="DrawerContacts"
+ onPress={() => navigation.navigate('Contacts')}
+ />
+ }
+ label={t('drawer.profile')}
+ testID="DrawerProfile"
+ onPress={() => navigation.navigate('Profile')}
+ />
+ }
+ label={t('drawer.widgets')}
+ testID="DrawerWidgets"
+ onPress={() => navigation.navigate('WidgetsSuggestions')}
+ />
+ }
+ label={t('drawer.settings')}
+ testID="DrawerSettings"
+ onPress={() => {
+ navigation.navigate('Settings', { screen: 'MainSettings' });
+ }}
+ />
+
+ {
+ navigation.navigate('Settings', { screen: 'AppStatus' });
+ }}
+ />
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ drawer: {
+ flex: 1,
+ },
+ drawerContent: {
+ flex: 1,
+ },
+ drawerItem: {
+ borderBottomWidth: 1,
+ borderBottomColor: colors.white10,
+ flexDirection: 'row',
+ alignItems: 'center',
+ height: 56,
+ },
+ drawerItemIcon: {
+ width: 24,
+ height: 24,
+ marginRight: 12,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ drawerItemLabel: {
+ textTransform: 'uppercase',
+ },
+ appStatus: {
+ marginTop: 'auto',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 8,
+ },
+});
+
+export default DrawerContent;
diff --git a/src/navigation/root/DrawerNavigator.tsx b/src/navigation/root/DrawerNavigator.tsx
new file mode 100644
index 000000000..d555eb835
--- /dev/null
+++ b/src/navigation/root/DrawerNavigator.tsx
@@ -0,0 +1,42 @@
+import {
+ DrawerNavigationProp,
+ createDrawerNavigator,
+} from '@react-navigation/drawer';
+import { CompositeNavigationProp } from '@react-navigation/native';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+import React, { ReactElement } from 'react';
+import { Platform } from 'react-native';
+
+import { RootStackParamList } from '../types';
+import DrawerContent from './DrawerContent';
+import RootNavigator from './RootNavigator';
+
+const Drawer = createDrawerNavigator();
+
+export type DrawerStackNavigationProp = CompositeNavigationProp<
+ DrawerNavigationProp<{ RootStack: undefined }>,
+ NativeStackNavigationProp
+>;
+
+const DrawerNavigator = (): ReactElement => {
+ const isAndroid = Platform.OS === 'android';
+
+ return (
+ }
+ screenOptions={{
+ headerShown: false,
+ drawerStyle: { width: 200 },
+ drawerPosition: 'right',
+ drawerType: 'front',
+ overlayColor: 'rgba(0,0,0,0.6)',
+ // Swipe is not working properly on Android
+ // TODO: Fix this
+ swipeEnabled: !isAndroid,
+ }}>
+
+
+ );
+};
+
+export default DrawerNavigator;
diff --git a/src/navigation/root/RootNavigationContainer.tsx b/src/navigation/root/RootNavigationContainer.tsx
new file mode 100644
index 000000000..a1d365aaa
--- /dev/null
+++ b/src/navigation/root/RootNavigationContainer.tsx
@@ -0,0 +1,77 @@
+import {
+ DarkTheme,
+ LinkingOptions,
+ NavigationContainer,
+ createNavigationContainerRef,
+} from '@react-navigation/native';
+import React, { ReactElement } from 'react';
+import { Linking } from 'react-native';
+
+import { processUri } from '../../utils/scanner/scanner';
+import { RootStackParamList } from '../types';
+
+export type NavigateScreenArgs = {
+ [K in keyof RootStackParamList]: undefined extends RootStackParamList[K]
+ ? [screen: K] | [screen: K, params: RootStackParamList[K]]
+ : [screen: K, params: RootStackParamList[K]];
+}[keyof RootStackParamList];
+
+/**
+ * Helper function to navigate from outside components.
+ */
+const navigationRef = createNavigationContainerRef();
+export const rootNavigation = {
+ getCurrentRoute: (): string | undefined => {
+ if (navigationRef.isReady()) {
+ const route = navigationRef.getCurrentRoute();
+ return route ? route.name : undefined;
+ }
+ return undefined;
+ },
+ navigate: (...args: NavigateScreenArgs): void => {
+ if (navigationRef.isReady()) {
+ navigationRef.navigate(...args);
+ } else {
+ // Decide what to do if react navigation is not ready
+ console.log('rootNavigation not ready');
+ }
+ },
+ goBack: (): void => {
+ if (navigationRef.isReady()) {
+ navigationRef.goBack();
+ }
+ },
+};
+
+const RootNavigationContainer = ({
+ children,
+}: {
+ children: ReactElement;
+}): ReactElement => {
+ const linking: LinkingOptions = {
+ prefixes: ['bitkit', 'slash', 'bitcoin', 'lightning'],
+ subscribe(listener): () => void {
+ // Deep linking if the app is already open
+ const subscription = Linking.addEventListener('url', ({ url }): void => {
+ rootNavigation.navigate('Wallet');
+ processUri({ uri: url });
+ listener(url);
+ });
+
+ return () => {
+ subscription.remove();
+ };
+ },
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default RootNavigationContainer;
diff --git a/src/navigation/root/RootNavigator.tsx b/src/navigation/root/RootNavigator.tsx
index 0acf5b2f3..a89a01471 100644
--- a/src/navigation/root/RootNavigator.tsx
+++ b/src/navigation/root/RootNavigator.tsx
@@ -1,8 +1,4 @@
import Clipboard from '@react-native-clipboard/clipboard';
-import {
- LinkingOptions,
- createNavigationContainerRef,
-} from '@react-navigation/native';
import {
NativeStackNavigationOptions,
createNativeStackNavigator,
@@ -41,16 +37,16 @@ import { resetSendTransaction } from '../../store/actions/wallet';
import { getStore } from '../../store/helpers';
import { isAuthenticatedSelector } from '../../store/reselect/ui';
import { updateUi } from '../../store/slices/ui';
-import { NavigationContainer } from '../../styles/components';
import BackupSubscriber from '../../utils/backup/backups-subscriber';
import { checkClipboardData } from '../../utils/clipboard';
import { processUri } from '../../utils/scanner/scanner';
+import SettingsNavigator from '../SettingsNavigator';
+import TransferNavigator from '../TransferNavigator';
+import WalletNavigator from '../WalletNavigator';
import BottomSheetsLazy from '../bottom-sheet/BottomSheetsLazy';
import ForceTransfer from '../bottom-sheet/ForceTransfer';
-import SettingsNavigator from '../settings/SettingsNavigator';
-import TransferNavigator from '../transfer/TransferNavigator';
import type { RootStackParamList } from '../types';
-import WalletNavigator from '../wallet/WalletNavigator';
+import { rootNavigation } from './RootNavigationContainer';
const Stack = createNativeStackNavigator();
@@ -59,41 +55,6 @@ const screenOptions: NativeStackNavigationOptions = {
animation: __E2E__ ? 'none' : 'default',
};
-/**
- * Helper function to navigate from outside components.
- */
-export const navigationRef = createNavigationContainerRef();
-export const rootNavigation = {
- getCurrenRoute: (): string | undefined => {
- if (navigationRef.isReady()) {
- const route = navigationRef.getCurrentRoute();
- return route ? route.name : undefined;
- }
- return undefined;
- },
- navigate(
- ...args: RouteName extends unknown
- ? undefined extends RootStackParamList[RouteName]
- ?
- | [screen: RouteName]
- | [screen: RouteName, params: RootStackParamList[RouteName]]
- : [screen: RouteName, params: RootStackParamList[RouteName]]
- : never
- ): void {
- if (navigationRef.isReady()) {
- navigationRef.navigate(...args);
- } else {
- // Decide what to do if react navigation is not ready
- console.log('rootNavigation not ready');
- }
- },
- goBack(): void {
- if (navigationRef.isReady()) {
- navigationRef.goBack();
- }
- },
-};
-
const RootNavigator = (): ReactElement => {
const { t } = useTranslation('other');
const appState = useRef(AppState.currentState);
@@ -103,28 +64,6 @@ const RootNavigator = (): ReactElement => {
const isAuthenticated = useAppSelector(isAuthenticatedSelector);
const renderCount = useRenderCount();
- const linking: LinkingOptions<{}> = {
- prefixes: ['bitkit', 'slash', 'bitcoin', 'lightning'],
- // This is just here to prevent a warning
- config: { screens: { Wallet: '' } },
- subscribe(listener): () => void {
- // Deep linking if the app is already open
- const onReceiveURL = ({ url }: { url: string }): void => {
- rootNavigation.navigate('Wallet');
- processUri({ uri: url });
- listener(url);
- return;
- };
-
- // Listen to incoming links from deep linking
- const subscription = Linking.addEventListener('url', onReceiveURL);
-
- return () => {
- subscription.remove();
- };
- },
- };
-
const checkClipboard = async (): Promise => {
const result = await checkClipboardData();
if (result.isOk()) {
@@ -194,7 +133,7 @@ const RootNavigator = (): ReactElement => {
}, [isAuthenticated]);
return (
-
+ <>
@@ -242,7 +181,7 @@ const RootNavigator = (): ReactElement => {
{/* Should be above AuthCheck */}
-
+ >
);
};
diff --git a/src/navigation/types/index.ts b/src/navigation/types/index.ts
index a15ede24b..0c8b11d98 100644
--- a/src/navigation/types/index.ts
+++ b/src/navigation/types/index.ts
@@ -10,6 +10,10 @@ import {
import type { RecoveryStackParamList } from '../../screens/Recovery/RecoveryNavigator';
import type { IActivityItem } from '../../store/types/activity';
import type { TWidgetId, TWidgetOptions } from '../../store/types/widgets';
+import type { OnboardingStackParamList } from '../OnboardingNavigator';
+import type { SettingsStackParamList } from '../SettingsNavigator';
+import type { TransferStackParamList } from '../TransferNavigator';
+import type { WalletStackParamList } from '../WalletNavigator';
import type { BackupStackParamList } from '../bottom-sheet/BackupNavigation';
import type { LNURLWithdrawStackParamList } from '../bottom-sheet/LNURLWithdrawNavigation';
import type { OrangeTicketStackParamList } from '../bottom-sheet/OrangeTicketNavigation';
@@ -18,10 +22,6 @@ import type { ProfileLinkStackParamList } from '../bottom-sheet/ProfileLinkNavig
import type { ReceiveStackParamList } from '../bottom-sheet/ReceiveNavigation';
import type { SendStackParamList } from '../bottom-sheet/SendNavigation';
import type { TreasureHuntStackParamList } from '../bottom-sheet/TreasureHuntNavigation';
-import type { OnboardingStackParamList } from '../onboarding/OnboardingNavigator';
-import type { SettingsStackParamList } from '../settings/SettingsNavigator';
-import type { TransferStackParamList } from '../transfer/TransferNavigator';
-import type { WalletStackParamList } from '../wallet/WalletNavigator';
// TODO: move all navigation related types here
// https://reactnavigation.org/docs/typescript#organizing-types
diff --git a/src/screens/Activity/ActivityDetail.tsx b/src/screens/Activity/ActivityDetail.tsx
index c8a2e7b87..fdf62f38f 100644
--- a/src/screens/Activity/ActivityDetail.tsx
+++ b/src/screens/Activity/ActivityDetail.tsx
@@ -41,7 +41,7 @@ import {
GitBranchIcon,
HourglassIcon,
HourglassSimpleIcon,
- LightningHollow,
+ LightningHollowIcon,
LightningIcon,
ReceiveIcon,
SendIcon,
@@ -454,7 +454,7 @@ const OnchainActivityDetail = ({
title={t('activity_transfer_to_spending')}
value={
- {
+
+ {/* TODO: move these up the tree, causing slow down when navigating */}
>
diff --git a/src/screens/Recovery/RecoveryNavigator.tsx b/src/screens/Recovery/RecoveryNavigator.tsx
index 1f79afab4..d591e7eae 100644
--- a/src/screens/Recovery/RecoveryNavigator.tsx
+++ b/src/screens/Recovery/RecoveryNavigator.tsx
@@ -1,3 +1,4 @@
+import { DarkTheme, NavigationContainer } from '@react-navigation/native';
import {
NativeStackNavigationOptions,
createNativeStackNavigator,
@@ -8,7 +9,6 @@ import AuthCheck from '../../components/AuthCheck';
import { __E2E__ } from '../../constants/env';
import Mnemonic from '../../screens/Recovery/Mnemonic';
import Recovery from '../../screens/Recovery/Recovery';
-import { NavigationContainer } from '../../styles/components';
export type RecoveryStackParamList = {
AuthCheck: { onSuccess: () => void };
@@ -25,7 +25,7 @@ const screenOptions: NativeStackNavigationOptions = {
const RecoveryNavigator = (): ReactElement => {
return (
-
+
diff --git a/src/screens/Settings/AppStatus/index.tsx b/src/screens/Settings/AppStatus/index.tsx
index 5b7922a1c..559aa32bd 100644
--- a/src/screens/Settings/AppStatus/index.tsx
+++ b/src/screens/Settings/AppStatus/index.tsx
@@ -1,216 +1,195 @@
-import React, { memo, ReactElement, useEffect, useMemo, useState } from 'react';
+import React, { memo, ReactElement, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { StyleSheet, View } from 'react-native';
+import {
+ Linking,
+ Platform,
+ Pressable,
+ StyleProp,
+ StyleSheet,
+ View,
+ ViewStyle,
+} from 'react-native';
import { useAppSelector } from '../../../hooks/redux';
+import { SettingsScreenProps } from '../../../navigation/types';
import { backupSelector } from '../../../store/reselect/backup';
-import { blocktankPaidOrdersFullSelector } from '../../../store/reselect/blocktank';
-import {
- openChannelsSelector,
- pendingChannelsSelector,
-} from '../../../store/reselect/lightning';
import {
- isConnectedToElectrumSelector,
- isElectrumThrottledSelector,
- isLDKReadySelector,
- isOnlineSelector,
+ backupStatusSelector,
+ channelsStatusSelector,
+ electrumStatusSelector,
+ internetStatusSelector,
+ nodeStatusSelector,
} from '../../../store/reselect/ui';
-import { TBackupItem } from '../../../store/types/backup';
-import { EBackupCategory } from '../../../store/utils/backup';
-import { IColors } from '../../../styles/colors';
+import { EBackupCategory } from '../../../store/types/backup';
+import { THealthState } from '../../../store/types/ui';
+import colors, { IColors } from '../../../styles/colors';
import { ScrollView, View as ThemedView } from '../../../styles/components';
import {
BitcoinSlantedIcon,
BroadcastIcon,
CloudCheckIcon,
GlobeSimpleIcon,
- LightningHollow,
+ LightningHollowIcon,
} from '../../../styles/icons';
import { BodyMSB, CaptionB } from '../../../styles/text';
-import { FAILED_BACKUP_CHECK_TIME } from '../../../utils/backup/backups-subscriber';
import { i18nTime } from '../../../utils/i18n';
import SettingsView from '../SettingsView';
-type TStatusItem =
+type TStatusId =
| 'internet'
- | 'bitcoin_node'
+ | 'electrum'
| 'lightning_node'
| 'lightning_connection'
- | 'full_backup';
-
-type TItemState = 'ready' | 'pending' | 'error';
+ | 'backup';
interface IStatusItemProps {
+ id: TStatusId;
Icon: React.FunctionComponent;
- item: TStatusItem;
- state: TItemState;
+ state: THealthState;
subtitle?: string;
+ style?: StyleProp;
+ onPress?: () => void;
}
const Status = ({
+ id,
Icon,
- item,
state,
subtitle,
+ style,
+ onPress,
}: IStatusItemProps): ReactElement => {
const { t } = useTranslation('settings');
- const { bg, fg }: { fg: keyof IColors; bg: keyof IColors } = useMemo(() => {
- switch (state) {
- case 'ready':
- return { bg: 'green16', fg: 'green' };
- case 'pending':
- return { bg: 'yellow16', fg: 'yellow' };
- case 'error':
- return { bg: 'red16', fg: 'red' };
- }
- }, [state]);
+ const {
+ backgroundColor,
+ foregroundColor,
+ }: { foregroundColor: keyof IColors; backgroundColor: keyof IColors } =
+ React.useMemo(() => {
+ switch (state) {
+ case 'ready':
+ return { backgroundColor: 'green16', foregroundColor: 'green' };
+ case 'pending':
+ return { backgroundColor: 'yellow16', foregroundColor: 'yellow' };
+ case 'error':
+ return { backgroundColor: 'red16', foregroundColor: 'red' };
+ }
+ }, [state]);
- subtitle = subtitle || t(`status.${item}.${state}`);
+ subtitle = subtitle || t(`status.${id}.${state}`);
return (
-
+
-
-
+
+
-
- {t(`status.${item}.title`)}
+
+ {t(`status.${id}.title`)}
{subtitle}
-
+
);
};
-const AppStatus = (): ReactElement => {
+const AppStatus = ({
+ navigation,
+}: SettingsScreenProps<'AppStatus'>): ReactElement => {
const { t } = useTranslation('settings');
const { t: tTime } = useTranslation('intl', { i18n: i18nTime });
- const isOnline = useAppSelector(isOnlineSelector);
- const isConnectedToElectrum = useAppSelector(isConnectedToElectrumSelector);
- const isElectrumThrottled = useAppSelector(isElectrumThrottledSelector);
- const isLDKReady = useAppSelector(isLDKReadySelector);
- const openChannels = useAppSelector(openChannelsSelector);
- const pendingChannels = useAppSelector(pendingChannelsSelector);
- const paidOrders = useAppSelector(blocktankPaidOrdersFullSelector);
- const backup = useAppSelector(backupSelector);
- const [now, setNow] = useState(new Date().getTime());
-
- const internetState: TItemState = useMemo(() => {
- return isOnline ? 'ready' : 'error';
- }, [isOnline]);
- const bitcoinNodeState: TItemState = useMemo(() => {
- if (isOnline && !isConnectedToElectrum && !isElectrumThrottled) {
- return 'pending';
- }
- return isConnectedToElectrum ? 'ready' : 'error';
- }, [isOnline, isConnectedToElectrum, isElectrumThrottled]);
-
- const lightningNodeState: TItemState = useMemo(() => {
- return isOnline && isLDKReady ? 'ready' : 'error';
- }, [isOnline, isLDKReady]);
+ const internetState = useAppSelector(internetStatusSelector);
+ const electrumState = useAppSelector(electrumStatusSelector);
+ const nodeState = useAppSelector(nodeStatusSelector);
+ const channelsState = useAppSelector(channelsStatusSelector);
+ const backupState = useAppSelector(backupStatusSelector);
+ const backup = useAppSelector(backupSelector);
- const lightningConnectionState: TItemState = useMemo(() => {
- if (!isOnline) {
- return 'error';
+ const backupSubtitle = useMemo(() => {
+ if (backupState === 'error') {
+ return t('status.backup.error');
}
- if (openChannels.length > 0) {
- return 'ready';
- }
- if (
- pendingChannels.length > 0 ||
- Object.keys(paidOrders.created).length > 0
- ) {
- return 'pending';
- }
- return 'error';
- }, [
- isOnline,
- openChannels.length,
- pendingChannels.length,
- paidOrders.created,
- ]);
-
- // Keep checking backup status
- useEffect(() => {
- const timer = setInterval(() => {
- setNow(new Date().getTime());
- }, FAILED_BACKUP_CHECK_TIME);
-
- return (): void => clearInterval(timer);
- }, []);
-
- const isBackupSyncOk = useMemo(() => {
- const isSyncOk = (b: TBackupItem): boolean => {
- return (
- b.synced > b.required || now - b.required < FAILED_BACKUP_CHECK_TIME
- );
- };
-
- return Object.values(EBackupCategory).every((key) => {
- return isSyncOk(backup[key]);
+ const syncTimes = Object.values(EBackupCategory).map((key) => {
+ return backup[key].synced;
});
- }, [backup, now]);
-
- const fullBackupState: { state: TItemState; subtitle?: string } =
- useMemo(() => {
- if (!isBackupSyncOk) {
- return { state: 'error' };
- }
- const syncTimes = Object.values(EBackupCategory).map((key) => {
- return backup[key].synced;
- });
- const max = Math.max(...syncTimes);
- const subtitle = tTime('dateTime', {
- v: new Date(max),
- formatParams: {
- v: {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- hour: 'numeric',
- minute: 'numeric',
- },
+ const max = Math.max(...syncTimes);
+ return tTime('dateTime', {
+ v: new Date(max),
+ formatParams: {
+ v: {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
},
- });
- return { state: 'ready', subtitle };
- }, [tTime, backup, isBackupSyncOk]);
+ },
+ });
+ }, [backup, backupState, t, tTime]);
const items: IStatusItemProps[] = [
{
+ id: 'internet',
Icon: GlobeSimpleIcon,
- item: 'internet',
state: internetState,
+ onPress: () => {
+ const goToSettings = (): void => {
+ Platform.OS === 'ios'
+ ? Linking.openURL('App-Prefs:Settings')
+ : Linking.sendIntent('android.settings.SETTINGS');
+ };
+ goToSettings();
+ },
},
{
+ id: 'electrum',
Icon: BitcoinSlantedIcon,
- item: 'bitcoin_node',
- state: bitcoinNodeState,
+ state: electrumState,
+ onPress: () => navigation.navigate('ElectrumConfig'),
},
{
+ id: 'lightning_node',
Icon: BroadcastIcon,
- item: 'lightning_node',
- state: lightningNodeState,
+ state: nodeState,
+ onPress: () => navigation.navigate('LightningNodeInfo'),
},
{
- Icon: LightningHollow,
- item: 'lightning_connection',
- state: lightningConnectionState,
+ id: 'lightning_connection',
+ Icon: LightningHollowIcon,
+ state: channelsState,
+ onPress: () => navigation.navigate('Channels'),
},
{
+ id: 'backup',
Icon: CloudCheckIcon,
- item: 'full_backup',
- state: fullBackupState.state,
- subtitle: fullBackupState.subtitle,
+ state: backupState,
+ subtitle: backupSubtitle,
+ onPress: () => navigation.navigate('BackupSettings'),
},
];
return (
- {items.map((it) => (
-
- ))}
+ {items.map((item, index) => {
+ const { id, Icon, state, subtitle } = item;
+ const isLast = index === items.length - 1;
+
+ return (
+
+ );
+ })}
);
@@ -223,8 +202,8 @@ const styles = StyleSheet.create({
status: {
marginHorizontal: 16,
borderBottomWidth: 1,
- borderBottomColor: 'rgba(255, 255, 255, 0.1)',
- height: 76,
+ borderBottomColor: colors.white10,
+ height: 72,
flexDirection: 'row',
alignItems: 'center',
},
@@ -239,7 +218,7 @@ const styles = StyleSheet.create({
width: 32,
height: 32,
},
- desc: {
+ description: {
flex: 1,
},
});
diff --git a/src/screens/Settings/Backup/ExportToPhone.tsx b/src/screens/Settings/Backup/ExportToPhone.tsx
deleted file mode 100644
index 55a1f8f66..000000000
--- a/src/screens/Settings/Backup/ExportToPhone.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import React, { memo, ReactElement, useEffect, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import { StyleSheet } from 'react-native';
-import Share, { ShareOptions } from 'react-native-share';
-
-import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView';
-import NavigationHeader from '../../../components/NavigationHeader';
-import SafeAreaInset from '../../../components/SafeAreaInset';
-import Button from '../../../components/buttons/Button';
-import type { SettingsScreenProps } from '../../../navigation/types';
-import { TextInput, View } from '../../../styles/components';
-import { BodyM } from '../../../styles/text';
-import {
- cleanupBackupFiles,
- createBackupFile,
-} from '../../../utils/backup/fileBackup';
-import { showToast } from '../../../utils/notifications';
-
-const ExportToPhone = ({
- navigation,
-}: SettingsScreenProps<'ExportToPhone'>): ReactElement => {
- const { t } = useTranslation('backup');
- const [password, setPassword] = useState('');
- const [isCreating, setIsCreating] = useState(false);
-
- useEffect(() => {
- return (): void => {
- cleanupBackupFiles().catch();
- };
- }, []);
-
- const shareToFiles = async (filePath: string): Promise => {
- const shareOptions: ShareOptions = {
- title: t('export_share'),
- failOnCancel: false,
- saveToFiles: true,
- urls: [filePath],
- };
-
- try {
- const res = await Share.open(shareOptions);
-
- if (res.success) {
- showToast({
- type: 'success',
- title: t('export_success_title'),
- description: t('export_success_msg'),
- });
- navigation.goBack();
- }
- } catch (error) {
- if (JSON.stringify(error).indexOf('CANCELLED') < 0) {
- showToast({
- type: 'warning',
- title: t('export_error_title'),
- description: t('export_error_msg'),
- });
- }
- }
- };
-
- const onCreateBackup = async (): Promise => {
- setIsCreating(true);
-
- const fileRes = await createBackupFile(password);
-
- if (fileRes.isErr()) {
- console.log(fileRes.error.message);
- setIsCreating(false);
- showToast({
- type: 'warning',
- title: t('export_error_title'),
- description: t('export_error_file'),
- });
- return;
- }
-
- await shareToFiles(fileRes.value);
-
- setIsCreating(false);
- };
-
- return (
-
-
-
- {t('export_text')}
-
-
-
-
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- root: {
- flex: 1,
- },
- content: {
- flexGrow: 1,
- paddingHorizontal: 16,
- },
- textField: {
- marginTop: 32,
- },
- buttonContainer: {
- flexDirection: 'row',
- justifyContent: 'center',
- marginTop: 'auto',
- },
- button: {
- flex: 1,
- marginTop: 16,
- },
-});
-
-export default memo(ExportToPhone);
diff --git a/src/screens/Settings/Backup/Metadata.tsx b/src/screens/Settings/Backup/Metadata.tsx
index 6ca4b91a8..b26fefe4e 100644
--- a/src/screens/Settings/Backup/Metadata.tsx
+++ b/src/screens/Settings/Backup/Metadata.tsx
@@ -9,7 +9,7 @@ import Button from '../../../components/buttons/Button';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
import { backupSelector } from '../../../store/reselect/backup';
import { closeSheet } from '../../../store/slices/ui';
-import { EBackupCategory } from '../../../store/utils/backup';
+import { EBackupCategory } from '../../../store/types/backup';
import { BodyM, BodyS, BodySB } from '../../../styles/text';
import { i18nTime } from '../../../utils/i18n';
diff --git a/src/screens/Settings/BackupSettings/index.tsx b/src/screens/Settings/BackupSettings/index.tsx
index 16ea9b9dc..a41de688e 100644
--- a/src/screens/Settings/BackupSettings/index.tsx
+++ b/src/screens/Settings/BackupSettings/index.tsx
@@ -11,12 +11,12 @@ 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 { EBackupCategory } from '../../../store/utils/backup';
+import { EBackupCategory } from '../../../store/types/backup';
import { toggleBottomSheet } from '../../../store/utils/ui';
import { ScrollView, View as ThemedView } from '../../../styles/components';
import {
ArrowClockwise,
- LightningHollow,
+ LightningHollowIcon,
NoteIcon,
RectanglesTwo,
SettingsIcon,
@@ -205,7 +205,7 @@ const BackupSettings = ({
if (lightning) {
categories.unshift({
- Icon: LightningHollow,
+ Icon: LightningHollowIcon,
title: t('backup.category_connections'),
status: {
running: false,
diff --git a/src/screens/Wallets/BoostPrompt.tsx b/src/screens/Wallets/BoostPrompt.tsx
index 463edf538..c570aa498 100644
--- a/src/screens/Wallets/BoostPrompt.tsx
+++ b/src/screens/Wallets/BoostPrompt.tsx
@@ -17,7 +17,7 @@ import {
} from '../../hooks/bottomSheet';
import { useFeeText } from '../../hooks/fees';
import { useAppDispatch, useAppSelector } from '../../hooks/redux';
-import { rootNavigation } from '../../navigation/root/RootNavigator';
+import { rootNavigation } from '../../navigation/root/RootNavigationContainer';
import { resetSendTransaction } from '../../store/actions/wallet';
import { viewControllerSelector } from '../../store/reselect/ui';
import { transactionSelector } from '../../store/reselect/wallet';
diff --git a/src/screens/Wallets/Header.tsx b/src/screens/Wallets/Header.tsx
index ce2211988..eec35dcc7 100644
--- a/src/screens/Wallets/Header.tsx
+++ b/src/screens/Wallets/Header.tsx
@@ -3,17 +3,18 @@ import React, { memo, ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
+import AppStatus from '../../components/AppStatus';
import ProfileImage from '../../components/ProfileImage';
import VerticalShadow from '../../components/VerticalShadow';
import { useProfile, useSlashtags } from '../../hooks/slashtags';
-import { RootNavigationProp } from '../../navigation/types';
+import { DrawerStackNavigationProp } from '../../navigation/root/DrawerNavigator';
import { Pressable } from '../../styles/components';
-import { ProfileIcon, SettingsIcon } from '../../styles/icons';
+import { BurgerIcon } from '../../styles/icons';
import { Title } from '../../styles/text';
import { truncate } from '../../utils/helpers';
const Header = ({ style }: { style?: StyleProp }): ReactElement => {
- const navigation = useNavigation();
+ const navigation = useNavigation();
const { t } = useTranslation('slashtags');
const { url } = useSlashtags();
const { profile } = useProfile(url);
@@ -22,12 +23,12 @@ const Header = ({ style }: { style?: StyleProp }): ReactElement => {
navigation.navigate('Profile');
};
- const openContacts = (): void => {
- navigation.navigate('Contacts');
+ const openAppStatus = (): void => {
+ navigation.navigate('Settings', { screen: 'AppStatus' });
};
- const openSettings = (): void => {
- navigation.navigate('Settings');
+ const openDrawer = (): void => {
+ navigation.openDrawer();
};
return (
@@ -36,40 +37,35 @@ const Header = ({ style }: { style?: StyleProp }): ReactElement => {
+ testID="Header"
+ onPressIn={openProfile}>
{profile.name ? (
- {truncate(profile?.name, 20)}
+ {truncate(profile?.name, 18)}
) : (
{t('your_name_capital')}
)}
-
-
-
-
+ testID="HeaderAppStatus"
+ onPress={openAppStatus}
+ />
-
+ testID="HeaderMenu"
+ onPressIn={openDrawer}>
+
@@ -88,30 +84,12 @@ const styles = StyleSheet.create({
shadowContainer: {
...StyleSheet.absoluteFillObject,
},
- cogIcon: {
- alignItems: 'center',
- justifyContent: 'center',
- minHeight: 32,
- paddingLeft: 10,
- paddingRight: 16,
- },
- profileIcon: {
- alignItems: 'center',
- justifyContent: 'center',
- minHeight: 32,
- paddingRight: 10,
- },
leftColumn: {
flex: 6,
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 16,
},
- middleColumn: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
rightColumn: {
flex: 1,
flexDirection: 'row',
@@ -121,8 +99,14 @@ const styles = StyleSheet.create({
profileImage: {
marginRight: 16,
},
- pressed: {
- opacity: 1,
+ appStatus: {
+ marginRight: 4,
+ },
+ menuIcon: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingLeft: 10,
+ paddingRight: 16,
},
});
diff --git a/src/screens/Wallets/LNURLPay/Confirm.tsx b/src/screens/Wallets/LNURLPay/Confirm.tsx
index 8ed441d98..1f015fb3f 100644
--- a/src/screens/Wallets/LNURLPay/Confirm.tsx
+++ b/src/screens/Wallets/LNURLPay/Confirm.tsx
@@ -30,7 +30,7 @@ import { addPendingPayment } from '../../../store/slices/lightning';
import { updateMetaTxComment } from '../../../store/slices/metadata';
import { EActivityType } from '../../../store/types/activity';
import { AnimatedView, BottomSheetTextInput } from '../../../styles/components';
-import { Checkmark, LightningHollow } from '../../../styles/icons';
+import { Checkmark, LightningHollowIcon } from '../../../styles/icons';
import { BodySSB, Caption13Up } from '../../../styles/text';
import { FeeText } from '../../../utils/fees';
import {
@@ -221,7 +221,7 @@ const LNURLConfirm = ({
title={t('send_fee_and_speed')}
value={
<>
-
- state.ui;
-
-const viewControllerState = (state: RootState): TUiViewController => {
+export const viewControllersSelector = (
+ state: RootState,
+): TUiViewController => {
return state.ui.viewControllers;
};
-/**
- * Returns all viewController data.
- */
-export const viewControllersSelector = createSelector(
- [uiState],
- (ui): TUiViewController => ui.viewControllers,
-);
-
-/**
- * Returns specified viewController data.
- * @param {RootState} state
- * @param {TViewController} viewController
- * @returns {IViewControllerData}
- */
-export const viewControllerSelector = createSelector(
- [
- viewControllerState,
- (_viewControllers, viewController: TViewController): TViewController => {
- return viewController;
- },
- ],
- (viewControllers, viewController): IViewControllerData => {
- return viewControllers[viewController];
- },
-);
+export const viewControllerSelector = (
+ state: RootState,
+ viewController: TViewController,
+): IViewControllerData => {
+ return state.ui.viewControllers[viewController];
+};
-/**
- * Returns boolean on whether a given viewController is open.
- * @param {RootState} state
- * @param {TViewController} viewController
- * @returns {boolean}
- */
-export const viewControllerIsOpenSelector = createSelector(
- [
- viewControllerState,
- (_viewControllers, viewController: TViewController): TViewController => {
- return viewController;
- },
- ],
- (viewControllers, viewController): boolean => {
- return viewControllers[viewController].isOpen;
- },
-);
+export const viewControllerIsOpenSelector = (
+ state: RootState,
+ viewController: TViewController,
+): boolean => {
+ return state.ui.viewControllers[viewController].isOpen;
+};
-export const showLaterButtonSelector = createSelector(
- [uiState],
- (ui) => ui.viewControllers.PINNavigation.showLaterButton,
-);
+export const showLaterButtonSelector = (state: RootState): boolean => {
+ return state.ui.viewControllers.PINNavigation.showLaterButton ?? false;
+};
-export const profileLinkSelector = createSelector(
- [uiState],
- (ui) => ui.profileLink,
-);
+export const profileLinkSelector = (state: RootState): TProfileLink => {
+ return state.ui.profileLink;
+};
-export const isAuthenticatedSelector = createSelector(
- [uiState],
- (ui) => ui.isAuthenticated,
-);
+export const isAuthenticatedSelector = (state: RootState): boolean => {
+ return state.ui.isAuthenticated;
+};
-export const isOnlineSelector = createSelector(
- [uiState],
- (ui): boolean => ui.isOnline,
-);
+export const isOnlineSelector = (state: RootState): boolean => {
+ return state.ui.isOnline;
+};
-export const isLDKReadySelector = createSelector(
- [uiState],
- (ui): boolean => ui.isLDKReady,
-);
+export const isLDKReadySelector = (state: RootState): boolean => {
+ return state.ui.isLDKReady;
+};
-export const isConnectedToElectrumSelector = createSelector(
- [uiState],
- (ui): boolean => ui.isConnectedToElectrum,
-);
+export const isConnectedToElectrumSelector = (state: RootState): boolean => {
+ return state.ui.isConnectedToElectrum;
+};
-export const isElectrumThrottledSelector = createSelector(
- [uiState],
- (ui): boolean => ui.isElectrumThrottled,
-);
+export const isElectrumThrottledSelector = (state: RootState): boolean => {
+ return state.ui.isElectrumThrottled;
+};
-export const appStateSelector = createSelector([uiState], (ui) => ui.appState);
+export const appStateSelector = (state: RootState) => {
+ return state.ui.appState;
+};
-export const availableUpdateSelector = createSelector(
- [uiState],
- (ui) => ui.availableUpdate,
-);
+export const availableUpdateSelector = (state: RootState) => {
+ return state.ui.availableUpdate;
+};
-export const criticalUpdateSelector = createSelector(
- [uiState],
- (ui) => ui.availableUpdate?.critical ?? false,
-);
+export const criticalUpdateSelector = (state: RootState): boolean => {
+ return state.ui.availableUpdate?.critical ?? false;
+};
-export const timeZoneSelector = createSelector([uiState], (ui) => ui.timeZone);
+export const timeZoneSelector = (state: RootState): string => {
+ return state.ui.timeZone;
+};
-export const languageSelector = createSelector([uiState], (ui) => ui.language);
+export const languageSelector = (state: RootState): string => {
+ return state.ui.language;
+};
export const sendTransactionSelector = (state: RootState): TSendTransaction => {
return state.ui.sendTransaction;
};
+
+export const internetStatusSelector = (state: RootState): THealthState => {
+ return state.ui.isOnline ? 'ready' : 'error';
+};
+
+export const electrumStatusSelector = (state: RootState): THealthState => {
+ const { isOnline, isConnectedToElectrum, isElectrumThrottled } = state.ui;
+ if (isOnline && !isConnectedToElectrum && !isElectrumThrottled) {
+ return 'pending';
+ }
+ return isConnectedToElectrum ? 'ready' : 'error';
+};
+
+export const nodeStatusSelector = (state: RootState): THealthState => {
+ const { isOnline, isLDKReady } = state.ui;
+ return isOnline && isLDKReady ? 'ready' : 'error';
+};
+
+export const channelsStatusSelector = (state: RootState): THealthState => {
+ const { isOnline } = state.ui;
+ const openChannels = openChannelsSelector(state);
+ const pendingChannels = pendingChannelsSelector(state);
+ const paidOrders = blocktankPaidOrdersFullSelector(state);
+
+ if (!isOnline) {
+ return 'error';
+ }
+ if (openChannels.length > 0) {
+ return 'ready';
+ }
+ if (
+ pendingChannels.length > 0 ||
+ Object.keys(paidOrders.created).length > 0
+ ) {
+ return 'pending';
+ }
+ return 'error';
+};
+
+export const backupStatusSelector = createSelector(
+ [backupSelector],
+ (backup): THealthState => {
+ const now = new Date().getTime();
+ const FAILED_BACKUP_CHECK_TIME = 300000; // 5 minutes in milliseconds
+
+ const isSyncOk = (b: TBackupItem): boolean => {
+ return (
+ b.synced > b.required || now - b.required < FAILED_BACKUP_CHECK_TIME
+ );
+ };
+
+ const isBackupSyncOk = Object.values(EBackupCategory).every((key) => {
+ return isSyncOk(backup[key]);
+ });
+
+ return isBackupSyncOk ? 'ready' : 'error';
+ },
+);
+
+/**
+ * Returns a combined status of all app components.
+ * Returns 'ready' if all components are ready,
+ * 'pending' if any component is pending and none are in error,
+ * 'error' if any component is in error state.
+ * // NOTE: We ignore channels for the global app status
+ */
+export const appStatusSelector = createSelector(
+ [
+ internetStatusSelector,
+ electrumStatusSelector,
+ nodeStatusSelector,
+ backupStatusSelector,
+ ],
+ (internetState, electrumState, nodeState, backupState): THealthState => {
+ const states = [internetState, electrumState, nodeState, backupState];
+
+ if (states.some((state) => state === 'error')) {
+ return 'error';
+ }
+ if (states.some((state) => state === 'pending')) {
+ return 'pending';
+ }
+ return 'ready';
+ },
+);
diff --git a/src/store/shapes/backup.ts b/src/store/shapes/backup.ts
index 05c4b44a7..3655f98a7 100644
--- a/src/store/shapes/backup.ts
+++ b/src/store/shapes/backup.ts
@@ -1,5 +1,4 @@
-import { TBackupItem, TBackupState } from '../types/backup';
-import { EBackupCategory } from '../utils/backup';
+import { EBackupCategory, TBackupItem, TBackupState } from '../types/backup';
const item: TBackupItem = {
required: Date.now() - 1000,
diff --git a/src/store/slices/backup.ts b/src/store/slices/backup.ts
index 23e4a1035..7f7c80a86 100644
--- a/src/store/slices/backup.ts
+++ b/src/store/slices/backup.ts
@@ -2,7 +2,7 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { initialBackupState } from '../shapes/backup';
import { EActivityType } from '../types/activity';
-import { EBackupCategory } from '../utils/backup';
+import { EBackupCategory } from '../types/backup';
import { updateActivityItems } from './activity';
import { addPaidBlocktankOrder, updateBlocktankOrder } from './blocktank';
import {
diff --git a/src/store/types/backup.ts b/src/store/types/backup.ts
index aca6c32dd..fceeb9d8c 100644
--- a/src/store/types/backup.ts
+++ b/src/store/types/backup.ts
@@ -1,5 +1,14 @@
import { ENetworks, TAccount } from '@synonymdev/react-native-ldk';
-import { EBackupCategory } from '../utils/backup';
+
+export enum EBackupCategory {
+ wallet = 'bitkit_wallet',
+ settings = 'bitkit_settings',
+ widgets = 'bitkit_widgets',
+ metadata = 'bitkit_metadata',
+ blocktank = 'bitkit_blocktank_orders',
+ slashtags = 'bitkit_slashtags_contacts',
+ ldkActivity = 'bitkit_lightning_activity',
+}
export type TBackupItem = {
running: boolean;
diff --git a/src/store/types/ui.ts b/src/store/types/ui.ts
index a2a85495a..d48f78e51 100644
--- a/src/store/types/ui.ts
+++ b/src/store/types/ui.ts
@@ -92,6 +92,8 @@ export type TSendTransaction = {
fromAddressViewer?: boolean;
};
+export type THealthState = 'ready' | 'pending' | 'error';
+
export type TUiState = {
appState: AppStateStatus;
availableUpdate: TAvailableUpdate | null;
diff --git a/src/store/utils/backup.ts b/src/store/utils/backup.ts
index 216e67c3e..6f065f0ab 100644
--- a/src/store/utils/backup.ts
+++ b/src/store/utils/backup.ts
@@ -45,23 +45,13 @@ import {
updateWidgets,
} from '../slices/widgets';
import { EActivityType } from '../types/activity';
-import { TBackupMetadata } from '../types/backup';
+import { EBackupCategory, TBackupMetadata } from '../types/backup';
import { IBlocktank } from '../types/blocktank';
import { TMetadataState } from '../types/metadata';
import { TSlashtagsState } from '../types/slashtags';
import { IWalletItem, TTransfer } from '../types/wallet';
import { updateOnChainActivityList } from './activity';
-export enum EBackupCategory {
- wallet = 'bitkit_wallet',
- settings = 'bitkit_settings',
- widgets = 'bitkit_widgets',
- metadata = 'bitkit_metadata',
- blocktank = 'bitkit_blocktank_orders',
- slashtags = 'bitkit_slashtags_contacts',
- ldkActivity = 'bitkit_lightning_activity',
-}
-
export const performLdkRestore = async ({
backupServerDetails,
selectedNetwork = getSelectedNetwork(),
diff --git a/src/styles/components.ts b/src/styles/components.ts
index cdc3eb31a..76da23e6c 100644
--- a/src/styles/components.ts
+++ b/src/styles/components.ts
@@ -1,8 +1,4 @@
import { BottomSheetTextInput as _BottomSheetTextInput } from '@gorhom/bottom-sheet';
-import {
- DefaultTheme,
- NavigationContainer as _NavigationContainer,
-} from '@react-navigation/native';
import Color from 'color';
import {
ColorValue,
@@ -52,22 +48,6 @@ export const Container = styled.View`
background-color: ${(props): string => props.theme.colors.background};
`;
-export const NavigationContainer = styled(_NavigationContainer).attrs(
- (props) => ({
- theme: {
- ...DefaultTheme,
- colors: {
- ...DefaultTheme.colors,
- card: 'transparent',
- text: props.theme.colors.primary,
- background: 'transparent',
- primary: 'transparent',
- border: 'transparent',
- },
- },
- }),
-)({});
-
export const View = styled.View((props) => ({
backgroundColor: props.color
? props.theme.colors[props.color]
@@ -119,7 +99,7 @@ export const Pressable = styled(RNPressable)(
(props) => ({
backgroundColor: props.color
? props.theme.colors[props.color]
- : props.theme.colors.background,
+ : 'transparent',
opacity: props.disabled ? 0.5 : 1,
}),
);
diff --git a/src/styles/icons.ts b/src/styles/icons.ts
index 18e24a7b9..c4f69c141 100644
--- a/src/styles/icons.ts
+++ b/src/styles/icons.ts
@@ -1,12 +1,12 @@
import { Platform } from 'react-native';
import { SvgXml } from 'react-native-svg';
-import { profileIcon, settings } from '../assets/icons/header';
import {
aboutIcon,
advancedIcon,
arrowClockwise,
arrowCounterClock,
+ arrowsClockwise,
backupIcon,
broadcastIcon,
checkmarkIcon,
@@ -16,7 +16,6 @@ import {
devSettingsIcon,
discordIcon,
downArrowIcon,
- emailIcon,
faceIdIcon,
generalSettingsIcon,
githubIcon,
@@ -43,16 +42,14 @@ import {
backspaceIcon,
bitcoinCircleIcon,
bitcoinSlantedIcon,
- brokenLinkIcon,
+ burgerIcon,
calendarIcon,
cameraIcon,
- chartLineIcon,
checkCircleIcon,
clipboardTextIcon,
clockIcon,
coinsIcon,
cornersOut,
- cubeIcon,
exclamationIcon,
eyeIcon,
fingerPrintIcon,
@@ -61,34 +58,27 @@ import {
heartbeatIcon,
hourglassIcon,
hourglassSimpleIcon,
- infoIcon,
- keyIcon,
- lightbulbIcon,
lightningCircleIcon,
lightningIcon,
- lockIcon,
magnifyingGlassIcon,
- mapPinLineIcon,
- mapTrifoldIcon,
minusCircledIcon,
- newspaperIcon,
noteIcon,
- penIcon,
pencilIcon,
pictureIcon,
plusCircledIcon,
plusIcon,
+ powerIcon,
qrIcon,
- questionMarkIcon,
receivedIcon,
- savingsIcon,
scanIcon,
sentIcon,
+ settingsIcon,
shareAndroidIcon,
shareIosIcon,
speedFastIcon,
speedNormalIcon,
speedSlowIcon,
+ stackIcon,
switchIcon,
tagIcon,
timerIcon,
@@ -102,7 +92,9 @@ import {
userIcon,
userMinusIcon,
userPlusIcon,
+ userSquareIcon,
usersIcon,
+ warningIcon,
xIcon,
} from '../assets/icons/wallet';
import styled from './styled-components';
@@ -112,6 +104,12 @@ type IconProps = {
color?: keyof IThemeColors;
};
+export const BurgerIcon = styled(SvgXml).attrs((props) => ({
+ xml: burgerIcon(),
+ height: props.height ?? '24px',
+ width: props.width ?? '24px',
+}))(() => ({}));
+
export const ScanIcon = styled(SvgXml).attrs((props) => ({
xml: scanIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '32px',
@@ -121,7 +119,15 @@ export const ScanIcon = styled(SvgXml).attrs((props) => ({
}));
export const SettingsIcon = styled(SvgXml).attrs((props) => ({
- xml: settings(props.color ? props.theme.colors[props.color] : 'white'),
+ xml: settingsIcon(props.color ? props.theme.colors[props.color] : 'white'),
+ height: props.height ?? '24px',
+ width: props.width ?? '24px',
+}))((props) => ({
+ color: props.color ? props.theme.colors[props.color] : 'white',
+}));
+
+export const StackIcon = styled(SvgXml).attrs((props) => ({
+ xml: stackIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '24px',
width: props.width ?? '24px',
}))((props) => ({
@@ -339,14 +345,6 @@ export const ShareAndroidIcon = styled(SvgXml).attrs((props) => ({
export const ShareIcon =
Platform.OS === 'ios' ? ShareIosIcon : ShareAndroidIcon;
-export const PenIcon = styled(SvgXml).attrs((props) => ({
- xml: penIcon(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 PencilIcon = styled(SvgXml).attrs((props) => ({
xml: pencilIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '32px',
@@ -355,14 +353,6 @@ export const PencilIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const SavingsIcon = styled(SvgXml).attrs((props) => ({
- xml: savingsIcon(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 BitcoinSlantedIcon = styled(SvgXml).attrs((props) => ({
xml: bitcoinSlantedIcon(
props.color ? props.theme.colors[props.color] : 'white',
@@ -447,14 +437,6 @@ export const LightningIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const LockIcon = styled(SvgXml).attrs((props) => ({
- xml: lockIcon(props.color ? props.theme.colors[props.color] : 'white'),
- height: props.height ?? '16px',
- width: props.width ?? '16px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const SendIcon = styled(SvgXml).attrs((props) => ({
xml: sentIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '17px',
@@ -580,14 +562,6 @@ export const SwitchIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const ProfileIcon = styled(SvgXml).attrs((props) => ({
- xml: profileIcon(props.color ? props.theme.colors[props.color] : 'white'),
- height: props.height ?? '24px',
- width: props.width ?? '24px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const ListIcon = styled(SvgXml).attrs((props) => ({
xml: listIcon(
props.color
@@ -610,14 +584,6 @@ export const SortAscendingIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const InfoIcon = styled(SvgXml).attrs((props) => ({
- xml: infoIcon(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 CameraIcon = styled(SvgXml).attrs((props) => ({
xml: cameraIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '32px',
@@ -658,16 +624,6 @@ export const CornersOutIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const EmailIcon = styled(SvgXml).attrs((props) => ({
- xml: emailIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
- ),
- height: props.height ?? '24px',
- width: props.width ?? '24px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const GithubIcon = styled(SvgXml).attrs((props) => ({
xml: githubIcon(
props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
@@ -774,16 +730,6 @@ export const FlashlightIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const BrokenLinkIcon = styled(SvgXml).attrs((props) => ({
- xml: brokenLinkIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
- ),
- height: props.height ?? '32px',
- width: props.width ?? '32px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const EyeIcon = styled(SvgXml).attrs((props) => ({
xml: eyeIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '24px',
@@ -818,35 +764,6 @@ export const PlusCircledIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const KeyIcon = styled(SvgXml).attrs((props) => ({
- xml: keyIcon(props.color ? props.theme.colors[props.color] : 'white'),
- height: props.height ?? '24px',
- width: props.width ?? '24px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
-// Widget icons
-export const ChartLineIcon = styled(SvgXml).attrs((props) => ({
- xml: chartLineIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
- ),
- height: props.height ?? '64px',
- width: props.width ?? '64px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
-export const NewspaperIcon = styled(SvgXml).attrs((props) => ({
- xml: newspaperIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
- ),
- height: props.height ?? '64px',
- width: props.width ?? '64px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const QrIcon = styled(SvgXml).attrs((props) => ({
xml: qrIcon(
props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
@@ -857,32 +774,6 @@ export const QrIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const QuestionMarkIcon = styled(SvgXml).attrs((props) => ({
- xml: questionMarkIcon(),
- height: props.height ?? '30px',
- width: props.width ?? '30px',
-}))(() => ({}));
-
-export const CubeIcon = styled(SvgXml).attrs((props) => ({
- xml: cubeIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
- ),
- height: props.height ?? '64px',
- width: props.width ?? '64px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
-export const LightBulbIcon = styled(SvgXml).attrs((props) => ({
- xml: lightbulbIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.brand,
- ),
- height: props.height ?? '64px',
- width: props.width ?? '64px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const LeftSign = styled(SvgXml).attrs((props) => ({
xml: leftSign(
props.color ? props.theme.colors[props.color] : props.theme.colors.white,
@@ -913,8 +804,8 @@ export const ArrowClockwise = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const RectanglesTwo = styled(SvgXml).attrs((props) => ({
- xml: rectanglesTwo(
+export const ArrowsClockwiseIcon = styled(SvgXml).attrs((props) => ({
+ xml: arrowsClockwise(
props.color ? props.theme.colors[props.color] : props.theme.colors.white,
),
height: props.height ?? '24px',
@@ -923,8 +814,8 @@ export const RectanglesTwo = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const LightningHollow = styled(SvgXml).attrs((props) => ({
- xml: lightningHollow(
+export const RectanglesTwo = styled(SvgXml).attrs((props) => ({
+ xml: rectanglesTwo(
props.color ? props.theme.colors[props.color] : props.theme.colors.white,
),
height: props.height ?? '24px',
@@ -933,22 +824,12 @@ export const LightningHollow = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const MapTrifoldIcon = styled(SvgXml).attrs((props) => ({
- xml: mapTrifoldIcon(
- props.color ? props.theme.colors[props.color] : props.theme.colors.white,
- ),
- height: props.height ?? '16px',
- width: props.width ?? '16px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
-export const MapPinLineIcon = styled(SvgXml).attrs((props) => ({
- xml: mapPinLineIcon(
+export const LightningHollowIcon = styled(SvgXml).attrs((props) => ({
+ xml: lightningHollow(
props.color ? props.theme.colors[props.color] : props.theme.colors.white,
),
- height: props.height ?? '16px',
- width: props.width ?? '16px',
+ height: props.height ?? '24px',
+ width: props.width ?? '24px',
}))((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
@@ -1010,3 +891,27 @@ export const DevSettingsIcon = styled(SvgXml).attrs((props) => ({
}))((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
+
+export const PowerIcon = styled(SvgXml).attrs((props) => ({
+ xml: powerIcon(props.color ? props.theme.colors[props.color] : 'white'),
+ height: props.height ?? '24px',
+ width: props.width ?? '24px',
+}))((props) => ({
+ color: props.color ? props.theme.colors[props.color] : 'white',
+}));
+
+export const UserSquareIcon = styled(SvgXml).attrs((props) => ({
+ xml: userSquareIcon(props.color ? props.theme.colors[props.color] : 'white'),
+ height: props.height ?? '24px',
+ width: props.width ?? '24px',
+}))((props) => ({
+ color: props.color ? props.theme.colors[props.color] : 'white',
+}));
+
+export const WarningIcon = styled(SvgXml).attrs((props) => ({
+ xml: warningIcon(props.color ? props.theme.colors[props.color] : 'white'),
+ height: props.height ?? '24px',
+ width: props.width ?? '24px',
+}))((props) => ({
+ color: props.color ? props.theme.colors[props.color] : 'white',
+}));
diff --git a/src/styles/text.ts b/src/styles/text.ts
index a35874f45..48be7a218 100644
--- a/src/styles/text.ts
+++ b/src/styles/text.ts
@@ -39,6 +39,13 @@ export const Headline = styled.Text(
}),
);
+export const DrawerText = styled.Text(({ theme, color }) => ({
+ ...theme.fonts.black,
+ fontSize: '24px',
+ color: theme.colors[color ?? 'primary'],
+ letterSpacing: -1,
+}));
+
export const Title = styled.Text(
({ theme, color, lineHeight = 26 }) => ({
...theme.fonts.bold,
diff --git a/src/styles/themes.ts b/src/styles/themes.ts
index 590d57dc7..25e712328 100644
--- a/src/styles/themes.ts
+++ b/src/styles/themes.ts
@@ -14,8 +14,6 @@ export interface IThemeColors extends IDefaultColors {
primary: string;
secondary: string;
background: string;
- surface: string;
- onBackground: string;
onSurface: string;
refreshControl: string;
}
@@ -69,8 +67,6 @@ const light: ITheme = {
primary: '#121212',
secondary: '#121212',
background: colors.white80,
- surface: '#E8E8E8',
- onBackground: '#121212',
onSurface: '#D6D6D6',
refreshControl: '#121212',
},
@@ -84,8 +80,6 @@ const dark: ITheme = {
primary: colors.white,
secondary: colors.white64,
background: colors.black,
- surface: '#101010',
- onBackground: '#FFFFFF',
onSurface: colors.gray6,
refreshControl: '#FFFFFF',
},
diff --git a/src/utils/backup/backups-subscriber.tsx b/src/utils/backup/backups-subscriber.tsx
index 2906555db..51e0738ad 100644
--- a/src/utils/backup/backups-subscriber.tsx
+++ b/src/utils/backup/backups-subscriber.tsx
@@ -5,7 +5,8 @@ import { __E2E__ } from '../../constants/env';
import { useDebouncedEffect } from '../../hooks/helpers';
import { useAppSelector } from '../../hooks/redux';
import { backupSelector } from '../../store/reselect/backup';
-import { EBackupCategory, performBackup } from '../../store/utils/backup';
+import { EBackupCategory } from '../../store/types/backup';
+import { performBackup } from '../../store/utils/backup';
import { showToast } from '../notifications';
const BACKUP_DEBOUNCE = 5000; // 5 seconds
diff --git a/src/utils/i18n/locales/ca/settings.json b/src/utils/i18n/locales/ca/settings.json
index 75b359789..80ca2fed5 100644
--- a/src/utils/i18n/locales/ca/settings.json
+++ b/src/utils/i18n/locales/ca/settings.json
@@ -206,7 +206,7 @@
"string": "Desconectat"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Node Bitcoin"
},
diff --git a/src/utils/i18n/locales/cs/settings.json b/src/utils/i18n/locales/cs/settings.json
index a120b6df5..d20827079 100644
--- a/src/utils/i18n/locales/cs/settings.json
+++ b/src/utils/i18n/locales/cs/settings.json
@@ -329,7 +329,7 @@
"string": "Odpojeno"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Bitcoinový uzel"
},
@@ -371,7 +371,7 @@
"string": "Žádná otevřená spojení"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Nejnovější úplná záloha dat"
},
diff --git a/src/utils/i18n/locales/de/settings.json b/src/utils/i18n/locales/de/settings.json
index 5ac581de8..3b69309a7 100644
--- a/src/utils/i18n/locales/de/settings.json
+++ b/src/utils/i18n/locales/de/settings.json
@@ -329,7 +329,7 @@
"string": "Nicht verbunden"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Bitcoin Node"
},
@@ -371,7 +371,7 @@
"string": "Keine offenen Verbindungen"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Vollständiges Backup"
},
diff --git a/src/utils/i18n/locales/en/settings.json b/src/utils/i18n/locales/en/settings.json
index 4efe685fe..18a89ced4 100644
--- a/src/utils/i18n/locales/en/settings.json
+++ b/src/utils/i18n/locales/en/settings.json
@@ -329,7 +329,7 @@
"string": "Disconnected"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Bitcoin Node"
},
@@ -371,7 +371,7 @@
"string": "No open connections"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Latest Full Data Backup"
},
diff --git a/src/utils/i18n/locales/en/wallet.json b/src/utils/i18n/locales/en/wallet.json
index f5acc1fcd..fc8d557f5 100644
--- a/src/utils/i18n/locales/en/wallet.json
+++ b/src/utils/i18n/locales/en/wallet.json
@@ -1,4 +1,27 @@
{
+ "drawer": {
+ "wallet": {
+ "string": "Wallet"
+ },
+ "activity": {
+ "string": "Activity"
+ },
+ "contacts": {
+ "string": "Contacts"
+ },
+ "profile": {
+ "string": "Profile"
+ },
+ "widgets": {
+ "string": "Widgets"
+ },
+ "settings": {
+ "string": "Settings"
+ },
+ "status": {
+ "string": "App Status"
+ }
+ },
"send": {
"string": "Send"
},
diff --git a/src/utils/i18n/locales/es_419/settings.json b/src/utils/i18n/locales/es_419/settings.json
index 424758887..b18108451 100644
--- a/src/utils/i18n/locales/es_419/settings.json
+++ b/src/utils/i18n/locales/es_419/settings.json
@@ -179,7 +179,7 @@
"string": "Conectado"
}
},
- "bitcoin_node": {
+ "electrum": {
"ready": {
"string": "Conectado"
}
diff --git a/src/utils/i18n/locales/es_ES/settings.json b/src/utils/i18n/locales/es_ES/settings.json
index ee226b732..13d3e1b11 100644
--- a/src/utils/i18n/locales/es_ES/settings.json
+++ b/src/utils/i18n/locales/es_ES/settings.json
@@ -254,7 +254,7 @@
"string": "Desconectado"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Nodo Bitcoin"
},
@@ -284,7 +284,7 @@
"string": "Abriendo..."
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Último Backup de Datos Completo"
},
diff --git a/src/utils/i18n/locales/fr/settings.json b/src/utils/i18n/locales/fr/settings.json
index fb5bc738d..9dd60a29d 100644
--- a/src/utils/i18n/locales/fr/settings.json
+++ b/src/utils/i18n/locales/fr/settings.json
@@ -293,7 +293,7 @@
"string": "Déconnecté"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Nœud Bitcoin"
},
@@ -335,7 +335,7 @@
"string": "Pas de connexions ouvertes"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Dernière sauvegarde complète"
},
diff --git a/src/utils/i18n/locales/it/settings.json b/src/utils/i18n/locales/it/settings.json
index f6ce380f4..ae900ffe7 100644
--- a/src/utils/i18n/locales/it/settings.json
+++ b/src/utils/i18n/locales/it/settings.json
@@ -296,7 +296,7 @@
"string": "Disconnesso"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Nodo Bitcoin"
},
@@ -338,7 +338,7 @@
"string": "Nessuna connessione aperta"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Ultimo backup completo dei dati"
},
diff --git a/src/utils/i18n/locales/nl/settings.json b/src/utils/i18n/locales/nl/settings.json
index e51d1dbb8..a16aa160f 100644
--- a/src/utils/i18n/locales/nl/settings.json
+++ b/src/utils/i18n/locales/nl/settings.json
@@ -296,7 +296,7 @@
"string": "Ontkoppeld"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Bitcoin-node"
},
@@ -338,7 +338,7 @@
"string": "Geen open verbindingen"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Laatste volledige gegevensback-up"
},
diff --git a/src/utils/i18n/locales/pt_BR/settings.json b/src/utils/i18n/locales/pt_BR/settings.json
index f6bfc8f4d..680ac6cb7 100644
--- a/src/utils/i18n/locales/pt_BR/settings.json
+++ b/src/utils/i18n/locales/pt_BR/settings.json
@@ -293,7 +293,7 @@
"string": "Desconectado"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Nó de Bitcoin"
},
@@ -335,7 +335,7 @@
"string": "Nenhuma conexão ativa"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Último Backup Completo"
},
diff --git a/src/utils/i18n/locales/ru/settings.json b/src/utils/i18n/locales/ru/settings.json
index 1081b1f00..a93cee6ae 100644
--- a/src/utils/i18n/locales/ru/settings.json
+++ b/src/utils/i18n/locales/ru/settings.json
@@ -293,7 +293,7 @@
"string": "Отключено"
}
},
- "bitcoin_node": {
+ "electrum": {
"title": {
"string": "Биткойн-нода"
},
@@ -335,7 +335,7 @@
"string": "Нет открытых соединений"
}
},
- "full_backup": {
+ "backup": {
"title": {
"string": "Последнее Полное Резервное Копирование Данных"
},
diff --git a/src/utils/scanner/scanner.ts b/src/utils/scanner/scanner.ts
index 1fb95dfab..0114ca728 100644
--- a/src/utils/scanner/scanner.ts
+++ b/src/utils/scanner/scanner.ts
@@ -20,7 +20,7 @@ import {
import URLParse from 'url-parse';
import { sendNavigation } from '../../navigation/bottom-sheet/SendNavigation';
-import { rootNavigation } from '../../navigation/root/RootNavigator';
+import { rootNavigation } from '../../navigation/root/RootNavigationContainer';
import {
resetSendTransaction,
setupOnChainTransaction,
diff --git a/src/utils/slashtags/index.ts b/src/utils/slashtags/index.ts
index 26a8dcc7c..eff99f1bb 100644
--- a/src/utils/slashtags/index.ts
+++ b/src/utils/slashtags/index.ts
@@ -4,7 +4,7 @@ import { format, parse } from '@synonymdev/slashtags-url';
import debounce from 'lodash/debounce';
import { webRelayClient } from '../../components/SlashtagsProvider';
-import { rootNavigation } from '../../navigation/root/RootNavigator';
+import { rootNavigation } from '../../navigation/root/RootNavigationContainer';
import { dispatch, getSettingsStore } from '../../store/helpers';
import { updateSettings } from '../../store/slices/settings';
import {
diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts
index 0f2f48c26..6db586bec 100644
--- a/src/utils/startup/index.ts
+++ b/src/utils/startup/index.ts
@@ -98,7 +98,7 @@ export const startWalletServices = async ({
staleBackupRecoveryMode?: boolean;
selectedWallet?: TWalletName;
selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
+} = {}): Promise> => {
try {
// wait for interactions/animations to be completed
await new Promise((resolve) => {
diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts
index fe822a57f..7c50f774e 100644
--- a/src/utils/wallet/transactions.ts
+++ b/src/utils/wallet/transactions.ts
@@ -22,8 +22,8 @@ import {
} from '../../store/helpers';
import { removeActivityItem } from '../../store/slices/activity';
import { requireBackup } from '../../store/slices/backup';
+import { EBackupCategory } from '../../store/types/backup';
import { ETransactionSpeed } from '../../store/types/settings';
-import { EBackupCategory } from '../../store/utils/backup';
import { reduceValue } from '../helpers';
import i18n from '../i18n';
import { EAvailableNetwork } from '../networks';
diff --git a/yarn.lock b/yarn.lock
index a05fd174b..c8fa9cbe9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4245,6 +4245,26 @@ __metadata:
languageName: node
linkType: hard
+"@react-navigation/drawer@npm:^7.1.1":
+ version: 7.1.1
+ resolution: "@react-navigation/drawer@npm:7.1.1"
+ dependencies:
+ "@react-navigation/elements": ^2.2.5
+ color: ^4.2.3
+ react-native-drawer-layout: ^4.1.1
+ use-latest-callback: ^0.2.1
+ peerDependencies:
+ "@react-navigation/native": ^7.0.14
+ react: ">= 18.2.0"
+ react-native: "*"
+ react-native-gesture-handler: ">= 2.0.0"
+ react-native-reanimated: ">= 2.0.0"
+ react-native-safe-area-context: ">= 4.0.0"
+ react-native-screens: ">= 4.0.0"
+ checksum: d849fa23ec346597534ce7f03dac91bdaa89560b229759ce6363a3afaf80a40486acba05fe701eeb06a6a77e35b43788fe8b129c3e91e0a03f8cf4ed6dc31dd7
+ languageName: node
+ linkType: hard
+
"@react-navigation/elements@npm:^2.2.5":
version: 2.2.5
resolution: "@react-navigation/elements@npm:2.2.5"
@@ -5826,6 +5846,7 @@ __metadata:
"@react-native/babel-preset": ^0.77.1
"@react-native/metro-config": ^0.77.1
"@react-native/typescript-config": ^0.77.1
+ "@react-navigation/drawer": ^7.1.1
"@react-navigation/native": 7.0.14
"@react-navigation/native-stack": 7.2.0
"@reduxjs/toolkit": 2.2.6
@@ -12564,6 +12585,20 @@ __metadata:
languageName: node
linkType: hard
+"react-native-drawer-layout@npm:^4.1.1":
+ version: 4.1.1
+ resolution: "react-native-drawer-layout@npm:4.1.1"
+ dependencies:
+ use-latest-callback: ^0.2.1
+ peerDependencies:
+ react: ">= 18.2.0"
+ react-native: "*"
+ react-native-gesture-handler: ">= 2.0.0"
+ react-native-reanimated: ">= 2.0.0"
+ checksum: 02366958e92f6d1ff6746cb1f85803e460235604ccf4970daebdc217f994b716d0334f361d1f087fac976d5e2beebfe8d17b6bc13e6236538d2dbd8d25226465
+ languageName: node
+ linkType: hard
+
"react-native-fetch-api@npm:3.0.0":
version: 3.0.0
resolution: "react-native-fetch-api@npm:3.0.0"