chore(wallet): add ui for send page (#17786) - merged in (#18017)

---------
Co-authored-by: Rende11 <artamonovn@gmail.com>
This commit is contained in:
Jamie Caprani 2023-11-29 13:12:21 +00:00 committed by GitHub
parent 0546a87e9a
commit 589a581298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 315 additions and 51 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

View File

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

View File

@ -1,7 +1,7 @@
(ns quo.components.numbered-keyboard.numbered-keyboard.style) (ns quo.components.numbered-keyboard.numbered-keyboard.style)
(def container (def container
{:flex 1 {:display :flex
:padding-top 8 :padding-top 8
:padding-horizontal 48}) :padding-horizontal 48})

View File

@ -17,13 +17,17 @@
(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
container-style]
:or {left-action :none}}]
[rn/view [rn/view
{:style style/container} {:style (merge style/container
container-style)}
(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 +62,10 @@
:theme theme}] :theme theme}]
(if delete-key? (if delete-key?
[keyboard-item [keyboard-item
{:item :i/delete {:item :i/backspace
: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])]]))

View File

@ -15,18 +15,26 @@
(defn calc-value (defn calc-value
[crypto? currency token value conversion] [crypto? currency token value conversion]
(let [num-value (if (string? value) (parse-double (or value "0")) value)]
(if crypto? (if crypto?
(str (get common/currency-label currency) (.toFixed (* value conversion) 2)) (str (get common/currency-label currency) (.toFixed (* num-value conversion) 2))
(str (.toFixed (/ value conversion) 2) " " (string/upper-case (clj->js token))))) (str (.toFixed (/ num-value conversion) 2) " " (string/upper-case (or (clj->js token) ""))))))
(defn- view-internal (defn- view-internal
[] [{external-value :value}]
(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]}] controlled-input? (some? external-value)]
[rn/view {:style (style/main-container width)} (fn [{:keys [theme token currency conversion networks title customization-color
on-change-text on-swap container-style show-keyboard?]
:or {show-keyboard? true}
external-value :value}]
[rn/view
{:style (merge
(style/main-container width)
container-style)}
[rn/view {:style style/amount-container} [rn/view {:style style/amount-container}
[rn/pressable [rn/pressable
{:on-press #(when @input-ref (.focus ^js @input-ref)) {:on-press #(when @input-ref (.focus ^js @input-ref))
@ -37,27 +45,39 @@
{:style style/token {:style style/token
:source (resources/get-token token)}] :source (resources/get-token token)}]
[rn/text-input [rn/text-input
{:ref #(reset! input-ref %) (cond-> {:auto-focus true
:ref #(reset! input-ref %)
:placeholder "0" :placeholder "0"
:placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme) :placeholder-text-color (colors/theme-colors colors/neutral-40
colors/neutral-50
theme)
:keyboard-type :numeric :keyboard-type :numeric
:max-length 12 :max-length 12
:default-value @value :on-change-text (fn [v]
:on-change-text #(reset! value %) (when-not controlled-input?
(reset! value v))
(when on-change-text
(on-change-text v)))
:style (style/text-input theme) :style (style/text-input theme)
:selection-color customization-color}] :selection-color customization-color
:show-soft-input-on-focus show-keyboard?}
controlled-input? (assoc :value external-value)
(not controlled-input?) (assoc :default-value @value))]
[text/text [text/text
{:size :paragraph-2 {:size :paragraph-2
:weight :semi-bold :weight :semi-bold
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme) :style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)
:margin-right 8 :margin-right 8
:padding-bottom 2}} :padding-bottom 2}}
(string/upper-case (clj->js (if @crypto? token currency)))]] (string/upper-case (or (clj->js (if @crypto? token currency)) ""))]]
[button/button [button/button
{:icon true {:icon true
:icon-only? true :icon-only? true
:size 32 :size 32
:on-press #(swap! crypto? not) :on-press (fn []
(swap! crypto? not)
(when on-swap
(on-swap @crypto?)))
:type :outline :type :outline
:accessibility-label :reorder} :accessibility-label :reorder}
:i/reorder]] :i/reorder]]
@ -68,6 +88,6 @@
{:size :paragraph-2 {:size :paragraph-2
:weight :medium :weight :medium
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}} :style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
(calc-value @crypto? currency token @value conversion)]]]))) (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))

View File

@ -1,28 +1,11 @@
(ns status-im2.contexts.wallet.common.temp (ns status-im2.contexts.wallet.common.temp
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[quo.core :as quo]
[quo.foundations.resources :as quo.resources] [quo.foundations.resources :as quo.resources]
[react-native.core :as rn] [react-native.core :as rn]
[status-im2.common.resources :as status.resources] [status-im2.common.resources :as status.resources]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[utils.i18n :as i18n] [utils.i18n :as i18n]))
[utils.re-frame :as rf]))
(defn wallet-temporary-navigation
[]
[rn/view
{:style {:flex 1
:align-items :center
:justify-content :center}}
[quo/text {} "TEMPORARY NAVIGATION"]
[quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-accounts])}
"Navigate to Account"]
[quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-create-account])}
"Create Account"]
[quo/button {:on-press #(rf/dispatch [:navigate-to :wallet-saved-addresses])}
"Saved Addresses"]])
(defn wallet-overview-state (defn wallet-overview-state
[networks] [networks]

View File

@ -0,0 +1,88 @@
(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
:rate 10}])
(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
{:rate 10
:limit 286}])
(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
{:rate 10
:limit 250}])
(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")))))

View File

@ -0,0 +1,12 @@
(ns status-im2.contexts.wallet.send.input-amount.style)
(def screen
{:flex 1})
(def input-container
{:padding-top 12
:padding-bottom 0})
(defn keyboard-container
[bottom]
{:padding-bottom bottom})

View File

@ -0,0 +1,142 @@
(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]
[react-native.safe-area :as safe-area]
[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))))
(def not-digits-or-dot-pattern
#"[^0-9+\.]")
(def dot ".")
(defn valid-input?
[current v]
(let [max-length 12
length-owerflow? (>= (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?))))
(defn- normalize-input
[current v]
(cond
(and (string/blank? current) (= v dot))
(str "0" v)
(and (= current "0") (not= v dot))
(str v)
:else
(str current v)))
(defn- make-new-input
[current v]
(if (valid-input? current v)
(normalize-input current v)
current))
(defn- f-view-internal
[{:keys [token limit rate]}]
(let [bottom (safe-area/get-bottom)
{:keys [currency]} (rf/sub [:profile/profile])
networks (rf/sub [:wallet/network-details])
;; Temporary values
token (or token :eth)
conversion-rate (or rate 10)
limit-crypto (or limit 2860000.32)
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 (or (parse-double new-value) 0)]
(when (<= num-value (:amount @current-limit))
(reset! input-value new-value)
(reagent/flush))))
handle-delete (fn [_]
(swap! input-value #(subs % 0 (dec (count %))))
(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)]
(if (> num-value current-limit-amount)
(reset! input-value (str current-limit-amount))
(reset! input-value v))
(reagent/flush))))]
(fn [{:keys [on-confirm]
:or {on-confirm #(js/alert "Confirmed")}}]
(let [limit-label (make-limit-label @current-limit)
input-num-value (parse-double @input-value)
confirm-disabled? (or
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (:amount @current-limit)))]
(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/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
:on-change-text (fn [text]
(handle-on-change text))}]
;; 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? confirm-disabled?
: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}]]))))
(defn- view-internal
[props]
[:f> f-view-internal props])
(def view (quo.theme/with-theme view-internal))

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

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.contexts.wallet.send.select-asset.view :as wallet-select-asset] [status-im2.contexts.wallet.send.select-asset.view :as wallet-select-asset]
[status-im2.navigation.options :as options] [status-im2.navigation.options :as options]
@ -288,6 +289,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}

View File

@ -2391,6 +2391,7 @@
"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!",
"select-asset": "Select asset" "select-asset": "Select asset",
"send-limit": "Max: {{limit}}"
} }