From 6e4931417b70d3deb3f11227a7a846d61f38d26f Mon Sep 17 00:00:00 2001 From: alwx Date: Fri, 7 Apr 2017 01:09:55 +0300 Subject: [PATCH] Password and phone inputs [chat-ui] --- env/dev/env/android/main.cljs | 2 +- env/dev/env/ios/main.cljs | 2 +- externs/externs.js | 5 +- resources/console.js | 59 ++++--- resources/status.js | 1 + src/status_im/chat/handlers/input.cljs | 153 ++++++++++++------ src/status_im/chat/models/input.cljs | 114 ++++++------- src/status_im/chat/models/password_input.cljs | 48 ------ src/status_im/chat/styles/input/input.cljs | 16 +- src/status_im/chat/subs.cljs | 10 +- src/status_im/chat/views/input/input.cljs | 125 ++++++++------ 11 files changed, 293 insertions(+), 242 deletions(-) delete mode 100644 src/status_im/chat/models/password_input.cljs diff --git a/env/dev/env/android/main.cljs b/env/dev/env/android/main.cljs index cc3b4f7493..f2554e135d 100644 --- a/env/dev/env/android/main.cljs +++ b/env/dev/env/android/main.cljs @@ -19,6 +19,6 @@ :heads-up-display false :jsload-callback callback) -(rr/enable-re-frisk-remote!) +(rr/enable-re-frisk-remote! {:host "10.0.2.2:4567"}) (core/init) diff --git a/env/dev/env/ios/main.cljs b/env/dev/env/ios/main.cljs index 276a16026d..0a66fc0d23 100644 --- a/env/dev/env/ios/main.cljs +++ b/env/dev/env/ios/main.cljs @@ -16,6 +16,6 @@ :heads-up-display false :jsload-callback #(swap! cnt inc)) -(rr/enable-re-frisk-remote!) +(rr/enable-re-frisk-remote! {:host "localhost:4567"}) (core/init) \ No newline at end of file diff --git a/externs/externs.js b/externs/externs.js index 2e75a0638f..de0491a06d 100644 --- a/externs/externs.js +++ b/externs/externs.js @@ -1,9 +1,11 @@ var TopLevel = { +"abs" : function () {}, "ActionSheetIOS" : function () {}, "addEntropy" : function () {}, "addEventListener" : function () {}, "addListener" : function () {}, "addOrientationListener" : function () {}, +"addSymKey" : function () {}, "Alert" : function () {}, "alert" : function () {}, "Animated" : function () {}, @@ -81,13 +83,13 @@ var TopLevel = { "goog" : function () {}, "guid" : function () {}, "hash" : function () {}, +"hasSymKey" : function () {}, "headers" : function () {}, "height" : function () {}, "hex" : function () {}, "hide" : function () {}, "HttpProvider" : function () {}, "IBGLog" : function () {}, -"indexOf" : function () {}, "initialPage" : function () {}, "initJail" : function () {}, "isAddress" : function () {}, @@ -196,6 +198,7 @@ var TopLevel = { "toAscii" : function () {}, "toBits" : function () {}, "toDecimal" : function () {}, +"toHex" : function () {}, "toLocaleString" : function () {}, "toLowerCase" : function () {}, "toNumber" : function () {}, diff --git a/resources/console.js b/resources/console.js index 4b5fcd7d57..6a945e7ba3 100644 --- a/resources/console.js +++ b/resources/console.js @@ -10,7 +10,7 @@ I18n.translations = { password_description: 'Password', password_placeholder: 'Type your password', - password_placeholder2: 'Please re-enter password to confirm', + password_placeholder2: 'Confirm', password_error: 'Password should be not less then 6 symbols.', password_error1: 'Password confirmation doesn\'t match password.', password_validation_title: 'Password', @@ -1653,6 +1653,7 @@ var phoneConfig = { color: "#5bb2a2", title: I18n.t('phone_title'), description: I18n.t('phone_description'), + sequentialParams: true, validator: function (params) { return { validationHandler: "phone", @@ -1872,32 +1873,40 @@ status.response({ color: "#7099e6", description: I18n.t('password_description'), icon: "lock_white", - params: [{ - name: "password", - type: status.types.PASSWORD, - placeholder: I18n.t('password_placeholder'), - hidden: true - }, { - name: "password_confirmation", - type: status.types.PASSWORD, - placeholder: "Confirm", - hidden: true - }], + sequentialParams: true, + params: [ + { + name: "password", + type: status.types.PASSWORD, + placeholder: I18n.t('password_placeholder'), + hidden: true + }, + { + name: "password-confirmation", + type: status.types.PASSWORD, + placeholder: I18n.t('password_placeholder2'), + hidden: true + } + ], validator: function (params, context) { - if (params["password_confirmation"] != params["password"]) { - var error = status.components.validationMessage( - I18n.t('password_validation_title'), - I18n.t('password_error1') - ); - return {markup: error}; - } - if (params.password.length < 6) { - var error = status.components.validationMessage( - I18n.t('password_validation_title'), - I18n.t('password_error') - ); - return {markup: error}; + if (!params.hasOwnProperty("password-confirmation") || params["password-confirmation"].length === 0) { + if (params.password === null || params.password.length < 6) { + var error = status.components.validationMessage( + I18n.t('password_validation_title'), + I18n.t('password_error') + ); + return {markup: error}; + } + } else { + if (params.password !== params["password-confirmation"]) { + var error = status.components.validationMessage( + I18n.t('password_validation_title'), + I18n.t('password_error1') + ); + return {markup: error}; + } } + }, preview: function (params, context) { var style = { diff --git a/resources/status.js b/resources/status.js index 00a04e61b8..5cda037319 100644 --- a/resources/status.js +++ b/resources/status.js @@ -36,6 +36,7 @@ Command.prototype.create = function (com) { this["on-send"] = com.onSend; this.fullscreen = com.fullscreen; this.request = com.request; + this["sequential-params"] = com.sequentialParams; this.addToCatalog(); return this; diff --git a/src/status_im/chat/handlers/input.cljs b/src/status_im/chat/handlers/input.cljs index cd69500c54..a5d19cdfb0 100644 --- a/src/status_im/chat/handlers/input.cljs +++ b/src/status_im/chat/handlers/input.cljs @@ -3,7 +3,6 @@ [taoensso.timbre :as log] [status-im.chat.constants :as const] [status-im.chat.models.input :as input-model] - [status-im.chat.models.password-input :as password-input] [status-im.chat.models.suggestions :as suggestions] [status-im.components.react :as react-comp] [status-im.components.status :as status] @@ -16,27 +15,17 @@ (handlers/register-handler :set-chat-input-text (fn [{:keys [current-chat-id chats chat-ui-props] :as db} [_ text chat-id]] - (let [chat-id (or chat-id current-chat-id) - selection (get-in chat-ui-props [chat-id :selection])] + (let [chat-id (or chat-id current-chat-id) + ends-with-space? (input-model/text-ends-with-space? text)] (dispatch [:update-suggestions chat-id text]) (if-let [{command :command} (input-model/selected-chat-command db chat-id text)] (let [{old-args :args} (input-model/selected-chat-command db chat-id) - text-splitted (input-model/split-command-args text) - new-args (rest text-splitted) - modifiers (input-model/add-modifiers (:params command) new-args) - addition (if (input-model/text-ends-with-space? text) - const/spacing-char) - new-params {:modified-text (str (input-model/apply-modifiers text-splitted modifiers) - addition) - :input-text (str (input-model/make-input-text modifiers - text-splitted - old-args - selection) - addition)}] - (update-in db [:chats chat-id] merge new-params)) - (update-in db [:chats chat-id] merge {:input-text text - :modified-text nil}))))) + text-splitted (input-model/split-command-args text) + new-args (rest text-splitted) + new-input-text (input-model/make-input-text text-splitted old-args)] + (assoc-in db [:chats chat-id :input-text] new-input-text)) + (assoc-in db [:chats chat-id :input-text] text))))) (handlers/register-handler :add-to-chat-input-text @@ -58,7 +47,11 @@ (dispatch [:set-chat-ui-props :result-box nil]) (dispatch [:set-chat-ui-props :validation-messages nil]) (dispatch [:load-chat-parameter-box command 0]) - (dispatch [:chat-input-focus])))) + (if (:sequential-params command) + (js/setTimeout + #(dispatch [:chat-input-focus :seq-input-ref]) + 100) + (dispatch [:chat-input-focus :input-ref]))))) (handlers/register-handler :set-chat-input-metadata @@ -72,22 +65,29 @@ (fn [{:keys [current-chat-id] :as db} [_ [index arg]]] (let [command (-> (get-in db [:chats current-chat-id :input-text]) (input-model/split-command-args)) - command-name (first command) - command-args (into [] (rest command)) - command-args (if (< index (count command-args)) - (assoc command-args index arg) - (conj command-args arg))] - (dispatch [:set-chat-input-text (str command-name - const/spacing-char - (input-model/join-command-args command-args) - const/spacing-char)]))))) + seq-params? (-> (input-model/selected-chat-command db current-chat-id) + (get-in [:command :sequential-params]))] + (if seq-params? + (dispatch [:set-chat-seq-arg-input-text arg]) + (let [command-name (first command) + command-args (into [] (rest command)) + command-args (if (< index (count command-args)) + (assoc command-args index arg) + (conj command-args arg))] + (dispatch [:set-chat-input-text (str command-name + const/spacing-char + (input-model/join-command-args command-args) + const/spacing-char)]))))))) (handlers/register-handler :chat-input-focus (handlers/side-effect! - (fn [{:keys [current-chat-id chat-ui-props] :as db}] - (when-let [ref (get-in chat-ui-props [current-chat-id :input-ref])] - (.focus ref))))) + (fn [{:keys [current-chat-id chat-ui-props] :as db} [_ ref]] + (try + (when-let [ref (get-in chat-ui-props [current-chat-id ref])] + (.focus ref)) + (catch :default e + (log/debug "Cannot focus the reference")))))) (handlers/register-handler :update-suggestions @@ -152,34 +152,35 @@ ::proceed-command (handlers/side-effect! (fn [db [_ command chat-id]] - (dispatch [::request-command-data - {:command command - :chat-id chat-id - :data-type :validator - :after #(dispatch [::proceed-validation-messages command chat-id %2])}])))) + (let [after-validation #(dispatch [::request-command-data + {:command command + :chat-id chat-id + :data-type :on-send + :after (fn [_ res] + (dispatch [::send-command res command chat-id]))}])] + (dispatch [::request-command-data + {:command command + :chat-id chat-id + :data-type :validator + :after #(dispatch [::proceed-validation-messages + command chat-id %2 after-validation])}]))))) (handlers/register-handler ::proceed-validation-messages (handlers/side-effect! - (fn [db [_ command chat-id {:keys [markup validationHandler parameters] :as errors}]] + (fn [db [_ command chat-id {:keys [markup validationHandler parameters] :as errors} proceed-fn]] (let [set-errors #(do (dispatch [:set-chat-ui-props :validation-messages %]) - (dispatch [:set-chat-ui-props :sending-in-progress? false])) - proceed #(dispatch [::request-command-data - {:command command - :chat-id chat-id - :data-type :on-send - :after (fn [_ res] - (dispatch [::send-command res command chat-id]))}])] + (dispatch [:set-chat-ui-props :sending-in-progress? false]))] (cond markup (set-errors markup) validationHandler - (do (dispatch [::execute-validation-handler validationHandler parameters set-errors proceed]) + (do (dispatch [::execute-validation-handler validationHandler parameters set-errors proceed-fn]) (dispatch [:set-chat-ui-props :sending-in-progress? false])) :default - (proceed)))))) + (proceed-fn)))))) (handlers/register-handler ::execute-validation-handler @@ -236,11 +237,19 @@ :send-current-message (handlers/side-effect! (fn [{:keys [current-chat-id] :as db} [_ chat-id]] + (dispatch [:set-chat-ui-props :sending-in-progress? true]) (let [chat-id (or chat-id current-chat-id) - chat-command (input-model/selected-chat-command db chat-id)] - (if chat-command + chat-command (input-model/selected-chat-command db chat-id) + seq-command? (get-in chat-command [:command :sequential-params]) + chat-command (if seq-command? + (let [args (get-in db [:chats chat-id :seq-arguments])] + (assoc chat-command :args args)) + (update chat-command :args #(remove str/blank? %)))] + (if (:command chat-command) (if (= :complete (input-model/command-completion chat-command)) - (dispatch [::proceed-command chat-command chat-id]) + (do + (dispatch [::proceed-command chat-command chat-id]) + (dispatch [:clear-seq-arguments chat-id])) (let [text (get-in db [:chats chat-id :input-text])] (dispatch [:set-chat-ui-props :sending-in-progress? false]) (when-not (input-model/text-ends-with-space? text) @@ -261,4 +270,48 @@ params (fn [{:keys [result] :as data}] (dispatch [:suggestions-handler {:chat-id chat-id - :result data}]))))))) \ No newline at end of file + :result data}]))))))) + +(handlers/register-handler + :clear-seq-arguments + (fn [{:keys [current-chat-id chats] :as db} [_ chat-id]] + (let [chat-id (or chat-id current-chat-id)] + (-> db + (assoc-in [:chats chat-id :seq-arguments] []) + (assoc-in [:chats chat-id :seq-argument-input-text] nil))))) + +(handlers/register-handler + :update-seq-arguments + (fn [{:keys [current-chat-id chats] :as db} [_ chat-id]] + (let [chat-id (or chat-id current-chat-id) + text (get-in chats [chat-id :seq-argument-input-text])] + (-> db + (update-in [:chats chat-id :seq-arguments] #(into [] (conj % text))) + (assoc-in [:chats chat-id :seq-argument-input-text] nil))))) + +(handlers/register-handler + :send-seq-argument + (handlers/side-effect! + (fn [{:keys [current-chat-id chats] :as db} [_ chat-id]] + (let [chat-id (or chat-id current-chat-id) + text (get-in chats [chat-id :seq-argument-input-text]) + seq-arguments (get-in db [:chats chat-id :seq-arguments]) + command (-> (input-model/selected-chat-command db chat-id) + (assoc :args (into [] (conj seq-arguments text)))) + args (get-in chats [chat-id :seq-arguments]) + after-validation #(do + (dispatch [:update-seq-arguments chat-id]) + (dispatch [:send-current-message]))] + (dispatch [::request-command-data + {:command command + :chat-id chat-id + :data-type :validator + :after #(do + (dispatch [::proceed-validation-messages + command chat-id %2 after-validation]))}]))))) + +(handlers/register-handler + :set-chat-seq-arg-input-text + (fn [{:keys [current-chat-id] :as db} [_ text chat-id]] + (let [chat-id (or chat-id current-chat-id)] + (assoc-in db [:chats chat-id :seq-argument-input-text] text)))) \ No newline at end of file diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index d1c78672f7..b933d74ae3 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -2,15 +2,15 @@ (:require [clojure.string :as str] [status-im.components.react :as rc] [status-im.chat.constants :as const] - [status-im.chat.models.password-input :as password-input] [status-im.chat.views.input.validation-messages :refer [validation-message]] [status-im.i18n :as i18n] [status-im.utils.phone-number :as phone-number] [taoensso.timbre :as log])) (defn text-ends-with-space? [text] - (= (str/last-index-of text const/spacing-char) - (dec (count text)))) + (when text + (= (str/last-index-of text const/spacing-char) + (dec (count text))))) (defn possible-chat-actions [db chat-id] (let [{:keys [commands requests]} (get-in db [:chats chat-id]) @@ -25,22 +25,27 @@ (into commands responses))) (defn split-command-args [command-text] - (let [splitted (str/split command-text const/spacing-char)] - (first - (reduce (fn [[list command-started?] arg] - (let [quotes-count (count (filter #(= % const/arg-wrapping-char) arg)) - has-quote? (and (= quotes-count 1) - (str/index-of arg const/arg-wrapping-char)) - arg (str/replace arg #"\"" "") - new-list (if command-started? - (let [index (dec (count list))] - (update list index str const/spacing-char arg)) - (conj list arg)) - command-continues? (or (and command-started? (not has-quote?)) - (and (not command-started?) has-quote?))] - [new-list command-continues?])) - [[] false] - splitted)))) + (let [space? (text-ends-with-space? command-text) + command-text (if space? + (str command-text ".") + command-text) + splitted (cond-> (str/split command-text const/spacing-char) + space? (drop-last))] + (->> splitted + (reduce (fn [[list command-started?] arg] + (let [quotes-count (count (filter #(= % const/arg-wrapping-char) arg)) + has-quote? (and (= quotes-count 1) + (str/index-of arg const/arg-wrapping-char)) + arg (str/replace arg #"\"" "") + new-list (if command-started? + (let [index (dec (count list))] + (update list index str const/spacing-char arg)) + (conj list arg)) + command-continues? (or (and command-started? (not has-quote?)) + (and (not command-started?) has-quote?))] + [new-list command-continues?])) + [[] false]) + (first)))) (defn join-command-args [args] (->> args @@ -54,6 +59,7 @@ ([{:keys [current-chat-id] :as db} chat-id input-text] (let [chat-id (or chat-id current-chat-id) input-metadata (get-in db [:chats chat-id :input-metadata]) + seq-arguments (get-in db [:chats chat-id :seq-arguments]) possible-actions (possible-chat-actions db chat-id) command-args (split-command-args input-text) command-name (first command-args)] @@ -66,24 +72,33 @@ :metadata (if (not= :any to-message-id) (assoc input-metadata :to-message-id to-message-id) input-metadata) - :args (remove empty? (rest command-args))})))) + :args (if (empty? seq-arguments) + (rest command-args) + seq-arguments)})))) ([{:keys [current-chat-id] :as db} chat-id] (selected-chat-command db chat-id (get-in db [:chats chat-id :input-text])))) (defn current-chat-argument-position - [{:keys [args] :as command} input-text] + [{:keys [args] :as command} input-text seq-arguments] (if command - (let [current (count args)] - (if (= (last input-text) const/spacing-char) - current - (dec current))) + (let [args-count (count args)] + (cond + (:sequential-params command) + (count seq-arguments) + + (= (last input-text) const/spacing-char) + args-count + + :default + (dec args-count))) -1)) (defn argument-position [{:keys [current-chat-id] :as db} chat-id] (let [chat-id (or chat-id current-chat-id) input-text (get-in db [:chats chat-id :input-text]) + seq-arguments (get-in db [:chats chat-id :seq-arguments]) chat-command (selected-chat-command db chat-id)] - (current-chat-argument-position chat-command input-text))) + (current-chat-argument-position chat-command input-text seq-arguments))) (defn command-completion ([{:keys [current-chat-id] :as db} chat-id] @@ -92,7 +107,8 @@ chat-command (selected-chat-command db chat-id)] (command-completion chat-command))) ([{:keys [args] :as chat-command}] - (let [params (get-in chat-command [:command :params]) + (let [args (remove str/blank? args) + params (get-in chat-command [:command :params]) required-params (remove :optional params)] (if chat-command (cond @@ -134,30 +150,6 @@ {:title (i18n/label :t/phone-number) :description (i18n/label :t/invalid-phone)}])))) -(def text-modifiers - [password-input/modifier]) - -(defn add-modifiers [params new-args] - (->> new-args - (map-indexed (fn [i arg] - {:position i - :value arg - :modifier (-> (filter - (fn [mod] - ((:execute-when mod) (get params i))) - text-modifiers) - (first))})) - (into []))) - -(defn apply-modifiers [text-splitted args] - (if-let [{:keys [position modifier]} (first args)] - (if modifier - (let [{:keys [get-modified-text]} modifier - modified-text (get-modified-text text-splitted position)] - (apply-modifiers modified-text (rest args))) - (apply-modifiers text-splitted (rest args))) - (str/join const/spacing-char text-splitted))) - (defn- changed-arg-position [xs ys] (let [longest (into [] (max-key count xs ys)) shortest (into [] (if (= longest xs) ys xs))] @@ -170,19 +162,15 @@ (remove nil?) (first)))) -(defn make-input-text [modifiers [command & args] old-args selection] - (let [arg-pos (changed-arg-position args old-args) - modifier (get-in modifiers [arg-pos :modifier]) - new-arg (if (and arg-pos modifier) - (let [{:keys [make-change]} modifier] - (make-change {:command-name (subs command 1) - :old-args old-args - :new-args args - :arg-pos arg-pos - :selection selection})) - (get (into [] args) arg-pos)) +(defn make-input-text [[command & args] old-args] + (let [args (into [] args) + old-args (into [] old-args) + + arg-pos (changed-arg-position args old-args) + new-arg (get args arg-pos) new-args (if arg-pos - (assoc (into [] old-args) arg-pos (when new-arg (str/trim new-arg))) + (assoc old-args arg-pos (when new-arg + (str/trim new-arg))) old-args)] (str command diff --git a/src/status_im/chat/models/password_input.cljs b/src/status_im/chat/models/password_input.cljs deleted file mode 100644 index d094d64dd5..0000000000 --- a/src/status_im/chat/models/password_input.cljs +++ /dev/null @@ -1,48 +0,0 @@ -(ns status-im.chat.models.password-input - (:require [status-im.chat.constants :as const] - [clojure.string :as str] - [taoensso.timbre :as log])) - -(defn- get-modified-text [text arg-pos] - (let [hide-fn #(apply str (repeat (count %) const/masking-char)) - updated-text (update text (inc arg-pos) hide-fn)] - updated-text)) - -(defn- get-change [{:keys [command-name old-args new-args arg-pos selection]}] - (let [old-args (into [] old-args) - new-args (into [] new-args) - modification (- (count (get new-args arg-pos)) - (count (get old-args arg-pos))) - type (if (> modification 0) :added :removed) - position (-> (:start selection) - (- (inc (count command-name))) - (- (count (str/join const/spacing-char (take arg-pos old-args)))) - (- modification) - (- (if (= arg-pos 0) 0 1))) - position (if (= :added type) position (inc position)) - symbols-count (.abs js/Math modification)] - {:type type - :position position - :symbols (when (= :added type) - (subs (get new-args arg-pos) - position - (+ position symbols-count)))})) - -(defn- make-change [{:keys [command-name old-args new-args arg-pos selection] :as args}] - (let [{:keys [type position symbols] :as c} (get-change args) - make-change #(if (= type :added) - (str (if % (subs % 0 position) "") - symbols - (if % (subs % position) "")) - (str (if % (subs % 0 position) "") - (if % (subs % (+ 1 position (count symbols))) ""))) - args (if (= (count old-args) 0) - [const/spacing-char] - (into [] old-args)) - updated-args (update args arg-pos make-change)] - (make-change (get args arg-pos)))) - -(def modifier - {:execute-when :hidden - :make-change make-change - :get-modified-text get-modified-text}) \ No newline at end of file diff --git a/src/status_im/chat/styles/input/input.cljs b/src/status_im/chat/styles/input/input.cljs index d30b8be928..019e836a43 100644 --- a/src/status_im/chat/styles/input/input.cljs +++ b/src/status_im/chat/styles/input/input.cljs @@ -70,10 +70,22 @@ :text-align-vertical :center :height min-input-height :align-items :center - :android {:left (+ 18 left) + :android {:left (+ 15 left) :top -1} :ios {:line-height min-input-height - :left (+ 15 left)}}) + :left (+ 9 left)}}) + +(defnstyle input-password-text [left] + {:min-width 200 + :font-size 14 + :position :absolute + :text-align-vertical :center + :height min-input-height + :align-items :center + :android {:left (+ 15 left) + :top 0.5} + :ios {:line-height min-input-height + :left (+ 9 left)}}) (def input-emoji-icon {:margin-top 7 diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index ecb45c287d..559664da86 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -24,7 +24,6 @@ (reaction))])) (into {})))) - (register-sub :chat-ui-props (fn [db [_ ui-element chat-id]] @@ -87,11 +86,12 @@ (register-sub :current-chat-argument-position (fn [db [_ chat-id]] - (let [chat-id (or chat-id (@db :current-chat-id)) - command (subscribe [:selected-chat-command chat-id]) - input-text (subscribe [:chat :input-text chat-id])] + (let [chat-id (or chat-id (@db :current-chat-id)) + command (subscribe [:selected-chat-command chat-id]) + input-text (subscribe [:chat :input-text chat-id]) + seq-arguments (subscribe [:chat :seq-arguments chat-id])] (reaction - (input-model/current-chat-argument-position @command @input-text))))) + (input-model/current-chat-argument-position @command @input-text @seq-arguments))))) (register-sub :chat-parameter-box diff --git a/src/status_im/chat/views/input/input.cljs b/src/status_im/chat/views/input/input.cljs index 0c028f8244..950ffdc3c8 100644 --- a/src/status_im/chat/views/input/input.cljs +++ b/src/status_im/chat/views/input/input.cljs @@ -52,36 +52,8 @@ ^{:key command-key} [command-view index command])]]]) -(defn- invisible-input [{:keys [set-layout-width value]}] - (let [input-text (subscribe [:chat :input-text]) - modified-text (subscribe [:chat :modified-text])] - [text {:style style/invisible-input-text - :on-layout #(let [w (-> (.-nativeEvent %) - (.-layout) - (.-width))] - (set-layout-width w))} - (utils/safe-trim (or @modified-text @input-text ""))])) - -(defn- input-helper [_] - (let [input-text (subscribe [:chat :input-text])] - (fn [{:keys [command width]}] - (when-let [placeholder (cond - (= @input-text const/command-char) - (i18n/label :t/type-a-command) - - (and command (empty? (:args command))) - (get-in command [:command :params 0 :placeholder]) - - (and command - (= (count (:args command)) 1) - (input-model/text-ends-with-space? @input-text)) - (get-in command [:command :params 1 :placeholder]))] - [text {:style (style/input-helper-text width)} - placeholder])))) - -(defn- text-field [_] +(defn- basic-text-input [_] (let [input-text (subscribe [:chat :input-text]) - modified-text (subscribe [:chat :modified-text]) command (subscribe [:selected-chat-command]) sending-in-progress? (subscribe [:chat-ui-props :sending-in-progress?])] (fn [{:keys [set-layout-height height]}] @@ -90,7 +62,7 @@ :accessibility-label id/chat-message-input :blur-on-submit false :multiline true - :default-value (or @modified-text @input-text "") + :default-value (or @input-text "") :editable (not @sending-in-progress?) :on-blur #(do (dispatch [:set-chat-ui-props :input-focused? false]) (set-layout-height 0)) @@ -107,21 +79,73 @@ (set-layout-height h)) :on-selection-change #(let [s (-> (.-nativeEvent %) (.-selection))] - ;; This will be a bit tricky: - ;; iOS calls onSelectionChange BEFORE onChangeText, while - ;; Android calls onChange text first. - ;; We need some consistency, so we call the callback with a small delay: - (js/setTimeout - (fn [] (dispatch [:set-chat-ui-props :selection {:start (.-start s) - :end (.-end s)}])) - 20)) - :on-submit-editing #(do (dispatch [:set-chat-ui-props :sending-in-progress? true]) - (dispatch [:send-current-message])) + (when (and (= (.-end s) 10) + (get command [:command :sequential-params])) + (dispatch [:chat-input-focus :seq-input-ref]))) + :on-submit-editing #(dispatch [:send-current-message]) :on-focus #(do (dispatch [:set-chat-ui-props :input-focused? true]) (dispatch [:set-chat-ui-props :show-emoji? false])) :style (style/input-view height) :placeholder-text-color style/color-input-helper-placeholder}]))) +(defn- invisible-input [{:keys [set-layout-width value]}] + (let [input-text (subscribe [:chat :input-text])] + [text {:style style/invisible-input-text + :on-layout #(let [w (-> (.-nativeEvent %) + (.-layout) + (.-width))] + (set-layout-width w))} + (or @input-text "")])) + +(defn- input-helper [_] + (let [input-text (subscribe [:chat :input-text])] + (fn [{:keys [command width]}] + (when-not (get-in command [:command :sequential-params]) + (let [real-args (remove str/blank? (:args command))] + (when-let [placeholder (cond + (= @input-text const/command-char) + (i18n/label :t/type-a-command) + + (and command (empty? real-args)) + (get-in command [:command :params 0 :placeholder]) + + (and command + (= (count real-args) 1) + (input-model/text-ends-with-space? @input-text)) + (get-in command [:command :params 1 :placeholder]))] + [text {:style (style/input-helper-text width)} + placeholder])))))) + +(defn get-options [type] + (case (keyword type) + :phone {:keyboard-type "phone-pad"} + :password {:secure-text-entry true} + :number {:keyboard-type "numeric"} + nil)) + +(defn- seq-input [_] + (let [command (subscribe [:selected-chat-command]) + arg-pos (subscribe [:current-chat-argument-position]) + seq-arg-input-text (subscribe [:chat :seq-argument-input-text])] + (fn [{:keys [command-width]}] + (when (get-in @command [:command :sequential-params]) + (let [{:keys [placeholder hidden type]} (get-in @command [:command :params @arg-pos])] + [text-input (merge {:ref #(dispatch [:set-chat-ui-props :seq-input-ref %]) + :style (style/input-password-text command-width) + :default-value (or @seq-arg-input-text "") + :on-change-text #(do (dispatch [:set-chat-seq-arg-input-text %]) + (dispatch [:set-chat-ui-props :validation-messages nil])) + :secure-text-entry hidden + :placeholder placeholder + :blur-on-submit false + :on-submit-editing (fn [] + (when-not (str/blank? @seq-arg-input-text) + (dispatch [:send-seq-argument])) + (js/setTimeout + #(dispatch [:chat-input-focus :seq-input-ref]) + 100))} + (get-options type))]))))) + (defn input-view [_] (let [component (r/current-component) set-layout-width #(r/set-state component {:width %}) @@ -137,11 +161,12 @@ (let [{:keys [width height]} (r/state component) command @command] [animated-view {:style (style/input-root height anim-margin)} - [text-field {:set-layout-height set-layout-height - :height height}] + [basic-text-input {:set-layout-height set-layout-height + :height height}] [invisible-input {:set-layout-width set-layout-width}] [input-helper {:command command :width width}] + [seq-input {:command-width width}] (if-not command [touchable-highlight {:on-press #(do (dispatch [:toggle-chat-ui-props :show-emoji?]) @@ -152,21 +177,29 @@ {:on-press #(do (dispatch [:set-chat-input-text nil]) (dispatch [:set-chat-input-metadata nil]) (dispatch [:set-chat-ui-props :result-box nil]) - (dispatch [:set-chat-ui-props :validation-messages nil]))} + (dispatch [:set-chat-ui-props :validation-messages nil]) + (dispatch [:clear-seq-arguments]))} [view style/input-clear-container [icon :close_gray style/input-clear-icon]]])]))}))) (defview input-container [{:keys [anim-margin]}] [command-completion [:command-completion] selected-command [:selected-chat-command] - input-text [:chat :input-text]] + input-text [:chat :input-text] + seq-arg-input-text [:chat :seq-argument-input-text]] [view style/input-container [input-view {:anim-margin anim-margin}] (when (and (not (str/blank? input-text)) (or (not selected-command) (some #{:complete :less-than-needed} [command-completion]))) - [touchable-highlight {:on-press #(do (dispatch [:set-chat-ui-props :sending-in-progress? true]) - (dispatch [:send-current-message]))} + [touchable-highlight {:on-press #(if (get-in selected-command [:command :sequential-params]) + (do + (when-not (str/blank? seq-arg-input-text) + (dispatch [:send-seq-argument])) + (js/setTimeout + (fn [] (dispatch [:chat-input-focus :seq-input-ref])) + 100)) + (dispatch [:send-current-message]))} [view style/send-message-container [icon :arrow_top style/send-message-icon]]])])