[#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
[: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]]]]]

View File

@ -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))

View File

@ -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))))))

View File

@ -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)}))

View File

@ -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))

View File

@ -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]

View File

@ -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]