diff --git a/src/quo/components/wallet/token_input/view.cljs b/src/quo/components/wallet/token_input/view.cljs index 73f37e29a99..5a6bbedd66b 100644 --- a/src/quo/components/wallet/token_input/view.cljs +++ b/src/quo/components/wallet/token_input/view.cljs @@ -1,6 +1,7 @@ (ns quo.components.wallet.token-input.view (:require [clojure.string :as string] + [oops.core :as oops] [quo.components.buttons.button.view :as button] [quo.components.dividers.divider-line.view :as divider-line] [quo.components.markdown.text :as text] @@ -72,18 +73,25 @@ [token-name-text theme text]]) (defn input-section - [{:keys [on-change-text value value-atom]}] - (let [input-ref (atom nil) - set-ref #(reset! input-ref %) - focus-input #(when-let [ref ^js @input-ref] - (.focus ref)) - controlled-input? (some? value) - handle-on-change-text (fn [v] - (when-not controlled-input? - (reset! value-atom v)) - (when on-change-text - (on-change-text v)))] - (fn [{:keys [theme token customization-color show-keyboard? crypto? currency value error?] + [{:keys [on-change-text value value-atom on-selection-change]}] + (let [input-ref (atom nil) + set-ref #(reset! input-ref %) + focus-input #(when-let [ref ^js @input-ref] + (.focus ref)) + controlled-input? (some? value) + handle-on-change-text (fn [v] + (when-not controlled-input? + (reset! value-atom v)) + (when on-change-text + (on-change-text v))) + handle-selection-change (fn [^js e] + (when on-selection-change + (-> e + (oops/oget "nativeEvent.selection") + (js->clj :keywordize-keys true) + (on-selection-change))))] + (fn [{:keys [theme token customization-color show-keyboard? crypto? currency value error? + selection] :or {show-keyboard? true}}] [rn/pressable {:on-press focus-input @@ -102,7 +110,9 @@ :max-length 12 :on-change-text handle-on-change-text :selection-color customization-color - :show-soft-input-on-focus show-keyboard?} + :show-soft-input-on-focus show-keyboard? + :on-selection-change handle-selection-change + :selection (clj->js selection)} controlled-input? (assoc :value value) (not controlled-input?) (assoc :default-value @value-atom))]] [token-label diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index 78d0eea8239..6a3240556d8 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -36,22 +36,48 @@ non-numeric? (re-find not-digits-or-dot-pattern (str v))] (not (or non-numeric? extra-dot? extra-leading-zero? length-overflow?)))) +(defn- add-char-to-string + [s c idx] + (let [size (count s)] + (if (= size idx) + (str s c) + (str (subs s 0 idx) + c + (subs s idx size))))) + +(defn- move-input-cursor + ([input-selection-atom new-idx] + (move-input-cursor input-selection-atom new-idx new-idx)) + ([input-selection-atom new-start-idx new-end-idx] + (let [start-idx (if (< new-start-idx 0) 0 new-start-idx) + end-idx (if (< new-end-idx 0) 0 new-start-idx)] + (swap! input-selection-atom assoc :start start-idx :end end-idx)))) + (defn- normalize-input - [current v] - (cond - (and (string/blank? current) (= v dot)) - (str "0" v) + [current v input-selection-atom] + (let [{:keys [start end]} @input-selection-atom] + (if (= start end) + (cond + (and (string/blank? current) (= v dot)) + (do + (move-input-cursor input-selection-atom 2) + (str "0" v)) - (and (= current "0") (not= v dot)) - (str v) + (and (= current "0") (not= v dot)) + (do + (move-input-cursor input-selection-atom 1) + (str v)) - :else - (str current v))) + :else + (do + (move-input-cursor input-selection-atom (inc start)) + (add-char-to-string current v start))) + current))) (defn- make-new-input - [current v] + [current v input-selection-atom] (if (valid-input? current v) - (normalize-input current v) + (normalize-input current v input-selection-atom) current)) (defn- reset-input-error @@ -59,6 +85,11 @@ (reset! input-error (> new-value prev-value))) +(defn delete-from-string + [s idx] + (let [size (count s)] + (str (subs s 0 (dec idx)) (subs s idx size)))) + (defn- f-view-internal ;; crypto-decimals, limit-crypto and initial-crypto-currency? args are needed ;; for component tests only @@ -72,24 +103,28 @@ input-value (reagent/atom "") input-error (reagent/atom false) crypto-currency? (reagent/atom initial-crypto-currency?) + input-selection (reagent/atom {:start 0 :end 0}) 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?) + (when-not loading-routes? + (let [current-value @input-value + new-value (make-new-input current-value v input-selection) + num-value (or (parse-double new-value) 0)] (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))) + (let [{:keys [start end]} @input-selection] + (reset-input-error @input-value current-limit-amount input-error) + (when (= start end) + (swap! input-value delete-from-string start) + (move-input-cursor input-selection (dec start))) + (reagent/flush)))) handle-on-change (fn [v current-limit-amount] (when (valid-input? @input-value v) (let [num-value (or (parse-double v) 0)] @@ -110,7 +145,14 @@ handle-on-confirm (fn [] (rf/dispatch [:wallet/send-select-amount {:amount @input-value - :stack-id :wallet-send-input-amount}]))] + :stack-id :wallet-send-input-amount}])) + selection-change (fn [selection] + ;; `reagent/flush` is needed to properly propagate the + ;; input cursor state. Since this is a controlled + ;; component the cursor will become static if + ;; `reagent/flush` is removed. + (reset! input-selection selection) + (reagent/flush))] (fn [] (let [{fiat-currency :currency} (rf/sub [:profile/profile]) {:keys [color]} (rf/sub [:wallet/current-viewing-account]) @@ -154,23 +196,25 @@ :on-press on-navigate-back :switcher-type :select-account}] [quo/token-input - {:container-style style/input-container - :token token-symbol - :currency current-currency - :crypto-decimals crypto-decimals - :error? @input-error - :networks token-networks - :title (i18n/label :t/send-limit {:limit limit-label}) - :conversion conversion-rate - :show-keyboard? false - :value @input-value - :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})}] + {:container-style style/input-container + :token token-symbol + :currency current-currency + :crypto-decimals crypto-decimals + :error? @input-error + :networks token-networks + :title (i18n/label :t/send-limit {:limit limit-label}) + :conversion conversion-rate + :show-keyboard? false + :value @input-value + :selection @input-selection + :on-change-text #(handle-on-change % current-limit) + :on-selection-change selection-change + :on-swap #(handle-swap + {:crypto? % + :currency current-currency + :token-symbol token-symbol + :limit-fiat fiat-limit + :limit-crypto crypto-limit})}] [routes/view {:amount amount-text :routes suggested-routes