diff --git a/__tests__/sagas/networkSettings.test.ts b/__tests__/sagas/networkSettings.test.ts
index 41f5e707c..33ca7839d 100644
--- a/__tests__/sagas/networkSettings.test.ts
+++ b/__tests__/sagas/networkSettings.test.ts
@@ -4,7 +4,7 @@ import { all, effectTypes, fork } from 'redux-saga/effects';
import createSagaMiddleware, { END, runSaga } from 'redux-saga';
import { applyMiddleware, createStore } from 'redux';
import { reducer } from '../../src/reducers/reducer';
-import { networkSettingsUpdate, networkSettingsUpdateSuccess, reloadWalletRequested, types } from '../../src/actions';
+import { networkSettingsUpdateRequest, networkSettingsUpdateSuccess, reloadWalletRequested, types } from '../../src/actions';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { networkSettingsKeyMap } from '../../src/constants';
import { STORE } from '../../src/store';
@@ -46,47 +46,47 @@ describe('updateNetworkSettings', () => {
const task = middleware.run(defaultSaga);
Promise.resolve()
- .then(() => store.dispatch(networkSettingsUpdate(null)))
- .then(() => store.dispatch(networkSettingsUpdate(undefined)))
- .then(() => store.dispatch(networkSettingsUpdate({})))
- .then(() => store.dispatch(networkSettingsUpdate({ explorerUrl: undefined })))
- .then(() => store.dispatch(networkSettingsUpdate({ explorerUrl: null })))
- .then(() => store.dispatch(networkSettingsUpdate({ explorerUrl: '' })))
- .then(() => store.dispatch(networkSettingsUpdate({ explorerUrl: 1 })))
- .then(() => store.dispatch(networkSettingsUpdate({ explorerUrl: 'invalid.url.com' })))
+ .then(() => store.dispatch(networkSettingsUpdateRequest(null)))
+ .then(() => store.dispatch(networkSettingsUpdateRequest(undefined)))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({})))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({ explorerUrl: undefined })))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({ explorerUrl: null })))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({ explorerUrl: '' })))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({ explorerUrl: 1 })))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({ explorerUrl: 'invalid.url.com' })))
// explorerUrl is valid, however it must have at least nodeUrl
- .then(() => store.dispatch(networkSettingsUpdate({ explorerUrl: 'http://localhost:8081/' })))
+ .then(() => store.dispatch(networkSettingsUpdateRequest({ explorerUrl: 'http://localhost:8081/' })))
// explorerUrl is valid, but explorerServiceUrl is empty
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: '',
})))
// explorerUrl is valid, but explorerServiceUrl is invalid
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'invalid.url.com',
})))
// explorer urls are valid, but nodeUrl is empty
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: '',
})))
// explorer urls are valid, but nodeUrl is invalid
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'invalid.url.com'
})))
// explorer and node urls are valid, but waletServiceUrl is invalid
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
walletServiceUrl: 'invalid.url.com'
})))
// explorer, node, and wallet service urls are valid, but walletServiceWsUrl is empty
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
@@ -94,7 +94,7 @@ describe('updateNetworkSettings', () => {
walletServiceWsUrl: ''
})))
// explorer, node, and wallet service urls are valid, but walletServiceWsUrl is invalid
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
@@ -102,7 +102,7 @@ describe('updateNetworkSettings', () => {
walletServiceWsUrl: 'invalid.url.com'
})))
// all urls are valid, except nodeUrl
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'invalid.url.com',
@@ -171,21 +171,21 @@ describe('updateNetworkSettings', () => {
Promise.resolve()
// calls getFullnodeNetwork
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
})))
// calls getWalletServiceNetwork
// it will fail because is lacking the walletServiceWsUrl
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
walletServiceUrl: 'http://localhost:8080/'
})))
// calls getWalletServiceNetwork
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
@@ -193,14 +193,14 @@ describe('updateNetworkSettings', () => {
walletServiceWsUrl: 'ws://ws.localhost:4040/'
})))
// calls getFullnodeNetwork
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
})))
// calls getFullnodeNetwork
// here the getFullnodeNetwork rejects throwing an error
- .then(() => store.dispatch(networkSettingsUpdate({
+ .then(() => store.dispatch(networkSettingsUpdateRequest({
explorerUrl: 'http://localhost:8081/',
explorerServiceUrl: 'http://localhost:8082/',
nodeUrl: 'http://localhost:3000/',
diff --git a/src/actions.js b/src/actions.js
index 89574e30a..72e80a852 100644
--- a/src/actions.js
+++ b/src/actions.js
@@ -120,11 +120,22 @@ export const types = {
// NOTE: These actions follows a taxonomy that should be applied
// to all other actions.
// See: https://github.com/HathorNetwork/hathor-wallet-mobile/issues/334
- NETWORKSETTINGS_UPDATE: 'NETWORK_SETTINGS_UPDATE',
+ /* It initiates an update of the network settings based on user input from a form. */
+ NETWORKSETTINGS_UPDATE_REQUEST: 'NETWORK_SETTINGS_UPDATE_REQUEST',
+ /* It updates the redux state */
+ NETWORKSETTINGS_UPDATE_STATE: 'NETWORKSETTINGS_UPDATE_STATE',
+ /* It persists the complete structure of network settings in the app storage and updates the redux store. */
+ NETWORKSETTINGS_PERSIST_STORE: 'NETWORKSETTINGS_PERSIST_STORE',
+ /* It indicates the persistence is complete and the wallet will be reloaded. */
+ NETWORKSETTINGS_UPDATE_WAITING: 'NETWORKSETTINGS_UPDATE_WAITING',
+ /* It indicates the update is complete after the wallet reloads. */
NETWORKSETTINGS_UPDATE_SUCCESS: 'NETWORK_SETTINGS_UPDATE_SUCCESS',
- NETWORKSETTINGS_UPDATE_READY: 'NETWORK_SETTINGS_UPDATE_READY',
+ /* It indicates the update request has invalid inputs. */
+ NETWORKSETTINGS_UPDATE_INVALID: 'NETWORKSETTINGS_UPDATE_INVALID',
+ /* It indicates the update request has failed. */
NETWORKSETTINGS_UPDATE_FAILURE: 'NETWORK_SETTINGS_UPDATE_FAILURE',
- NETWORKSETTINGS_UPDATE_ERRORS: 'NETWORK_SETTINGS_UPDATE_ERRORS',
+ /* It updates the redux state of network settings status */
+ NETWORKSETTINGS_UPDATE_READY: 'NETWORK_SETTINGS_UPDATE_READY',
};
export const featureToggleInitialized = () => ({
@@ -861,13 +872,13 @@ export const setWCConnectionFailed = (failed) => ({
* walletServiceWsUrl?: string
* }} customNetworkRequest Request input
*/
-export const networkSettingsUpdate = (customNetworkRequest) => ({
- type: types.NETWORKSETTINGS_UPDATE,
+export const networkSettingsUpdateRequest = (customNetworkRequest) => ({
+ type: types.NETWORKSETTINGS_UPDATE_REQUEST,
payload: customNetworkRequest,
});
/**
- * Emits the custom network settings to be stored and persisted.
+ * Emits the custom network settings to update the redux store.
* @param {{
* stage: string,
* network: string,
@@ -878,22 +889,58 @@ export const networkSettingsUpdate = (customNetworkRequest) => ({
* walletServiceWsUrl?: string
* }} customNetwork Settings to persist
*/
-export const networkSettingsUpdateSuccess = (customNetwork) => ({
- type: types.NETWORKSETTINGS_UPDATE_SUCCESS,
+export const networkSettingsUpdateState = (customNetwork) => ({
+ type: types.NETWORKSETTINGS_UPDATE_STATE,
+ payload: customNetwork,
+});
+
+/**
+ * Emits the custom network settings to persist in the app storage and update the redux store.
+ * @param {{
+ * stage: string,
+ * network: string,
+ * nodeUrl: string,
+ * explorerUrl: string,
+ * explorerServiceUrl: string,
+ * walletServiceUrl?: string
+ * walletServiceWsUrl?: string
+ * }} customNetwork Settings to persist
+ */
+export const networkSettingsPersistStore = (customNetwork) => ({
+ type: types.NETWORKSETTINGS_PERSIST_STORE,
payload: customNetwork,
});
/**
- * Emits the failure signal for custom network settings request.
+ * Action indicating that the network settings update process
+ * is in a waiting state.
+ * This is used after persisting custom network configurations,
+ * resulting in a wallet reload.
+ */
+export const networkSettingsUpdateWaiting = () => ({
+ type: types.NETWORKSETTINGS_UPDATE_WAITING,
+});
+
+/**
+ * Action indicating that the network settings update was successful.
+ * This serves as a hook for the frontend to provide feedback to the user.
+ */
+export const networkSettingsUpdateSuccess = () => ({
+ type: types.NETWORKSETTINGS_UPDATE_SUCCESS,
+});
+
+/**
+ * Action indicating a failure state for the custom network settings request.
* It means the request couldn't be processed due to internal error.
+ * This serves as a hook for the frontend to provide feedback to the user.
*/
export const networkSettingsUpdateFailure = () => ({
type: types.NETWORKSETTINGS_UPDATE_FAILURE,
});
/**
- * Emits errors signal for custom network settings form representing
- * invalid inputs.
+ * Action indicating an invalid state for the custom network settings request inputs.
+ * It means the form should present the invalid message on the corresponding inputs.
* @param {{
* message: string,
* nodeUrl: string,
@@ -903,8 +950,8 @@ export const networkSettingsUpdateFailure = () => ({
* walletServiceWsUrl?: string
* }} errors The validation errors from custom network settings form
*/
-export const networkSettingsUpdateErrors = (errors) => ({
- type: types.NETWORKSETTINGS_UPDATE_ERRORS,
+export const networkSettingsUpdateInvalid = (errors) => ({
+ type: types.NETWORKSETTINGS_UPDATE_INVALID,
payload: errors,
});
diff --git a/src/components/NetworkSettings/NetworkStatusBar.js b/src/components/NetworkSettings/NetworkStatusBar.js
index 64717833c..05329248b 100644
--- a/src/components/NetworkSettings/NetworkStatusBar.js
+++ b/src/components/NetworkSettings/NetworkStatusBar.js
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import { useSelector } from 'react-redux';
-import { eq } from 'lodash';
+import { isEqual } from 'lodash';
import { t } from 'ttag';
import { AlertUI } from '../../styles/themes';
import { ToplineBar } from '../ToplineBar';
@@ -13,18 +13,45 @@ import { PRE_SETTINGS_MAINNET } from '../../constants';
const customNetworkText = t`Custom network`;
-export const NetworkStatusBar = () => {
- const networkSettings = useSelector((state) => state.networkSettings);
- if (eq(networkSettings, PRE_SETTINGS_MAINNET)) {
- return null;
+function notMainnet(networkSettings) {
+ // If the networkSettings has a walletServiceUrl, then
+ // we should run a full check against the mainnet presettings.
+ // This is important because the wallet service has precedence
+ // over fullnode.
+ if (networkSettings.walletServiceUrl) {
+ return !isEqual(networkSettings, PRE_SETTINGS_MAINNET);
}
- const style = {
- backgroundColor: AlertUI.primaryColor,
- color: AlertUI.dark40Color,
+ // In the absence of walletServiceUrl we can remove wallet
+ // service URLs from the equality check against the mainnet
+ // presettings.
+ const currNetwork = {
+ stage: networkSettings.stage,
+ network: networkSettings.network,
+ nodeUrl: networkSettings.nodeUrl,
+ explorerUrl: networkSettings.explorerUrl,
+ explorerServiceUrl: networkSettings.explorerServiceUrl,
+ };
+ const mainnet = {
+ stage: PRE_SETTINGS_MAINNET.stage,
+ network: PRE_SETTINGS_MAINNET.network,
+ nodeUrl: PRE_SETTINGS_MAINNET.nodeUrl,
+ explorerUrl: PRE_SETTINGS_MAINNET.explorerUrl,
+ explorerServiceUrl: PRE_SETTINGS_MAINNET.explorerServiceUrl,
};
- const text = `${customNetworkText}: ${networkSettings.network}`;
- return (
-
+ return !isEqual(currNetwork, mainnet);
+}
+
+const style = {
+ backgroundColor: AlertUI.primaryColor,
+ color: AlertUI.dark40Color,
+};
+
+export const NetworkStatusBar = () => {
+ const getStatusText = (networkSettings) => `${customNetworkText}: ${networkSettings.network}`;
+ const networkSettings = useSelector((state) => state.networkSettings);
+
+ return notMainnet(networkSettings) && (
+
);
};
diff --git a/src/constants.js b/src/constants.js
index 6d50c7055..302eb4e6b 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -209,6 +209,8 @@ export const NETWORKSETTINGS_STATUS = {
READY: 'ready',
FAILED: 'failed',
LOADING: 'loading',
+ WAITING: 'waiting',
+ SUCCESSFUL: 'successful',
};
/**
diff --git a/src/reducers/reducer.js b/src/reducers/reducer.js
index 9dd21c39c..81211ec56 100644
--- a/src/reducers/reducer.js
+++ b/src/reducers/reducer.js
@@ -207,7 +207,7 @@ const initialState = {
...FEATURE_TOGGLE_DEFAULTS,
},
networkSettings: PRE_SETTINGS_MAINNET,
- networkSettingsErrors: {},
+ networkSettingsInvalid: {},
networkSettingsStatus: NETWORKSETTINGS_STATUS.READY,
};
@@ -327,6 +327,8 @@ export const reducer = (state = initialState, action) => {
return onPushReset(state);
case types.EXCEPTION_CAPTURED:
return onExceptionCaptured(state, action);
+ case types.RELOAD_WALLET_REQUESTED:
+ return onReloadWalletRequested(state);
case types.WALLET_RELOADING:
return onWalletReloading(state);
case types.SHARED_ADDRESS_UPDATE:
@@ -345,16 +347,22 @@ export const reducer = (state = initialState, action) => {
return onSetWalletConnectSessions(state, action);
case types.WC_SET_CONNECTION_FAILED:
return onSetWCConnectionFailed(state, action);
- case types.NETWORKSETTINGS_UPDATE:
- return onNetworkSettingsUpdate(state);
+ case types.NETWORKSETTINGS_UPDATE_REQUEST:
+ return onNetworkSettingsUpdateRequest(state);
+ case types.NETWORKSETTINGS_UPDATE_STATE:
+ return onNetworkSettingsUpdateState(state, action);
+ case types.NETWORKSETTINGS_PERSIST_STORE:
+ return onNetworkSettingsPersistStore(state, action);
+ case types.NETWORKSETTINGS_UPDATE_WAITING:
+ return onNetworkSettingsUpdateWaiting(state);
case types.NETWORKSETTINGS_UPDATE_SUCCESS:
- return onNetworkSettingsUpdateSucess(state, action);
+ return onNetworkSettingsUpdateSuccess(state);
case types.NETWORKSETTINGS_UPDATE_READY:
return onNetworkSettingsUpdateReady(state);
case types.NETWORKSETTINGS_UPDATE_FAILURE:
return onNetworkSettingsUpdateFailure(state);
- case types.NETWORKSETTINGS_UPDATE_ERRORS:
- return onNetworkSettingsUpdateErrors(state, action);
+ case types.NETWORKSETTINGS_UPDATE_INVALID:
+ return onNetworkSettingsUpdateInvalid(state, action);
default:
return state;
}
@@ -1026,6 +1034,16 @@ export const onExceptionCaptured = (state, { payload }) => {
};
};
+/**
+ * On wallet reload, tokens data will be reloaded as well.
+ */
+export const onReloadWalletRequested = (state) => ({
+ ...state,
+ tokensHistory: initialState.tokensHistory,
+ tokensBalance: initialState.tokensBalance,
+ loadHistoryStatus: initialState.loadHistoryStatus,
+});
+
const onWalletReloading = (state) => ({
...state,
walletStartState: WALLET_STATUS.LOADING,
@@ -1086,21 +1104,46 @@ export const onSetWCConnectionFailed = (state, { payload }) => ({
* @param {Object} action.payload The network settings emitted in saga
* @see updateNetworkSettings
*/
-export const onNetworkSettingsUpdate = (state) => ({
+export const onNetworkSettingsUpdateRequest = (state) => ({
...state,
networkSettingsStatus: NETWORKSETTINGS_STATUS.LOADING,
});
/**
* @param {Object} action.payload The network settings emitted in saga
- * @see updateNetworkSettings
+ * @see networkSettingsUpdateState customNetwork
+ */
+export const onNetworkSettingsUpdateState = (state, { payload }) => ({
+ ...state,
+ networkSettings: payload,
+});
+
+/**
+ * @param {Object} action.payload The network settings emitted in saga
+ * @see networkSettingsPersistStore customNetwork
*/
-export const onNetworkSettingsUpdateSucess = (state, { payload }) => ({
+export const onNetworkSettingsPersistStore = (state, { payload }) => ({
...state,
networkSettings: payload,
networkSettingsStatus: NETWORKSETTINGS_STATUS.LOADING,
});
+/**
+ * Set `WAITING` state on network settings status.
+ */
+export const onNetworkSettingsUpdateWaiting = (state) => ({
+ ...state,
+ networkSettingsStatus: NETWORKSETTINGS_STATUS.WAITING,
+});
+
+/**
+ * Set `SUCCESSFUL` state on network settings status.
+ */
+export const onNetworkSettingsUpdateSuccess = (state) => ({
+ ...state,
+ networkSettingsStatus: NETWORKSETTINGS_STATUS.SUCCESSFUL,
+});
+
/**
* @param {Object} action.payload The errors from network settings input validation
* @see updateNetworkSettings
@@ -1111,16 +1154,19 @@ export const onNetworkSettingsUpdateReady = (state) => ({
});
/**
- * @param {Object} action.payload The errors from network settings input validation
- * @see updateNetworkSettings
+ * Set `FAILED` state on network settings status.
*/
export const onNetworkSettingsUpdateFailure = (state) => ({
...state,
networkSettingsStatus: NETWORKSETTINGS_STATUS.FAILED
});
-export const onNetworkSettingsUpdateErrors = (state, { payload }) => ({
+/**
+ * @param {Object} action.payload The errors from network settings input validation
+ * @see networkSettingsUpdateInvalid errors
+ */
+export const onNetworkSettingsUpdateInvalid = (state, { payload }) => ({
...state,
- networkSettingsErrors: payload,
+ networkSettingsInvalid: payload,
networkSettingsStatus: NETWORKSETTINGS_STATUS.READY,
});
diff --git a/src/sagas/networkSettings.js b/src/sagas/networkSettings.js
index 9521e0597..2fead2b7d 100644
--- a/src/sagas/networkSettings.js
+++ b/src/sagas/networkSettings.js
@@ -1,22 +1,53 @@
-import { all, takeEvery, put, call, race, take, delay, select } from 'redux-saga/effects';
+import { all, takeEvery, put, call, race, delay, select } from 'redux-saga/effects';
import { config } from '@hathor/wallet-lib';
import { isEmpty } from 'lodash';
-import AsyncStorage from '@react-native-async-storage/async-storage';
import { t } from 'ttag';
-import { featureToggleUpdate, networkSettingsUpdateErrors, networkSettingsUpdateFailure, networkSettingsUpdateReady, networkSettingsUpdateSuccess, reloadWalletRequested, types } from '../actions';
-import { HTTP_REQUEST_TIMEOUT, NETWORK, networkSettingsKeyMap, NETWORK_TESTNET, STAGE, STAGE_DEV_PRIVNET, STAGE_TESTNET, WALLET_SERVICE_REQUEST_TIMEOUT } from '../constants';
-import { getFullnodeNetwork, getWalletServiceNetwork } from './helpers';
+import {
+ networkSettingsPersistStore,
+ networkSettingsUpdateInvalid,
+ networkSettingsUpdateFailure,
+ networkSettingsUpdateState,
+ networkSettingsUpdateSuccess,
+ networkSettingsUpdateWaiting,
+ types,
+ reloadWalletRequested,
+ onExceptionCaptured,
+ networkSettingsUpdateReady
+} from '../actions';
+import {
+ NETWORK,
+ networkSettingsKeyMap,
+ NETWORKSETTINGS_STATUS,
+ NETWORK_TESTNET,
+ STAGE,
+ STAGE_DEV_PRIVNET,
+ STAGE_TESTNET,
+ WALLET_SERVICE_REQUEST_TIMEOUT
+} from '../constants';
+import {
+ getFullnodeNetwork,
+ getWalletServiceNetwork,
+} from './helpers';
import { STORE } from '../store';
/**
- * Initialize network settings saga.
- *
- * It looks up a stored network settings to update the redux state.
+ * Initialize the network settings saga when the wallet starts successfully.
*/
export function* initNetworkSettings() {
const customNetwork = STORE.getItem(networkSettingsKeyMap.networkSettings);
if (customNetwork) {
- yield put(networkSettingsUpdateSuccess(customNetwork));
+ yield put(networkSettingsUpdateState(customNetwork));
+ }
+
+ const status = yield select((state) => state.networkSettingsStatus);
+ if (status === NETWORKSETTINGS_STATUS.WAITING) {
+ // This branch completes the network update by delivering
+ // a success feedback to the user.
+ yield put(networkSettingsUpdateSuccess());
+ } else {
+ // This branch is a fallback to set network status to READY
+ // after wallet initialization.
+ yield put(networkSettingsUpdateReady());
}
}
@@ -47,53 +78,50 @@ export function* updateNetworkSettings(action) {
walletServiceWsUrl,
} = action.payload || {};
- const errors = {};
+ const invalidPayload = {};
// validates input emptyness
if (isEmpty(action.payload)) {
- errors.message = t`Custom Network Settings cannot be empty.`;
+ invalidPayload.message = t`Custom Network Settings cannot be empty.`;
}
// validates explorerUrl
// - required
// - should have a valid URL
if (isEmpty(explorerUrl) || invalidUrl(explorerUrl)) {
- errors.explorerUrl = t`explorerUrl should be a valid URL.`;
+ invalidPayload.explorerUrl = t`explorerUrl should be a valid URL.`;
}
// validates explorerServiceUrl
// - required
// - should have a valid URL
if (isEmpty(explorerServiceUrl) || invalidUrl(explorerServiceUrl)) {
- errors.explorerServiceUrl = t`explorerServiceUrl should be a valid URL.`;
+ invalidPayload.explorerServiceUrl = t`explorerServiceUrl should be a valid URL.`;
}
// validates nodeUrl
// - required
// - should have a valid URl
if (isEmpty(nodeUrl) || invalidUrl(nodeUrl)) {
- errors.nodeUrl = t`nodeUrl should be a valid URL.`;
+ invalidPayload.nodeUrl = t`nodeUrl should be a valid URL.`;
}
// validates walletServiceUrl
// - optional
// - should have a valid URL, if given
if (walletServiceUrl && invalidUrl(walletServiceUrl)) {
- errors.walletServiceUrl = t`walletServiceUrl should be a valid URL.`;
+ invalidPayload.walletServiceUrl = t`walletServiceUrl should be a valid URL.`;
}
// validates walletServiceWsUrl
// - conditionally required
// - should have a valid URL, if walletServiceUrl is given
if (walletServiceUrl && invalidUrl(walletServiceWsUrl)) {
- errors.walletServiceWsUrl = t`walletServiceWsUrl should be a valid URL.`;
+ invalidPayload.walletServiceWsUrl = t`walletServiceWsUrl should be a valid URL.`;
}
- // TODO: Refactor by segregating Failure from Errors
- // - create networkSettingsUpdateErrors
- // - implement reaction to networkSettingsUpdateFailure
- yield put(networkSettingsUpdateErrors(errors));
- if (Object.keys(errors).length > 0) {
+ yield put(networkSettingsUpdateInvalid(invalidPayload));
+ if (Object.keys(invalidPayload).length > 0) {
return;
}
@@ -138,7 +166,6 @@ export function* updateNetworkSettings(action) {
try {
network = yield call(getFullnodeNetwork);
} catch (err) {
- // NOTE: Keep the console?
console.error('Error calling the fullnode while trying to get network details in updateNetworkSettings effect..', err);
rollbackConfigUrls(backupUrl);
yield put(networkSettingsUpdateFailure());
@@ -148,6 +175,7 @@ export function* updateNetworkSettings(action) {
// Fail after try get network from fullnode
if (!network) {
+ console.warn('The network could not be found.');
yield put(networkSettingsUpdateFailure());
return;
}
@@ -171,7 +199,7 @@ export function* updateNetworkSettings(action) {
walletServiceWsUrl,
};
- yield put(networkSettingsUpdateSuccess(customNetwork));
+ yield put(networkSettingsPersistStore(customNetwork));
}
/**
@@ -217,31 +245,42 @@ function invalidUrl(tryUrl) {
export function* persistNetworkSettings(action) {
// persists after reducer being updated
const networkSettings = action.payload;
- const strNetworkSettings = JSON.stringify(networkSettings);
- yield call(AsyncStorage.setItem, networkSettingsKeyMap.networkSettings, strNetworkSettings);
-
- // trigger toggle update to be managed by featureToggle saga
- yield put(featureToggleUpdate());
-
- // if wallet-service is being deactivated, it will trigger the reload,
- // otherwise we should trigger by ourselves
- const { timeout } = yield race({
- reload: take(types.RELOAD_WALLET_REQUESTED),
- timeout: delay(HTTP_REQUEST_TIMEOUT),
- });
+ try {
+ STORE.setItem(networkSettingsKeyMap.networkSettings, networkSettings);
+ yield put(networkSettingsUpdateWaiting());
+ } catch (err) {
+ console.error('Error while persisting the custom network settings.', err);
+ yield put(networkSettingsUpdateFailure());
+ return;
+ }
- if (timeout) {
- yield put(reloadWalletRequested());
+ const wallet = yield select((state) => state.wallet);
+ if (!wallet) {
+ // If we fall into this situation, the app should be killed
+ // for the custom new network settings take effect.
+ const errMsg = t`Wallet not found while trying to persist the custom network settings.`;
+ console.warn(errMsg);
+ yield put(onExceptionCaptured(errMsg, /* isFatal */ true));
+ return;
}
- yield put(networkSettingsUpdateReady());
+ // Stop wallet and clean its storage without clean its access data.
+ wallet.stop({ cleanStorage: true, cleanAddresses: true });
+ // This action should clean the tokens history on redux.
+ // In addition, the reload also clean the inmemory storage.
+ yield put(reloadWalletRequested());
}
/**
* Deletes the network settings from the application storage.
*/
export function* cleanNetworkSettings() {
- STORE.removeItem(networkSettingsKeyMap.networkSettings);
+ try {
+ STORE.removeItem(networkSettingsKeyMap.networkSettings);
+ } catch (err) {
+ console.error('Error while deleting the custom network settings from app storage.', err);
+ yield 1;
+ }
yield 0;
}
@@ -251,8 +290,8 @@ export function* cleanNetworkSettings() {
export function* saga() {
yield all([
takeEvery(types.START_WALLET_SUCCESS, initNetworkSettings),
- takeEvery(types.NETWORKSETTINGS_UPDATE, updateNetworkSettings),
- takeEvery(types.NETWORKSETTINGS_UPDATE_SUCCESS, persistNetworkSettings),
+ takeEvery(types.NETWORKSETTINGS_UPDATE_REQUEST, updateNetworkSettings),
+ takeEvery(types.NETWORKSETTINGS_PERSIST_STORE, persistNetworkSettings),
takeEvery(types.RESET_WALLET, cleanNetworkSettings),
]);
}
diff --git a/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js b/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js
index ef7f810ef..e0f59ec9d 100644
--- a/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js
+++ b/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js
@@ -3,65 +3,51 @@ import { View, Text, StyleSheet, Image } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { t } from 'ttag';
import { isEmpty } from 'lodash';
-import { networkSettingsUpdate, networkSettingsUpdateErrors, networkSettingsUpdateReady } from '../../actions';
+import { networkSettingsUpdateRequest, networkSettingsUpdateInvalid, networkSettingsUpdateReady } from '../../actions';
import FeedbackModal from '../../components/FeedbackModal';
import HathorHeader from '../../components/HathorHeader';
import NewHathorButton from '../../components/NewHathorButton';
import SimpleInput from '../../components/SimpleInput';
-import { NETWORKSETTINGS_STATUS } from '../../constants';
import errorIcon from '../../assets/images/icErrorBig.png';
+import checkIcon from '../../assets/images/icCheckBig.png';
import Spinner from '../../components/Spinner';
+import { hasSucceed, hasFailed, isLoading } from './helper';
const customNetworkSettingsTitleText = t`Custom Network Settings`.toUpperCase();
const warningText = t`Any token outside mainnet network bear no value. Only change if you know what you are doing.`;
const feedbackLoadingText = t`Updating custom network settings...`;
+const feedbackSucceedText = t`Network settings successfully customized.`;
const feedbackFailedText = t`There was an error while customizing network settings. Please try again later.`;
/**
- * Check if the network settings status is failed.
- * @param {object} networkSettingsStatus - status from redux store
- * @returns {boolean} - true if the status is failed, false otherwise
+ * Verifies if the invalidModel of the form has an error message.
*/
-// eslint-disable-next-line max-len
-const hasFailed = (networkSettingsStatus) => networkSettingsStatus === NETWORKSETTINGS_STATUS.FAILED;
-
-/**
- * Check if the network settings status is loading.
- * @param {object} networkSettingsStatus - status from redux store
- * @returns {boolean} - true if the status is loading, false otherwise
- */
-// eslint-disable-next-line max-len
-const isLoading = (networkSettingsStatus) => networkSettingsStatus === NETWORKSETTINGS_STATUS.LOADING;
-
-/**
- * Verifies if the errorModel of the form has an error message.
- */
-function hasError(errorModel) {
+function hasError(invalidModel) {
return Object
- .values({ ...errorModel })
+ .values({ ...invalidModel })
.reduce((_hasError, currValue) => _hasError || !isEmpty(currValue), false);
}
/**
- * Validates the formModel, returning the errorModel.
- * If there is no error in the formModel, the errorModel is returned empty.
+ * Validates the formModel, returning the invalidModel.
+ * If there is no error in the formModel, the invalidModel is returned empty.
*/
function validate(formModel) {
- const errorModel = {};
+ const invalidModel = {};
if (!formModel.nodeUrl) {
- errorModel.nodeUrl = t`nodeUrl is required.`;
+ invalidModel.nodeUrl = t`nodeUrl is required.`;
}
if (!formModel.explorerUrl) {
- errorModel.explorerUrl = t`explorerUrl is required.`;
+ invalidModel.explorerUrl = t`explorerUrl is required.`;
}
if (!formModel.explorerServiceUrl) {
- errorModel.explorerServiceUrl = t`explorerServiceUrl is required.`;
+ invalidModel.explorerServiceUrl = t`explorerServiceUrl is required.`;
}
- return errorModel;
+ return invalidModel;
}
const styles = StyleSheet.create({
@@ -103,7 +89,7 @@ export const CustomNetworkSettingsNav = Symbol('CustomNetworkSettings').toString
export const CustomNetworkSettingsScreen = ({ navigation }) => {
const dispatch = useDispatch();
const networkSettings = useSelector((state) => state.networkSettings);
- const networkSettingsErrors = useSelector((state) => state.networkSettingsErrors);
+ const networkSettingsInvalid = useSelector((state) => state.networkSettingsInvalid);
const networkSettingsStatus = useSelector((state) => state.networkSettingsStatus);
const [formModel, setFormModel] = useState({
@@ -114,21 +100,21 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
walletServiceWsUrl: networkSettings.walletServiceWsUrl || '',
});
- const [errorModel, setErrorModel] = useState({
- nodeUrl: networkSettingsErrors?.nodeUrl || '',
- explorerUrl: networkSettingsErrors?.explorerUrl || '',
- explorerServiceUrl: networkSettingsErrors?.explorerServiceUrl || '',
- walletServiceUrl: networkSettingsErrors?.walletServiceUrl || '',
- walletServiceWsUrl: networkSettingsErrors?.walletServiceWsUrl || '',
+ const [invalidModel, setInvalidModel] = useState({
+ nodeUrl: networkSettingsInvalid?.nodeUrl || '',
+ explorerUrl: networkSettingsInvalid?.explorerUrl || '',
+ explorerServiceUrl: networkSettingsInvalid?.explorerServiceUrl || '',
+ walletServiceUrl: networkSettingsInvalid?.walletServiceUrl || '',
+ walletServiceWsUrl: networkSettingsInvalid?.walletServiceWsUrl || '',
});
// eslint-disable-next-line max-len
/* @param {'nodeUrl' | 'explorerUrl' | 'explorerServiceUrl' | 'walletServiceUrl' | 'walletServiceWsUrl' } name */
const handleInputChange = (name) => (value) => {
- // update error model
- const errors = { ...errorModel };
- delete errors[name];
- setErrorModel(errors);
+ // update invalid model
+ const invalidModelCopy = { ...invalidModel };
+ delete invalidModelCopy[name];
+ setInvalidModel(invalidModelCopy);
// update form model
const form = {
@@ -137,8 +123,8 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
};
setFormModel(form);
- // validate form model and update error model
- setErrorModel(validate(form));
+ // validate form model and update invalid model
+ setInvalidModel(validate(form));
};
const handleFeedbackModalDismiss = () => {
@@ -146,27 +132,27 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
};
const handleSubmit = () => {
- const errors = validate(formModel);
- if (hasError(errors)) {
- setErrorModel(errors);
+ const newInvalidModel = validate(formModel);
+ if (hasError(newInvalidModel)) {
+ setInvalidModel(newInvalidModel);
return;
}
- dispatch(networkSettingsUpdate(formModel));
+ dispatch(networkSettingsUpdateRequest(formModel));
};
useEffect(() => {
- setErrorModel({
- nodeUrl: networkSettingsErrors?.nodeUrl || '',
- explorerUrl: networkSettingsErrors?.explorerUrl || '',
- explorerServiceUrl: networkSettingsErrors?.explorerServiceUrl || '',
- walletServiceUrl: networkSettingsErrors?.walletServiceUrl || '',
- walletServiceWsUrl: networkSettingsErrors?.walletServiceWsUrl || '',
+ setInvalidModel({
+ nodeUrl: networkSettingsInvalid?.nodeUrl || '',
+ explorerUrl: networkSettingsInvalid?.explorerUrl || '',
+ explorerServiceUrl: networkSettingsInvalid?.explorerServiceUrl || '',
+ walletServiceUrl: networkSettingsInvalid?.walletServiceUrl || '',
+ walletServiceWsUrl: networkSettingsInvalid?.walletServiceWsUrl || '',
});
- }, [networkSettingsErrors]);
+ }, [networkSettingsInvalid]);
useEffect(() => function cleanUp() {
- dispatch(networkSettingsUpdateErrors({}));
+ dispatch(networkSettingsUpdateInvalid({}));
}, []);
return (
@@ -183,6 +169,14 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
/>
)}
+ {hasSucceed(networkSettingsStatus) && (
+ )}
+ text={feedbackSucceedText}
+ onDismiss={handleFeedbackModalDismiss}
+ />
+ )}
+
{hasFailed(networkSettingsStatus) && (
)}
@@ -200,7 +194,7 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
label={t`Node URL`}
autoFocus
onChangeText={handleInputChange('nodeUrl')}
- error={errorModel.nodeUrl}
+ error={invalidModel.nodeUrl}
value={formModel.nodeUrl}
/>
@@ -209,7 +203,7 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
label={t`Explorer URL`}
autoFocus
onChangeText={handleInputChange('explorerUrl')}
- error={errorModel.explorerUrl}
+ error={invalidModel.explorerUrl}
value={formModel.explorerUrl}
/>
@@ -218,7 +212,7 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
label={t`Explorer Service URL`}
autoFocus
onChangeText={handleInputChange('explorerServiceUrl')}
- error={errorModel.explorerServiceUrl}
+ error={invalidModel.explorerServiceUrl}
value={formModel.explorerServiceUrl}
/>
@@ -227,7 +221,7 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
label={t`Wallet Service URL (optional)`}
autoFocus
onChangeText={handleInputChange('walletServiceUrl')}
- error={errorModel.walletServiceUrl}
+ error={invalidModel.walletServiceUrl}
value={formModel.walletServiceUrl}
/>
@@ -236,13 +230,13 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => {
label={t`Wallet Service WS URL (optional)`}
autoFocus
onChangeText={handleInputChange('walletServiceWsUrl')}
- error={errorModel.walletServiceWsUrl}
+ error={invalidModel.walletServiceWsUrl}
value={formModel.walletServiceWsUrl}
/>
diff --git a/src/screens/NetworkSettings/NetworkPreSettingsScreen.js b/src/screens/NetworkSettings/NetworkPreSettingsScreen.js
index df7f1df63..f87064a22 100644
--- a/src/screens/NetworkSettings/NetworkPreSettingsScreen.js
+++ b/src/screens/NetworkSettings/NetworkPreSettingsScreen.js
@@ -18,13 +18,15 @@ import HathorHeader from '../../components/HathorHeader';
import NewHathorButton from '../../components/NewHathorButton';
import Spinner from '../../components/Spinner';
import FeedbackModal from '../../components/FeedbackModal';
-import { networkSettingsUpdateReady, networkSettingsUpdateSuccess } from '../../actions';
+import { networkSettingsPersistStore, networkSettingsUpdateReady } from '../../actions';
import { PRE_SETTINGS_MAINNET, PRE_SETTINGS_TESTNET } from '../../constants';
import { CustomNetworkSettingsNav } from './CustomNetworkSettingsScreen';
-import { feedbackFailedText, feedbackLoadingText, hasFailed, isLoading } from './helper';
+import { feedbackSucceedText, feedbackFailedText, feedbackLoadingText, hasFailed, isLoading, hasSucceed } from './helper';
import errorIcon from '../../assets/images/icErrorBig.png';
+import checkIcon from '../../assets/images/icCheckBig.png';
const presettingsTitleText = t`Network Pre-Settings`.toUpperCase();
+
const styles = StyleSheet.create({
container: {
flex: 1,
@@ -76,8 +78,8 @@ export const NetworkPreSettingsNav = Symbol('NetworkPreSettings').toString();
export function NetworkPreSettingsScreen({ navigation }) {
const dispatch = useDispatch();
const networkSettingsStatus = useSelector((state) => state.networkSettingsStatus);
- const setMainnetNetwork = () => dispatch(networkSettingsUpdateSuccess(PRE_SETTINGS_MAINNET));
- const setTestnetNetwork = () => dispatch(networkSettingsUpdateSuccess(PRE_SETTINGS_TESTNET));
+ const setMainnetNetwork = () => dispatch(networkSettingsPersistStore(PRE_SETTINGS_MAINNET));
+ const setTestnetNetwork = () => dispatch(networkSettingsPersistStore(PRE_SETTINGS_TESTNET));
const setCustomNetwork = () => {
navigation.push(CustomNetworkSettingsNav);
};
@@ -100,6 +102,14 @@ export function NetworkPreSettingsScreen({ navigation }) {
/>
)}
+ {hasSucceed(networkSettingsStatus) && (
+ )}
+ text={feedbackSucceedText}
+ onDismiss={handleFeedbackModalDismiss}
+ />
+ )}
+
{hasFailed(networkSettingsStatus) && (
)}
diff --git a/src/screens/NetworkSettings/NetworkSettingsFlowStack.js b/src/screens/NetworkSettings/NetworkSettingsFlowStack.js
index ac7589054..61c5c44c9 100644
--- a/src/screens/NetworkSettings/NetworkSettingsFlowStack.js
+++ b/src/screens/NetworkSettings/NetworkSettingsFlowStack.js
@@ -24,8 +24,14 @@ export const NetworkSettingsFlowStack = () => {
name={NetworkSettingsDisclaimerNav}
component={NetworkSettingsDisclaimerScreen}
/>
-
-
+
+
);
};
diff --git a/src/screens/NetworkSettings/helper.js b/src/screens/NetworkSettings/helper.js
index 229d386dd..11b7ffd45 100644
--- a/src/screens/NetworkSettings/helper.js
+++ b/src/screens/NetworkSettings/helper.js
@@ -2,8 +2,18 @@ import { t } from 'ttag';
import { NETWORKSETTINGS_STATUS } from '../../constants';
export const feedbackLoadingText = t`Updating custom network settings...`;
+export const feedbackSucceedText = t`Network settings successfully customized.`;
export const feedbackFailedText = t`There was an error while customizing network settings. Please try again later.`;
+/**
+ * Check if the network settings status is successful.
+ * @param {object} networkSettingsStatus - status from redux store
+ * @returns {boolean} - true if the status is successful, false otherwise
+ */
+export function hasSucceed(networkSettingsStatus) {
+ return networkSettingsStatus === NETWORKSETTINGS_STATUS.SUCCESSFUL;
+}
+
/**
* Check if the network settings status is failed.
* @param {object} networkSettingsStatus - status from redux store