Compare commits

...
Author SHA1 Message Date
Ulises M a0a3a97596 WIP: first attempt 2023-11-21 17:30:40 -06:00
Rende11 664048468b Send page > input amount #16926
Numbered keyboard sane defaults

Top bar

Use input

Progress

Update token-input

Update keyboard

Base specs

Backspace icon

Revert "Backspace icon"

This reverts commit e591c5297ae149eb3800df853b956d3154fe206c.

Update tests

Clean up

Fix formatting

Fix styles

Add autofocus

Add icons and update tests

Add comment

Add on-swap

Current limit state

Update tests

Update column key

Improve input interaction

Extract make-new-input
2023-11-21 16:06:25 +03:00
15 changed files with 317 additions and 80 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -36,7 +36,7 @@
:blur? false :blur? false
:type :digit} 1]) :type :digit} 1])
(h/is-truthy (h/query-by-label-text :text-label)) (h/is-truthy (h/query-by-label-text :text-label))
(h/fire-event :press (h/query-by-label-text :keyboard-key)) (h/fire-event :press (h/query-by-label-text :keyboard-key-1))
(h/was-called on-press))) (h/was-called on-press)))
(h/test "Is not pressable when disabled is true" (h/test "Is not pressable when disabled is true"
@@ -47,5 +47,5 @@
:blur? false :blur? false
:type :digit} 1]) :type :digit} 1])
(h/is-truthy (h/query-by-label-text :text-label)) (h/is-truthy (h/query-by-label-text :text-label))
(h/fire-event :press (h/query-by-label-text :keyboard-key)) (h/fire-event :press (h/query-by-label-text :keyboard-key-1))
(h/was-not-called on-press)))) (h/was-not-called on-press))))
@@ -7,6 +7,11 @@
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent])) [reagent.core :as reagent]))
(defn- label->accessibility-label
[label]
(let [label-name (if (keyword? label) (name label) label)]
(keyword (str "keyboard-key-" label-name))))
(defn- view-internal (defn- view-internal
[] []
(let [pressed? (reagent/atom false)] (let [pressed? (reagent/atom false)]
@@ -14,9 +19,11 @@
(let [label-color (style/get-label-color disabled? theme blur?) (let [label-color (style/get-label-color disabled? theme blur?)
background-color (style/toggle-background-color @pressed? blur? theme)] background-color (style/toggle-background-color @pressed? blur? theme)]
[rn/pressable [rn/pressable
{:accessibility-label :keyboard-key {:accessibility-label (label->accessibility-label label)
:disabled (or disabled? (not label)) :disabled (or disabled? (not label))
:on-press (fn [] (on-press label)) :on-press (fn []
(when on-press
(on-press label)))
:on-press-in #(reset! pressed? true) :on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false) :on-press-out #(reset! pressed? false)
:style (style/container background-color)} :style (style/container background-color)}
@@ -17,13 +17,15 @@
(defn- view-internal (defn- view-internal
[] []
(fn [{:keys [disabled? theme blur? left-action delete-key? on-press]}] (fn [{:keys [disabled? theme blur? left-action delete-key? on-press on-delete]
:or {left-action :none}}]
[rn/view [rn/view
{:style style/container} {:style style/container}
(for [row-index (range 1 4)] (for [row-index (range 1 4)]
^{:key row-index} ^{:key row-index}
[rn/view {:style style/row-container} [rn/view {:style style/row-container}
(for [column-index (range 1 4)] (for [column-index (range 1 4)]
^{:key (str row-index column-index)}
[keyboard-item [keyboard-item
{:item (+ (* (dec row-index) 3) column-index) {:item (+ (* (dec row-index) 3) column-index)
:type :digit :type :digit
@@ -58,10 +60,10 @@
:theme theme}] :theme theme}]
(if delete-key? (if delete-key?
[keyboard-item [keyboard-item
{:item :i/delete {:item :i/travel
:type :key :type :key
:disabled? disabled? :disabled? disabled?
:on-press on-press :on-press on-delete
:blur? blur? :blur? blur?
:theme theme}] :theme theme}]
[keyboard-item])]])) [keyboard-item])]]))
@@ -14,7 +14,10 @@
:padding-bottom 4 :padding-bottom 4
:height 36 :height 36
:flex-direction :row :flex-direction :row
:justify-content :space-between}) :justify-content :space-between
;;
:background-color :pink
})
(def token (def token
{:width 32 {:width 32
@@ -22,14 +25,12 @@
(defn text-input (defn text-input
[theme] [theme]
(merge typography/heading-1 (assoc typography/heading-1
{:font-weight "600" :font-weight "600"
:margin-left 8 :color (colors/theme-colors colors/neutral-100 colors/white theme)
:margin-right (if platform/ios? 6 4) :padding 0
:color (colors/theme-colors colors/neutral-100 colors/white theme) ;; TODO: fix the padding
:padding 0 :height "100%"))
:text-align :center
:height "100%"}))
(defn divider (defn divider
[width theme] [width theme]
+94 -60
View File
@@ -1,73 +1,107 @@
(ns quo.components.wallet.token-input.view (ns quo.components.wallet.token-input.view
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[quo.components.buttons.button.view :as button] [quo.components.buttons.button.view :as button]
[quo.components.dividers.divider-line.view :as divider-line] [quo.components.dividers.divider-line.view :as divider-line]
[quo.components.markdown.text :as text] [quo.components.markdown.text :as text]
[quo.components.tags.network-tags.view :as network-tag] [quo.components.tags.network-tags.view :as network-tag]
[quo.components.wallet.token-input.style :as style] [quo.components.wallet.token-input.style :as style]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[quo.foundations.common :as common] [quo.foundations.common :as common]
[quo.foundations.resources :as resources] [oops.core :as oops]
[quo.theme :as quo.theme] [quo.foundations.resources :as resources]
[react-native.core :as rn] [quo.theme :as quo.theme]
[reagent.core :as reagent])) [react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]))
(defn calc-value (defn calc-value
[crypto? currency token value conversion] [crypto? currency token value conversion]
(if crypto? (let [num-value (if (string? value) (parse-double (or value "0")) value)]
(str (get common/currency-label currency) (.toFixed (* value conversion) 2)) (if crypto?
(str (.toFixed (/ value conversion) 2) " " (string/upper-case (clj->js token))))) (str (get common/currency-label currency) (.toFixed (* num-value conversion) 2))
(str (.toFixed (/ num-value conversion) 2) " " (string/upper-case (or (clj->js token) ""))))))
(defn- view-internal (defn- view-internal
[] []
(let [width (:width (rn/get-window)) (let [width (:width (rn/get-window))
value (reagent/atom 0) value (reagent/atom nil)
crypto? (reagent/atom true) crypto? (reagent/atom true)
input-ref (atom nil)] input-ref (atom nil)]
(fn [{:keys [theme token currency conversion networks title customization-color]}] (fn [{:keys [theme token currency conversion networks title customization-color
[rn/view {:style (style/main-container width)} on-swap container-style show-keyboard?]
[rn/view {:style style/amount-container} :or {show-keyboard? true}
[rn/pressable external-value :value}]
{:on-press #(when @input-ref (.focus ^js @input-ref)) (let [number-of-chars (count (or external-value @value))
:style {:flex-direction :row text-unit-position (+ 23
:flex-grow 1 (* (if (<= number-of-chars 1)
:align-items :flex-end}} 0
[rn/image (- number-of-chars 1))
{:style style/token 15.333))]
:source (resources/get-token token)}] [rn/view
[rn/text-input {:style (merge
{:ref #(reset! input-ref %) (style/main-container width)
:placeholder "0" container-style)}
:placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme) [rn/view {:style style/amount-container}
:keyboard-type :numeric [rn/pressable
:max-length 12 {:on-press #(when @input-ref (.focus ^js @input-ref))
:default-value @value :style {:flex 1
:on-change-text #(reset! value %) :background-color :orange
:style (style/text-input theme) }}
:selection-color customization-color}] [rn/image
[text/text {:style style/token
{:size :paragraph-2 :source (resources/get-token token)}]
:weight :semi-bold
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)
:margin-right 8 [rn/view {:style {:background-color :lightblue
:padding-bottom 2}} ;;
(string/upper-case (clj->js (if @crypto? token currency)))]] :position :absolute
[button/button :top 0
{:icon true :bottom 0
:icon-only? true :left 40
:size 32 :right 0
:on-press #(swap! crypto? not) }}
:type :outline [rn/text-input
:accessibility-label :reorder} {:ref #(reset! input-ref %)
:i/reorder]] :auto-focus true
[divider-line/view {:container-style {:margin-vertical 8}}] :placeholder "0"
[rn/view {:style style/data-container} :placeholder-text-color (colors/theme-colors colors/neutral-40
[network-tag/view {:networks networks :title title}] colors/neutral-50
[text/text theme)
{:size :paragraph-2 :default-value (or external-value @value)
:weight :medium :keyboard-type :numeric
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}} :max-length 12
(calc-value @crypto? currency token @value conversion)]]]))) :on-change-text #(reset! value %)
:style (style/text-input theme)
:selection-color customization-color
:show-soft-input-on-focus show-keyboard?}]
;;
[rn/view {:style {:position :absolute
:bottom 1
:left text-unit-position}}
[text/text
{:size :paragraph-2
:weight :semi-bold
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
(string/upper-case (or (clj->js (if @crypto? token currency)) ""))]]]]
[button/button
{:icon true
:icon-only? true
:size 32
:on-press (fn []
(swap! crypto? not)
(when on-swap
(on-swap @crypto?)))
:type :outline
:accessibility-label :reorder}
:i/reorder]]
[divider-line/view {:container-style {:margin-vertical 8}}]
[rn/view {:style style/data-container}
[network-tag/view {:networks networks :title title}]
[text/text
{:size :paragraph-2
:weight :medium
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
(calc-value @crypto? currency token (or external-value @value) conversion)]]]))))
(def view (quo.theme/with-theme view-internal)) (def view (quo.theme/with-theme view-internal))
@@ -22,7 +22,9 @@
[quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-create-account])} [quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-create-account])}
"Create Account"] "Create Account"]
[quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-saved-addresses])} [quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-saved-addresses])}
"Saved Addresses"]]) "Saved Addresses"]
[quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-send-input-amount])}
"Send: input amount"]])
(defn wallet-overview-state (defn wallet-overview-state
[networks] [networks]
@@ -30,7 +30,9 @@
(defn- new-account-card-data (defn- new-account-card-data
[] []
{:customization-color (rf/sub [:profile/customization-color]) {:customization-color (rf/sub [:profile/customization-color])
:on-press #(rf/dispatch [:show-bottom-sheet {:content new-account}]) :on-press #(rf/dispatch [:navigate-to :wallet-send-input-amount
] ;[:show-bottom-sheet {:content new-account}]
)
:type :add-account}) :type :add-account})
(def tabs-data (def tabs-data
@@ -0,0 +1,80 @@
(ns status-im2.contexts.wallet.send.input-amount.component-spec
(:require
[re-frame.core :as re-frame]
[status-im2.contexts.wallet.send.input-amount.view :as input-amount]
[test-helpers.component :as h]))
(defn setup-subs
[subscriptions]
(doseq [keyval subscriptions]
(re-frame/reg-sub
(key keyval)
(fn [_] (val keyval)))))
(def sub-mocks
{:profile/profile {:currency :usd}
:wallet/network-details [{:source 525
:short-name "eth"
:network-name :ethereum
:chain-id 1
:related-chain-id 5}]})
(h/describe "Send > input amount screen"
(h/test "Default render"
(setup-subs sub-mocks)
(h/render [input-amount/view {}])
(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"))
(h/is-disabled (h/get-by-label-text :button-one)))
(h/test "Fill token input and confirm"
(setup-subs sub-mocks)
(let [on-confirm (h/mock-fn)]
(h/render [input-amount/view {:on-confirm on-confirm}])
(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-3))
(h/fire-event :press (h/query-by-label-text :keyboard-key-.))
(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/is-truthy (h/get-by-text "$1234.50")))
(h/is-truthy (h/get-by-label-text :button-one))
(h/fire-event :press (h/get-by-label-text :button-one))
(h/was-called on-confirm)))
(h/test "Try to fill more than limit"
(setup-subs sub-mocks)
(h/render [input-amount/view {}])
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-9))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "$290.00")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-backspace))
(h/fire-event :press (h/query-by-label-text :keyboard-key-8))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "$2850.00"))))
(h/test "Switch from crypto to fiat and check limit"
(setup-subs sub-mocks)
(h/render [input-amount/view {}])
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
(h/fire-event :press (h/query-by-label-text :keyboard-key-0))
(h/wait-for #(h/is-truthy (h/get-by-text "$200.00")))
(h/fire-event :press (h/query-by-label-text :reorder))
(h/wait-for #(h/is-truthy (h/get-by-text "2.00 ETH")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "205.50 ETH")))
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
(h/wait-for #(h/is-truthy (h/get-by-text "205.50 ETH")))))
@@ -0,0 +1,8 @@
(ns status-im2.contexts.wallet.send.input-amount.style)
(def screen
{:flex 1})
(def input-container
{:padding-top 12
:padding-bottom 0})
@@ -0,0 +1,93 @@
(ns status-im2.contexts.wallet.send.input-amount.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.wallet.send.input-amount.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- make-limit-label
[{:keys [amount currency]}]
(str amount " " (string/upper-case (name currency))))
(defn- make-new-input
[current v]
(let [dot "."
max-length 12
length-owerflow? (>= (count current) max-length)
ignore-extra-dot? (and (= v dot) (string/includes? current dot))
ignore-value? (or
ignore-extra-dot?
length-owerflow?)]
(if ignore-value?
current
(str current v))))
(defn- view-internal
[_]
(let [token :eth
{:keys [currency]} (rf/sub [:profile/profile])
networks (rf/sub [:wallet/network-details])
conversion-rate 10
limit-crypto 100000000
limit-fiat (* limit-crypto conversion-rate)
input-value (reagent/atom "")
current-limit (reagent/atom {:amount limit-crypto
:currency token})
handle-swap (fn [crypto?]
(let [num-value (parse-double @input-value)]
(reset! current-limit (if crypto?
{:amount limit-crypto
:currency token}
{:amount limit-fiat
:currency currency}))
(when (> num-value (:amount @current-limit))
(reset! input-value ""))))
handle-keyboard-press (fn [v]
(let [current-value @input-value
new-value (make-new-input current-value v)
num-value (parse-double new-value)]
(when (<= num-value (:amount @current-limit))
(reset! input-value new-value))))
handle-delete (fn [_]
(swap! input-value #(subs % 0 (dec (count %)))))]
(fn [{:keys [on-confirm]
:or {on-confirm #(js/alert "Confirmed")}}]
(let [limit-label (make-limit-label @current-limit)]
[rn/view
{:style style/screen}
[quo/page-nav
{:background :blur
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back])
:right-side :account-switcher
:account-switcher {:customization-color :yellow
:emoji "🎮"
:on-press #(js/alert "Switch account")}}]
[quo/token-input
{:container-style style/input-container
:token token
:currency currency
:networks networks
:title (i18n/label :t/send-limit {:limit limit-label})
:conversion conversion-rate
:show-keyboard? false
:value @input-value
:on-swap handle-swap}]
;; Network routing content to be added
[rn/scroll-view]
[quo/bottom-actions
{:actions :1-action
:button-one-label (i18n/label :t/confirm)
:button-one-props {:disabled? (empty? @input-value)
:on-press on-confirm}}]
[quo/numbered-keyboard
{:left-action :dot
:delete-key? true
:on-press handle-keyboard-press
:on-delete handle-delete}]]))))
(def view (quo.theme/with-theme view-internal))
+2 -1
View File
@@ -4,4 +4,5 @@
[status-im2.contexts.chat.messages.content.audio.component-spec] [status-im2.contexts.chat.messages.content.audio.component-spec]
[status-im2.contexts.communities.actions.community-options.component-spec] [status-im2.contexts.communities.actions.community-options.component-spec]
[status-im2.contexts.wallet.add-address-to-watch.component-spec] [status-im2.contexts.wallet.add-address-to-watch.component-spec]
[status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec])) [status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec]
[status-im2.contexts.wallet.send.input-amount.component-spec]))
+6
View File
@@ -49,6 +49,7 @@
[status-im2.contexts.wallet.edit-account.view :as wallet-edit-account] [status-im2.contexts.wallet.edit-account.view :as wallet-edit-account]
[status-im2.contexts.wallet.saved-addresses.view :as wallet-saved-addresses] [status-im2.contexts.wallet.saved-addresses.view :as wallet-saved-addresses]
[status-im2.contexts.wallet.scan-account.view :as scan-address] [status-im2.contexts.wallet.scan-account.view :as scan-address]
[status-im2.contexts.wallet.send.input-amount.view :as wallet-send-input-amount]
[status-im2.contexts.wallet.send.select-address.view :as wallet-select-address] [status-im2.contexts.wallet.send.select-address.view :as wallet-select-address]
[status-im2.navigation.options :as options] [status-im2.navigation.options :as options]
[status-im2.navigation.transitions :as transitions])) [status-im2.navigation.transitions :as transitions]))
@@ -287,6 +288,11 @@
{:name :wallet-saved-addresses {:name :wallet-saved-addresses
:component wallet-saved-addresses/view} :component wallet-saved-addresses/view}
{:name :wallet-send-input-amount
:options {:modalPresentationStyle :overCurrentContext
:insets {:top? true}}
:component wallet-send-input-amount/view}
{:name :wallet-select-address {:name :wallet-select-address
:options {:modalPresentationStyle :overCurrentContext} :options {:modalPresentationStyle :overCurrentContext}
:component wallet-select-address/view} :component wallet-select-address/view}
+2 -1
View File
@@ -2390,6 +2390,7 @@
"invalid-address": "Its not Ethereum address or ENS name", "invalid-address": "Its not Ethereum address or ENS name",
"address-already-in-use": "Address already being used", "address-already-in-use": "Address already being used",
"address-copied": "Address copied", "address-copied": "Address copied",
"no-dapps-description": "We want dApps!" "no-dapps-description": "We want dApps!",
"send-limit": "Max: {{limit}}"
} }