From 1fb6c60f72c3ddc83fa6fa428717a4651de19e03 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Thu, 25 Jan 2024 15:45:45 +0400 Subject: [PATCH] Wallet: network receiver preferences (#18583) * wallet: network receiver preferences --- .../wallet/network_bridge/style.cljs | 2 +- .../wallet/network_bridge/view.cljs | 15 +- .../contexts/wallet/common/utils.cljs | 5 + .../contexts/wallet/send/events.cljs | 15 +- .../send/input_amount/component_spec.cljs | 3 +- .../wallet/send/input_amount/view.cljs | 37 ++--- .../contexts/wallet/send/routes/style.cljs | 26 ++- .../contexts/wallet/send/routes/view.cljs | 156 +++++++++++++++--- src/status_im/subs/wallet/wallet.cljs | 5 + translations/en.json | 10 +- 10 files changed, 212 insertions(+), 62 deletions(-) diff --git a/src/quo/components/wallet/network_bridge/style.cljs b/src/quo/components/wallet/network_bridge/style.cljs index 82b5e2cfa6..477a2c4e9e 100644 --- a/src/quo/components/wallet/network_bridge/style.cljs +++ b/src/quo/components/wallet/network_bridge/style.cljs @@ -4,7 +4,7 @@ (defn container [network state theme] - {:flex 1 + {:width 136 :height 44 :border-width 1 :border-radius 12 diff --git a/src/quo/components/wallet/network_bridge/view.cljs b/src/quo/components/wallet/network_bridge/view.cljs index e89746b5c0..ddd8ca2c2f 100644 --- a/src/quo/components/wallet/network_bridge/view.cljs +++ b/src/quo/components/wallet/network_bridge/view.cljs @@ -10,8 +10,12 @@ [react-native.core :as rn])) (defn network-bridge-add - [{:keys [network state theme]}] - [rn/view {:style (merge (style/container network state theme) (style/add-container theme))} + [{:keys [network state theme container-style on-press]}] + [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}]]) (defn- network->text @@ -21,13 +25,14 @@ :else (string/capitalize (name network)))) (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) [network-bridge-add args] - [rn/view + [rn/pressable {:style (merge (style/container network status theme) container-style) :accessible true - :accessibility-label :container} + :accessibility-label :container + :on-press on-press} (if (= status :loading) [rn/view {:style (style/loading-skeleton theme) diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index f01333a65e..aae0b3ce9c 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -185,6 +185,11 @@ constants/arbitrum-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 [crypto-value currency-symbol fiat-value] (if (string/includes? crypto-value "<") diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index a43de6b0a3..6db6d893ce 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -2,6 +2,7 @@ (:require [camel-snake-kebab.core :as csk] [camel-snake-kebab.extras :as cske] + [clojure.string :as string] [status-im.constants :as constants] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.send.utils :as send-utils] @@ -57,16 +58,23 @@ (rf/reg-event-fx :wallet/select-send-address (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 (assoc-in [:wallet :ui :send :recipient] (or recipient 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 (if token [:wallet-send-input-amount 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 (fn [{:keys [db]} [{:keys [token stack-id]}]] {:db (assoc-in db [:wallet :ui :send :token] token) @@ -92,10 +100,11 @@ (let [wallet-address (get-in db [:wallet :current-viewing-account-address]) token (get-in db [:wallet :ui :send :token]) 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])) token-decimal (:decimals token) token-id (:symbol token) - network-preferences [] + network-preferences selected-networks gas-rates constants/gas-rate-medium amount-in (send-utils/amount-in-hex amount token-decimal) from-address wallet-address diff --git a/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs b/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs index 288f4e398c..f5cdab8dc2 100644 --- a/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs @@ -44,7 +44,8 @@ :market-values-per-currency {:usd {:price 10}}} :wallet/wallet-send-loading-suggested-routes? false :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/setup-restorable-re-frame) diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index e91c509898..34a53af7d2 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -54,13 +54,6 @@ (normalize-input current v) 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 [new-value prev-value input-error] (reset! input-error @@ -129,21 +122,22 @@ (<= input-num-value 0) (> input-num-value (:amount @current-limit))) amount (str @input-value " " token-symbol) - {:keys [color]} (rf/sub [:wallet/current-viewing-account])] + {:keys [color]} (rf/sub [:wallet/current-viewing-account]) + fetch-routes (fn [] + (rf/dispatch [:wallet/clean-suggested-routes]) + (when-not (or + (empty? @input-value) + (<= input-num-value 0) + (> input-num-value (:amount @current-limit))) + (debounce/debounce-and-dispatch [:wallet/get-suggested-routes + @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 (fn [] - (rf/dispatch [:wallet/clean-suggested-routes]) - (when-not (or - (empty? @input-value) - (<= input-num-value 0) - (> input-num-value (:amount @current-limit))) - (debounce/debounce-and-dispatch [:wallet/get-suggested-routes @input-value] - 100))) - [@input-value]) + (rn/use-effect #(fetch-routes) [@input-value]) [rn/view {:style style/screen :accessibility-label (str "container" (when @input-error "-error"))} @@ -166,10 +160,11 @@ :on-change-text (fn [text] (handle-on-change text))}] [routes/view - {:amount amount - :routes suggested-routes - :loading-networks (find-affordable-networks token @input-value) - :networks (:networks token)}] + {:amount amount + :routes suggested-routes + :token token + :input-value @input-value + :fetch-routes fetch-routes}] [quo/bottom-actions {:actions :1-action :button-one-label (i18n/label :t/confirm) diff --git a/src/status_im/contexts/wallet/send/routes/style.cljs b/src/status_im/contexts/wallet/send/routes/style.cljs index ce3d604ced..55958c9845 100644 --- a/src/status_im/contexts/wallet/send/routes/style.cljs +++ b/src/status_im/contexts/wallet/send/routes/style.cljs @@ -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 {:padding-horizontal 20 @@ -30,3 +31,26 @@ {:flex-grow 1 :align-items :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}) diff --git a/src/status_im/contexts/wallet/send/routes/view.cljs b/src/status_im/contexts/wallet/send/routes/view.cljs index ff8c1f7ea9..0772b83775 100644 --- a/src/status_im/contexts/wallet/send/routes/view.cljs +++ b/src/status_im/contexts/wallet/send/routes/view.cljs @@ -1,39 +1,134 @@ (ns status-im.contexts.wallet.send.routes.view (:require + [clojure.string :as string] [quo.core :as quo] + [quo.foundations.colors :as colors] + [quo.foundations.resources :as resources] + [quo.theme :as quo.theme] [react-native.core :as rn] + [reagent.core :as reagent] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.send.routes.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) -(defn route-item - [{:keys [amount from-network to-network status]}] - [rn/view {:style style/routes-inner-container} - [quo/network-bridge - {:amount amount - :network from-network - :status status}] - (if (= status :default) - [quo/network-link - {:shape :linear - :source from-network - :destination to-network - :container-style style/network-link}] - [rn/view {:style {:width 73}}]) - [quo/network-bridge - {:amount amount - :network to-network - :status status - :container-style {:right 12}}]]) +(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 view - [{:keys [amount routes loading-networks]}] - (let [loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) - candidates (:candidates routes)] - (if (or (and (not-empty loading-networks) loading-suggested-routes?) (not-empty candidates)) +(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 + [{: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} + [quo/network-bridge + {:amount amount + :network from-network + :status status}] + (if (= status :default) + [quo/network-link + {:shape :linear + :source from-network + :destination to-network + :container-style style/network-link}] + [rn/view {:style {:width 73}}]) + [quo/network-bridge + {:amount amount + :network to-network + :status status + :container-style {:right 12}}]])) + +(defn- view-internal + [{:keys [amount routes token input-value theme fetch-routes]}] + (let [selected-networks (rf/sub [:wallet/wallet-send-selected-networks]) + loading-networks (find-affordable-networks token input-value selected-networks) + 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 - {: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 :header [rn/view {:style style/routes-header-container} [quo/section-label @@ -45,7 +140,12 @@ :render-fn (fn [item] [route-item {: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? (utils/id->network item) (utils/id->network (get-in item [:from :chain-id]))) @@ -54,5 +154,7 @@ (utils/id->network (get-in item [:to :chain-id])))}])}] [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)])]))) + +(def view (quo.theme/with-theme view-internal)) diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index 66e0d5eb1e..334e6cbe2a 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -48,6 +48,11 @@ :<- [:wallet/wallet-send] :-> :address-prefix) +(rf/reg-sub + :wallet/wallet-send-selected-networks + :<- [:wallet/wallet-send] + :-> :selected-networks) + (rf/reg-sub :wallet/wallet-send-route :<- [:wallet/wallet-send] diff --git a/translations/en.json b/translations/en.json index e59516945f..3541dfd56c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2453,11 +2453,10 @@ "share-details": "Share details", "what-are-you-waiting-for": "What are you waiting for?", "no-relevant-tokens": "No relevant tokens", - "on-the-web": "On the web", "sending-with-ellipsis": "Sending...", + "sending-with-elipsis": "Sending...", "transaction-confirmed": "Transaction confirmed!", "transacation-finalised": "Transaction finalised!", - "no-relevant-tokens": "No relevant tokens", "from-label": "From", "to-label": "To", "oops-wrong-word": "Oops! Wrong word", @@ -2469,5 +2468,10 @@ "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 it’s not default).", "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" }