From dd4e08a11a0003bd0e670f78186872c0c773e1e1 Mon Sep 17 00:00:00 2001 From: Volodymyr Kozieiev Date: Tue, 17 Dec 2024 15:25:01 +0000 Subject: [PATCH] Screens for editing transaction settings --- src/quo/components/inputs/input/style.cljs | 2 +- src/quo/components/inputs/input/view.cljs | 36 ++- .../components/tags/network_tags/style.cljs | 4 +- .../components/tags/network_tags/view.cljs | 16 +- .../components/wallet/token_input/schema.cljs | 6 +- .../components/wallet/token_input/style.cljs | 5 +- .../components/wallet/token_input/view.cljs | 65 ++--- .../common/controlled_input/utils.cljs | 15 +- .../preview/quo/tags/network_tags.cljs | 32 +-- .../preview/quo/wallet/token_input.cljs | 44 ++-- .../contexts/wallet/send/events.cljs | 38 +++ .../send/transaction_confirmation/view.cljs | 5 + .../transaction_settings/gas_amount/view.cljs | 30 +++ .../transaction_settings/max_fee/view.cljs | 30 +++ .../send/transaction_settings/nonce/view.cljs | 78 ++++++ .../priority_fee/view.cljs | 30 +++ .../send/transaction_settings/view.cljs | 222 +++++++++++++----- src/status_im/navigation/events.cljs | 5 +- src/status_im/navigation/screens.cljs | 32 ++- src/status_im/subs/wallet/send.cljs | 25 ++ translations/en.json | 19 ++ 21 files changed, 587 insertions(+), 152 deletions(-) create mode 100644 src/status_im/contexts/wallet/send/transaction_settings/gas_amount/view.cljs create mode 100644 src/status_im/contexts/wallet/send/transaction_settings/max_fee/view.cljs create mode 100644 src/status_im/contexts/wallet/send/transaction_settings/nonce/view.cljs create mode 100644 src/status_im/contexts/wallet/send/transaction_settings/priority_fee/view.cljs diff --git a/src/quo/components/inputs/input/style.cljs b/src/quo/components/inputs/input/style.cljs index ece7c7267b..f16a54c233 100644 --- a/src/quo/components/inputs/input/style.cljs +++ b/src/quo/components/inputs/input/style.cljs @@ -133,7 +133,7 @@ [variant-colors] {:color (:label variant-colors)}) -(def counter-container +(def right-label-container {:flex 1 :align-items :flex-end}) diff --git a/src/quo/components/inputs/input/view.cljs b/src/quo/components/inputs/input/view.cljs index 95760dca06..6bef2d2309 100644 --- a/src/quo/components/inputs/input/view.cljs +++ b/src/quo/components/inputs/input/view.cljs @@ -7,8 +7,8 @@ [react-native.core :as rn] [react-native.platform :as platform])) -(defn- label-&-counter - [{:keys [label current-chars char-limit variant-colors theme]}] +(defn- label-line + [{:keys [label label-right current-chars char-limit variant-colors theme]}] [rn/view {:accessibility-label :input-labels :style style/texts-container} @@ -18,17 +18,25 @@ :weight :medium :size :paragraph-2} label]] - (when-let [count-text (some->> char-limit - (str current-chars "/"))] - [rn/view {:style style/counter-container} + (when label-right + [rn/view {:style style/right-label-container} [text/text - {:style (style/counter-color {:current-chars current-chars - :char-limit char-limit - :variant-colors variant-colors - :theme theme}) + {:style (style/label-color variant-colors) :weight :regular :size :paragraph-2} - count-text]])]) + label-right]]) + (when char-limit + (when-let [count-text (some->> char-limit + (str current-chars "/"))] + [rn/view {:style style/right-label-container} + [text/text + {:style (style/counter-color {:current-chars current-chars + :char-limit char-limit + :variant-colors variant-colors + :theme theme}) + :weight :regular + :size :paragraph-2} + count-text]]))]) (defn- left-accessory [{:keys [variant-colors small? icon-name]}] @@ -65,7 +73,7 @@ (defn- base-input [{:keys [blur? error? right-icon left-icon disabled? small? button label char-limit multiline? clearable? on-focus on-blur container-style input-container-style - on-change-text on-char-limit-reach weight default-value on-clear placeholder] + on-change-text on-char-limit-reach weight default-value on-clear placeholder label-right] :as props}] (let [theme (quo.theme/use-theme) ref (rn/use-ref-atom nil) @@ -115,10 +123,11 @@ ;; https://github.com/facebook/react-native/issues/27687 modified-placeholder (if platform/android? (str "\u2009" placeholder) placeholder)] [rn/view {:style container-style} - (when (or label char-limit) - [label-&-counter + (when (or label char-limit label-right) + [label-line {:variant-colors variant-colors :label label + :label-right label-right :current-chars char-count :char-limit char-limit :theme theme}]) @@ -199,6 +208,7 @@ - :on-clear - Function executed when the clear button is pressed. - :button - Map containing `:on-press` & `:text` keys, if provided renders a button - :label - A string to set as label for this input. + - :label-right - Additional label aligned to the right - :char-limit - A number to set a maximum char limit for this input. - :on-char-limit-reach - Function executed each time char limit is reached or exceeded. - :default-shown? - boolean to show password input initially diff --git a/src/quo/components/tags/network_tags/style.cljs b/src/quo/components/tags/network_tags/style.cljs index 09acc1f682..ef449ad567 100644 --- a/src/quo/components/tags/network_tags/style.cljs +++ b/src/quo/components/tags/network_tags/style.cljs @@ -32,8 +32,8 @@ :padding-bottom 2}) (defn title-style - [{:keys [status theme]}] - {:padding-left 4 + [{:keys [status theme networks-shown?]}] + {:padding-left (if networks-shown? 4 0) :margin-top -1 :color (when (= status :error) (colors/theme-colors diff --git a/src/quo/components/tags/network_tags/view.cljs b/src/quo/components/tags/network_tags/view.cljs index 189bd2123f..c82d5b95d5 100644 --- a/src/quo/components/tags/network_tags/view.cljs +++ b/src/quo/components/tags/network_tags/view.cljs @@ -14,14 +14,16 @@ :theme theme :blur? blur?}) container-style)} - [preview-list/view - {:type :network - :number (count networks) - :size :size-16} - networks] + (when networks + [preview-list/view + {:type :network + :number (count networks) + :size :size-16} + networks]) [text/text {:weight :medium :size :paragraph-2 - :style (style/title-style {:status status - :theme theme})} + :style (style/title-style {:status status + :theme theme + :networks-shown? networks})} title]])) diff --git a/src/quo/components/wallet/token_input/schema.cljs b/src/quo/components/wallet/token_input/schema.cljs index 2f744eb133..e3aa938335 100644 --- a/src/quo/components/wallet/token_input/schema.cljs +++ b/src/quo/components/wallet/token_input/schema.cljs @@ -9,9 +9,11 @@ [: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?]] + [:on-swap {:optional true} [:maybe fn?]] [:container-style {:optional true} [:maybe :map]] [:error? [:maybe :boolean]] + [:show-token-icon? {:optional true} [:maybe :boolean]] [:value [:maybe :string]] - [:converted-value [:maybe :string]]]]] + [:converted-value {:optional true} [:maybe :string]] + [:swappable? {:optional true} [:maybe :boolean]]]]] :any]) diff --git a/src/quo/components/wallet/token_input/style.cljs b/src/quo/components/wallet/token_input/style.cljs index ca79e504df..025c4a1f19 100644 --- a/src/quo/components/wallet/token_input/style.cljs +++ b/src/quo/components/wallet/token_input/style.cljs @@ -23,9 +23,8 @@ {:flex-direction :row :align-items :flex-end}) -(defn input-container - [window-width] - {:width (- window-width 120) +(def input-container + {:flex 1 :margin-left 8 :margin-right 8 :flex-direction :row diff --git a/src/quo/components/wallet/token_input/view.cljs b/src/quo/components/wallet/token_input/view.cljs index c1f4e080f0..250f7bd46f 100644 --- a/src/quo/components/wallet/token_input/view.cljs +++ b/src/quo/components/wallet/token_input/view.cljs @@ -20,17 +20,18 @@ (string/upper-case (or (clj->js text) ""))]) (defn input-section - [{:keys [token-symbol on-token-press value error? on-swap currency-symbol]}] - (let [theme (quo.theme/use-theme) - window-width (:width (rn/get-window))] + [{:keys [token-symbol on-token-press value error? on-swap currency-symbol show-token-icon? + swappable?]}] + (let [theme (quo.theme/use-theme)] [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)} + (when show-token-icon? + [token/view + {:token token-symbol + :size :size-32}]) + [rn/view {:style style/input-container} [rn/text-input {:style (style/text-input theme error?) :placeholder-text-color (style/placeholder-text theme) @@ -39,14 +40,15 @@ :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]])) + (when swappable? + [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 [token-symbol @@ -57,26 +59,33 @@ on-swap converted-value hint-component - currency-symbol]}] + show-token-icon? + currency-symbol + swappable?] + :or {show-token-icon? true + swappable? true}}] (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 - {:theme theme - :token-symbol token-symbol - :on-token-press on-token-press - :value value - :error? error? - :on-swap on-swap - :currency-symbol currency-symbol}]] + {:theme theme + :token-symbol token-symbol + :on-token-press on-token-press + :value value + :error? error? + :on-swap on-swap + :currency-symbol currency-symbol + :show-token-icon? show-token-icon? + :swappable? swappable?}]] [divider-line/view {:container-style (style/divider theme)}] [rn/view {:style style/data-container} hint-component - [text/text - {:size :paragraph-2 - :weight :medium - :style (style/converted-amount theme)} - converted-value]]])) + (when swappable? + [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 7c87ec4bd0..fc17b4eaf2 100644 --- a/src/status_im/common/controlled_input/utils.cljs +++ b/src/status_im/common/controlled_input/utils.cljs @@ -54,7 +54,7 @@ (when (money/bignumber? (value-bn state)) (money/greater-than (value-bn state) (upper-limit-bn state))))) -(defn- lower-limit-exceeded? +(defn lower-limit-exceeded? [state] (and (lower-limit state) (when (money/bignumber? (value-bn state)) @@ -78,16 +78,19 @@ (defn set-upper-limit [state limit] - (when limit + (if limit (-> state (assoc :upper-limit limit) - recheck-errorness))) + recheck-errorness) + state)) (defn set-lower-limit [state limit] - (-> state - (assoc :lower-limit limit) - recheck-errorness)) + (if limit + (-> state + (assoc :lower-limit limit) + recheck-errorness) + state)) (defn increase [state] diff --git a/src/status_im/contexts/preview/quo/tags/network_tags.cljs b/src/status_im/contexts/preview/quo/tags/network_tags.cljs index 289b99c063..9b943c82da 100644 --- a/src/status_im/contexts/preview/quo/tags/network_tags.cljs +++ b/src/status_im/contexts/preview/quo/tags/network_tags.cljs @@ -23,29 +23,31 @@ :key :title} {:type :select :key :networks - :options [{:key 1} + :options [{:key 0} + {:key 1} {:key 2} {:key 3}]} {:type :boolean :key :blur?}]) - (defn view [] (let [state (reagent/atom {:title "Tag" :status :default :networks 3})] (fn [] - [preview/preview-container - {:state state - :descriptor descriptor - :blur? (:blur? @state) - :show-blur-background? true} - [rn/view - {:style {:align-self :center - :justify-content :center - :flex 1}} - [quo/network-tags - (assoc @state - :networks - (nth community-networks (dec (:networks @state))))]]]))) + (let [selected-networks-id (dec (:networks @state))] + [preview/preview-container + {:state state + :descriptor descriptor + :blur? (:blur? @state) + :show-blur-background? true} + [rn/view + {:style {:align-self :center + :justify-content :center + :flex 1}} + [quo/network-tags + (assoc @state + :networks + (when (pos? selected-networks-id) + (nth community-networks selected-networks-id)))]]])))) 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 fa41c81224..0313ff126a 100644 --- a/src/status_im/contexts/preview/quo/wallet/token_input.cljs +++ b/src/status_im/contexts/preview/quo/wallet/token_input.cljs @@ -22,21 +22,29 @@ [{:key :token-symbol :type :select :options [{:key :eth} - {:key :snt}]} + {:key :snt} + {:key :gwei} + {:key :units}]} {:key :currency :type :select :options [{:key "$"} {:key "€"}]} {:key :error? + :type :boolean} + {:key :show-token-icon? + :type :boolean} + {:key :swappable? :type :boolean}]) (defn view [] - (let [state (reagent/atom {:token-symbol :eth - :currency "$" - :crypto? true - :error? false})] + (let [state (reagent/atom {:token-symbol :eth + :currency "$" + :crypto? true + :error? false + :show-token-icon? true + :swappable? true})] (fn [] (let [{:keys [currency token-symbol crypto? error?]} @state [input-state set-input-state] (rn/use-state controlled-input/init-state) @@ -67,18 +75,20 @@ :component-container-style {:flex 1 :justify-content :space-between}} [quo/token-input - {: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)}]}] + {: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)}] + :show-token-icon? (:show-token-icon? @state) + :swappable? (:swappable? @state)}] [quo/numbered-keyboard {:container-style {:padding-bottom (safe-area/get-top)} :left-action :dot diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index 752a02c2fd..8fbd2b7fe7 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -768,3 +768,41 @@ :always (update-in [:wallet :ui :send] dissoc :amount :route) (not keep-tx-data?) (update-in [:wallet :ui :send] dissoc :tx-type)) :fx [[:dispatch [:navigate-back]]]}))) + +(rf/reg-event-fx + :wallet/init-tx-settings + (fn [{db :db}] + {:db (assoc-in db + [:wallet :ui :send :tx-settings] + {:max-base-fee {:low 5 + :current 8.2 + :high 9} + :priority-fee {:low 0.6 + :high 5.1 + :current 1.1} + :max-gas-amount {:low 30000 + :current 31000} + :nonce {:last-transaction 21 + :current 22}})})) + +(rf/reg-event-fx + :wallet/set-max-base-fee + (fn [{db :db} [value]] + {:db (assoc-in db [:wallet :ui :send :tx-settings :max-base-fee :current] value)})) + +(rf/reg-event-fx + :wallet/set-priority-fee + (fn [{db :db} [value]] + {:db (assoc-in db [:wallet :ui :send :tx-settings :priority-fee :current] value)})) +(rf/reg-event-fx + + :wallet/set-max-gas-amount + (fn [{db :db} [value]] + {:db (assoc-in db [:wallet :ui :send :tx-settings :max-gas-amount :current] value)})) + +(rf/reg-event-fx + :wallet/set-nonce + (fn [{db :db} [value]] + {:db (assoc-in db [:wallet :ui :send :tx-settings :nonce :current] value)})) + +#_(rf/dispatch [:wallet/init-tx-settings]) diff --git a/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs b/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs index 6401225091..f6cee15cdf 100644 --- a/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs +++ b/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs @@ -262,6 +262,11 @@ first-route) (rf/dispatch [:wallet/build-transaction-for-collectible-route]))) [first-route]) + ;; Until we getting transaction params from status-go, we use placeholder values + (rn/use-mount + (fn [] + (when (ff/enabled? ::ff/wallet.transaction-params) + (rf/dispatch [:wallet/init-tx-settings])))) [rn/view {:style {:flex 1}} [floating-button-page/view {:footer-container-padding 0 diff --git a/src/status_im/contexts/wallet/send/transaction_settings/gas_amount/view.cljs b/src/status_im/contexts/wallet/send/transaction_settings/gas_amount/view.cljs new file mode 100644 index 0000000000..d5471a6e51 --- /dev/null +++ b/src/status_im/contexts/wallet/send/transaction_settings/gas_amount/view.cljs @@ -0,0 +1,30 @@ +(ns status-im.contexts.wallet.send.transaction-settings.gas-amount.view + (:require + [quo.theme] + [status-im.contexts.wallet.send.transaction-settings.view :as transaction-settings] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn gas-amount-hint-text + [{:keys [current]} lower-limit-exceeded? _upper-limit-exceeded?] + (cond + lower-limit-exceeded? (i18n/label :t/gas-amount-lower {:current current}) + :else (i18n/label :t/current-units {:current current}))) + +(defn view + [] + (let [suggested-values (rf/sub [:wallet/tx-settings-max-gas-amount]) + hint-text (partial gas-amount-hint-text suggested-values)] + [transaction-settings/custom-setting-screen + {:screen-title (i18n/label :t/max-gas-amount) + :token-symbol :units + :hint-text-fn hint-text + :suggested-values suggested-values + :info-title (i18n/label :t/gas-amount) + :info-content (i18n/label :t/about-gas-amount) + :on-save (fn [new-val] + (rf/dispatch [:wallet/set-max-gas-amount new-val]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:show-bottom-sheet + {:content transaction-settings/custom-settings-sheet}])) + :with-decimals? false}])) diff --git a/src/status_im/contexts/wallet/send/transaction_settings/max_fee/view.cljs b/src/status_im/contexts/wallet/send/transaction_settings/max_fee/view.cljs new file mode 100644 index 0000000000..ed8d229ae1 --- /dev/null +++ b/src/status_im/contexts/wallet/send/transaction_settings/max_fee/view.cljs @@ -0,0 +1,30 @@ +(ns status-im.contexts.wallet.send.transaction-settings.max-fee.view + (:require + [quo.theme] + [status-im.contexts.wallet.send.transaction-settings.view :as transaction-settings] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn max-base-fee-hint-text + [current lower-limit-exceeded? upper-limit-exceeded?] + (cond + upper-limit-exceeded? (i18n/label :t/max-base-fee-higher {:current current}) + lower-limit-exceeded? (i18n/label :t/max-base-fee-lower {:current current}) + :else (i18n/label :t/fee-current-gwei {:current current}))) + +(defn view + [] + (let [suggested-values (rf/sub [:wallet/tx-settings-max-base-fee]) + hint-text (partial max-base-fee-hint-text (:current suggested-values))] + [transaction-settings/custom-setting-screen + {:screen-title (i18n/label :t/max-base-fee) + :token-sybmol :gwei + :hint-text-fn hint-text + :suggested-values suggested-values + :info-title (i18n/label :t/max-base-fee) + :info-content (i18n/label :t/about-max-base-fee) + :on-save (fn [new-val] + (rf/dispatch [:wallet/set-max-base-fee new-val]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:show-bottom-sheet + {:content transaction-settings/custom-settings-sheet}]))}])) diff --git a/src/status_im/contexts/wallet/send/transaction_settings/nonce/view.cljs b/src/status_im/contexts/wallet/send/transaction_settings/nonce/view.cljs new file mode 100644 index 0000000000..bb267d758a --- /dev/null +++ b/src/status_im/contexts/wallet/send/transaction_settings/nonce/view.cljs @@ -0,0 +1,78 @@ +(ns status-im.contexts.wallet.send.transaction-settings.nonce.view + (:require + [quo.core :as quo] + [quo.foundations.colors :as colors] + [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.send.transaction-settings.view :as transaction-settings] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn view + [] + (let [{:keys [last-transaction current]} (rf/sub [:wallet/tx-settings-nonce]) + suggested-nonce (inc last-transaction) + [input-state set-input-state] (rn/use-state (controlled-input/set-value-numeric + controlled-input/init-state + current)) + input-value (controlled-input/input-value input-state) + warning? (> (controlled-input/value-numeric input-state) + suggested-nonce)] + [rn/view + {:style {:flex 1}} + [quo/page-nav + {:type :title + :title (i18n/label :t/nonce) + :text-align :center + :right-side [{:icon-name :i/info + :on-press #(rf/dispatch [:show-bottom-sheet + {:content (fn [] [transaction-settings/info-sheet + (i18n/label :t/nonce) + (i18n/label :t/about-nonce)])}])}] + :icon-name :i/arrow-left + :on-press (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:show-bottom-sheet + {:content transaction-settings/custom-settings-sheet}]))}] + [rn/view + {:style {:padding-horizontal 20 + :gap 8}} + [quo/input + {:type :text + :label (i18n/label :t/type-nonce) + :label-right (i18n/label :t/last-transaction-is {:number last-transaction}) + :editable false + :default-value input-value + :clearable? true + :error? warning? + :on-clear (fn [] + (set-input-state controlled-input/delete-all))}] + (when warning? + [quo/text + {:style {:color colors/warning-50} + :weight :regular + :size :paragraph-2} + (i18n/label :t/nonce-higher {:number suggested-nonce})])] + [rn/view {:style {:flex 1}}] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/save-changes) + :button-one-props {:disabled? (controlled-input/empty-value? input-state) + :on-press (fn [] + (rf/dispatch [:wallet/set-nonce + (controlled-input/value-numeric input-state)]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:show-bottom-sheet + {:content + transaction-settings/custom-settings-sheet}]))}}] + [quo/numbered-keyboard + {:container-style {:padding-bottom (safe-area/get-bottom)} + :left-action :none + :delete-key? true + :on-press (fn [c] + (set-input-state #(controlled-input/add-character % c))) + :on-delete (fn [] + (set-input-state controlled-input/delete-last)) + :on-long-press-delete (fn [] + (set-input-state controlled-input/delete-all))}]])) diff --git a/src/status_im/contexts/wallet/send/transaction_settings/priority_fee/view.cljs b/src/status_im/contexts/wallet/send/transaction_settings/priority_fee/view.cljs new file mode 100644 index 0000000000..31a2895e86 --- /dev/null +++ b/src/status_im/contexts/wallet/send/transaction_settings/priority_fee/view.cljs @@ -0,0 +1,30 @@ +(ns status-im.contexts.wallet.send.transaction-settings.priority-fee.view + (:require + [quo.theme] + [status-im.contexts.wallet.send.transaction-settings.view :as transaction-settings] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn priority-fee-hint-text + [{:keys [current low high]} lower-limit-exceeded? upper-limit-exceeded?] + (cond + upper-limit-exceeded? (i18n/label :t/priority-fee-higher {:low low :high high}) + lower-limit-exceeded? (i18n/label :t/priority-fee-lower {:low low :high high}) + :else (i18n/label :t/fee-current-gwei {:current current}))) + +(defn view + [] + (let [suggested-values (rf/sub [:wallet/tx-settings-priority-fee]) + hint-text (partial priority-fee-hint-text suggested-values)] + [transaction-settings/custom-setting-screen + {:screen-title (i18n/label :t/priority-fee) + :token-sybmol :gwei + :hint-text-fn hint-text + :suggested-values suggested-values + :info-title (i18n/label :t/priority-fee) + :info-content (i18n/label :t/about-priority-fee) + :on-save (fn [new-val] + (rf/dispatch [:wallet/set-priority-fee new-val]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:show-bottom-sheet + {:content transaction-settings/custom-settings-sheet}]))}])) diff --git a/src/status_im/contexts/wallet/send/transaction_settings/view.cljs b/src/status_im/contexts/wallet/send/transaction_settings/view.cljs index 3395fa92be..3f839ffd7b 100644 --- a/src/status_im/contexts/wallet/send/transaction_settings/view.cljs +++ b/src/status_im/contexts/wallet/send/transaction_settings/view.cljs @@ -2,53 +2,68 @@ (:require [quo.core :as quo] [react-native.core :as rn] + [react-native.platform :as platform] + [react-native.safe-area :as safe-area] + [status-im.common.controlled-input.utils :as controlled-input] [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn custom-settings-sheet [_] - [rn/view - [quo/drawer-top - {:title "Custom"}] - [quo/category - {:list-type :settings - :data [{:title "Max base fee" - :description-props {:text "8.2 GWEI - €1.45"} - :image :none - :description :text - :action :arrow - :on-press #() - :label :text - :preview-size :size-32} - {:title "Priority fee" - :description-props {:text "0.06 GWEI - €0.03"} - :image :none - :description :text - :action :arrow - :on-press #() - :label :text - :preview-size :size-32} - {:title "Gas amount" - :description-props {:text "31,500 UNITS"} - :image :none - :description :text - :action :arrow - :on-press #() - :label :text - :preview-size :size-32} - {:title "Nonce" - :description-props {:text "22"} - :image :none - :description :text - :action :arrow - :on-press #() - :label :text - :preview-size :size-32}]}] - [quo/bottom-actions - {:actions :one-action - :button-one-props {:on-press #(rf/dispatch [:hide-bottom-sheet])} - :button-one-label (i18n/label :t/confirm)}]]) + (let [max-base-fee (:current (rf/sub [:wallet/tx-settings-max-base-fee])) + priority-fee (:current (rf/sub [:wallet/tx-settings-priority-fee])) + max-gas-amount (:current (rf/sub [:wallet/tx-settings-max-gas-amount])) + nonce (:current (rf/sub [:wallet/tx-settings-nonce]))] + [rn/view + [quo/drawer-top + {:title "Custom"}] + [quo/category + {:list-type :settings + :data [{:title (i18n/label :t/max-base-fee) + :description-props {:text (str max-base-fee " GWEI")} + :image :none + :description :text + :action :arrow + :on-press #(rf/dispatch [:navigate-to-within-stack + [:screen/wallet.tx-settings-max-fee + :screen/wallet.transaction-confirmation]]) + :label :text + :preview-size :size-32} + {:title (i18n/label :t/priority-fee) + :description-props {:text (str priority-fee " GWEI")} + :image :none + :description :text + :action :arrow + :on-press #(rf/dispatch [:navigate-to-within-stack + [:screen/wallet.tx-settings-priority-fee + :screen/wallet.transaction-confirmation]]) + :label :text + :preview-size :size-32} + {:title (i18n/label :t/max-gas-amount) + :description-props {:text (str max-gas-amount " UNITS")} + :image :none + :description :text + :action :arrow + :on-press #(rf/dispatch [:navigate-to-within-stack + [:screen/wallet.tx-settings-gas-amount + :screen/wallet.transaction-confirmation]]) + :label :text + :preview-size :size-32} + {:title (i18n/label :t/nonce) + :description-props {:text nonce} + :image :none + :description :text + :action :arrow + :on-press #(rf/dispatch [:navigate-to-within-stack + [:screen/wallet.tx-settings-nonce + :screen/wallet.transaction-confirmation]]) + :label :text + :preview-size :size-32}]}] + [quo/bottom-actions + {:actions :one-action + :button-one-props {:on-press #(rf/dispatch [:hide-bottom-sheet])} + :button-one-label (i18n/label :t/confirm)}]])) (defn settings-sheet [_] @@ -58,18 +73,27 @@ {:title "Transaction settings"}] [quo/category {:list-type :settings - :data [{:title "Normal ~60s" - :image-props "🍿" - :description-props {:text "€1.45"} - :image :emoji - :description :text - :action :selector - :action-props {:type :radio - :checked? (= :normal selected-id)} - :on-press #(set-selected-id :normal) - :label :text - :preview-size :size-32} - {:title "Fast ~40s" + :data [{:title (str (i18n/label :t/normal) "~60s") + :image-props + "🍿" + :description-props + {:text "€1.45"} + :image + :emoji + :description + :text + :action + :selector + :action-props + {:type :radio + :checked? (= :normal selected-id)} + :on-press + #(set-selected-id :normal) + :label + :text + :preview-size + :size-32} + {:title (str (i18n/label :t/fast) "~40s") :image-props "🚗" :description-props {:text "€1.65"} :image :emoji @@ -80,7 +104,7 @@ :on-press #(set-selected-id :fast) :label :text :preview-size :size-32} - {:title "Urgent ~15s" + {:title (str (i18n/label :t/urgent) "~15s") :image-props "🚀" :description-props {:text "€1.85"} :image :emoji @@ -91,7 +115,7 @@ :on-press #(set-selected-id :urgent) :label :text :preview-size :size-32} - {:title "Custom" + {:title (i18n/label :t/custom) :image-props :i/edit :description-props {:text "Set your own fees and nonce"} :image :icon @@ -106,3 +130,91 @@ {:actions :one-action :button-one-props {:on-press #(rf/dispatch [:hide-bottom-sheet])} :button-one-label (i18n/label :t/confirm)}]])) + + + +(defn- hint + [{:keys [error? text]}] + [quo/network-tags + {:title text + :status (when error? :error)}]) + +(defn info-sheet + [info-title info-content] + [quo/documentation-drawers + {:title info-title + :shell? true} + [rn/view + [quo/text {:size :paragraph-2} info-content] + [quo/button + {:type :outline + :size 24 + :icon-right :i/info + :container-style {:padding-top 21 + :padding-bottom (if platform/ios? 14 24) + :align-self :flex-start + :justify-content :center} + :on-press #()} + (i18n/label :t/read-more)]]]) + +(defn custom-setting-screen + [{:keys [screen-title token-symbol hint-text-fn suggested-values info-title info-content on-save + with-decimals?] + :or {with-decimals? true}}] + (let [[input-state set-input-state] (rn/use-state (controlled-input/set-value-numeric + controlled-input/init-state + (:current suggested-values))) + input-value (controlled-input/input-value input-state) + out-of-limits? (controlled-input/input-error input-state) + valid-input? (not (or (controlled-input/empty-value? input-state) + out-of-limits?))] + (rn/use-mount + (fn [] + + (set-input-state (fn [state] + (-> state + (controlled-input/set-upper-limit (:high suggested-values)) + (controlled-input/set-lower-limit (:low suggested-values))))))) + [rn/view + {:style {:flex 1}} + [quo/page-nav + {:type :title + :title screen-title + :text-align :center + :right-side [{:icon-name :i/info + :on-press #(rf/dispatch [:show-bottom-sheet + {:content (fn [] [info-sheet info-title + info-content])}])}] + :icon-name :i/arrow-left + :on-press (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:show-bottom-sheet + {:content custom-settings-sheet}]))}] + [quo/token-input + {:token-symbol token-symbol + :swappable? false + :show-token-icon? false + :value input-value + :error? out-of-limits? + :currency-symbol token-symbol + :hint-component [hint + {:error? out-of-limits? + :text (hint-text-fn + (controlled-input/lower-limit-exceeded? input-state) + (controlled-input/upper-limit-exceeded? input-state))}]}] + [rn/view {:style {:flex 1}}] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/save-changes) + :button-one-props {:disabled? (not valid-input?) + :on-press #(on-save (controlled-input/value-numeric input-state))}}] + [quo/numbered-keyboard + {:container-style {:padding-bottom (safe-area/get-bottom)} + :left-action (if with-decimals? :dot :none) + :delete-key? true + :on-press (fn [c] + (set-input-state #(controlled-input/add-character % c))) + :on-delete (fn [] + (set-input-state controlled-input/delete-last)) + :on-long-press-delete (fn [] + (set-input-state controlled-input/delete-all))}]])) diff --git a/src/status_im/navigation/events.cljs b/src/status_im/navigation/events.cljs index ac0c66312f..6db3f81c24 100644 --- a/src/status_im/navigation/events.cljs +++ b/src/status_im/navigation/events.cljs @@ -30,8 +30,9 @@ (rf/defn navigate-to-within-stack {:events [:navigate-to-within-stack]} [{:keys [db]} comp-id screen-params] - {:db (all-screens-params db (first comp-id) screen-params) - :fx [[:navigate-to-within-stack (conj comp-id (:theme db))]]}) + {:db (all-screens-params db (first comp-id) screen-params) + :dispatch-n [[:hide-bottom-sheet]] + :fx [[:navigate-to-within-stack (conj comp-id (:theme db))]]}) (re-frame/reg-event-fx :open-modal (fn [{:keys [db]} [component screen-params]] diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index b039a5d014..75e2d5fd59 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -138,6 +138,12 @@ [status-im.contexts.wallet.send.send-amount.view :as wallet-send-input-amount] [status-im.contexts.wallet.send.transaction-confirmation.view :as wallet-transaction-confirmation] [status-im.contexts.wallet.send.transaction-progress.view :as wallet-transaction-progress] + [status-im.contexts.wallet.send.transaction-settings.gas-amount.view :as + wallet-tx-settings-gas-amount] + [status-im.contexts.wallet.send.transaction-settings.max-fee.view :as wallet-tx-settings-max-fee] + [status-im.contexts.wallet.send.transaction-settings.nonce.view :as wallet-tx-settings-nonce] + [status-im.contexts.wallet.send.transaction-settings.priority-fee.view :as + wallet-tx-settings-priority-fee] [status-im.contexts.wallet.swap.select-account.view :as wallet-swap-select-account] [status-im.contexts.wallet.swap.select-asset-to-pay.view :as wallet-swap-select-asset-to-pay] [status-im.contexts.wallet.swap.set-spending-cap.view :as wallet-swap-set-spending-cap] @@ -623,7 +629,31 @@ :metrics {:track? true :alias-id :wallet-send.select-collectible-amount} :options {:insets {:top? true}} - :component wallet-select-collectible-amount/view}]) + :component wallet-select-collectible-amount/view} + + {:name :screen/wallet.tx-settings-max-fee + :metrics {:track? true + :alias-id :wallet-send.tx-settings-max-fee} + :options {:insets {:top? true}} + :component wallet-tx-settings-max-fee/view} + + {:name :screen/wallet.tx-settings-priority-fee + :metrics {:track? true + :alias-id :wallet-send.tx-settings-priority-fee} + :options {:insets {:top? true}} + :component wallet-tx-settings-priority-fee/view} + + {:name :screen/wallet.tx-settings-gas-amount + :metrics {:track? true + :alias-id :wallet-send.tx-settings-gas-amount} + :options {:insets {:top? true}} + :component wallet-tx-settings-gas-amount/view} + + {:name :screen/wallet.tx-settings-nonce + :metrics {:track? true + :alias-id :wallet-send.tx-settings-nonce} + :options {:insets {:top? true}} + :component wallet-tx-settings-nonce/view}]) (def wallet-bridge-screens [{:name :screen/wallet.bridge-select-asset diff --git a/src/status_im/subs/wallet/send.cljs b/src/status_im/subs/wallet/send.cljs index e413ab65de..fd0e5a91d8 100644 --- a/src/status_im/subs/wallet/send.cljs +++ b/src/status_im/subs/wallet/send.cljs @@ -173,3 +173,28 @@ (= (:chain-id network) bridge-to-chain-id) network)) networks)))) + +(rf/reg-sub + :wallet/tx-settings + :<- [:wallet/wallet-send] + :-> :tx-settings) + +(rf/reg-sub + :wallet/tx-settings-max-base-fee + :<- [:wallet/tx-settings] + :-> :max-base-fee) + +(rf/reg-sub + :wallet/tx-settings-priority-fee + :<- [:wallet/tx-settings] + :-> :priority-fee) + +(rf/reg-sub + :wallet/tx-settings-max-gas-amount + :<- [:wallet/tx-settings] + :-> :max-gas-amount) + +(rf/reg-sub + :wallet/tx-settings-nonce + :<- [:wallet/tx-settings] + :-> :nonce) diff --git a/translations/en.json b/translations/en.json index 9e591a0363..0191cc856c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,10 +1,14 @@ { "about": "About", "about-app": "About", + "about-gas-amount": "AKA gas limit. Refers to the maximum number of computational steps (or units of gas) that a transaction can consume. It represents the complexity or amount of work required to execute a transaction or smart contract.\n\nThe gas limit is a cap on how much work the transaction can do on the blockchain. If the gas limit is set too low, the transaction may fail due to insufficient gas.", "about-key-storage-content": "Status will never access your private key. Be sure to backup your seed phrase. If you lose your phone it is the only way to access your keys.", "about-key-storage-title": "About key storage", + "about-max-base-fee": "When your transaction gets included in the block, any difference between your max base fee and the actual base fee will be refunded.", "about-names-content": "No one can pretend to be you! You’re anonymous by default and never have to reveal your real name. You can register a custom name for a small fee.", "about-names-title": "Names can’t be changed", + "about-nonce": "Transaction counter ensuring transactions from your account are processed in the correct order and can’t be replayed. Each new transaction increments the nonce by 1, ensuring uniqueness and preventing double-spending.\n\nIf a transaction with a lower nonce is pending, higher nonce transactions will remain in the queue until the earlier one is confirmed.", + "about-priority-fee": "AKA miner tip. A voluntary fee you can add to incentivise miners or validators to prioritise your transaction.\n\nThe higher the tip, the faster your transaction is likely to be processed, especially curing periods of higher network congestion.", "about-sharing-data": "About sharing data", "accent-colour": "Accent colour", "accent-colour-updated": "Accent colour updated", @@ -653,6 +657,7 @@ "current-password": "Current password", "current-pin": "Enter 6-digit passcode", "current-pin-description": "Enter your 6-digit passcode to proceed", + "current-units": "Current: {{current}} UNITS", "custom": "Custom", "custom-node": "You are using custom RPC endpoint. Your local transfers history might be incomplete.", "custom-seed-phrase": "Invalid seed phrase", @@ -1044,6 +1049,7 @@ "featured": "Featured", "feb": "Feb", "fee-cap": "Fee cap", + "fee-current-gwei": "Current: {{current}} GWEI", "fee-explanation": "Maximum overall price for the transaction. If the block base fee exceeds this, it will be included in a following block with a lower base fee.", "fee-options": "Suggested fee options", "fees": "Fees", @@ -1085,7 +1091,9 @@ "from-all-profiles-on-device": "From all profiles on device", "from-capitalized": "From", "from-label": "From", + "gas-amount": "Gas amount", "gas-amount-limit": "Gas amount limit", + "gas-amount-lower": "Too low. Recommended: {{current}} UNITS", "gas-limit": "Gas limit", "gas-price": "Gas price", "gas-used": "Gas used", @@ -1445,6 +1453,7 @@ "language-and-currency": "Language and currency", "last-backup-performed": "Last backup performed:", "last-transaction": "Last transaction", + "last-transaction-is": "Last transaction: {{number}}", "layer-2": "Layer 2", "learn-more": "Learn more", "learn-more-about-keycard": "Learn more about Keycard", @@ -1551,8 +1560,12 @@ "master-account": "Master account", "max": "Max: {{number}}", "max-2-decimals": "Max. 2 decimals", + "max-base-fee": "Max base fee", + "max-base-fee-higher": "Higher than necessary. Current: {{current}} GWEI", + "max-base-fee-lower": "Too low. Current: {{current}} GWEI ", "max-fee": "Max fee", "max-fees": "Max fees", + "max-gas-amount": "Max gas amount", "max-priority-fee": "Max priority fee", "max-slippage": "Max slippage", "max-token": "Max: {{number}} {{token-symbol}}", @@ -1796,6 +1809,7 @@ "non-archival-node": "RPC endpoint doesn't support archival requests. Your local transfers history might be incomplete.", "non-contacts": "Non contacts", "nonce": "Nonce", + "nonce-higher": "Higher than suggested nonce of {{number}}", "none": "None", "normal": "Normal", "not-a-chatkey": "This is not a chatkey", @@ -2022,6 +2036,9 @@ "price-impact-too-high": "Price impact too high. Lower token amount or try again later.", "principles": "Principles", "priority": "Priority", + "priority-fee": "Priority fee", + "priority-fee-higher": "More than necessary. Current: {{low}} - {{high}} GWEI", + "priority-fee-lower": "Too low. Current: {{low}} - {{high}} GWEI", "privacy": "Privacy", "privacy-and-security": "Privacy and security", "privacy-photos": "Profile Photo Privacy", @@ -2672,6 +2689,7 @@ "type": "Type", "type-a-message": "Message", "type-nickname": "Type nickname", + "type-nonce": "Type nonce", "type-pairing-code": "Type or paste pairing code", "type-slippage": "Type slippage", "type-some-chat-key": "zQ3...1sgt5N", @@ -2728,6 +2746,7 @@ "updates-to-tos": "Updates to Terms of Use", "updates-to-tos-desc": "Before you continue, please review the Terms of Use and confirm you take full responsibility for how you use the app.", "upvote-it": "upvote it", + "urgent": "Urgent", "url": "URL", "usage-data-shared-from-all-profiles": "Usage data will be shared from all profiles added to device. ", "usd-currency": "USD",