[#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
This commit is contained in:
Ulises Manuel 2024-02-01 17:51:12 -06:00 committed by GitHub
parent 3f9cd3a688
commit b6ddd8b5ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 166 additions and 150 deletions

View File

@ -12,7 +12,7 @@
[:cat [:cat
[:map {:closed true} [:map {:closed true}
[:size {:optional true :default 32} [:or keyword? pos-int?]] [: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?] [:style {:optional true} map?]
;; Ignores `token` and uses this as parameter to `rn/image`'s source. ;; Ignores `token` and uses this as parameter to `rn/image`'s source.
[:image-source {:optional true} [:maybe [:or :schema.common/image-source :string]]]]] [:image-source {:optional true} [:maybe [:or :schema.common/image-source :string]]]]]

View File

@ -111,15 +111,14 @@
:value (if controlled-input? value @value-atom)}]]))) :value (if controlled-input? value @value-atom)}]])))
(defn- view-internal (defn- view-internal
[{:keys [on-swap]}] []
(let [width (:width (rn/get-window)) (let [width (:width (rn/get-window))
value-atom (reagent/atom nil) value-atom (reagent/atom nil)
crypto? (reagent/atom true) crypto? (reagent/atom true)]
handle-on-swap (fn [] (fn [{:keys [theme container-style value on-swap] :as props}]
(let [handle-on-swap (fn []
(swap! crypto? not) (swap! crypto? not)
(when on-swap (when on-swap (on-swap @crypto?)))]
(on-swap @crypto?)))]
(fn [{:keys [theme container-style value] :as props}]
[rn/view {:style (merge (style/main-container width) container-style)} [rn/view {:style (merge (style/main-container width) container-style)}
[rn/view {:style style/amount-container} [rn/view {:style style/amount-container}
[input-section [input-section
@ -138,6 +137,6 @@
[data-info [data-info
(assoc props (assoc props
:crypto? @crypto? :crypto? @crypto?
:amount (or value @value-atom))]]))) :amount (or value @value-atom))]]))))
(def view (quo.theme/with-theme view-internal)) (def view (quo.theme/with-theme view-internal))

View File

@ -74,11 +74,13 @@
(defn get-standard-crypto-format (defn get-standard-crypto-format
"For full details: https://github.com/status-im/status-mobile/issues/18225" "For full details: https://github.com/status-im/status-mobile/issues/18225"
[{:keys [market-values-per-currency]} token-units] [{:keys [market-values-per-currency]} token-units]
(let [price (get-in market-values-per-currency [:usd :price]) (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) one-cent-value (if (pos? price) (/ 0.01 price) 0)
decimals-count (calc-max-crypto-decimals one-cent-value)] decimals-count (calc-max-crypto-decimals one-cent-value)]
(if (money/equal-to token-units 0)
"0"
(if (< token-units one-cent-value) (if (< token-units one-cent-value)
(str "<" (remove-trailing-zeroes (.toFixed one-cent-value decimals-count))) (str "<" (remove-trailing-zeroes (.toFixed one-cent-value decimals-count)))
(remove-trailing-zeroes (.toFixed token-units decimals-count)))))) (remove-trailing-zeroes (.toFixed token-units decimals-count))))))

View File

@ -58,7 +58,8 @@
(fn [{:keys [db]}] (fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :recipient :to-address)})) {: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]}]] (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 #":") prefix-seq (string/split prefix #":")
@ -75,7 +76,8 @@
[: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 (rf/reg-event-fx
:wallet/update-receiver-networks
(fn [{:keys [db]} [selected-networks]] (fn [{:keys [db]} [selected-networks]]
{:db (assoc-in db [:wallet :ui :send :selected-networks] selected-networks)})) {:db (assoc-in db [:wallet :ui :send :selected-networks] selected-networks)}))
@ -84,11 +86,10 @@
{:db (-> db {:db (-> db
(update-in [:wallet :ui :send] dissoc :collectible) (update-in [:wallet :ui :send] dissoc :collectible)
(assoc-in [:wallet :ui :send :token] token)) (assoc-in [:wallet :ui :send :token] token))
:fx [[:dispatch-later :fx [[:navigate-to-within-stack [:wallet-send-input-amount stack-id]]]}))
{:ms 1
:dispatch [: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]}]] (fn [{:keys [db]} [{:keys [token]}]]
{:db (assoc-in db [:wallet :ui :send :token] token)})) {:db (assoc-in db [:wallet :ui :send :token] token)}))

View File

@ -54,7 +54,8 @@
(h/setup-subs sub-mocks) (h/setup-subs sub-mocks)
(h/render [input-amount/view (h/render [input-amount/view
{:crypto-decimals 2 {:crypto-decimals 2
:limit-crypto 250}]) :limit-crypto 250
:initial-crypto-currency? false}])
(h/is-truthy (h/get-by-text "0")) (h/is-truthy (h/get-by-text "0"))
(h/is-truthy (h/get-by-text "ETH")) (h/is-truthy (h/get-by-text "ETH"))
(h/is-truthy (h/get-by-text "$0.00")) (h/is-truthy (h/get-by-text "$0.00"))
@ -67,7 +68,8 @@
(h/render [input-amount/view (h/render [input-amount/view
{:on-confirm on-confirm {:on-confirm on-confirm
:crypto-decimals 10 :crypto-decimals 10
:limit-crypto 1000}]) :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-1))
(h/fire-event :press (h/query-by-label-text :keyboard-key-2)) (h/fire-event :press (h/query-by-label-text :keyboard-key-2))
@ -90,7 +92,8 @@
(h/render [input-amount/view (h/render [input-amount/view
{:crypto-decimals 10 {:crypto-decimals 10
:limit-crypto 1000 :limit-crypto 1000
:on-confirm on-confirm}]) :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-1))
(h/fire-event :press (h/query-by-label-text :keyboard-key-2)) (h/fire-event :press (h/query-by-label-text :keyboard-key-2))

View File

@ -30,11 +30,11 @@
(defn valid-input? (defn valid-input?
[current v] [current v]
(let [max-length 12 (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-dot? (and (= v dot) (string/includes? current dot))
extra-leading-zero? (and (= current "0") (= "0" (str v))) extra-leading-zero? (and (= current "0") (= "0" (str v)))
non-numeric? (re-find not-digits-or-dot-pattern (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 (defn- normalize-input
[current v] [current v]
@ -60,123 +60,135 @@
(> new-value prev-value))) (> new-value prev-value)))
(defn- f-view-internal (defn- f-view-internal
;; crypto-decimals and limit-crypto args are needed for component tests only ;; crypto-decimals, limit-crypto and initial-crypto-currency? args are needed
[{:keys [crypto-decimals limit-crypto]}] ;; for component tests only
(let [bottom (safe-area/get-bottom) [{default-on-confirm :on-confirm
{:keys [currency]} (rf/sub [:profile/profile]) default-limit-crypto :limit-crypto
token (rf/sub [:wallet/wallet-send-token]) default-crypto-decimals :crypto-decimals
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) initial-crypto-currency? :initial-crypto-currency?
token-symbol (:symbol token) :or {initial-crypto-currency? true}}]
limit-crypto (or limit-crypto (let [_ (rn/dismiss-keyboard!)
(utils/get-standard-crypto-format token (:total-balance token))) bottom (safe-area/get-bottom)
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-value (reagent/atom "")
input-error (reagent/atom false) input-error (reagent/atom false)
current-limit (reagent/atom {:amount limit-crypto crypto-currency? (reagent/atom initial-crypto-currency?)
:currency token-symbol}) handle-swap (fn [{:keys [crypto? limit-fiat limit-crypto]}]
handle-swap (fn [crypto?] (let [num-value (parse-double @input-value)
(let [num-value (parse-double @input-value)] current-limit (if crypto? limit-crypto limit-fiat)]
(reset! current-limit (if crypto? (reset! crypto-currency? crypto?)
{:amount limit-crypto (reset-input-error num-value current-limit input-error)))
:currency token-symbol} handle-keyboard-press (fn [v loading-routes? current-limit-amount]
{: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 (let [current-value @input-value
new-value (make-new-input current-value v) new-value (make-new-input current-value v)
num-value (or (parse-double new-value) 0) num-value (or (parse-double new-value) 0)]
current-limit-amount (:amount @current-limit)] (when (not loading-routes?)
(when (not loading-suggested-routes?)
(reset! input-value new-value) (reset! input-value new-value)
(reset-input-error num-value current-limit-amount input-error) (reset-input-error num-value current-limit-amount input-error)
(reagent/flush)))) (reagent/flush))))
handle-delete (fn [_] handle-delete (fn [loading-routes? current-limit-amount]
(when-not loading-suggested-routes? (when-not loading-routes?
(let [current-limit-amount (:amount @current-limit)]
(swap! input-value #(subs % 0 (dec (count %)))) (swap! input-value #(subs % 0 (dec (count %))))
(reset-input-error @input-value current-limit-amount input-error) (reset-input-error @input-value current-limit-amount input-error)
(reagent/flush)))) (reagent/flush)))
handle-on-change (fn [v] handle-on-change (fn [v current-limit-amount]
(when (valid-input? @input-value v) (when (valid-input? @input-value v)
(let [num-value (or (parse-double v) 0) (let [num-value (or (parse-double v) 0)]
current-limit-amount (:amount @current-limit)]
(reset! input-value v) (reset! input-value v)
(reset-input-error num-value current-limit-amount input-error) (reset-input-error num-value current-limit-amount input-error)
(reagent/flush))))] (reagent/flush))))
(fn [{:keys [on-confirm] on-navigate-back (fn []
:or {on-confirm #(rf/dispatch [:wallet/send-select-amount (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 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 {:amount @input-value
:stack-id :wallet-send-input-amount}])}}] :stack-id :wallet-send-input-amount}]))]
(let [limit-label (make-limit-label @current-limit) (fn []
input-num-value (parse-double @input-value) (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]) suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
route (rf/sub [:wallet/wallet-send-route]) route (rf/sub [:wallet/wallet-send-route])
confirm-disabled? (or on-confirm (or default-on-confirm handle-on-confirm)
(nil? route) 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) (empty? @input-value)
(<= input-num-value 0) (<= input-num-value 0)
(> input-num-value (:amount @current-limit))) (> input-num-value current-limit))
amount (str @input-value " " token-symbol) amount-text (str @input-value " " token-symbol)]
{: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 (rn/use-effect
(fn [] (fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!)) (let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)] app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener)))) #(.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 [rn/view
{:style style/screen {:style style/screen
:accessibility-label (str "container" (when @input-error "-error"))} :accessibility-label (str "container" (when @input-error "-error"))}
[account-switcher/view [account-switcher/view
{:icon-name :i/arrow-left {: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}] :switcher-type :select-account}]
[quo/token-input [quo/token-input
{:container-style style/input-container {:container-style style/input-container
:token token-symbol :token token-symbol
:currency currency :currency current-currency
:crypto-decimals crypto-decimals :crypto-decimals crypto-decimals
:error? @input-error :error? @input-error
:networks (:networks token) :networks token-networks
:title (i18n/label :t/send-limit {:limit limit-label}) :title (i18n/label :t/send-limit {:limit limit-label})
:conversion conversion-rate :conversion conversion-rate
:show-keyboard? false :show-keyboard? false
:value @input-value :value @input-value
:on-swap handle-swap :on-change-text #(handle-on-change % current-limit)
:on-change-text (fn [text] :on-swap #(handle-swap
(handle-on-change text))}] {:crypto? %
:currency current-currency
:token-symbol token-symbol
:limit-fiat fiat-limit
:limit-crypto crypto-limit})}]
[routes/view [routes/view
{:amount amount {:amount amount-text
:routes suggested-routes :routes suggested-routes
:token token :token token
:input-value @input-value :input-value @input-value
:fetch-routes fetch-routes}] :fetch-routes #(fetch-routes % current-limit)}]
[quo/bottom-actions [quo/bottom-actions
{:actions :1-action {:actions :1-action
:customization-color color
:button-one-label (i18n/label :t/confirm) :button-one-label (i18n/label :t/confirm)
:button-one-props {:disabled? confirm-disabled? :button-one-props {:disabled? confirm-disabled?
:on-press on-confirm} :on-press on-confirm}}]
:customization-color color}]
[quo/numbered-keyboard [quo/numbered-keyboard
{:container-style (style/keyboard-container bottom) {:container-style (style/keyboard-container bottom)
:left-action :dot :left-action :dot
:delete-key? true :delete-key? true
:on-press handle-keyboard-press :on-press #(handle-keyboard-press % loading-routes? current-limit)
:on-delete handle-delete}]])))) :on-delete #(handle-delete loading-routes? current-limit)}]]))))
(defn- view-internal (defn- view-internal
[props] [props]

View File

@ -17,8 +17,7 @@
{:id :tab/collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}]) {:id :tab/collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}])
(defn- asset-component (defn- asset-component
[] [token _ _ {:keys [currency currency-symbol]}]
(fn [token _ _ {:keys [currency currency-symbol]}]
(let [on-press #(rf/dispatch [:wallet/send-select-token (let [on-press #(rf/dispatch [:wallet/send-select-token
{:token token {:token token
:stack-id :wallet-select-asset}]) :stack-id :wallet-select-asset}])
@ -32,7 +31,7 @@
:token-value (str crypto-formatted " " (:symbol token)) :token-value (str crypto-formatted " " (:symbol token))
:fiat-value fiat-formatted :fiat-value fiat-formatted
:networks (:networks token) :networks (:networks token)
:on-press on-press}]))) :on-press on-press}]))
(defn- asset-list (defn- asset-list
[search-text] [search-text]