[Feature] Wallet - Account Switcher for send flow (#18100)

This commit:

- adds another variant ("Select account" bottom sheet) of the account switcher for wallet - send flow
- adds watch-only? key in the account to prevent repetitive account type checks across the codebase

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
This commit is contained in:
Mohamed Javid 2023-12-13 22:49:54 +05:30 committed by GitHub
parent 879a82c35f
commit b668e4dea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 342 additions and 114 deletions

View File

@ -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

View File

@ -15,6 +15,10 @@
[ids]
(string/join constants/chain-id-separator ids))
(defn add-keys-to-account
[account]
(assoc account :watch-only? (= (:type account) :watch)))
(defn rpc->account
[account]
(-> account
@ -25,7 +29,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 +45,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]

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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}}]))

View File

@ -0,0 +1,4 @@
(ns status-im2.contexts.wallet.common.sheets.select-account.style)
(def list-container
{:margin-horizontal 8})

View File

@ -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))

View File

@ -12,12 +12,33 @@
(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}
:network-preferences-names #{:ethereum :arbitrum :optimism}
: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"

View File

@ -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

View File

@ -128,7 +128,9 @@
{:content-container-style style/container
: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

View File

@ -75,7 +75,10 @@
{:content-container-style {:flex 1}
:keyboard-should-persist-taps :handled
:scroll-enabled false}
[account-switcher/view {:on-press on-close}]
[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

View File

@ -79,10 +79,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))))
@ -124,6 +124,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)

View File

@ -40,6 +40,22 @@
3 {:raw-balance (money/bignumber "<nil>") :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 (money/bignumber "5000") :has-error false}
2 {:raw-balance (money/bignumber "2000") :has-error false}
3 {:raw-balance (money/bignumber "<nil>") :has-error false}}
:market-values-per-currency {:usd {:price 200}}}
{:decimals 10
:symbol "DAI"
:name "Dai Stablecoin"
:balances-per-chain {1 {:raw-balance (money/bignumber "10000000000") :has-error false}
2 {:raw-balance (money/bignumber "0") :has-error false}
3 {:raw-balance (money/bignumber "<nil>") :has-error false}}
:market-values-per-currency {:usd {:price 1000}}}])
(def accounts
{"0x1" {:path "m/44'/60'/0'/0/0"
:emoji "😃"
@ -48,6 +64,7 @@
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :blue
@ -68,6 +85,7 @@
:wallet false
:name "Account Two"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
@ -80,7 +98,28 @@
: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}})
(def network-data
{:test [{:test? true
@ -115,10 +154,12 @@
(swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts))
(let [result (rf/sub [sub-name])
balance-0x1 (money/bignumber 3250)
balance-0x2 (money/bignumber 2100)]
balance-0x2 (money/bignumber 2100)
balance-0x3 (money/bignumber 2400)]
(is (money/equal-to balance-0x1 (get result "0x1")))
(is (money/equal-to balance-0x2 (get result "0x2"))))))
(is (money/equal-to balance-0x2 (get result "0x2")))
(is (money/equal-to balance-0x3 (get result "0x3"))))))
(h/deftest-sub :wallet/accounts
[sub-name]
@ -128,55 +169,81 @@
(assoc-in [:wallet :accounts] accounts)
(assoc :wallet/networks network-data)))
(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}
:network-preferences-names #{:ethereum :arbitrum :optimism}
: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}
:network-preferences-names #{:ethereum :arbitrum :optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:tokens tokens-0x2})
(rf/sub [sub-name])))))
(=
(list {:path "m/44'/60'/0'/0/0"
: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 :blue
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:ethereum :arbitrum :optimism}
: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
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:ethereum :arbitrum :optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:tokens tokens-0x2}
{: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}
:network-preferences-names #{}
:position 2
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x"
:removed false
:tokens tokens-0x3})
(rf/sub [sub-name])))))
(h/deftest-sub :wallet/current-viewing-account-address
[sub-name]
(testing "returns current viewing account address"
(swap! rf-db/app-db #(assoc-in % [:wallet :current-viewing-account-address] "0x1"))
(is (= "0x1" (rf/sub [sub-name])))))
(testing "returns the address of the current viewing account"
(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/current-viewing-account
[sub-name]
@ -186,6 +253,7 @@
(assoc-in [:wallet :accounts] accounts)
(assoc-in [:wallet :current-viewing-account-address] "0x1")
(assoc :wallet/networks network-data)))
(let [result (rf/sub [sub-name])]
(is
(= {:path "m/44'/60'/0'/0/0"
@ -195,6 +263,7 @@
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :blue
@ -216,26 +285,22 @@
(h/deftest-sub :wallet/addresses
[sub-name]
(testing "returns all addresses"
(swap! rf-db/app-db
#(-> %
(assoc-in [:wallet :accounts] accounts)
(assoc-in [:wallet :current-viewing-account-address] "0x1")))
(is
(= (set ["0x1" "0x2"])
(rf/sub [sub-name])))))
(swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts))
(is (match? #{"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 (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/accounts-without-current-viewing-account
[sub-name]
@ -254,6 +319,7 @@
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :blue
@ -267,7 +333,84 @@
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:tokens tokens-0x1})
:tokens tokens-0x1}
{: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}
:network-preferences-names #{}
:position 2
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x"
:removed false
:tokens tokens-0x3})
(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)
(assoc :wallet/networks network-data)))
(is
(= (list
{:path "m/44'/60'/0'/0/0"
: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 :blue
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:ethereum :arbitrum :optimism}
: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
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:ethereum :arbitrum :optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:tokens tokens-0x2})
(rf/sub [sub-name])))))
(h/deftest-sub :wallet/network-preference-details
@ -279,21 +422,21 @@
(assoc-in [:wallet :current-viewing-account-address] "0x1")
(assoc :wallet/networks network-data)))
(is
(= [{:short-name "eth"
:network-name :ethereum
:chain-id 1
:related-chain-id nil
:layer 1}
{:short-name "arb1"
:network-name :arbitrum
:chain-id 42161
:related-chain-id nil
:layer 2}
{:short-name "opt"
:network-name :optimism
:chain-id 10
:related-chain-id nil
:layer 2}]
(->> (rf/sub [sub-name])
;; Removed `#js source` property for correct compare
(map #(dissoc % :source)))))))
(match? [{:short-name "eth"
:network-name :ethereum
:chain-id 1
:related-chain-id nil
:layer 1}
{:short-name "arb1"
:network-name :arbitrum
:chain-id 42161
:related-chain-id nil
:layer 2}
{:short-name "opt"
:network-name :optimism
:chain-id 10
:related-chain-id nil
:layer 2}]
(->> (rf/sub [sub-name])
;; Removed `#js source` property for correct compare
(map #(dissoc % :source)))))))