Wallet: network receiver preferences (#18583)

* wallet: network receiver preferences
This commit is contained in:
Omar Basem 2024-01-25 15:45:45 +04:00 committed by GitHub
parent 8999b2b9e8
commit 1fb6c60f72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 212 additions and 62 deletions

View File

@ -4,7 +4,7 @@
(defn container (defn container
[network state theme] [network state theme]
{:flex 1 {:width 136
:height 44 :height 44
:border-width 1 :border-width 1
:border-radius 12 :border-radius 12

View File

@ -10,8 +10,12 @@
[react-native.core :as rn])) [react-native.core :as rn]))
(defn network-bridge-add (defn network-bridge-add
[{:keys [network state theme]}] [{:keys [network state theme container-style on-press]}]
[rn/view {:style (merge (style/container network state theme) (style/add-container theme))} [rn/pressable
{:style (merge (style/container network state theme)
(style/add-container theme)
container-style)
:on-press on-press}
[icon/icon :i/add-circle {:size 12 :no-color true}]]) [icon/icon :i/add-circle {:size 12 :no-color true}]])
(defn- network->text (defn- network->text
@ -21,13 +25,14 @@
:else (string/capitalize (name network)))) :else (string/capitalize (name network))))
(defn view-internal (defn view-internal
[{:keys [theme network status amount container-style] :as args}] [{:keys [theme network status amount container-style on-press] :as args}]
(if (= status :add) (if (= status :add)
[network-bridge-add args] [network-bridge-add args]
[rn/view [rn/pressable
{:style (merge (style/container network status theme) container-style) {:style (merge (style/container network status theme) container-style)
:accessible true :accessible true
:accessibility-label :container} :accessibility-label :container
:on-press on-press}
(if (= status :loading) (if (= status :loading)
[rn/view [rn/view
{:style (style/loading-skeleton theme) {:style (style/loading-skeleton theme)

View File

@ -185,6 +185,11 @@
constants/arbitrum-chain-id :arbitrum constants/arbitrum-chain-id :arbitrum
constants/arbitrum-test-chain-id :arbitrum}) constants/arbitrum-test-chain-id :arbitrum})
(def short-name->id
{:eth constants/mainnet-chain-id
:opt constants/optimism-chain-id
:arb1 constants/arbitrum-chain-id})
(defn get-standard-fiat-format (defn get-standard-fiat-format
[crypto-value currency-symbol fiat-value] [crypto-value currency-symbol fiat-value]
(if (string/includes? crypto-value "<") (if (string/includes? crypto-value "<")

View File

@ -2,6 +2,7 @@
(:require (:require
[camel-snake-kebab.core :as csk] [camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske] [camel-snake-kebab.extras :as cske]
[clojure.string :as string]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.send.utils :as send-utils] [status-im.contexts.wallet.send.utils :as send-utils]
@ -57,16 +58,23 @@
(rf/reg-event-fx :wallet/select-send-address (rf/reg-event-fx :wallet/select-send-address
(fn [{:keys [db]} [{:keys [address token recipient stack-id]}]] (fn [{:keys [db]} [{:keys [address token recipient stack-id]}]]
(let [[prefix to-address] (utils/split-prefix-and-address address)] (let [[prefix to-address] (utils/split-prefix-and-address address)
prefix-seq (string/split prefix #":")
selected-networks (mapv #(utils/short-name->id (keyword %)) prefix-seq)]
{:db (-> db {:db (-> db
(assoc-in [:wallet :ui :send :recipient] (or recipient address)) (assoc-in [:wallet :ui :send :recipient] (or recipient address))
(assoc-in [:wallet :ui :send :to-address] to-address) (assoc-in [:wallet :ui :send :to-address] to-address)
(assoc-in [:wallet :ui :send :address-prefix] prefix)) (assoc-in [:wallet :ui :send :address-prefix] prefix)
(assoc-in [:wallet :ui :send :selected-networks] selected-networks))
:fx [[:navigate-to-within-stack :fx [[:navigate-to-within-stack
(if token (if token
[:wallet-send-input-amount stack-id] [:wallet-send-input-amount stack-id]
[:wallet-select-asset stack-id])]]}))) [:wallet-select-asset stack-id])]]})))
(rf/reg-event-fx :wallet/update-receiver-networks
(fn [{:keys [db]} [selected-networks]]
{:db (assoc-in db [:wallet :ui :send :selected-networks] selected-networks)}))
(rf/reg-event-fx :wallet/send-select-token (rf/reg-event-fx :wallet/send-select-token
(fn [{:keys [db]} [{:keys [token stack-id]}]] (fn [{:keys [db]} [{:keys [token stack-id]}]]
{:db (assoc-in db [:wallet :ui :send :token] token) {:db (assoc-in db [:wallet :ui :send :token] token)
@ -92,10 +100,11 @@
(let [wallet-address (get-in db [:wallet :current-viewing-account-address]) (let [wallet-address (get-in db [:wallet :current-viewing-account-address])
token (get-in db [:wallet :ui :send :token]) token (get-in db [:wallet :ui :send :token])
account-address (get-in db [:wallet :ui :send :send-account-address]) account-address (get-in db [:wallet :ui :send :send-account-address])
selected-networks (get-in db [:wallet :ui :send :selected-networks])
to-address (or account-address (get-in db [:wallet :ui :send :to-address])) to-address (or account-address (get-in db [:wallet :ui :send :to-address]))
token-decimal (:decimals token) token-decimal (:decimals token)
token-id (:symbol token) token-id (:symbol token)
network-preferences [] network-preferences selected-networks
gas-rates constants/gas-rate-medium gas-rates constants/gas-rate-medium
amount-in (send-utils/amount-in-hex amount token-decimal) amount-in (send-utils/amount-in-hex amount token-decimal)
from-address wallet-address from-address wallet-address

View File

@ -44,7 +44,8 @@
:market-values-per-currency {:usd {:price 10}}} :market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false :wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route {:route []} :wallet/wallet-send-route {:route []}
:wallet/wallet-send-suggested-routes {:candidates []}}) :wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks []})
(h/describe "Send > input amount screen" (h/describe "Send > input amount screen"
(h/setup-restorable-re-frame) (h/setup-restorable-re-frame)

View File

@ -54,13 +54,6 @@
(normalize-input current v) (normalize-input current v)
current)) current))
(defn- find-affordable-networks
[{:keys [balances-per-chain]} input-value]
(->> balances-per-chain
(filter (fn [[_ {:keys [balance]}]]
(>= (js/parseFloat balance) input-value)))
(map first)))
(defn- reset-input-error (defn- reset-input-error
[new-value prev-value input-error] [new-value prev-value input-error]
(reset! input-error (reset! input-error
@ -129,21 +122,22 @@
(<= input-num-value 0) (<= input-num-value 0)
(> input-num-value (:amount @current-limit))) (> input-num-value (:amount @current-limit)))
amount (str @input-value " " token-symbol) amount (str @input-value " " token-symbol)
{:keys [color]} (rf/sub [:wallet/current-viewing-account])] {:keys [color]} (rf/sub [:wallet/current-viewing-account])
(rn/use-effect fetch-routes (fn []
(fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener))))
(rn/use-effect (fn []
(rf/dispatch [:wallet/clean-suggested-routes]) (rf/dispatch [:wallet/clean-suggested-routes])
(when-not (or (when-not (or
(empty? @input-value) (empty? @input-value)
(<= input-num-value 0) (<= input-num-value 0)
(> input-num-value (:amount @current-limit))) (> input-num-value (:amount @current-limit)))
(debounce/debounce-and-dispatch [:wallet/get-suggested-routes @input-value] (debounce/debounce-and-dispatch [:wallet/get-suggested-routes
100))) @input-value]
[@input-value]) 100)))]
(rn/use-effect
(fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener))))
(rn/use-effect #(fetch-routes) [@input-value])
[rn/view [rn/view
{:style style/screen {:style style/screen
:accessibility-label (str "container" (when @input-error "-error"))} :accessibility-label (str "container" (when @input-error "-error"))}
@ -168,8 +162,9 @@
[routes/view [routes/view
{:amount amount {:amount amount
:routes suggested-routes :routes suggested-routes
:loading-networks (find-affordable-networks token @input-value) :token token
:networks (:networks token)}] :input-value @input-value
:fetch-routes fetch-routes}]
[quo/bottom-actions [quo/bottom-actions
{:actions :1-action {:actions :1-action
:button-one-label (i18n/label :t/confirm) :button-one-label (i18n/label :t/confirm)

View File

@ -1,4 +1,5 @@
(ns status-im.contexts.wallet.send.routes.style) (ns status-im.contexts.wallet.send.routes.style
(:require [quo.foundations.colors :as colors]))
(def routes-container (def routes-container
{:padding-horizontal 20 {:padding-horizontal 20
@ -30,3 +31,26 @@
{:flex-grow 1 {:flex-grow 1
:align-items :center :align-items :center
:justify-content :center}) :justify-content :center})
(def add-network
{:margin-top 8
:align-self :flex-end
:left 12})
(defn warning-container
[color theme]
{:flex-direction :row
:border-width 1
:border-color (colors/resolve-color color theme 10)
:background-color (colors/resolve-color color theme 5)
:margin-horizontal 20
:margin-top 4
:margin-bottom 8
:padding-left 12
:padding-vertical 11
:border-radius 12})
(def warning-text
{:margin-left 8
:margin-right 12
:padding-right 12})

View File

@ -1,14 +1,104 @@
(ns status-im.contexts.wallet.send.routes.view (ns status-im.contexts.wallet.send.routes.view
(:require (:require
[clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.foundations.resources :as resources]
[quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.send.routes.style :as style] [status-im.contexts.wallet.send.routes.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- find-affordable-networks
[{:keys [balances-per-chain]} input-value selected-networks]
(->> balances-per-chain
(filter (fn [[_ {:keys [balance chain-id]}]]
(and
(>= (js/parseFloat balance) input-value)
(some #(= % chain-id) selected-networks))))
(map first)))
(defn- make-network-item
[{:keys [network-name chain-id] :as _network}
{:keys [title color on-change network-preferences] :as _options}]
{:title (or title (string/capitalize (name network-name)))
:image :icon-avatar
:image-props {:icon (resources/get-network network-name)
:size :size-20}
:action :selector
:action-props {:type :checkbox
:customization-color color
:checked? (some #(= % chain-id) @network-preferences)
:on-change on-change}})
(defn networks-drawer
[{:keys [fetch-routes theme]}]
(let [network-details (rf/sub [:wallet/network-details])
{:keys [color]} (rf/sub [:wallet/current-viewing-account])
selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
prefix (rf/sub [:wallet/wallet-send-address-prefix])
prefix-seq (string/split prefix #":")
grouped-details (group-by #(contains? (set prefix-seq) (:short-name %)) network-details)
preferred (get grouped-details true [])
not-preferred (get grouped-details false [])
network-preferences (reagent/atom selected-networks)
toggle-network (fn [{:keys [chain-id]}]
(swap! network-preferences
(fn [preferences]
(if (some #(= % chain-id) preferences)
(vec (remove #(= % chain-id) preferences))
(conj preferences chain-id)))))]
(fn []
[rn/view
[quo/drawer-top {:title (i18n/label :t/edit-receiver-networks)}]
[quo/category
{:list-type :settings
:label (i18n/label :t/preferred-by-receiver)
:data (mapv (fn [network]
(make-network-item network
{:color color
:network-preferences network-preferences
:on-change #(toggle-network network)}))
preferred)}]
(when (pos? (count not-preferred))
[quo/category
{:list-type :settings
:label (i18n/label :t/not-preferred-by-receiver)
:data (mapv (fn [network]
(make-network-item network
{:color color
:network-preferences network-preferences
:on-change #(toggle-network network)}))
not-preferred)}])
(when (not= selected-networks @network-preferences)
[rn/view {:style (style/warning-container color theme)}
[quo/icon :i/info {:color (colors/resolve-color color theme)}]
[quo/text
{:size :paragraph-2
:style style/warning-text} (i18n/label :t/receiver-networks-warning)]])
[quo/bottom-actions
{:button-one-label (i18n/label :t/apply-changes)
:button-one-props {:disabled? (= selected-networks @network-preferences)
:on-press (fn []
(rf/dispatch [:wallet/update-receiver-networks
@network-preferences])
(rf/dispatch [:hide-bottom-sheet])
(fetch-routes))
:customization-color color}}]])))
(defn route-item (defn route-item
[{:keys [amount from-network to-network status]}] [{:keys [amount from-network to-network status theme fetch-routes]}]
(if (= status :add)
[quo/network-bridge
{:status :add
:container-style style/add-network
:on-press #(rf/dispatch [:show-bottom-sheet
{:content (fn [] [networks-drawer
{:theme theme
:fetch-routes fetch-routes}])}])}]
[rn/view {:style style/routes-inner-container} [rn/view {:style style/routes-inner-container}
[quo/network-bridge [quo/network-bridge
{:amount amount {:amount amount
@ -25,15 +115,20 @@
{:amount amount {:amount amount
:network to-network :network to-network
:status status :status status
:container-style {:right 12}}]]) :container-style {:right 12}}]]))
(defn view (defn- view-internal
[{:keys [amount routes loading-networks]}] [{:keys [amount routes token input-value theme fetch-routes]}]
(let [loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) (let [selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
candidates (:candidates routes)] loading-networks (find-affordable-networks token input-value selected-networks)
(if (or (and (not-empty loading-networks) loading-suggested-routes?) (not-empty candidates)) loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
best-routes (:best routes)
data (if loading-suggested-routes? loading-networks best-routes)]
(if (or (and (not-empty loading-networks) loading-suggested-routes?) (not-empty best-routes))
[rn/flat-list [rn/flat-list
{:data (if loading-suggested-routes? loading-networks candidates) {:data (if (and (< (count data) 3) (pos? (count data)))
(concat data [{:status :add}])
data)
:content-container-style style/routes-container :content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container} :header [rn/view {:style style/routes-header-container}
[quo/section-label [quo/section-label
@ -45,7 +140,12 @@
:render-fn (fn [item] :render-fn (fn [item]
[route-item [route-item
{:amount amount {:amount amount
:status (if loading-suggested-routes? :loading :default) :theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
loading-suggested-routes? :loading
:else :default)
:from-network (if loading-suggested-routes? :from-network (if loading-suggested-routes?
(utils/id->network item) (utils/id->network item)
(utils/id->network (get-in item [:from :chain-id]))) (utils/id->network (get-in item [:from :chain-id])))
@ -54,5 +154,7 @@
(utils/id->network (get-in item (utils/id->network (get-in item
[:to :chain-id])))}])}] [:to :chain-id])))}])}]
[rn/view {:style style/empty-container} [rn/view {:style style/empty-container}
(when (and (not (nil? candidates)) (not loading-suggested-routes?)) (when (and (not (nil? best-routes)) (not loading-suggested-routes?))
[quo/text (i18n/label :t/no-routes-found)])]))) [quo/text (i18n/label :t/no-routes-found)])])))
(def view (quo.theme/with-theme view-internal))

View File

@ -48,6 +48,11 @@
:<- [:wallet/wallet-send] :<- [:wallet/wallet-send]
:-> :address-prefix) :-> :address-prefix)
(rf/reg-sub
:wallet/wallet-send-selected-networks
:<- [:wallet/wallet-send]
:-> :selected-networks)
(rf/reg-sub (rf/reg-sub
:wallet/wallet-send-route :wallet/wallet-send-route
:<- [:wallet/wallet-send] :<- [:wallet/wallet-send]

View File

@ -2453,11 +2453,10 @@
"share-details": "Share details", "share-details": "Share details",
"what-are-you-waiting-for": "What are you waiting for?", "what-are-you-waiting-for": "What are you waiting for?",
"no-relevant-tokens": "No relevant tokens", "no-relevant-tokens": "No relevant tokens",
"on-the-web": "On the web",
"sending-with-ellipsis": "Sending...", "sending-with-ellipsis": "Sending...",
"sending-with-elipsis": "Sending...",
"transaction-confirmed": "Transaction confirmed!", "transaction-confirmed": "Transaction confirmed!",
"transacation-finalised": "Transaction finalised!", "transacation-finalised": "Transaction finalised!",
"no-relevant-tokens": "No relevant tokens",
"from-label": "From", "from-label": "From",
"to-label": "To", "to-label": "To",
"oops-wrong-word": "Oops! Wrong word", "oops-wrong-word": "Oops! Wrong word",
@ -2469,5 +2468,10 @@
"remove-account-title": "Remove account", "remove-account-title": "Remove account",
"remove-account-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your keypair or recovery phrase and derivation path (if its not default).", "remove-account-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your keypair or recovery phrase and derivation path (if its not default).",
"derivation-path-copied": "Derivation path copied", "derivation-path-copied": "Derivation path copied",
"remove-account-confirmation": "I have taken note of the derivation path" "remove-account-confirmation": "I have taken note of the derivation path",
"edit-receiver-networks": "Edit receiver networks",
"preferred-by-receiver": "Preferred by receiver",
"not-preferred-by-receiver": "Not preferred by receiver",
"apply-changes": "Apply changes",
"receiver-networks-warning": "Changing these settings may result in sending tokens to networks the recipient doesn't use"
} }