From 8e3031f106030471b887a35e129797c6ca0bddb7 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Sat, 17 Aug 2024 00:48:01 -0300 Subject: [PATCH] feat(wallet): Make wallet behave well when device is offline (#21067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We make the wallet closer to being offline-first, that is, once data is loaded, going offline won’t cause unnecessary data re-fetches which currently cause all balances and data to stay loading forever or eventually balances end up zeroed. Areas that may be impacted: read-only data displayed in the wallet and editing accounts. Fixes https://github.com/status-im/status-mobile/issues/21066 --- .../contexts/network/data_store.cljs | 5 +++ .../contexts/wallet/collectible/events.cljs | 18 ++++---- src/status_im/contexts/wallet/data_store.cljs | 4 ++ src/status_im/contexts/wallet/db.cljs | 5 ++- src/status_im/contexts/wallet/events.cljs | 42 ++++++++++++++----- src/status_im/contexts/wallet/signals.cljs | 12 ++++-- src/status_im/subs/general.cljs | 8 +++- src/status_im/subs/wallet/wallet.cljs | 17 ++++---- 8 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 src/status_im/contexts/network/data_store.cljs diff --git a/src/status_im/contexts/network/data_store.cljs b/src/status_im/contexts/network/data_store.cljs new file mode 100644 index 0000000000..6648125f49 --- /dev/null +++ b/src/status_im/contexts/network/data_store.cljs @@ -0,0 +1,5 @@ +(ns status-im.contexts.network.data-store) + +(defn online? + [{:network/keys [status]}] + (= :online status)) diff --git a/src/status_im/contexts/wallet/collectible/events.cljs b/src/status_im/contexts/wallet/collectible/events.cljs index f78b06bb9a..cb57e8dd88 100644 --- a/src/status_im/contexts/wallet/collectible/events.cljs +++ b/src/status_im/contexts/wallet/collectible/events.cljs @@ -2,6 +2,7 @@ (:require [camel-snake-kebab.extras :as cske] [clojure.string :as string] [react-native.platform :as platform] + [status-im.contexts.network.data-store :as network.data-store] [status-im.contexts.wallet.collectible.utils :as collectible-utils] [taoensso.timbre :as log] [utils.ethereum.chain :as chain] @@ -122,14 +123,15 @@ (rf/reg-event-fx :wallet/request-collectibles-for-current-viewing-account (fn [{:keys [db]} _] - (let [current-viewing-account (-> db :wallet :current-viewing-account-address) - [request-id] (get-unique-collectible-request-id 1)] - {:db (assoc-in db [:wallet :ui :collectibles :pending-requests] 1) - :fx [[:dispatch - [:wallet/request-new-collectibles-for-account - {:request-id request-id - :account current-viewing-account - :amount collectibles-request-batch-size}]]]}))) + (when (network.data-store/online? db) + (let [current-viewing-account (-> db :wallet :current-viewing-account-address) + [request-id] (get-unique-collectible-request-id 1)] + {:db (assoc-in db [:wallet :ui :collectibles :pending-requests] 1) + :fx [[:dispatch + [:wallet/request-new-collectibles-for-account + {:request-id request-id + :account current-viewing-account + :amount collectibles-request-batch-size}]]]})))) (defn- update-fetched-collectibles-progress [db owner-address collectibles offset has-more?] diff --git a/src/status_im/contexts/wallet/data_store.cljs b/src/status_im/contexts/wallet/data_store.cljs index d305295ae2..707e8bcf64 100644 --- a/src/status_im/contexts/wallet/data_store.cljs +++ b/src/status_im/contexts/wallet/data_store.cljs @@ -262,3 +262,7 @@ ;; :cost () ;; tbd not used on desktop :token-fees token-fees :gas-amount (:tx-gas-amount new-path)})) + +(defn tokens-never-loaded? + [db] + (nil? (get-in db [:wallet :ui :tokens-loading]))) diff --git a/src/status_im/contexts/wallet/db.cljs b/src/status_im/contexts/wallet/db.cljs index 2e53cb87f9..921476a62b 100644 --- a/src/status_im/contexts/wallet/db.cljs +++ b/src/status_im/contexts/wallet/db.cljs @@ -7,4 +7,7 @@ (def defaults {:ui {:network-filter network-filter-defaults - :tokens-loading {}}}) + ;; Note: we set it to nil by default to differentiate when the user logs + ;; in and the device is offline, versus re-fetching when offline and + ;; tokens already exist in the app-db. + :tokens-loading nil}}) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 66a1283a3f..9b2d981c23 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -5,6 +5,7 @@ [clojure.string :as string] [react-native.platform :as platform] [status-im.constants :as constants] + [status-im.contexts.network.data-store :as network.data-store] [status-im.contexts.settings.wallet.effects] [status-im.contexts.settings.wallet.events] [status-im.contexts.wallet.common.activity-tab.events] @@ -107,23 +108,44 @@ [:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]] [:dispatch [:wallet/check-recent-history-for-account address]]]})) -(rf/reg-event-fx - :wallet/get-accounts-success +(defn- reconcile-accounts + [db-accounts-by-address new-accounts] + (reduce + (fn [res {:keys [address] :as account}] + ;; Because we add extra fields (tokens and collectibles) into the RPC + ;; response from accounts_getAccounts, if we are offline we want to keep + ;; the old balances in the accounts, thus we merge the up-to-date account + ;; from status-go into the cached accounts. We also merge when online + ;; because we will re-fetch balances anyway. + ;; + ;; Refactor improvement: don't augment entities from status-go, store + ;; tokens and collectibles in separate keys in the app-db indexed by + ;; account address. + (assoc res + address + (-> (get db-accounts-by-address address) + (merge account) + ;; These should not be cached, otherwise when going + ;; offline->online collectibles won't be fetched. + (dissoc :current-collectible-idx :has-more-collectibles?)))) + {} + new-accounts)) + +(rf/reg-event-fx :wallet/get-accounts-success (fn [{:keys [db]} [accounts]] (let [wallet-accounts (data-store/rpc->accounts accounts) wallet-db (get db :wallet) new-account? (:new-account? wallet-db) navigate-to-account (:navigate-to-account wallet-db)] - {:db (assoc-in db - [:wallet :accounts] - (utils.collection/index-by :address wallet-accounts)) - :fx (concat refresh-accounts-fx-dispatches + {:db (update-in db [:wallet :accounts] reconcile-accounts wallet-accounts) + :fx (concat (when (or (data-store/tokens-never-loaded? db) + (network.data-store/online? db)) + refresh-accounts-fx-dispatches) [(when new-account? [:dispatch [:wallet/navigate-to-new-account navigate-to-account]])])}))) -(rf/reg-event-fx - :wallet/get-accounts - (fn [_] +(rf/reg-event-fx :wallet/get-accounts + (fn [] {:fx [[:json-rpc/call [{:method "accounts_getAccounts" :on-success [:wallet/get-accounts-success] @@ -486,7 +508,7 @@ {:test-networks-enabled? test-networks-enabled? :is-goerli-enabled? is-goerli-enabled?}) chains-filtered-by-mode (remove #(not (contains? chain-ids-by-mode %)) down-chain-ids) - chains-down? (seq chains-filtered-by-mode) + chains-down? (and (network.data-store/online? db) (seq chains-filtered-by-mode)) chain-names (when chains-down? (->> (map #(-> (network-utils/id->network %) name diff --git a/src/status_im/contexts/wallet/signals.cljs b/src/status_im/contexts/wallet/signals.cljs index cb9b37f571..24ba8507dc 100644 --- a/src/status_im/contexts/wallet/signals.cljs +++ b/src/status_im/contexts/wallet/signals.cljs @@ -43,9 +43,15 @@ {:fx [[:dispatch [:wallet/reload]]]} "wallet-blockchain-status-changed" - {:fx [[:dispatch - [:wallet/blockchain-status-changed - (transforms/js->clj event-js)]]]} + {:fx [[:dispatch-later + ;; Don't dispatch immediately because the signal may arrive as + ;; soon as the device goes offline. We need to give some time for + ;; RN to dispatch the network status update, otherwise when going + ;; offline the user will immediately see a toast saying "provider + ;; X is down". + [{:ms 500 + :dispatch [:wallet/blockchain-status-changed + (transforms/js->clj event-js)]}]]]} "wallet-activity-filtering-done" {:fx diff --git a/src/status_im/subs/general.cljs b/src/status_im/subs/general.cljs index 9f9cb445be..365e29730d 100644 --- a/src/status_im/subs/general.cljs +++ b/src/status_im/subs/general.cljs @@ -152,8 +152,12 @@ (fn [toasts [_ toast-id & cursor]] (get-in toasts (into [:toasts toast-id] cursor)))) -(re-frame/reg-sub - :network/offline? +(re-frame/reg-sub :network/offline? :<- [:network/status] (fn [status] (= status :offline))) + +(re-frame/reg-sub :network/online? + :<- [:network/status] + (fn [status] + (= status :online))) diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index cef2e27b23..d342fcaec6 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -48,11 +48,10 @@ :wallet/home-tokens-loading? :<- [:wallet/tokens-loading] (fn [tokens-loading] - (or (empty? tokens-loading) - (->> tokens-loading - vals - (some true?) - boolean)))) + (->> tokens-loading + vals + (some true?) + boolean))) (rf/reg-sub :wallet/current-viewing-account-tokens-loading? @@ -351,9 +350,10 @@ :<- [:wallet/accounts] :<- [:wallet/balances-in-selected-networks] :<- [:wallet/tokens-loading] + :<- [:network/online?] :<- [:profile/currency-symbol] :<- [:wallet/keypairs] - (fn [[accounts balances tokens-loading currency-symbol keypairs]] + (fn [[accounts balances tokens-loading online? currency-symbol keypairs]] (mapv (fn [{:keys [color address watch-only? key-uid operable] :as account}] (let [account-type (cond (= operable :no) :missing-keypair @@ -373,8 +373,9 @@ account keypair)}])) #(rf/dispatch [:wallet/navigate-to-account address])) - :loading? (or (get tokens-loading address) - (not (contains? tokens-loading address))) + :loading? (and online? + (or (get tokens-loading address) + (not (contains? tokens-loading address)))) :balance (utils/prettify-balance currency-symbol (get balances address))))) accounts)))