From b6ddd8b5fff7458ba494ca7b1d478d5d3654431a Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:51:12 -0600 Subject: [PATCH] [#18495] Fix token not ready for next screen (#18532) * Fix exception thrown re-frame don't have the subs' value ready * Make Token malli schema more lenient * Make `get-standard-crypto-format` able to work with `nil` values * Refactor token screen to move subscriptions inside render function * Make token input reactive to on-swap * Remove `crypto-currency?` atom to properly react to state changes * Fix component tests --- src/quo/components/utilities/token/view.cljs | 2 +- .../components/wallet/token_input/view.cljs | 55 +++--- .../contexts/wallet/common/utils.cljs | 12 +- .../contexts/wallet/send/events.cljs | 13 +- .../send/input_amount/component_spec.cljs | 19 +- .../wallet/send/input_amount/view.cljs | 184 ++++++++++-------- .../wallet/send/select_asset/view.cljs | 31 ++- 7 files changed, 166 insertions(+), 150 deletions(-) diff --git a/src/quo/components/utilities/token/view.cljs b/src/quo/components/utilities/token/view.cljs index 88bca5a896..d460488225 100644 --- a/src/quo/components/utilities/token/view.cljs +++ b/src/quo/components/utilities/token/view.cljs @@ -12,7 +12,7 @@ [:cat [:map {:closed true} [:size {:optional true :default 32} [:or keyword? pos-int?]] - [:token {:optional true} [:or keyword? string?]] + [:token {:optional true} [:maybe [:or keyword? string?]]] [:style {:optional true} map?] ;; Ignores `token` and uses this as parameter to `rn/image`'s source. [:image-source {:optional true} [:maybe [:or :schema.common/image-source :string]]]]] diff --git a/src/quo/components/wallet/token_input/view.cljs b/src/quo/components/wallet/token_input/view.cljs index 7ee37a9168..73f37e29a9 100644 --- a/src/quo/components/wallet/token_input/view.cljs +++ b/src/quo/components/wallet/token_input/view.cljs @@ -111,33 +111,32 @@ :value (if controlled-input? value @value-atom)}]]))) (defn- view-internal - [{:keys [on-swap]}] - (let [width (:width (rn/get-window)) - value-atom (reagent/atom nil) - crypto? (reagent/atom true) - handle-on-swap (fn [] - (swap! crypto? not) - (when on-swap - (on-swap @crypto?)))] - (fn [{:keys [theme container-style value] :as props}] - [rn/view {:style (merge (style/main-container width) container-style)} - [rn/view {:style style/amount-container} - [input-section - (assoc props - :value-atom value-atom - :crypto? @crypto?)] - [button/button - {:icon true - :icon-only? true - :size 32 - :on-press handle-on-swap - :type :outline - :accessibility-label :reorder} - :i/reorder]] - [divider-line/view {:container-style (style/divider theme)}] - [data-info - (assoc props - :crypto? @crypto? - :amount (or value @value-atom))]]))) + [] + (let [width (:width (rn/get-window)) + value-atom (reagent/atom nil) + crypto? (reagent/atom true)] + (fn [{:keys [theme container-style value on-swap] :as props}] + (let [handle-on-swap (fn [] + (swap! crypto? not) + (when on-swap (on-swap @crypto?)))] + [rn/view {:style (merge (style/main-container width) container-style)} + [rn/view {:style style/amount-container} + [input-section + (assoc props + :value-atom value-atom + :crypto? @crypto?)] + [button/button + {:icon true + :icon-only? true + :size 32 + :on-press handle-on-swap + :type :outline + :accessibility-label :reorder} + :i/reorder]] + [divider-line/view {:container-style (style/divider theme)}] + [data-info + (assoc props + :crypto? @crypto? + :amount (or value @value-atom))]])))) (def view (quo.theme/with-theme view-internal)) diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index c183c98af9..1141af9e9a 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -74,11 +74,13 @@ (defn get-standard-crypto-format "For full details: https://github.com/status-im/status-mobile/issues/18225" [{:keys [market-values-per-currency]} token-units] - (let [price (get-in market-values-per-currency [:usd :price]) - one-cent-value (if (pos? price) (/ 0.01 price) 0) - decimals-count (calc-max-crypto-decimals one-cent-value)] - (if (money/equal-to token-units 0) - "0" + (if (or (nil? token-units) + (nil? market-values-per-currency) + (money/equal-to token-units 0)) + "0" + (let [price (-> market-values-per-currency :usd :price) + one-cent-value (if (pos? price) (/ 0.01 price) 0) + decimals-count (calc-max-crypto-decimals one-cent-value)] (if (< token-units one-cent-value) (str "<" (remove-trailing-zeroes (.toFixed one-cent-value decimals-count))) (remove-trailing-zeroes (.toFixed token-units decimals-count)))))) diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index c2ca0c325b..24be3b889e 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -58,7 +58,8 @@ (fn [{:keys [db]}] {:db (update-in db [:wallet :ui :send] dissoc :recipient :to-address)})) -(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]}]] (let [[prefix to-address] (utils/split-prefix-and-address address) prefix-seq (string/split prefix #":") @@ -75,7 +76,8 @@ [:wallet-send-input-amount stack-id] [:wallet-select-asset stack-id])]]}))) -(rf/reg-event-fx :wallet/update-receiver-networks +(rf/reg-event-fx + :wallet/update-receiver-networks (fn [{:keys [db]} [selected-networks]] {:db (assoc-in db [:wallet :ui :send :selected-networks] selected-networks)})) @@ -84,11 +86,10 @@ {:db (-> db (update-in [:wallet :ui :send] dissoc :collectible) (assoc-in [:wallet :ui :send :token] token)) - :fx [[:dispatch-later - {:ms 1 - :dispatch [:navigate-to-within-stack [:wallet-send-input-amount stack-id]]}]]})) + :fx [[:navigate-to-within-stack [:wallet-send-input-amount stack-id]]]})) -(rf/reg-event-fx :wallet/send-select-token-drawer +(rf/reg-event-fx + :wallet/send-select-token-drawer (fn [{:keys [db]} [{:keys [token]}]] {:db (assoc-in db [:wallet :ui :send :token] token)})) 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 f5cdab8dc2..150adef5a9 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 @@ -53,8 +53,9 @@ (h/test "Default render" (h/setup-subs sub-mocks) (h/render [input-amount/view - {:crypto-decimals 2 - :limit-crypto 250}]) + {:crypto-decimals 2 + :limit-crypto 250 + :initial-crypto-currency? false}]) (h/is-truthy (h/get-by-text "0")) (h/is-truthy (h/get-by-text "ETH")) (h/is-truthy (h/get-by-text "$0.00")) @@ -65,9 +66,10 @@ (h/setup-subs sub-mocks) (let [on-confirm (h/mock-fn)] (h/render [input-amount/view - {:on-confirm on-confirm - :crypto-decimals 10 - :limit-crypto 1000}]) + {:on-confirm on-confirm + :crypto-decimals 10 + :limit-crypto 1000 + :initial-crypto-currency? false}]) (h/fire-event :press (h/query-by-label-text :keyboard-key-1)) (h/fire-event :press (h/query-by-label-text :keyboard-key-2)) @@ -88,9 +90,10 @@ (let [on-confirm (h/mock-fn)] (h/render [input-amount/view - {:crypto-decimals 10 - :limit-crypto 1000 - :on-confirm on-confirm}]) + {:crypto-decimals 10 + :limit-crypto 1000 + :on-confirm on-confirm + :initial-crypto-currency? false}]) (h/fire-event :press (h/query-by-label-text :keyboard-key-1)) (h/fire-event :press (h/query-by-label-text :keyboard-key-2)) 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 34a53af7d2..78d0eea823 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -30,11 +30,11 @@ (defn valid-input? [current v] (let [max-length 12 - length-owerflow? (>= (count current) max-length) + length-overflow? (>= (count current) max-length) extra-dot? (and (= v dot) (string/includes? current dot)) extra-leading-zero? (and (= current "0") (= "0" (str v))) non-numeric? (re-find not-digits-or-dot-pattern (str v))] - (not (or non-numeric? extra-dot? extra-leading-zero? length-owerflow?)))) + (not (or non-numeric? extra-dot? extra-leading-zero? length-overflow?)))) (defn- normalize-input [current v] @@ -60,123 +60,135 @@ (> new-value prev-value))) (defn- f-view-internal - ;; crypto-decimals and limit-crypto args are needed for component tests only - [{:keys [crypto-decimals limit-crypto]}] - (let [bottom (safe-area/get-bottom) - {:keys [currency]} (rf/sub [:profile/profile]) - token (rf/sub [:wallet/wallet-send-token]) - loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) - token-symbol (:symbol token) - limit-crypto (or limit-crypto - (utils/get-standard-crypto-format token (:total-balance token))) - conversion-rate (get-in token [:market-values-per-currency :usd :price]) - limit-fiat (.toFixed (* (:total-balance token) conversion-rate) 2) - crypto-decimals (or crypto-decimals (utils/get-crypto-decimals-count token)) - input-value (reagent/atom "") - input-error (reagent/atom false) - current-limit (reagent/atom {:amount limit-crypto - :currency token-symbol}) - handle-swap (fn [crypto?] - (let [num-value (parse-double @input-value)] - (reset! current-limit (if crypto? - {:amount limit-crypto - :currency token-symbol} - {:amount limit-fiat - :currency currency})) - (reset-input-error num-value - (:amount @current-limit) - input-error))) - handle-keyboard-press (fn [v] - (let [current-value @input-value - new-value (make-new-input current-value v) - num-value (or (parse-double new-value) 0) - current-limit-amount (:amount @current-limit)] - (when (not loading-suggested-routes?) - (reset! input-value new-value) - (reset-input-error num-value current-limit-amount input-error) - (reagent/flush)))) - handle-delete (fn [_] - (when-not loading-suggested-routes? - (let [current-limit-amount (:amount @current-limit)] - (swap! input-value #(subs % 0 (dec (count %)))) - (reset-input-error @input-value current-limit-amount input-error) - (reagent/flush)))) - handle-on-change (fn [v] - (when (valid-input? @input-value v) - (let [num-value (or (parse-double v) 0) - current-limit-amount (:amount @current-limit)] - (reset! input-value v) - (reset-input-error num-value current-limit-amount input-error) - (reagent/flush))))] - (fn [{:keys [on-confirm] - :or {on-confirm #(rf/dispatch [:wallet/send-select-amount - {:amount @input-value - :stack-id :wallet-send-input-amount}])}}] - (let [limit-label (make-limit-label @current-limit) - input-num-value (parse-double @input-value) - suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes]) - route (rf/sub [:wallet/wallet-send-route]) - confirm-disabled? (or - (nil? route) - (empty? @input-value) - (<= input-num-value 0) - (> input-num-value (:amount @current-limit))) - amount (str @input-value " " token-symbol) - {:keys [color]} (rf/sub [:wallet/current-viewing-account]) - fetch-routes (fn [] + ;; crypto-decimals, limit-crypto and initial-crypto-currency? args are needed + ;; for component tests only + [{default-on-confirm :on-confirm + default-limit-crypto :limit-crypto + default-crypto-decimals :crypto-decimals + initial-crypto-currency? :initial-crypto-currency? + :or {initial-crypto-currency? true}}] + (let [_ (rn/dismiss-keyboard!) + bottom (safe-area/get-bottom) + input-value (reagent/atom "") + input-error (reagent/atom false) + crypto-currency? (reagent/atom initial-crypto-currency?) + handle-swap (fn [{:keys [crypto? limit-fiat limit-crypto]}] + (let [num-value (parse-double @input-value) + current-limit (if crypto? limit-crypto limit-fiat)] + (reset! crypto-currency? crypto?) + (reset-input-error num-value current-limit input-error))) + handle-keyboard-press (fn [v loading-routes? current-limit-amount] + (let [current-value @input-value + new-value (make-new-input current-value v) + num-value (or (parse-double new-value) 0)] + (when (not loading-routes?) + (reset! input-value new-value) + (reset-input-error num-value current-limit-amount input-error) + (reagent/flush)))) + handle-delete (fn [loading-routes? current-limit-amount] + (when-not loading-routes? + (swap! input-value #(subs % 0 (dec (count %)))) + (reset-input-error @input-value current-limit-amount input-error) + (reagent/flush))) + handle-on-change (fn [v current-limit-amount] + (when (valid-input? @input-value v) + (let [num-value (or (parse-double v) 0)] + (reset! input-value v) + (reset-input-error num-value current-limit-amount input-error) + (reagent/flush)))) + on-navigate-back (fn [] + (rf/dispatch [:wallet/clean-selected-token]) + (rf/dispatch [:navigate-back-within-stack :wallet-send-input-amount])) + fetch-routes (fn [input-num-value current-limit-amount] (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)))] + (when-not (or (empty? @input-value) + (<= input-num-value 0) + (> input-num-value current-limit-amount)) + (debounce/debounce-and-dispatch + [:wallet/get-suggested-routes @input-value] + 100))) + handle-on-confirm (fn [] + (rf/dispatch [:wallet/send-select-amount + {:amount @input-value + :stack-id :wallet-send-input-amount}]))] + (fn [] + (let [{fiat-currency :currency} (rf/sub [:profile/profile]) + {:keys [color]} (rf/sub [:wallet/current-viewing-account]) + {token-balance :total-balance + token-symbol :symbol + token-networks :networks + :as token} (rf/sub [:wallet/wallet-send-token]) + conversion-rate (-> token :market-values-per-currency :usd :price) + loading-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) + suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes]) + route (rf/sub [:wallet/wallet-send-route]) + on-confirm (or default-on-confirm handle-on-confirm) + crypto-decimals (or default-crypto-decimals + (utils/get-crypto-decimals-count token)) + crypto-limit (or default-limit-crypto + (utils/get-standard-crypto-format token token-balance)) + fiat-limit (.toFixed (* token-balance conversion-rate) 2) + current-limit (if @crypto-currency? crypto-limit fiat-limit) + current-currency (if @crypto-currency? token-symbol fiat-currency) + limit-label (make-limit-label {:amount current-limit + :currency current-currency}) + input-num-value (parse-double @input-value) + confirm-disabled? (or (nil? route) + (empty? @input-value) + (<= input-num-value 0) + (> input-num-value current-limit)) + amount-text (str @input-value " " token-symbol)] (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/use-effect + #(fetch-routes input-num-value current-limit) + [@input-value]) [rn/view {:style style/screen :accessibility-label (str "container" (when @input-error "-error"))} [account-switcher/view {:icon-name :i/arrow-left - :on-press #(rf/dispatch [:navigate-back-within-stack :wallet-send-input-amount]) + :on-press on-navigate-back :switcher-type :select-account}] [quo/token-input {:container-style style/input-container :token token-symbol - :currency currency + :currency current-currency :crypto-decimals crypto-decimals :error? @input-error - :networks (:networks token) + :networks token-networks :title (i18n/label :t/send-limit {:limit limit-label}) :conversion conversion-rate :show-keyboard? false :value @input-value - :on-swap handle-swap - :on-change-text (fn [text] - (handle-on-change text))}] + :on-change-text #(handle-on-change % current-limit) + :on-swap #(handle-swap + {:crypto? % + :currency current-currency + :token-symbol token-symbol + :limit-fiat fiat-limit + :limit-crypto crypto-limit})}] [routes/view - {:amount amount + {:amount amount-text :routes suggested-routes :token token :input-value @input-value - :fetch-routes fetch-routes}] + :fetch-routes #(fetch-routes % current-limit)}] [quo/bottom-actions {:actions :1-action + :customization-color color :button-one-label (i18n/label :t/confirm) :button-one-props {:disabled? confirm-disabled? - :on-press on-confirm} - :customization-color color}] + :on-press on-confirm}}] [quo/numbered-keyboard {:container-style (style/keyboard-container bottom) :left-action :dot :delete-key? true - :on-press handle-keyboard-press - :on-delete handle-delete}]])))) + :on-press #(handle-keyboard-press % loading-routes? current-limit) + :on-delete #(handle-delete loading-routes? current-limit)}]])))) (defn- view-internal [props] diff --git a/src/status_im/contexts/wallet/send/select_asset/view.cljs b/src/status_im/contexts/wallet/send/select_asset/view.cljs index 6c129c3dd8..ff1e3ab00b 100644 --- a/src/status_im/contexts/wallet/send/select_asset/view.cljs +++ b/src/status_im/contexts/wallet/send/select_asset/view.cljs @@ -17,22 +17,21 @@ {:id :tab/collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}]) (defn- asset-component - [] - (fn [token _ _ {:keys [currency currency-symbol]}] - (let [on-press #(rf/dispatch [:wallet/send-select-token - {:token token - :stack-id :wallet-select-asset}]) - token-units (utils/total-token-units-in-all-chains token) - crypto-formatted (utils/get-standard-crypto-format token token-units) - fiat-value (utils/total-token-fiat-value currency token) - fiat-formatted (utils/get-standard-fiat-format crypto-formatted currency-symbol fiat-value)] - [quo/token-network - {:token (:symbol token) - :label (:name token) - :token-value (str crypto-formatted " " (:symbol token)) - :fiat-value fiat-formatted - :networks (:networks token) - :on-press on-press}]))) + [token _ _ {:keys [currency currency-symbol]}] + (let [on-press #(rf/dispatch [:wallet/send-select-token + {:token token + :stack-id :wallet-select-asset}]) + token-units (utils/total-token-units-in-all-chains token) + crypto-formatted (utils/get-standard-crypto-format token token-units) + fiat-value (utils/total-token-fiat-value currency token) + fiat-formatted (utils/get-standard-fiat-format crypto-formatted currency-symbol fiat-value)] + [quo/token-network + {:token (:symbol token) + :label (:name token) + :token-value (str crypto-formatted " " (:symbol token)) + :fiat-value fiat-formatted + :networks (:networks token) + :on-press on-press}])) (defn- asset-list [search-text]