From b6e2e8cba41e40465b18a87ccffd3eb7a89207a2 Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:45:03 -0600 Subject: [PATCH] [#16858] Input text multiline height (#17536) * Fix preview screen receiving wrong parameter * Fix input multiline style on iOS and counter label always showed * Fix icon metadata and wrong values passed to svg icons * Use quo/input in add contact sheet --- src/quo/components/icon.cljs | 56 ++--- src/quo/components/inputs/input/style.cljs | 28 ++- src/quo/components/inputs/input/view.cljs | 57 ++--- .../contexts/add_new_contact/style.cljs | 86 ++----- .../contexts/add_new_contact/views.cljs | 211 +++++++++--------- .../contexts/quo_preview/inputs/input.cljs | 11 +- src/utils/re_frame.cljs | 2 + 7 files changed, 212 insertions(+), 239 deletions(-) diff --git a/src/quo/components/icon.cljs b/src/quo/components/icon.cljs index 6ec2e08d79..cb15ed6bf1 100644 --- a/src/quo/components/icon.cljs +++ b/src/quo/components/icon.cljs @@ -13,37 +13,37 @@ (and (string? color) (not (string/blank? color))))) +(defn- image-icon-style + [{:keys [color no-color size container-style theme]}] + (cond-> {:width size + :height size} + (not no-color) + (assoc :tint-color + (if (and (string? color) (not (string/blank? color))) + color + (colors/theme-colors colors/neutral-100 colors/white theme))) + :always + (merge container-style))) + (defn memo-icon-fn - [{:keys [color color-2 no-color - container-style size accessibility-label theme] - :or {accessibility-label :icon}} + [{:keys [color color-2 container-style size accessibility-label] + :or {accessibility-label :icon} + :as props} icon-name] (let [size (or size 20)] - ^{:key icon-name} - (if-let [svg-icon (icons.svg/get-icon icon-name size)] - [svg-icon - (cond-> {:size size - :accessibility-label accessibility-label - :style container-style} - - (and color (valid-color? color)) - (assoc :color color) - - (and color-2 (valid-color? color-2)) - (assoc :color-2 color-2))] - [rn/image - {:style - (merge {:width size - :height size} - - (when (not no-color) - {:tint-color (if (and (string? color) (not (string/blank? color))) - color - (colors/theme-colors colors/neutral-100 colors/white theme))}) - - container-style) - :accessibility-label accessibility-label - :source (icons/icon-source (str (name icon-name) size))}]))) + (with-meta + (if-let [svg-icon (icons.svg/get-icon icon-name size)] + [svg-icon + (cond-> {:size size + :accessibility-label accessibility-label + :style container-style} + (and color (valid-color? color)) (assoc :color color) + (and color-2 (valid-color? color-2)) (assoc :color-2 color-2))] + [rn/image + {:style (image-icon-style (assoc props :size size)) + :accessibility-label accessibility-label + :source (icons/icon-source (str (name icon-name) size))}]) + {:key icon-name}))) (def ^:private themed-icon (memoize (quo.theme/with-theme memo-icon-fn))) diff --git a/src/quo/components/inputs/input/style.cljs b/src/quo/components/inputs/input/style.cljs index 5f6acece04..584ddc60fb 100644 --- a/src/quo/components/inputs/input/style.cljs +++ b/src/quo/components/inputs/input/style.cljs @@ -70,6 +70,7 @@ :border-radius (if small? 10 12) :opacity (if disabled? 0.3 1)}) + (defn left-icon-container [small?] {:margin-left (if small? 0 4) @@ -84,15 +85,21 @@ (defn input [colors-by-status small? multiple-lines? weight] - (let [base-props (assoc (text/text-style {:size :paragraph-1 :weight (or weight :regular)}) - :flex 1 - :padding-right 0 - :padding-left (if small? 4 8) - :padding-vertical (if small? 4 8) - :color (:text colors-by-status))] + (let [padding (if small? 4 8) + base-props (assoc (text/text-style {:size :paragraph-1 :weight (or weight :regular)}) + :flex 1 + :padding-right 0 + :padding-left padding + :padding-top padding + :padding-bottom padding + :color (:text colors-by-status))] (if multiple-lines? - (assoc base-props :text-align-vertical :top) - (assoc base-props :height (if small? 30 38) :line-height nil)))) + (assoc base-props + :text-align-vertical :top + :line-height 22) + (assoc base-props + :height (if small? 30 38) + :line-height nil)))) (defn right-icon-touchable-area [small?] @@ -107,8 +114,9 @@ (defn clear-icon [variant-colors] - {:size 20 - :color (:clear-icon variant-colors)}) + {:size 20 + :color (:clear-icon variant-colors) + :color-2 colors/white}) (def texts-container {:flex-direction :row diff --git a/src/quo/components/inputs/input/view.cljs b/src/quo/components/inputs/input/view.cljs index b6e5bc69d7..6f9d9411be 100644 --- a/src/quo/components/inputs/input/view.cljs +++ b/src/quo/components/inputs/input/view.cljs @@ -1,31 +1,32 @@ (ns quo.components.inputs.input.view - (:require - [oops.core :as oops] - [quo.components.icon :as icon] - [quo.components.inputs.input.style :as style] - [quo.components.markdown.text :as text] - [quo.theme :as quo.theme] - [react-native.core :as rn] - [reagent.core :as reagent])) + (:require [oops.core :as oops] + [quo.components.icon :as icon] + [quo.components.inputs.input.style :as style] + [quo.components.markdown.text :as text] + [quo.theme :as quo.theme] + [react-native.core :as rn] + [react-native.platform :as platform] + [reagent.core :as reagent])) (defn- label-&-counter [{:keys [label current-chars char-limit variant-colors]}] - (let [count-text (when char-limit (str current-chars "/" char-limit))] - [rn/view - {:accessibility-label :input-labels - :style style/texts-container} - [rn/view {:style style/label-container} - [text/text - {:style (style/label-color variant-colors) - :weight :medium - :size :paragraph-2} - label]] + [rn/view + {:accessibility-label :input-labels + :style style/texts-container} + [rn/view {:style style/label-container} + [text/text + {:style (style/label-color variant-colors) + :weight :medium + :size :paragraph-2} + label]] + (when-let [count-text (some->> char-limit + (str current-chars "/"))] [rn/view {:style style/counter-container} [text/text {:style (style/counter-color current-chars char-limit variant-colors) :weight :regular :size :paragraph-2} - count-text]]])) + count-text]])]) (defn- left-accessory [{:keys [variant-colors small? icon-name]}] @@ -56,16 +57,19 @@ (def ^:private custom-props "Custom properties that must be removed from properties map passed to InputText." [:type :blur? :theme :error? :right-icon :left-icon :disabled? :small? :button - :label :char-limit :on-char-limit-reach :icon-name :multiline? :on-focus :on-blur]) + :label :char-limit :on-char-limit-reach :icon-name :multiline? :on-focus :on-blur + :container-style]) (defn- base-input - [{:keys [on-change-text on-char-limit-reach container-style weight]}] + [{:keys [on-change-text on-char-limit-reach weight]}] (let [status (reagent/atom :default) internal-on-focus #(reset! status :focus) internal-on-blur #(reset! status :default) multiple-lines? (reagent/atom false) - set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height")] - (if (> height 57) + set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height") + ;; In Android height comes with padding + min-height (if platform/android? 40 22)] + (if (> height min-height) (reset! multiple-lines? true) (reset! multiple-lines? false))) char-count (reagent/atom 0) @@ -76,7 +80,7 @@ (when (>= amount-chars char-limit) (on-char-limit-reach amount-chars))))] (fn [{:keys [blur? theme error? right-icon left-icon disabled? small? button - label char-limit multiline? clearable? on-focus on-blur] + label char-limit multiline? clearable? on-focus on-blur container-style] :as props}] (let [status-kw (cond disabled? :disabled @@ -92,8 +96,7 @@ :label label :current-chars @char-count :char-limit char-limit}]) - [rn/view - {:style (style/input-container colors-by-status small? disabled?)} + [rn/view {:style (style/input-container colors-by-status small? disabled?)} (when-let [{:keys [icon-name]} left-icon] [left-accessory {:variant-colors variant-colors @@ -160,7 +163,7 @@ - :icon-name - The name of an icon to display at the left of the input. - :error? - Boolean to specify it this input marks an error. - :disabled? - Boolean to specify if this input is disabled or not. - - :clearable? - Booolean to specify if this input has a clear button at the end. + - :clearable? - Boolean to specify if this input has a clear button at the end. - :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. diff --git a/src/status_im2/contexts/add_new_contact/style.cljs b/src/status_im2/contexts/add_new_contact/style.cljs index c7677c3f7a..cd8def4786 100644 --- a/src/status_im2/contexts/add_new_contact/style.cljs +++ b/src/status_im2/contexts/add_new_contact/style.cljs @@ -1,28 +1,16 @@ (ns status-im2.contexts.add-new-contact.style - (:require - [quo.foundations.colors :as colors] - [quo.foundations.typography :as typography] - [react-native.platform :as platform] - [react-native.safe-area :as safe-area])) + (:require [quo.foundations.colors :as colors] + [react-native.safe-area :as safe-area])) (defn container-outer [] - {:style {:flex 1 - :background-color (colors/theme-colors colors/white colors/neutral-95) - :justify-content :space-between - :align-items :center - :padding-vertical 16 - :padding-horizontal 20 + {:flex 1 + :background-color (colors/theme-colors colors/white colors/neutral-95) + :justify-content :space-between + :align-items :center + :padding-horizontal 20}) - :border-radius 20}}) - -(def container-inner - {:style {:flex-direction :column - :align-items :flex-start}}) - -(def container-text-input - {:style {:flex-direction :row - :justify-content :space-between}}) +(def container-inner {:align-items :flex-start}) (def container-invalid {:style {:flex-direction :row @@ -35,27 +23,21 @@ :weight :semi-bold :style {:margin-top 32 :margin-bottom 6 - :color (colors/theme-colors - colors/neutral-100 - colors/white)}}) + :color (colors/theme-colors colors/neutral-100 colors/white)}}) (defn text-subtitle [] {:size :paragraph-1 :weight :regular :style {:margin-bottom 20 - :color (colors/theme-colors - colors/neutral-100 - colors/white)}}) + :color (colors/theme-colors colors/neutral-100 colors/white)}}) (defn text-description [] {:size :paragraph-2 :weight :medium :style {:margin-bottom 6 - :color (colors/theme-colors - colors/neutral-50 - colors/neutral-40)}}) + :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}) (def icon-invalid {:size 16 @@ -67,46 +49,14 @@ :style {:margin-left 4 :color colors/danger-50}}) -(defn text-input-container - [invalid?] - {:style {:padding-top 1 - :padding-left 12 - :padding-right 7 - :padding-bottom 7 - :margin-right 10 - :flex 1 - :flex-direction :row - :background-color (colors/theme-colors - colors/white - colors/neutral-95) - :border-width 1 - :border-radius 12 - :border-color (if invalid? - colors/danger-50-opa-40 - (colors/theme-colors - colors/neutral-20 - colors/neutral-80))}}) +(def input-and-scan-container + {:flex-direction :row + :align-items :flex-start}) -(defn text-input - [] - {:accessibility-label :enter-contact-code-input - :auto-capitalize :none - :placeholder-text-color (colors/theme-colors - colors/neutral-40 - colors/neutral-50) - :multiline true - :style - (merge typography/monospace - typography/paragraph-1 - {:flex 1 - :margin-right 5 - :margin-top (if platform/android? - 4 - 0) - :padding 0 - :color (colors/theme-colors - colors/black - colors/white)})}) +(def scan-button-container + {:margin-left 12 + :height 66 + :justify-content :flex-end}) (def found-user {:padding-top 16 diff --git a/src/status_im2/contexts/add_new_contact/views.cljs b/src/status_im2/contexts/add_new_contact/views.cljs index 86f6c91810..91154d4f90 100644 --- a/src/status_im2/contexts/add_new_contact/views.cljs +++ b/src/status_im2/contexts/add_new_contact/views.cljs @@ -1,16 +1,15 @@ (ns status-im2.contexts.add-new-contact.views - (:require - [clojure.string :as string] - [quo.core :as quo] - [react-native.clipboard :as clipboard] - [react-native.core :as rn] - [reagent.core :as reagent] - [status-im.qr-scanner.core :as qr-scanner] - [status-im2.contexts.add-new-contact.style :as style] - [utils.address :as address] - [utils.debounce :as debounce] - [utils.i18n :as i18n] - [utils.re-frame :as rf])) + (:require [clojure.string :as string] + [quo.core :as quo] + [react-native.clipboard :as clipboard] + [react-native.core :as rn] + [reagent.core :as reagent] + [status-im.qr-scanner.core :as qr-scanner] + [status-im2.contexts.add-new-contact.style :as style] + [utils.address :as address] + [utils.debounce :as debounce] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) (defn found-contact [public-key] @@ -38,94 +37,104 @@ :style (style/found-user-key)} (address/get-shortened-compressed-key compressed-key)]]]]))) +(defn- header + [] + [:<> + [quo/button + {:type :grey + :icon-only? true + :accessibility-label :new-contact-close-button + :size 32 + :on-press #(rf/dispatch [:navigate-back])} + :i/close] + [quo/text (style/text-title) (i18n/label :t/add-a-contact)] + [quo/text (style/text-subtitle) (i18n/label :t/find-your-friends)]]) + +(defn- search-input + [] + (reagent/with-let [input-value (reagent/atom nil) + input-ref (atom nil) + clear-input (fn [] + (reset! input-value nil) + (rf/dispatch [:contacts/clear-new-identity])) + paste-on-input #(clipboard/get-string + (fn [clipboard-text] + (reset! input-value clipboard-text) + (rf/dispatch [:contacts/set-new-identity clipboard-text nil])))] + (let [{:keys [scanned]} (rf/sub [:contacts/new-identity]) + empty-input? (and (string/blank? @input-value) + (string/blank? scanned))] + [rn/view {:style style/input-and-scan-container} + [quo/input + {:accessibility-label :enter-contact-code-input + :ref #(reset! input-ref %) + :container-style {:flex 1} + :auto-capitalize :none + :multiline? true + :blur-on-submit true + :return-key-type :done + :label (i18n/label :t/ens-or-chat-key) + :placeholder (i18n/label :t/type-some-chat-key) + :clearable? (not empty-input?) + :on-clear clear-input + :button (when empty-input? + {:on-press paste-on-input + :text (i18n/label :t/paste)}) + ;; NOTE: `scanned` has priority over `@input-value`, we clean it when the input + ;; is updated so that it's `nil` and `@input-value` is shown. + ;; To fastly clean it, we use `dispatch-sync`. + ;; This call could be avoided if `::qr-scanner/scan-code` were able to receive a + ;; callback function, not only a re-frame event as callback. + :value (or scanned @input-value) + :on-change-text (fn [new-text] + (reset! input-value new-text) + (as-> [:contacts/set-new-identity new-text nil] $ + (if (string/blank? scanned) + (debounce/debounce-and-dispatch $ 600) + (rf/dispatch-sync $))))}] + [rn/view {:style style/scan-button-container} + [quo/button + {:type :outline + :icon-only? true + :size 40 + :on-press #(rf/dispatch [::qr-scanner/scan-code + {:handler :contacts/qr-code-scanned}])} + :i/scan]]]) + (finally + (rf/dispatch [:contacts/clear-new-identity])))) + +(defn- invalid-text + [message] + [rn/view style/container-invalid + [quo/icon :i/alert style/icon-invalid] + [quo/text style/text-invalid + (i18n/label (or message :t/invalid-ens-or-key))]]) + (defn new-contact [] - (let [clipboard (reagent/atom nil) - default-value (reagent/atom nil)] - (fn [] - (clipboard/get-string #(reset! clipboard %)) - (let [{:keys [input scanned public-key ens state msg]} - (rf/sub [:contacts/new-identity]) - customization-color (rf/sub [:profile/customization-color]) - invalid? (= state :invalid) - show-paste-button? (and (not (string/blank? @clipboard)) - (string/blank? @default-value) - (string/blank? input))] - [rn/keyboard-avoiding-view - {:style {:flex 1}} - [rn/touchable-without-feedback - {:on-press rn/dismiss-keyboard!} - [rn/view (style/container-outer) - [rn/view style/container-inner - [quo/button - {:type :grey - :icon-only? true - :accessibility-label :new-contact-close-button - :size 32 - :on-press - (fn [] - (reset! clipboard nil) - (reset! default-value nil) - (rf/dispatch [:contacts/clear-new-identity]) - (rf/dispatch [:navigate-back]))} :i/close] - [quo/text (style/text-title) - (i18n/label :t/add-a-contact)] - [quo/text (style/text-subtitle) - (i18n/label :t/find-your-friends)] - [quo/text (style/text-description) - (i18n/label :t/ens-or-chat-key)] - [rn/view style/container-text-input - [rn/view (style/text-input-container invalid?) - [rn/text-input - (merge (style/text-input) - {:default-value (or scanned @default-value input) - :placeholder (i18n/label :t/type-some-chat-key) - :on-change-text (fn [v] - (reset! default-value v) - (debounce/debounce-and-dispatch - [:contacts/set-new-identity v nil] - 600)) - :blur-on-submit true - :return-key-type :done})] - (when show-paste-button? - [quo/button - {:type :outline - :size 24 - :container-style {:margin-top 6} - :on-press - (fn [] - (reset! default-value @clipboard) - (rf/dispatch - [:contacts/set-new-identity @clipboard nil]))} - (i18n/label :t/paste)])] - [quo/button - {:type :outline - :icon-only? true - :size 40 - :on-press #(rf/dispatch - [::qr-scanner/scan-code - {:handler :contacts/qr-code-scanned}])} - :i/scan]] - (when invalid? - [rn/view style/container-invalid - [quo/icon :i/alert style/icon-invalid] - [quo/text style/text-invalid - (i18n/label (or msg :t/invalid-ens-or-key))]]) - (when (= state :valid) - [found-contact public-key])] - [quo/button - {:type :primary - :customization-color customization-color - :size 40 - :container-style style/button-view-profile - :accessibility-label :new-contact-button - :icon-left :i/profile - :disabled? (not= state :valid) - :on-press - (fn [] - (reset! clipboard nil) - (reset! default-value nil) - (rf/dispatch [:contacts/clear-new-identity]) - (rf/dispatch [:navigate-back]) - (rf/dispatch [:chat.ui/show-profile public-key ens]))} - (i18n/label :t/view-profile)]]]])))) + (let [{:keys [public-key ens state msg]} (rf/sub [:contacts/new-identity]) + customization-color (rf/sub [:profile/customization-color])] + [rn/keyboard-avoiding-view {:style {:flex 1}} + [rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!} + [rn/view {:style (style/container-outer)} + [rn/view {:style style/container-inner} + [header] + [search-input] + (case state + :invalid [invalid-text msg] + :valid [found-contact public-key] + nil)] + [quo/button + {:type :primary + :customization-color customization-color + :size 40 + :container-style style/button-view-profile + :accessibility-label :new-contact-button + :icon-left :i/profile + :disabled? (not= state :valid) + :on-press (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:chat.ui/show-profile public-key ens]) + (js/setTimeout #(rf/dispatch [:contacts/clear-new-identity]) + 600))} + (i18n/label :t/view-profile)]]]])) diff --git a/src/status_im2/contexts/quo_preview/inputs/input.cljs b/src/status_im2/contexts/quo_preview/inputs/input.cljs index 594d41a022..d4608f9335 100644 --- a/src/status_im2/contexts/quo_preview/inputs/input.cljs +++ b/src/status_im2/contexts/quo_preview/inputs/input.cljs @@ -22,7 +22,7 @@ :type :boolean} {:key :small? :type :boolean} - {:key :multiline + {:key :multiline? :type :boolean} {:key :button :type :boolean} @@ -53,10 +53,11 @@ (fn [] (let [blank-label? (string/blank? (:label @state))] [preview/preview-container - {:state state - :descriptor descriptor - :blur? (:blur? @state) - :show-blur-background? true} + {:component-container-style {:margin-bottom 200} + :state state + :descriptor descriptor + :blur? (:blur? @state) + :show-blur-background? true} [quo/input (cond-> (assoc @state :on-clear? #(swap! state assoc :value "") diff --git a/src/utils/re_frame.cljs b/src/utils/re_frame.cljs index 839a1e7e62..14cc4257ea 100644 --- a/src/utils/re_frame.cljs +++ b/src/utils/re_frame.cljs @@ -81,3 +81,5 @@ (def dispatch re-frame/dispatch) (def reg-fx re-frame/reg-fx) + +(def dispatch-sync re-frame/dispatch-sync)