From 7dc19877de1e1e3b991473e3f7ed5be8bca1f26d Mon Sep 17 00:00:00 2001 From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> Date: Wed, 6 Dec 2023 23:57:45 +0530 Subject: [PATCH] [Feature] Account Switcher for send flow Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> --- .../components/drawers/drawer_top/view.cljs | 17 +- src/status_im2/common/data_store/wallet.cljs | 11 +- .../wallet/account/tabs/about/view.cljs | 5 +- .../contexts/wallet/account/tabs/view.cljs | 1 + .../contexts/wallet/account/view.cljs | 3 +- .../wallet/common/account_switcher/view.cljs | 19 +- .../common/sheets/select_account/style.cljs | 4 + .../common/sheets/select_account/view.cljs | 33 ++++ .../send/input_amount/component_spec.cljs | 32 ++- .../wallet/send/input_amount/view.cljs | 13 +- .../wallet/send/select_address/view.cljs | 4 +- .../wallet/send/select_asset/view.cljs | 14 +- src/status_im2/subs/wallet/wallet.cljs | 10 +- src/status_im2/subs/wallet/wallet_test.cljs | 187 +++++++----------- 14 files changed, 196 insertions(+), 157 deletions(-) create mode 100644 src/status_im2/contexts/wallet/common/sheets/select_account/style.cljs create mode 100644 src/status_im2/contexts/wallet/common/sheets/select_account/view.cljs diff --git a/src/quo/components/drawers/drawer_top/view.cljs b/src/quo/components/drawers/drawer_top/view.cljs index 25e1e0b05f88..2a6697b719e8 100644 --- a/src/quo/components/drawers/drawer_top/view.cljs +++ b/src/quo/components/drawers/drawer_top/view.cljs @@ -14,6 +14,8 @@ [react-native.core :as rn] [utils.i18n :as i18n])) +(def ^:private left-image-supported-types #{:account :keypair :default-keypair}) + (defn- left-image [{:keys [type customization-color account-avatar-emoji icon-avatar profile-picture]}] (case type @@ -165,13 +167,14 @@ button-disabled? account-avatar-emoji customization-color icon-avatar profile-picture keycard? networks label]}] [rn/view {:style style/container} - [rn/view {:style style/left-container} - [left-image - {:type type - :customization-color customization-color - :account-avatar-emoji account-avatar-emoji - :icon-avatar icon-avatar - :profile-picture profile-picture}]] + (when (left-image-supported-types type) + [rn/view {:style style/left-container} + [left-image + {:type type + :customization-color customization-color + :account-avatar-emoji account-avatar-emoji + :icon-avatar icon-avatar + :profile-picture profile-picture}]]) [rn/view {:style style/body-container} [left-title {:type type diff --git a/src/status_im2/common/data_store/wallet.cljs b/src/status_im2/common/data_store/wallet.cljs index ed172b7614e4..0abff539200e 100644 --- a/src/status_im2/common/data_store/wallet.cljs +++ b/src/status_im2/common/data_store/wallet.cljs @@ -15,6 +15,11 @@ [ids] (string/join constants/chain-id-separator ids)) +(defn add-keys-to-account + [account] + (-> account + (assoc :watch-only? (= (:type account) :watch)))) + (defn rpc->account [account] (-> account @@ -25,7 +30,8 @@ (update :prod-preferred-chain-ids chain-ids-string->set) (update :test-preferred-chain-ids chain-ids-string->set) (update :type keyword) - (update :color #(if (seq %) (keyword %) constants/account-default-customization-color)))) + (update :color #(if (seq %) (keyword %) constants/account-default-customization-color)) + add-keys-to-account)) (defn rpc->accounts [accounts] @@ -40,7 +46,8 @@ :test-preferred-chain-ids :testPreferredChainIds :color :colorId}) (update :prodPreferredChainIds chain-ids-set->string) - (update :testPreferredChainIds chain-ids-set->string))) + (update :testPreferredChainIds chain-ids-set->string) + (dissoc :watch-only?))) (defn <-rpc [network] diff --git a/src/status_im2/contexts/wallet/account/tabs/about/view.cljs b/src/status_im2/contexts/wallet/account/tabs/about/view.cljs index 3b0e7168c5fb..711b71bdee7c 100644 --- a/src/status_im2/contexts/wallet/account/tabs/about/view.cljs +++ b/src/status_im2/contexts/wallet/account/tabs/about/view.cljs @@ -35,9 +35,8 @@ (defn view [] (let [{:keys [customization-color] :as profile} (rf/sub [:profile/profile-with-image]) - {:keys [type address path]} (rf/sub [:wallet/current-viewing-account]) - networks (rf/sub [:wallet/network-details]) - watch-only? (= type :watch)] + {:keys [address path watch-only?]} (rf/sub [:wallet/current-viewing-account]) + networks (rf/sub [:wallet/network-details])] [rn/view {:style style/about-tab} [quo/data-item {:description :default diff --git a/src/status_im2/contexts/wallet/account/tabs/view.cljs b/src/status_im2/contexts/wallet/account/tabs/view.cljs index 2d50eff0496b..eb08cb4e82eb 100644 --- a/src/status_im2/contexts/wallet/account/tabs/view.cljs +++ b/src/status_im2/contexts/wallet/account/tabs/view.cljs @@ -16,6 +16,7 @@ (case selected-tab :assets [rn/flat-list {:render-fn token-value/view + :style {:flex 1} :data tokens :content-container-style {:padding-horizontal 8}}] :collectibles [collectibles/view] diff --git a/src/status_im2/contexts/wallet/account/view.cljs b/src/status_im2/contexts/wallet/account/view.cljs index fc26b174af1c..5c5bb7fe2e42 100644 --- a/src/status_im2/contexts/wallet/account/view.cljs +++ b/src/status_im2/contexts/wallet/account/view.cljs @@ -35,8 +35,7 @@ [] (let [selected-tab (reagent/atom first-tab-id)] (fn [] - (let [{:keys [name color balance type]} (rf/sub [:wallet/current-viewing-account]) - watch-only? (= type :watch)] + (let [{:keys [name color balance watch-only?]} (rf/sub [:wallet/current-viewing-account])] [rn/view {:style {:flex 1}} [account-switcher/view {:on-press #(rf/dispatch [:wallet/close-account-page])}] [quo/account-overview diff --git a/src/status_im2/contexts/wallet/common/account_switcher/view.cljs b/src/status_im2/contexts/wallet/common/account_switcher/view.cljs index 2bccfe55ceb6..2d6585421be5 100644 --- a/src/status_im2/contexts/wallet/common/account_switcher/view.cljs +++ b/src/status_im2/contexts/wallet/common/account_switcher/view.cljs @@ -1,14 +1,26 @@ (ns status-im2.contexts.wallet.common.account-switcher.view (:require [quo.core :as quo] [status-im2.contexts.wallet.common.sheets.account-options.view :as account-options] + [status-im2.contexts.wallet.common.sheets.select-account.view :as select-account] [utils.re-frame :as rf])) +(defn get-bottom-sheet-args + [switcher-type] + (case switcher-type + :account-options {:content account-options/view + :hide-handle? true} + :select-account {:content select-account/view} + nil)) + (defn view - [{:keys [on-press accessibility-label] :or {accessibility-label :top-bar}}] + [{:keys [on-press accessibility-label icon-name switcher-type] + :or {icon-name :i/close + accessibility-label :top-bar + switcher-type :account-options}}] (let [{:keys [color emoji]} (rf/sub [:wallet/current-viewing-account]) networks (rf/sub [:wallet/network-details])] [quo/page-nav - {:icon-name :i/close + {:icon-name icon-name :background :blur :on-press on-press :accessibility-label accessibility-label @@ -17,6 +29,5 @@ :right-side :account-switcher :account-switcher {:customization-color color :on-press #(rf/dispatch [:show-bottom-sheet - {:content account-options/view - :hide-handle? true}]) + (get-bottom-sheet-args switcher-type)]) :emoji emoji}}])) diff --git a/src/status_im2/contexts/wallet/common/sheets/select_account/style.cljs b/src/status_im2/contexts/wallet/common/sheets/select_account/style.cljs new file mode 100644 index 000000000000..ad15e29f6a35 --- /dev/null +++ b/src/status_im2/contexts/wallet/common/sheets/select_account/style.cljs @@ -0,0 +1,4 @@ +(ns status-im2.contexts.wallet.common.sheets.select-account.style) + +(def list-container + {:margin-horizontal 8}) diff --git a/src/status_im2/contexts/wallet/common/sheets/select_account/view.cljs b/src/status_im2/contexts/wallet/common/sheets/select_account/view.cljs new file mode 100644 index 000000000000..0ff206bd03c3 --- /dev/null +++ b/src/status_im2/contexts/wallet/common/sheets/select_account/view.cljs @@ -0,0 +1,33 @@ +(ns status-im2.contexts.wallet.common.sheets.select-account.view + (:require [quo.core :as quo] + quo.theme + [react-native.gesture :as gesture] + [status-im2.contexts.wallet.common.sheets.select-account.style :as style] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- render-account-item + [{:keys [color address] :as account} _ _ {:keys [selected-account-address]}] + [quo/account-item + {:type :default + :account-props (assoc account :customization-color color) + :customization-color color + :state (if (= address selected-account-address) :selected :default) + :on-press (fn [] + (rf/dispatch [:wallet/switch-current-viewing-account address]) + (rf/dispatch [:hide-bottom-sheet]))}]) + +(defn- view-internal + [] + (let [selected-account-address (rf/sub [:wallet/current-viewing-account-address]) + accounts (rf/sub [:wallet/accounts-without-watched-accounts])] + [:<> + [quo/drawer-top {:title (i18n/label :t/select-account)}] + [gesture/flat-list + {:data accounts + :render-fn render-account-item + :render-data {:selected-account-address selected-account-address} + :content-container-style style/list-container + :shows-vertical-scroll-indicator false}]])) + +(def view (quo.theme/with-theme view-internal)) diff --git a/src/status_im2/contexts/wallet/send/input_amount/component_spec.cljs b/src/status_im2/contexts/wallet/send/input_amount/component_spec.cljs index a741ebd15097..6f60daf6c5d1 100644 --- a/src/status_im2/contexts/wallet/send/input_amount/component_spec.cljs +++ b/src/status_im2/contexts/wallet/send/input_amount/component_spec.cljs @@ -12,12 +12,32 @@ (fn [_] (val keyval))))) (def sub-mocks - {:profile/profile {:currency :usd} - :wallet/network-details [{:source 525 - :short-name "eth" - :network-name :ethereum - :chain-id 1 - :related-chain-id 5}]}) + {:profile/profile {:currency :usd} + :wallet/network-details [{:source 525 + :short-name "eth" + :network-name :ethereum + :chain-id 1 + :related-chain-id 5}] + :wallet/current-viewing-account {:path "m/44'/60'/0'/0/1" + :emoji "💎" + :key-uid "0x2f5ea39" + :address "0x1" + :wallet false + :name "Account One" + :type :generated + :watch-only? false + :chat false + :test-preferred-chain-ids #{5 420 421613} + :color :purple + :hidden false + :prod-preferred-chain-ids #{1 10 42161} + :position 1 + :clock 1698945829328 + :created-at 1698928839000 + :operable "fully" + :mixedcase-address "0x7bcDfc75c431" + :public-key "0x04371e2d9d66b82f056bc128064" + :removed false}}) (h/describe "Send > input amount screen" (h/test "Default render" diff --git a/src/status_im2/contexts/wallet/send/input_amount/view.cljs b/src/status_im2/contexts/wallet/send/input_amount/view.cljs index 1c0294146e01..d60663c1461e 100644 --- a/src/status_im2/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im2/contexts/wallet/send/input_amount/view.cljs @@ -6,6 +6,7 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [reagent.core :as reagent] + [status-im2.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im2.contexts.wallet.send.input-amount.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -101,14 +102,10 @@ #(.remove app-keyboard-listener)))) [rn/view {:style style/screen} - [quo/page-nav - {:background :blur - :icon-name :i/arrow-left - :on-press #(rf/dispatch [:navigate-back]) - :right-side :account-switcher - :account-switcher {:customization-color :yellow - :emoji "🎮" - :on-press #(js/alert "Switch account")}}] + [account-switcher/view + {:icon-name :i/arrow-left + :on-press #(rf/dispatch [:navigate-back]) + :switcher-type :select-account}] [quo/token-input {:container-style style/input-container :token token diff --git a/src/status_im2/contexts/wallet/send/select_address/view.cljs b/src/status_im2/contexts/wallet/send/select_address/view.cljs index 20d365c06bf1..8acb655cbdca 100644 --- a/src/status_im2/contexts/wallet/send/select_address/view.cljs +++ b/src/status_im2/contexts/wallet/send/select_address/view.cljs @@ -147,7 +147,9 @@ {:content-container-style (style/container margin-top) :keyboard-should-persist-taps :handled :scroll-enabled false} - [account-switcher/view {:on-press on-close}] + [account-switcher/view + {:on-press on-close + :switcher-type :select-account}] [quo/text-combinations {:title (i18n/label :t/send-to) :container-style style/title-container diff --git a/src/status_im2/contexts/wallet/send/select_asset/view.cljs b/src/status_im2/contexts/wallet/send/select_asset/view.cljs index 984dda3338e1..d7ae694e9924 100644 --- a/src/status_im2/contexts/wallet/send/select_asset/view.cljs +++ b/src/status_im2/contexts/wallet/send/select_asset/view.cljs @@ -7,6 +7,7 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [reagent.core :as reagent] + [status-im2.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im2.contexts.wallet.send.select-asset.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -76,15 +77,10 @@ {:content-container-style {:flex 1} :keyboard-should-persist-taps :handled :scroll-enabled false} - [quo/page-nav - {:icon-name :i/arrow-left - :on-press on-close - :accessibility-label :top-bar - :right-side :account-switcher - :account-switcher {:customization-color :purple - :on-press #(js/alert "Not implemented yet") - :state :default - :emoji "🍑"}}] + [account-switcher/view + {:icon-name :i/arrow-left + :on-press on-close + :switcher-type :select-account}] [quo/text-combinations {:title (i18n/label :t/select-asset) :container-style style/title-container diff --git a/src/status_im2/subs/wallet/wallet.cljs b/src/status_im2/subs/wallet/wallet.cljs index 2f7fe983bae5..6768a34f8b58 100644 --- a/src/status_im2/subs/wallet/wallet.cljs +++ b/src/status_im2/subs/wallet/wallet.cljs @@ -48,10 +48,10 @@ :<- [:wallet/balances] :<- [:wallet/tokens-loading?] (fn [[accounts balances tokens-loading?]] - (mapv (fn [{:keys [color address type] :as account}] + (mapv (fn [{:keys [color address watch-only?] :as account}] (assoc account :customization-color color - :type (if (= type :watch) :watch-only :empty) + :type (if watch-only? :watch-only :empty) :on-press #(rf/dispatch [:wallet/navigate-to-account address]) :loading? tokens-loading? :balance (utils/prettify-balance (get balances address)))) @@ -100,6 +100,12 @@ (fn [[accounts current-viewing-account-address]] (remove #(= (:address %) current-viewing-account-address) accounts))) +(rf/reg-sub + :wallet/accounts-without-watched-accounts + :<- [:wallet/accounts] + (fn [accounts] + (remove #(:watch-only? %) accounts))) + (defn- calc-token-value [{:keys [market-values-per-currency] :as item} chain-id] (let [crypto-value (utils/token-value-in-chain item chain-id) diff --git a/src/status_im2/subs/wallet/wallet_test.cljs b/src/status_im2/subs/wallet/wallet_test.cljs index 1a2aa321b3c3..83a5e30fc200 100644 --- a/src/status_im2/subs/wallet/wallet_test.cljs +++ b/src/status_im2/subs/wallet/wallet_test.cljs @@ -38,6 +38,22 @@ 3 {:raw-balance "" :has-error false}} :market-values-per-currency {:usd {:price 1000}}}]) +(def tokens-0x3 + [{:decimals 3 + :symbol "ETH" + :name "Ether" + :balances-per-chain {1 {:raw-balance "5000" :has-error false} + 2 {:raw-balance "2000" :has-error false} + 3 {:raw-balance "" :has-error false}} + :market-values-per-currency {:usd {:price 200}}} + {:decimals 10 + :symbol "DAI" + :name "Dai Stablecoin" + :balances-per-chain {1 {:raw-balance "10000000000" :has-error false} + 2 {:raw-balance "0" :has-error false} + 3 {:raw-balance "" :has-error false}} + :market-values-per-currency {:usd {:price 1000}}}]) + (def accounts {"0x1" {:path "m/44'/60'/0'/0/0" :emoji "😃" @@ -46,6 +62,7 @@ :wallet false :name "Account One" :type :generated + :watch-only? false :chat false :test-preferred-chain-ids #{5 420 421613} :color :blue @@ -66,6 +83,7 @@ :wallet false :name "Account Two" :type :generated + :watch-only? false :chat false :test-preferred-chain-ids #{5 420 421613} :color :purple @@ -78,96 +96,53 @@ :mixedcase-address "0x7bcDfc75c431" :public-key "0x04371e2d9d66b82f056bc128064" :removed false - :tokens tokens-0x2}}) + :tokens tokens-0x2} + "0x3" {:path "" + :emoji "💎" + :key-uid "0x2f5ea39" + :address "0x3" + :wallet false + :name "Watched Account 1" + :type :watch + :watch-only? true + :chat false + :test-preferred-chain-ids #{0} + :color :magenta + :hidden false + :prod-preferred-chain-ids #{0} + :position 2 + :clock 1698945829328 + :created-at 1698928839000 + :operable "fully" + :mixedcase-address "0x7bcDfc75c431" + :public-key "0x" + :removed false + :tokens tokens-0x3}}) (h/deftest-sub :wallet/balances [sub-name] (testing "a map: address->balance" (swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) - - (is (= {"0x1" 3250 "0x2" 2100} - (rf/sub [sub-name]))))) + (is (match? {"0x1" 3250 "0x2" 2100 "0x3" 2400} + (rf/sub [sub-name]))))) (h/deftest-sub :wallet/accounts [sub-name] - (testing "returns all accounts without balance" + (testing "returns all accounts" (swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) - - (is - (= (list {:path "m/44'/60'/0'/0/0" - :emoji "😃" - :key-uid "0x2f5ea39" - :address "0x1" - :wallet false - :name "Account One" - :type :generated - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :blue - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :position 0 - :clock 1698945829328 - :created-at 1698928839000 - :operable "fully" - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :tokens tokens-0x1} - {:path "m/44'/60'/0'/0/1" - :emoji "💎" - :key-uid "0x2f5ea39" - :address "0x2" - :wallet false - :name "Account Two" - :type :generated - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :purple - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :position 1 - :clock 1698945829328 - :created-at 1698928839000 - :operable "fully" - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :tokens tokens-0x2}) - (rf/sub [sub-name]))))) + (is (match? (vals accounts) + (rf/sub [sub-name]))))) (h/deftest-sub :wallet/current-viewing-account [sub-name] (testing "returns current account with balance base" - (swap! rf-db/app-db - #(-> % - (assoc-in [:wallet :accounts] accounts) - (assoc-in [:wallet :current-viewing-account-address] "0x1"))) - - (is - (= {:path "m/44'/60'/0'/0/0" - :emoji "😃" - :key-uid "0x2f5ea39" - :address "0x1" - :wallet false - :name "Account One" - :type :generated - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :blue - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :position 0 - :clock 1698945829328 - :created-at 1698928839000 - :operable "fully" - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :balance 3250 - :tokens tokens-0x1} - (rf/sub [sub-name]))))) - + (let [viewing-address "0x1"] + (swap! rf-db/app-db + #(-> % + (assoc-in [:wallet :accounts] accounts) + (assoc-in [:wallet :current-viewing-account-address] viewing-address))) + (is (match? (get accounts viewing-address) + (rf/sub [sub-name])))))) (h/deftest-sub :wallet/addresses [sub-name] @@ -176,57 +151,43 @@ #(-> % (assoc-in [:wallet :accounts] accounts) (assoc-in [:wallet :current-viewing-account-address] "0x1"))) - - (is - (= (set ["0x1" "0x2"]) - (rf/sub [sub-name]))))) + (is (match? (set ["0x1" "0x2" "0x3"]) + (rf/sub [sub-name]))))) (h/deftest-sub :wallet/watch-address-activity-state [sub-name] (testing "watch address activity state with nil value" - (is (= nil (rf/sub [sub-name])))) + (is (match? nil (rf/sub [sub-name])))) (testing "watch address activity state with no-activity value" (swap! rf-db/app-db #(assoc-in % [:wallet :ui :watch-address-activity-state] :no-activity)) - (is (= :no-activity (rf/sub [sub-name])))) + (is (match? :no-activity (rf/sub [sub-name])))) (testing "watch address activity state with has-activity value" (swap! rf-db/app-db #(assoc-in % [:wallet :ui :watch-address-activity-state] :has-activity)) - (is (= :has-activity (rf/sub [sub-name]))))) + (is (match? :has-activity (rf/sub [sub-name]))))) (h/deftest-sub :wallet/current-viewing-account-address [sub-name] (testing "returns the address of the current viewing account" - (swap! rf-db/app-db #(assoc-in % [:wallet :current-viewing-account-address] "0x1")) - (is (= "0x1" (rf/sub [sub-name]))))) + (let [viewing-address "0x1"] + (swap! rf-db/app-db #(assoc-in % [:wallet :current-viewing-account-address] viewing-address)) + (is (match? viewing-address (rf/sub [sub-name])))))) (h/deftest-sub :wallet/accounts-without-current-viewing-account [sub-name] (testing "returns the accounts list without the current viewing account in it" - (swap! rf-db/app-db - #(-> % - (assoc-in [:wallet :accounts] accounts) - (assoc-in [:wallet :current-viewing-account-address] "0x2"))) - (is - (= (list - {:path "m/44'/60'/0'/0/0" - :emoji "😃" - :key-uid "0x2f5ea39" - :address "0x1" - :wallet false - :name "Account One" - :type :generated - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :blue - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :position 0 - :clock 1698945829328 - :created-at 1698928839000 - :operable "fully" - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :tokens tokens-0x1}) - (rf/sub [sub-name]))))) + (let [viewing-address "0x2"] + (swap! rf-db/app-db + #(-> % + (assoc-in [:wallet :accounts] accounts) + (assoc-in [:wallet :current-viewing-account-address] "0x2"))) + (is (match? (remove #(= (:address %) viewing-address) (vals accounts)) + (rf/sub [sub-name])))))) + +(h/deftest-sub :wallet/accounts-without-watched-accounts + [sub-name] + (testing "returns the accounts list without the watched accounts in it" + (swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) + (is (match? (remove #(:watch-only? %) (vals accounts)) + (rf/sub [sub-name])))))