From 40f98f82383de3464e2197983863fe4cabcfc146 Mon Sep 17 00:00:00 2001 From: Volodymyr Kozieiev Date: Wed, 4 Sep 2024 13:05:19 +0100 Subject: [PATCH] token input refactoring (#21136) --- ios/Podfile.lock | 2 +- .../wallet/token_input/component_spec.cljs | 20 +- .../components/wallet/token_input/schema.cljs | 21 +- .../components/wallet/token_input/style.cljs | 10 +- .../components/wallet/token_input/view.cljs | 141 +++----- .../common/controlled_input/utils.cljs | 60 +++- .../preview/quo/wallet/token_input.cljs | 90 +++-- .../contexts/wallet/common/utils.cljs | 16 +- .../send/input_amount/component_spec.cljs | 22 +- .../wallet/send/input_amount/view.cljs | 154 ++++----- .../contexts/wallet/send/routes/view.cljs | 324 +++++++++--------- src/utils/money.cljs | 5 +- 12 files changed, 399 insertions(+), 466 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index da57903fd3..b860322441 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1485,7 +1485,7 @@ SPEC CHECKSUMS: FBLazyVector: 56e0e498dbb513b96c40bac6284729ba4e62672d FBReactNativeSpec: 146c741a3f40361f6bc13a4ba284678cbedb5881 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 + glog: 36c15c01d70ed395c6e4f3619f98a23ee415b07a hermes-engine: 1d1835b2cc54c381909d94d1b3c8e0a2f1a94a0e HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352 Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8 diff --git a/src/quo/components/wallet/token_input/component_spec.cljs b/src/quo/components/wallet/token_input/component_spec.cljs index 1aefca789a..856bd81302 100644 --- a/src/quo/components/wallet/token_input/component_spec.cljs +++ b/src/quo/components/wallet/token_input/component_spec.cljs @@ -6,16 +6,20 @@ (h/describe "Wallet: Token Input" (h/test "Token label renders" (h/render-with-theme-provider [token-input/view - {:token :snt - :currency :eur - :crypto? true - :conversion 1}]) + {:token-symbol :snt + :currency-symbol :snt + :on-swap nil + :error? false + :value "1" + :converted-value "10"}]) (h/is-truthy (h/get-by-text "SNT"))) (h/test "Amount renders" (h/render-with-theme-provider [token-input/view - {:token :snt - :currency :eur - :converted-value "€0.00" - :conversion 1}]) + {:token-symbol :snt + :currency-symbol :snt + :on-swap nil + :error? false + :value "1" + :converted-value "€0.00"}]) (h/is-truthy (h/get-by-text "€0.00")))) diff --git a/src/quo/components/wallet/token_input/schema.cljs b/src/quo/components/wallet/token_input/schema.cljs index 9c8b0ddfd3..2f744eb133 100644 --- a/src/quo/components/wallet/token_input/schema.cljs +++ b/src/quo/components/wallet/token_input/schema.cljs @@ -4,15 +4,14 @@ [:=> [:catn [:props - [:map - [:token {:optional true} [:maybe [:or :string :keyword]]] - [:currency {:optional true} [:maybe [:or :string :keyword]]] - [:error? {:optional true} [:maybe :boolean]] - [:title {:optional true} [:maybe :string]] - [:conversion {:optional true} [:maybe :double]] - [:show-keyboard? {:optional true} [:maybe :boolean]] - [:networks {:optional true} - [:maybe [:sequential [:map [:source [:maybe :schema.common/image-source]]]]]] - [:customization-color {:optional true} [:maybe :schema.common/customization-color]] - [:value {:optional true} [:maybe :string]]]]] + [:map {:closed true} + [:token-symbol [:maybe [:or :string :keyword]]] + [:currency-symbol [:maybe [:or :string :keyword]]] + [:hint-component {:optional true} [:maybe :schema.common/hiccup]] + [:on-token-press {:optional true} [:maybe fn?]] + [:on-swap [:maybe fn?]] + [:container-style {:optional true} [:maybe :map]] + [:error? [:maybe :boolean]] + [:value [:maybe :string]] + [:converted-value [:maybe :string]]]]] :any]) diff --git a/src/quo/components/wallet/token_input/style.cljs b/src/quo/components/wallet/token_input/style.cljs index f0860cf4dc..ca79e504df 100644 --- a/src/quo/components/wallet/token_input/style.cljs +++ b/src/quo/components/wallet/token_input/style.cljs @@ -25,9 +25,11 @@ (defn input-container [window-width] - {:width (- window-width 120) - :margin-left 8 - :margin-right 8}) + {:width (- window-width 120) + :margin-left 8 + :margin-right 8 + :flex-direction :row + :align-items :flex-end}) (def text-input-dimensions (-> typography/heading-1 @@ -65,6 +67,6 @@ :justify-content :space-between :align-items :center}) -(defn fiat-amount +(defn converted-amount [theme] {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}) diff --git a/src/quo/components/wallet/token_input/view.cljs b/src/quo/components/wallet/token_input/view.cljs index 1af1b3231b..c1f4e080f0 100644 --- a/src/quo/components/wallet/token_input/view.cljs +++ b/src/quo/components/wallet/token_input/view.cljs @@ -1,11 +1,9 @@ (ns quo.components.wallet.token-input.view (:require [clojure.string :as string] - [oops.core :as oops] [quo.components.buttons.button.view :as button] [quo.components.dividers.divider-line.view :as divider-line] [quo.components.markdown.text :as text] - [quo.components.tags.network-tags.view :as network-tag] [quo.components.utilities.token.view :as token] [quo.components.wallet.token-input.schema :as component-schema] [quo.components.wallet.token-input.style :as style] @@ -13,19 +11,6 @@ [react-native.core :as rn] [schema.core :as schema])) -(defn- data-info - [{:keys [theme networks title converted-value error?]}] - [rn/view {:style style/data-container} - [network-tag/view - {:networks networks - :title title - :status (when error? :error)}] - [text/text - {:size :paragraph-2 - :weight :medium - :style (style/fiat-amount theme)} - converted-value]]) - (defn- token-name-text [theme text] [text/text @@ -35,85 +20,63 @@ (string/upper-case (or (clj->js text) ""))]) (defn input-section - [{:keys [on-change-text value value-internal set-value-internal on-selection-change - on-token-press]}] - (let [input-ref (atom nil) - set-ref #(reset! input-ref %) - focus-input #(when-let [ref ^js @input-ref] - (.focus ref)) - controlled-input? (some? value) - handle-on-change-text (fn [v] - (when-not controlled-input? - (set-value-internal v)) - (when on-change-text - (on-change-text v))) - handle-selection-change (fn [^js e] - (when on-selection-change - (-> e - (oops/oget "nativeEvent.selection") - (js->clj :keywordize-keys true) - (on-selection-change))))] - (fn [{:keys [token customization-color show-keyboard? crypto? currency value error? selection - handle-on-swap allow-selection?] - :or {show-keyboard? true - allow-selection? true}}] - (let [theme (quo.theme/use-theme) - window-width (:width (rn/get-window))] - [rn/pressable - {:style {:width "100%" - :flex-direction :row} - :on-press on-token-press} - [token/view - {:token token - :size :size-32}] - [rn/view {:style (style/input-container window-width)} - [rn/pressable - {:style style/text-input-container - :on-press focus-input} - [rn/text-input - (cond-> {:style (style/text-input theme error?) - :placeholder-text-color (style/placeholder-text theme) - :auto-focus true - :ref set-ref - :placeholder "0" - :keyboard-type :numeric - :on-change-text handle-on-change-text - :selection-color customization-color - :show-soft-input-on-focus show-keyboard? - :on-selection-change handle-selection-change - :selection (clj->js selection) - :editable allow-selection?} - controlled-input? (assoc :value value) - (not controlled-input?) (assoc :default-value value-internal))] - [token-name-text theme (if crypto? token currency)]]] - [button/button - {:icon true - :icon-only? true - :size 32 - :on-press handle-on-swap - :type :outline - :accessibility-label :reorder} - :i/reorder]])))) + [{:keys [token-symbol on-token-press value error? on-swap currency-symbol]}] + (let [theme (quo.theme/use-theme) + window-width (:width (rn/get-window))] + [rn/pressable + {:style {:width "100%" + :flex-direction :row} + :on-press on-token-press} + [token/view + {:token token-symbol + :size :size-32}] + [rn/view {:style (style/input-container window-width)} + [rn/text-input + {:style (style/text-input theme error?) + :placeholder-text-color (style/placeholder-text theme) + :placeholder "0" + :keyboard-type :numeric + :editable false + :value value}] + [token-name-text theme currency-symbol]] + [button/button + {:icon true + :icon-only? true + :size 32 + :on-press #(when on-swap (on-swap)) + :type :outline + :accessibility-label :reorder} + :i/reorder]])) (defn- view-internal - [{:keys [container-style on-swap crypto?] :as props}] - (let [theme (quo.theme/use-theme) - width (:width (rn/get-window)) - [value-internal set-value-internal] (rn/use-state nil) - handle-on-swap (rn/use-callback - (fn [] - (when on-swap (on-swap (not crypto?)))) - [on-swap])] + [{:keys [token-symbol + value + on-token-press + error? + container-style + on-swap + converted-value + hint-component + currency-symbol]}] + (let [theme (quo.theme/use-theme) + width (:width (rn/get-window))] [rn/view {:style (merge (style/main-container width) container-style)} [rn/view {:style style/amount-container} [input-section - (assoc props - :value-internal value-internal - :theme theme - :set-value-internal set-value-internal - :handle-on-swap handle-on-swap - :crypto? crypto?)]] + {:theme theme + :token-symbol token-symbol + :on-token-press on-token-press + :value value + :error? error? + :on-swap on-swap + :currency-symbol currency-symbol}]] [divider-line/view {:container-style (style/divider theme)}] - [data-info (assoc props :theme theme)]])) + [rn/view {:style style/data-container} + hint-component + [text/text + {:size :paragraph-2 + :weight :medium + :style (style/converted-amount theme)} + converted-value]]])) (def view (schema/instrument #'view-internal component-schema/?schema)) diff --git a/src/status_im/common/controlled_input/utils.cljs b/src/status_im/common/controlled_input/utils.cljs index de422c0da4..87ca21c96d 100644 --- a/src/status_im/common/controlled_input/utils.cljs +++ b/src/status_im/common/controlled_input/utils.cljs @@ -1,6 +1,8 @@ (ns status-im.common.controlled-input.utils (:require - [clojure.string :as string])) + [clojure.string :as string] + [status-im.contexts.wallet.common.utils :as wallet-utils] + [utils.money :as money])) (def init-state {:value "" @@ -14,7 +16,7 @@ (defn numeric-value [state] - (or (parse-double (input-value state)) 0)) + (money/bignumber (input-value state))) (defn input-error [state] @@ -26,23 +28,27 @@ (defn upper-limit [state] - (:upper-limit state)) + (money/bignumber (:upper-limit state))) (defn lower-limit [state] - (:lower-limit state)) + (money/bignumber (:lower-limit state))) (defn upper-limit-exceeded? [state] - (and - (upper-limit state) - (> (numeric-value state) (upper-limit state)))) + (let [num-value (numeric-value state)] + (and + (upper-limit state) + (when (money/bignumber? num-value) + (money/greater-than (numeric-value state) (upper-limit state)))))) -(defn lower-limit-exceeded? +(defn- lower-limit-exceeded? [state] - (and - (lower-limit state) - (< (numeric-value state) (lower-limit state)))) + (let [num-value (numeric-value state)] + (and + (lower-limit state) + (when (money/bignumber? num-value) + (money/less-than (numeric-value state) (lower-limit state)))))) (defn- recheck-errorness [state] @@ -75,13 +81,11 @@ (defn increase [state] - (let [new-val (inc (numeric-value state))] - (set-input-value state (str new-val)))) + (set-input-value state (str (money/add (numeric-value state) 1)))) (defn decrease [state] - (let [new-val (dec (numeric-value state))] - (set-input-value state (str new-val)))) + (set-input-value state (str (money/add (numeric-value state) -1)))) (def ^:private not-digits-or-dot-pattern #"[^0-9+\.]") @@ -130,3 +134,29 @@ (defn empty-value? [state] (string/blank? (:value state))) + +(defn- fiat->crypto + [value conversion-rate] + (-> value + (money/fiat->crypto conversion-rate) + (wallet-utils/cut-crypto-decimals-to-fit-usd-cents conversion-rate))) + +(defn- crypto->fiat + [value conversion-rate] + (-> value + (money/crypto->fiat conversion-rate) + (wallet-utils/cut-fiat-balance-to-two-decimals))) + +(defn ->crypto + [state conversion-rate] + (-> state + (set-input-value (fiat->crypto (input-value state) conversion-rate)) + (set-upper-limit (fiat->crypto (upper-limit state) conversion-rate)) + (set-lower-limit (fiat->crypto (lower-limit state) conversion-rate)))) + +(defn ->fiat + [state conversion-rate] + (-> state + (set-input-value (crypto->fiat (input-value state) conversion-rate)) + (set-upper-limit (crypto->fiat (upper-limit state) conversion-rate)) + (set-lower-limit (crypto->fiat (lower-limit state) conversion-rate)))) diff --git a/src/status_im/contexts/preview/quo/wallet/token_input.cljs b/src/status_im/contexts/preview/quo/wallet/token_input.cljs index e8f05fb6af..fa41c81224 100644 --- a/src/status_im/contexts/preview/quo/wallet/token_input.cljs +++ b/src/status_im/contexts/preview/quo/wallet/token_input.cljs @@ -8,8 +8,7 @@ [status-im.common.controlled-input.utils :as controlled-input] [status-im.contexts.preview.quo.preview :as preview] [status-im.contexts.wallet.common.utils :as utils] - [utils.money :as money] - [utils.number :as number])) + [utils.money :as money])) (def networks [{:source (resources/get-network :arbitrum)} @@ -17,9 +16,10 @@ {:source (resources/get-network :ethereum)}]) (def title "Max: 200 SNT") +(def conversion-rate 3450.28) (def descriptor - [{:key :token + [{:key :token-symbol :type :select :options [{:key :eth} {:key :snt}]} @@ -28,50 +28,38 @@ :options [{:key "$"} {:key "€"}]} {:key :error? - :type :boolean} - {:key :allow-selection? :type :boolean}]) + (defn view [] - (let [state (reagent/atom {:token :eth - :currency "$" - :conversion-rate 3450.28 - :networks networks - :title title - :customization-color :blue - :show-keyboard? false - :allow-selection? true - :crypto? true})] + (let [state (reagent/atom {:token-symbol :eth + :currency "$" + :crypto? true + :error? false})] (fn [] - (let [{:keys [currency token conversion-rate - crypto?]} @state - [input-state set-input-state] (rn/use-state controlled-input/init-state) - input-amount (controlled-input/input-value input-state) - swap-between-fiat-and-crypto (fn [] - (set-input-state - (fn [input-state] - (controlled-input/set-input-value - input-state - (let [new-value - (if-not crypto? - (utils/cut-crypto-decimals-to-fit-usd-cents - conversion-rate - (money/fiat->crypto input-amount - conversion-rate)) - (utils/cut-fiat-balance-to-two-decimals - (money/crypto->fiat input-amount - conversion-rate)))] - (number/remove-trailing-zeroes - new-value)))))) - converted-value (if crypto? - (utils/prettify-balance currency - (money/crypto->fiat input-amount - conversion-rate)) - (utils/prettify-crypto-balance - (or (clj->js token) "") - (money/fiat->crypto input-amount conversion-rate) - conversion-rate))] + (let [{:keys [currency token-symbol crypto? error?]} @state + [input-state set-input-state] (rn/use-state controlled-input/init-state) + input-amount (controlled-input/input-value input-state) + swap-between-fiat-and-crypto (fn [] + (if crypto? + (set-input-state #(controlled-input/->fiat + % + conversion-rate)) + (set-input-state + #(controlled-input/->crypto + % + conversion-rate)))) + converted-value (if crypto? + (utils/prettify-balance currency + (money/crypto->fiat + input-amount + conversion-rate)) + (utils/prettify-crypto-balance + (or (clj->js token-symbol) "") + (money/fiat->crypto input-amount + conversion-rate) + conversion-rate))] [preview/preview-container {:state state :descriptor descriptor @@ -79,12 +67,18 @@ :component-container-style {:flex 1 :justify-content :space-between}} [quo/token-input - (merge @state - {:value input-amount - :converted-value converted-value - :on-swap (fn [crypto] - (swap! state assoc :crypto? crypto) - (swap-between-fiat-and-crypto))})] + {:token-symbol token-symbol + :currency-symbol (if crypto? token-symbol currency) + :error? error? + :value input-amount + :converted-value converted-value + :on-swap (fn [] + (swap! state assoc :crypto? (not crypto?)) + (swap-between-fiat-and-crypto)) + :hint-component [quo/network-tags + {:networks networks + :title title + :status (when (:error? @state) :error)}]}] [quo/numbered-keyboard {:container-style {:padding-bottom (safe-area/get-top)} :left-action :dot diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index 44c7e6173d..dde72dd84e 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -88,7 +88,7 @@ (calc-max-crypto-decimals cent-value))}))) (defn cut-crypto-decimals-to-fit-usd-cents - [token-price-in-usd token-units] + [token-units token-price-in-usd] (let [{:keys [zero-value? usd-cent-value standardized-decimals-count]} (analyze-token-amount-for-price token-price-in-usd token-units)] (cond @@ -99,7 +99,7 @@ (defn prettify-crypto-balance [token-symbol crypto-balance conversion-rate] - (str (cut-crypto-decimals-to-fit-usd-cents conversion-rate crypto-balance) + (str (cut-crypto-decimals-to-fit-usd-cents crypto-balance conversion-rate) " " (string/upper-case token-symbol))) @@ -323,18 +323,6 @@ (let [counter (count address)] (str (subs address 0 6) "\u2026" (subs address (- counter 3) counter))))) -(defn make-limit-label-crypto - [amount currency] - (str amount - " " - (some-> currency - name - string/upper-case))) - -(defn make-limit-label-fiat - [amount currency-symbol] - (str currency-symbol amount)) - (defn get-account-name-error [s existing-account-names] (cond 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 eb08546af0..0d085f029f 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 @@ -110,7 +110,7 @@ (h/setup-subs sub-mocks) (h/render-with-theme-provider [input-amount/view {:crypto-decimals 2 - :limit-crypto 250 + :limit-crypto (money/bignumber 250) :initial-crypto-currency? false}]) (h/is-truthy (h/get-by-text "0")) (h/is-truthy (h/get-by-text "USD")) @@ -124,7 +124,7 @@ (let [on-confirm (h/mock-fn)] (h/render-with-theme-provider [input-amount/view {:crypto-decimals 10 - :limit-crypto 1000 + :limit-crypto (money/bignumber 1000) :on-confirm on-confirm :initial-crypto-currency? true}]) @@ -135,18 +135,19 @@ (h/fire-event :press (h/query-by-label-text :keyboard-key-4)) (h/fire-event :press (h/query-by-label-text :keyboard-key-5)) + (-> (h/wait-for #(h/get-by-text "$1234.50")) (.then (fn [] - (h/is-truthy (h/get-by-label-text :button-one)) - (h/is-truthy (h/get-by-label-text :container)) - (h/fire-event :press (h/get-by-label-text :button-one)) - (h/was-called on-confirm)))))) + (let [btn (h/get-by-label-text :button-one)] + (h/is-truthy btn) + (h/fire-event :press btn) + (h/was-called on-confirm))))))) (h/test "Try to fill more than limit" (h/setup-subs sub-mocks) (h/render-with-theme-provider [input-amount/view {:crypto-decimals 1 - :limit-crypto 1}]) + :limit-crypto (money/bignumber 1)}]) (h/fire-event :press (h/query-by-label-text :keyboard-key-2)) (h/fire-event :press (h/query-by-label-text :keyboard-key-9)) @@ -158,13 +159,12 @@ (h/setup-subs sub-mocks) (h/render-with-theme-provider [input-amount/view {:crypto-decimals 1 - :limit-crypto 1 + :limit-crypto (money/bignumber 10) :on-confirm #()}]) + (h/fire-event :press (h/query-by-label-text :keyboard-key-9)) (h/fire-event :press (h/query-by-label-text :keyboard-key-9)) (h/is-truthy (h/get-by-label-text :container-error)) (h/fire-event :press (h/query-by-label-text :reorder)) + (h/is-truthy #(h/get-by-text "Max: $100.00")))) - (-> (h/wait-for #(h/get-by-text "Max: $1000.00")) - (.then (fn [] - (h/wait-for #(h/is-truthy (h/get-by-label-text :container)))))))) 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 ab7b275bc6..46a43a6f39 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -153,9 +153,8 @@ bottom (safe-area/get-bottom) [crypto-currency? set-crypto-currency] (rn/use-state initial-crypto-currency?) on-navigate-back on-navigate-back - [input-state set-input-state] (rn/use-state controlled-input/init-state) + [just-toggled-mode? set-just-toggled-mode?] (rn/use-state false) - clear-input! #(set-input-state controlled-input/delete-all) handle-on-confirm (fn [amount] (rf/dispatch [:wallet/set-token-amount-to-send {:amount amount @@ -166,12 +165,10 @@ :as token} (rf/sub [:wallet/wallet-send-token]) send-from-locked-amounts (rf/sub [:wallet/wallet-send-from-locked-amounts]) - {token-balance :total-balance - available-balance :available-balance - :as token-by-symbol} (rf/sub [:wallet/token-by-symbol + {token-balance :total-balance + :as token-by-symbol} (rf/sub [:wallet/token-by-symbol (str token-symbol) enabled-from-chain-ids]) - currency-symbol (rf/sub [:profile/currency-symbol]) currency (rf/sub [:profile/currency]) conversion-rate (-> token :market-values-per-currency @@ -181,48 +178,29 @@ utils/token-usd-price utils/one-cent-value utils/calc-max-crypto-decimals) + [input-state set-input-state] (rn/use-state + (-> controlled-input/init-state + (controlled-input/set-upper-limit + (utils/cut-crypto-decimals-to-fit-usd-cents + (or default-limit-crypto token-balance) + conversion-rate)))) + clear-input! #(set-input-state controlled-input/delete-all) + currency-symbol (rf/sub [:profile/currency-symbol]) loading-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) route (rf/sub [:wallet/wallet-send-route]) on-confirm (or default-on-confirm handle-on-confirm) crypto-decimals (or token-decimals default-crypto-decimals) - current-crypto-limit (or default-limit-crypto - (utils/get-standard-crypto-format - token - token-balance)) - available-crypto-limit (or default-limit-crypto - (utils/get-standard-crypto-format - token - available-balance)) - current-fiat-limit (.toFixed (* token-balance conversion-rate) 2) - available-fiat-limit (.toFixed (* available-balance conversion-rate) 2) - current-limit (if crypto-currency? - current-crypto-limit - current-fiat-limit) - available-limit (if crypto-currency? - available-crypto-limit - available-fiat-limit) - valid-input? (not (or (string/blank? - (controlled-input/input-value - input-state)) - (<= (controlled-input/numeric-value - input-state) - 0) - (> (controlled-input/numeric-value - input-state) - available-limit))) - input-num-value (controlled-input/numeric-value input-state) - input-amount (controlled-input/input-value input-state) + input-value (controlled-input/input-value input-state) + valid-input? (not (or (controlled-input/empty-value? input-state) + (controlled-input/input-error input-state))) confirm-disabled? (or (nil? route) (empty? route) - (string/blank? (controlled-input/input-value - input-state)) - (<= input-num-value 0) - (> input-num-value current-limit)) + (not valid-input?)) amount-in-crypto (if crypto-currency? - input-amount + input-value (number/remove-trailing-zeroes - (.toFixed (/ input-amount conversion-rate) + (.toFixed (/ input-value conversion-rate) crypto-decimals))) total-amount-receiver (rf/sub [:wallet/total-amount true]) amount-text (str (number/remove-trailing-zeroes @@ -270,8 +248,7 @@ receiver-selected-network)) receiver-networks)) input-error (controlled-input/input-error input-state) - limit-insufficient? (> (controlled-input/numeric-value input-state) - current-limit) + limit-insufficient? (controlled-input/upper-limit-exceeded? input-state) should-try-again? (and (not limit-insufficient?) no-routes-found?) current-address (rf/sub [:wallet/current-viewing-account-address]) owned-eth-token (rf/sub [:wallet/token-by-symbol @@ -284,7 +261,9 @@ (if (= token-symbol (string/upper-case constants/mainnet-short-name)) - (= current-limit input-amount) + (money/equal-to + (controlled-input/numeric-value input-state) + (controlled-input/upper-limit input-state)) (money/equal-to (:total-balance owned-eth-token) 0))) @@ -298,38 +277,20 @@ :valid-input? valid-input? :bounce-duration-ms bounce-duration-ms :token token})) - swap-currency (fn [to-crypto? value] - (if to-crypto? - (utils/cut-crypto-decimals-to-fit-usd-cents - conversion-rate - (money/fiat->crypto value conversion-rate)) - (utils/cut-fiat-balance-to-two-decimals - (money/crypto->fiat value - conversion-rate)))) - swap-between-fiat-and-crypto (fn [swap-to-crypto-currency?] + swap-between-fiat-and-crypto (fn [] (set-just-toggled-mode? true) - (set-crypto-currency swap-to-crypto-currency?) - (set-input-state - (fn [input-state] - (controlled-input/set-input-value - input-state - (let [value (controlled-input/input-value - input-state) - new-value (swap-currency - swap-to-crypto-currency? - value)] - (number/remove-trailing-zeroes - new-value))))))] + (if crypto-currency? + (set-input-state + #(controlled-input/->fiat % conversion-rate)) + (set-input-state + #(controlled-input/->crypto % conversion-rate))) + (set-crypto-currency (not crypto-currency?)))] (rn/use-mount (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)))) (hot-reload/use-safe-unmount on-navigate-back) - (rn/use-effect - (fn [] - (set-input-state #(controlled-input/set-upper-limit % current-limit))) - [current-limit]) (rn/use-effect (fn [] (when input-error (debounce/clear-all)) @@ -353,35 +314,36 @@ :on-press #(rf/dispatch [:navigate-back]) :switcher-type :select-account}] [quo/token-input - {:container-style style/input-container - :token token-symbol - :currency fiat-currency - :currency-symbol currency-symbol - :crypto-decimals (min token-decimals 6) - :error? (controlled-input/input-error input-state) - :networks (seq from-enabled-networks) - :title (i18n/label - :t/send-limit - {:limit (if crypto-currency? - (utils/make-limit-label-crypto current-limit token-symbol) - (utils/make-limit-label-fiat current-limit currency-symbol))}) - :conversion conversion-rate - :show-keyboard? false - :value input-amount - :on-swap swap-between-fiat-and-crypto - :on-token-press show-select-asset-sheet - :allow-selection? false - :crypto? crypto-currency? - :converted-value (if crypto-currency? - (utils/prettify-balance - currency-symbol - (money/crypto->fiat input-amount - conversion-rate)) - (utils/prettify-crypto-balance - (or (clj->js token-symbol) "") - (money/fiat->crypto input-amount + {:container-style style/input-container + :token-symbol token-symbol + :value input-value + :on-swap swap-between-fiat-and-crypto + :on-token-press show-select-asset-sheet + :error? (controlled-input/input-error input-state) + :currency-symbol (if crypto-currency? token-symbol fiat-currency) + :converted-value (if crypto-currency? + (utils/prettify-balance + currency-symbol + (money/crypto->fiat input-value + conversion-rate)) + (utils/prettify-crypto-balance + (or (clj->js token-symbol) "") + (money/fiat->crypto input-value + conversion-rate) + conversion-rate)) + :hint-component [quo/network-tags + {:networks (seq from-enabled-networks) + :title (i18n/label + :t/send-limit + {:limit (if crypto-currency? + (utils/prettify-crypto-balance + (or (clj->js token-symbol) "") + (controlled-input/upper-limit input-state) conversion-rate) - conversion-rate))}] + (utils/prettify-balance currency-symbol + (controlled-input/upper-limit + input-state)))}) + :status (when (controlled-input/input-error input-state) :error)}]}] [routes/view {:token token-by-symbol :send-amount-in-crypto amount-in-crypto @@ -426,7 +388,7 @@ :left-action :dot :delete-key? true :on-press (fn [c] - (let [new-text (str input-amount c) + (let [new-text (str input-value c) max-decimals (if crypto-currency? crypto-decimals 2) regex-pattern (str "^\\d*\\.?\\d{0," max-decimals "}$") regex (re-pattern regex-pattern)] diff --git a/src/status_im/contexts/wallet/send/routes/view.cljs b/src/status_im/contexts/wallet/send/routes/view.cljs index 9d3cd93f6f..8daf9e6edc 100644 --- a/src/status_im/contexts/wallet/send/routes/view.cljs +++ b/src/status_im/contexts/wallet/send/routes/view.cljs @@ -1,19 +1,12 @@ (ns status-im.contexts.wallet.send.routes.view (:require - [clojure.string :as string] [quo.core :as quo] [react-native.core :as rn] - [react-native.safe-area :as safe-area] - [status-im.common.controlled-input.utils :as controlled-input] - [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils.networks :as network-utils] [status-im.contexts.wallet.send.routes.style :as style] [status-im.contexts.wallet.send.utils :as send-utils] [status-im.contexts.wallet.sheets.network-preferences.view :as network-preferences] - [status-im.feature-flags :as ff] [utils.i18n :as i18n] - [utils.money :as money] - [utils.number :as number] [utils.re-frame :as rf])) @@ -87,160 +80,164 @@ (rf/dispatch [:wallet/update-receiver-networks chain-ids]))}]))}])) -(defn- edit-amount - [{:keys [chain-id token-symbol send-amount-in-crypto init-amount]}] - (rf/dispatch - [:show-bottom-sheet - {:content - (fn [] - (let [{:keys [network-name] :as network-details} (rf/sub [:wallet/network-details-by-chain-id - chain-id]) - {fiat-currency :currency} (rf/sub [:profile/profile]) - {token-decimals :decimals - :as - token} (rf/sub [:wallet/wallet-send-token]) - currency (rf/sub [:profile/currency]) - currency-symbol (rf/sub [:profile/currency-symbol]) - send-from-locked-amounts (rf/sub - [:wallet/wallet-send-from-locked-amounts]) - {account-color :color} (rf/sub [:wallet/current-viewing-account]) - locked-amount (get send-from-locked-amounts chain-id) - network-name-str (string/capitalize (name network-name)) - [input-state set-input-state] (rn/use-state (cond-> controlled-input/init-state - init-amount - (controlled-input/set-input-value - (money/to-string init-amount)) - locked-amount - (controlled-input/set-input-value - locked-amount))) - [crypto-currency? set-crypto-currency] (rn/use-state true) - conversion-rate (-> token - :market-values-per-currency - currency - :price) - {token-balance :total-balance} (rf/sub [:wallet/token-by-symbol - (str token-symbol) - [chain-id]]) - current-crypto-limit (utils/get-standard-crypto-format - token - token-balance) - current-fiat-limit (.toFixed (* token-balance conversion-rate) 2) - current-limit (if crypto-currency? - current-crypto-limit - current-fiat-limit) - crypto-decimals token-decimals - input-amount (controlled-input/input-value input-state) - [is-amount-locked? set-is-amount-locked] (rn/use-state (some? locked-amount)) - bottom (safe-area/get-bottom) - amount-in-crypto (if crypto-currency? - input-amount - (number/remove-trailing-zeroes - (.toFixed (/ input-amount - conversion-rate) - crypto-decimals))) - locked-greater-then-send-amount? (let [amount (money/bignumber - amount-in-crypto) - send-amount (money/bignumber - send-amount-in-crypto)] - (and (money/bignumber? amount) - (money/bignumber? send-amount) - (money/greater-than amount send-amount))) - swap-between-fiat-and-crypto (fn [swap-to-crypto-currency?] - (set-crypto-currency swap-to-crypto-currency?) - (set-input-state - (fn [input-state] +#_(defn- edit-amount + [{:keys [chain-id token-symbol send-amount-in-crypto init-amount]}] + (rf/dispatch + [:show-bottom-sheet + {:content + (fn [] + (let [{:keys [network-name] :as network-details} (rf/sub [:wallet/network-details-by-chain-id + chain-id]) + {fiat-currency :currency} (rf/sub [:profile/profile]) + {token-decimals :decimals + :as + token} (rf/sub [:wallet/wallet-send-token]) + currency (rf/sub [:profile/currency]) + currency-symbol (rf/sub [:profile/currency-symbol]) + send-from-locked-amounts (rf/sub + [:wallet/wallet-send-from-locked-amounts]) + {account-color :color} (rf/sub [:wallet/current-viewing-account]) + locked-amount (get send-from-locked-amounts chain-id) + network-name-str (string/capitalize (name network-name)) + [input-state set-input-state] (rn/use-state + (cond-> controlled-input/init-state + init-amount (controlled-input/set-input-value - input-state - (let [value (controlled-input/input-value - input-state) - new-value (if - swap-to-crypto-currency? - (.toFixed - (/ value - conversion-rate) - crypto-decimals) - (.toFixed - (* value - conversion-rate) - 12))] - (number/remove-trailing-zeroes - new-value)))))) - lock-or-unlock-amount (fn [] - (if is-amount-locked? - (rf/dispatch [:wallet/lock-from-amount - chain-id - amount-in-crypto]) - (rf/dispatch [:wallet/unlock-from-amount - chain-id])) - (rf/dispatch [:hide-bottom-sheet]))] - (rn/use-effect - (fn [] - (set-input-state #(controlled-input/set-upper-limit % current-limit))) - [current-limit]) - [:<> - [quo/drawer-top - {:title (i18n/label :t/send-from-network {:network network-name-str}) - :description (i18n/label :t/define-amount-sent-from-network {:network network-name-str})}] - [quo/token-input - {:container-style style/input-container - :token token-symbol - :currency fiat-currency - :currency-symbol currency-symbol - :crypto-decimals (min token-decimals 6) - :error? (controlled-input/input-error input-state) - :networks [network-details] - :title (i18n/label - :t/send-limit - {:limit (if crypto-currency? - (utils/make-limit-label-crypto current-limit token-symbol) - (utils/make-limit-label-fiat current-limit currency-symbol))}) - :conversion conversion-rate - :show-keyboard? false - :value (controlled-input/input-value input-state) - :on-swap swap-between-fiat-and-crypto - :allow-selection? false}] - (when locked-greater-then-send-amount? - [quo/information-box - {:type :error - :icon :i/info - :style style/error-box} - (i18n/label :t/value-higher-than-send-amount)]) - [quo/disclaimer - {:on-change (fn [checked?] - (set-is-amount-locked checked?)) - :checked? is-amount-locked? - :container-style style/disclaimer - :icon (if is-amount-locked? - :i/locked - :i/unlocked) - :customization-color account-color} - (i18n/label :t/dont-auto-recalculate-network {:network network-name-str})] - [quo/bottom-actions - {:actions :one-action - :button-one-label (i18n/label :t/update) - :button-one-props {:on-press lock-or-unlock-amount - :customization-color account-color - :disabled? (or (controlled-input/empty-value? input-state) - (controlled-input/input-error input-state) - locked-greater-then-send-amount?)}}] - [quo/numbered-keyboard - {:container-style (style/keyboard-container bottom) - :left-action :dot - :delete-key? true - :on-press (fn [c] - (let [new-text (str input-amount c) - max-decimals (if crypto-currency? crypto-decimals 2) - regex-pattern (str "^\\d*\\.?\\d{0," max-decimals "}$") - regex (re-pattern regex-pattern)] - (when (re-matches regex new-text) - (set-is-amount-locked true) - (set-input-state #(controlled-input/add-character % c))))) - :on-delete (fn [] - (set-is-amount-locked true) - (set-input-state controlled-input/delete-last)) - :on-long-press-delete (fn [] - (set-is-amount-locked true) - (set-input-state controlled-input/delete-all))}]]))}])) + (money/to-string init-amount)) + locked-amount + (controlled-input/set-input-value + locked-amount))) + [crypto-currency? set-crypto-currency] (rn/use-state true) + conversion-rate (-> token + :market-values-per-currency + currency + :price) + {token-balance :total-balance} (rf/sub [:wallet/token-by-symbol + (str token-symbol) + [chain-id]]) + current-crypto-limit (utils/get-standard-crypto-format + token + token-balance) + current-fiat-limit (.toFixed (* token-balance conversion-rate) 2) + current-limit (if crypto-currency? + current-crypto-limit + current-fiat-limit) + crypto-decimals token-decimals + input-amount (controlled-input/input-value input-state) + [is-amount-locked? set-is-amount-locked] (rn/use-state (some? locked-amount)) + bottom (safe-area/get-bottom) + amount-in-crypto (if crypto-currency? + input-amount + (number/remove-trailing-zeroes + (.toFixed (/ input-amount + conversion-rate) + crypto-decimals))) + locked-greater-then-send-amount? (let [amount (money/bignumber + amount-in-crypto) + send-amount (money/bignumber + send-amount-in-crypto)] + (and (money/bignumber? amount) + (money/bignumber? send-amount) + (money/greater-than amount + send-amount))) + swap-between-fiat-and-crypto (fn [swap-to-crypto-currency?] + (set-crypto-currency + swap-to-crypto-currency?) + (set-input-state + (fn [input-state] + (controlled-input/set-input-value + input-state + (let [value (controlled-input/input-value + input-state) + new-value + (if + swap-to-crypto-currency? + (.toFixed + (/ value + conversion-rate) + crypto-decimals) + (.toFixed + (* value + conversion-rate) + 12))] + (number/remove-trailing-zeroes + new-value)))))) + lock-or-unlock-amount (fn [] + (if is-amount-locked? + (rf/dispatch [:wallet/lock-from-amount + chain-id + amount-in-crypto]) + (rf/dispatch [:wallet/unlock-from-amount + chain-id])) + (rf/dispatch [:hide-bottom-sheet]))] + (rn/use-effect + (fn [] + (set-input-state #(controlled-input/set-upper-limit % current-limit))) + [current-limit]) + [:<> + [quo/drawer-top + {:title (i18n/label :t/send-from-network {:network network-name-str}) + :description (i18n/label :t/define-amount-sent-from-network {:network network-name-str})}] + [quo/token-input + {:container-style style/input-container + :token token-symbol + :currency fiat-currency + :currency-symbol currency-symbol + :crypto-decimals (min token-decimals 6) + :error? (controlled-input/input-error input-state) + :networks [network-details] + :title (i18n/label + :t/send-limit + {:limit (if crypto-currency? + (utils/make-limit-label-crypto current-limit token-symbol) + (utils/make-limit-label-fiat current-limit currency-symbol))}) + :conversion conversion-rate + :show-keyboard? false + :value (controlled-input/input-value input-state) + :on-swap swap-between-fiat-and-crypto + :allow-selection? false}] + (when locked-greater-then-send-amount? + [quo/information-box + {:type :error + :icon :i/info + :style style/error-box} + (i18n/label :t/value-higher-than-send-amount)]) + [quo/disclaimer + {:on-change (fn [checked?] + (set-is-amount-locked checked?)) + :checked? is-amount-locked? + :container-style style/disclaimer + :icon (if is-amount-locked? + :i/locked + :i/unlocked) + :customization-color account-color} + (i18n/label :t/dont-auto-recalculate-network {:network network-name-str})] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/update) + :button-one-props {:on-press lock-or-unlock-amount + :customization-color account-color + :disabled? (or (controlled-input/empty-value? input-state) + (controlled-input/input-error input-state) + locked-greater-then-send-amount?)}}] + [quo/numbered-keyboard + {:container-style (style/keyboard-container bottom) + :left-action :dot + :delete-key? true + :on-press (fn [c] + (let [new-text (str input-amount c) + max-decimals (if crypto-currency? crypto-decimals 2) + regex-pattern (str "^\\d*\\.?\\d{0," max-decimals "}$") + regex (re-pattern regex-pattern)] + (when (re-matches regex new-text) + (set-is-amount-locked true) + (set-input-state #(controlled-input/add-character % c))))) + :on-delete (fn [] + (set-is-amount-locked true) + (set-input-state controlled-input/delete-last)) + :on-long-press-delete (fn [] + (set-is-amount-locked true) + (set-input-state controlled-input/delete-all))}]]))}])) (defn render-network-values [{:keys [network-values token-symbol on-press on-long-press receiver? loading-routes? @@ -388,13 +385,6 @@ chain-id-to-disable disabled-from-chain-ids token-available-networks-for-suggested-routes)) - (when (ff/enabled? ::ff/wallet.custom-network-amounts) - :on-long-press) (fn [chain-id amount-calculated-for-chain] - (edit-amount - {:chain-id chain-id - :token-symbol token-symbol - :send-amount-in-crypto send-amount-in-crypto - :init-amount amount-calculated-for-chain})) :receiver? false :theme theme :loading-routes? loading-routes? diff --git a/src/utils/money.cljs b/src/utils/money.cljs index 95f92141d3..0d77fc2ab7 100644 --- a/src/utils/money.cljs +++ b/src/utils/money.cljs @@ -55,8 +55,9 @@ (.lessThan ^js bn1 bn2)) (defn equal-to - [bn1 bn2] - (.eq ^js bn1 bn2)) + [n1 n2] + (when-let [^js bn1 (bignumber n1)] + (.eq ^js bn1 n2))) (extend-type BigNumber IEquiv