From 7c3c3d80086acf86cb851371af676090b696e566 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Thu, 7 Jul 2016 19:09:16 +0300 Subject: [PATCH] validation handlers & views --- resources/commands.js | 18 +- resources/status.js | 9 +- src/status_im/android/core.cljs | 4 +- src/status_im/chat/handlers.cljs | 103 +--------- src/status_im/chat/handlers/animation.cljs | 2 +- src/status_im/chat/handlers/commands.cljs | 186 ++++++++++++++++++ .../chat/styles/command_validation.cljs | 1 + src/status_im/chat/subs.cljs | 10 + src/status_im/chat/views/command.cljs | 3 +- .../chat/views/command_validation.cljs | 30 +++ src/status_im/chat/views/message_input.cljs | 14 +- src/status_im/chat/views/new_message.cljs | 3 +- src/status_im/models/commands.cljs | 8 +- src/status_im/utils/handlers.cljs | 2 +- 14 files changed, 268 insertions(+), 125 deletions(-) create mode 100644 src/status_im/chat/handlers/commands.cljs create mode 100644 src/status_im/chat/styles/command_validation.cljs create mode 100644 src/status_im/chat/views/command_validation.cljs diff --git a/resources/commands.js b/resources/commands.js index 1191fdd311..7c317eae6e 100644 --- a/resources/commands.js +++ b/resources/commands.js @@ -3,7 +3,7 @@ status.command({ description: "Send location", color: "#9a5dcf", preview: function (params) { - var text = status.components.text( + var text = status.components.text( { style: { marginTop: 5, @@ -138,6 +138,12 @@ status.response({ name: "phone", description: "Send phone number", color: "#5fc48d", + validator: function (params) { + return { + validationHandler: "phone", + parameters: [params.value] + }; + }, params: [{ name: "phone", type: status.types.PHONE, @@ -156,6 +162,16 @@ status.command({ name: "help", description: "Help", color: "#7099e6", + /* Validator example + validator: function (params) { + if (params.value != "3") { + var error = status.components.view( + {backgroundColor: "red"}, + [status.components.text({}, "ooops :(")] + ); + return {errors: [error]} + } + },*/ params: [{ name: "query", type: status.types.TEXT diff --git a/resources/status.js b/resources/status.js index 2eec9a3d44..b9d73021c4 100644 --- a/resources/status.js +++ b/resources/status.js @@ -22,6 +22,7 @@ Command.prototype.create = function (com) { this.name = com.name; this.description = com.description; this.handler = com.handler; + this.validator = com.validator; this.color = com.color; this.icon = com.icon; this.params = com.params || []; @@ -83,13 +84,13 @@ function scrollView(options, elements) { } var status = { - command: function (n, d, h) { + command: function (h) { var command = new Command(); - return command.create(n, d, h); + return command.create(h); }, - response: function (n, d, h) { + response: function (h) { var response = new Response(); - return response.create(n, d, h); + return response.create(h); }, types: { TEXT: 'text', diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index f205c1e497..9288405b76 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -54,11 +54,11 @@ "keyboardDidShow" (fn [e] (let [h (.. e -endCoordinates -height)] - (when-not (= h keyboard-height) + (when-not (= h @keyboard-height) (dispatch [:set :keyboard-height h]))))) (.addListener device-event-emitter "keyboardDidHide" - (when-not (= 0 keyboard-height) + (when-not (= 0 @keyboard-height) #(dispatch [:set :keyboard-height 0])))) :render (fn [] diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 97df9c4281..09290db99e 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -21,10 +21,10 @@ [status-im.utils.phone-number :refer [format-phone-number]] [status-im.utils.datetime :as time] [status-im.components.react :refer [geth]] - [status-im.utils.logging :as log] [status-im.components.jail :as j] [status-im.utils.types :refer [json->clj]] - [status-im.commands.utils :refer [generate-hiccup]])) + [status-im.commands.utils :refer [generate-hiccup]] + status-im.chat.handlers.commands)) (register-handler :set-show-actions (fn [db [_ show-actions]] @@ -53,81 +53,15 @@ (assoc-in [:chats current-chat-id :command-input] {}) (update-in [:chats current-chat-id :input-text] safe-trim)))) -(defn invoke-suggestions-handler! - [{:keys [current-chat-id canceled-command] :as db} _] - (when-not canceled-command - (let [{:keys [command content]} (get-in db [:chats current-chat-id :command-input]) - {:keys [name type]} command - path [(if (= :command type) :commands :responses) - name - :params - 0 - :suggestions] - params {:value content}] - (j/call current-chat-id - path - params - #(dispatch [:suggestions-handler {:command command - :content content - :chat-id current-chat-id} %]))))) - (register-handler :start-cancel-command (u/side-effect! (fn [db _] (dispatch [:animate-cancel-command])))) -(def command-prefix "c ") - -(defn cancel-command! - [{:keys [canceled-command]}] - (when canceled-command - (dispatch [:start-cancel-command]))) - -(register-handler :set-chat-command-content - [(after invoke-suggestions-handler!) - (after cancel-command!)] - (fn [{:keys [current-chat-id] :as db} [_ content]] - (let [starts-as-command? (str/starts-with? content command-prefix) - path [:chats current-chat-id :command-input :command :type] - command? (= :command (get-in db path))] - (as-> db db - (commands/set-chat-command-content db content) - (assoc-in db [:chats current-chat-id :input-text] nil) - (assoc db :canceled-command (and command? (not starts-as-command?))))))) - (defn update-input-text [{:keys [current-chat-id] :as db} text] (assoc-in db [:chats current-chat-id :input-text] text)) -(defn invoke-command-preview! - [{:keys [current-chat-id staged-command]} _] - (let [{:keys [command content]} staged-command - {:keys [name type]} command - path [(if (= :command type) :commands :responses) - name - :preview] - params {:value content}] - (j/call current-chat-id - path - params - #(dispatch [:command-preview current-chat-id %])))) - -(register-handler :stage-command - (after invoke-command-preview!) - (fn [{:keys [current-chat-id] :as db} _] - (let [db (update-input-text db nil) - {:keys [command content to-msg-id]} - (get-in db [:chats current-chat-id :command-input]) - content' (if (= :command (:type command)) - (subs content 2) - content) - command-info {:command command - :content content' - :to-message to-msg-id}] - (-> db - (commands/stage-command command-info) - (assoc :staged-command command-info))))) - (register-handler :set-message-input [] (fn [db [_ input]] (assoc db :message-input input))) @@ -138,15 +72,6 @@ (when-let [message-input (:message-input db)] (.blur message-input))))) -(register-handler :set-response-chat-command - [(after invoke-suggestions-handler!) - (after #(dispatch [:command-edit-mode])) - (after #(dispatch [:set-chat-input-text ""]))] - (fn [db [_ to-msg-id command-key]] - (-> db - (commands/set-response-chat-command to-msg-id command-key) - (assoc :canceled-command false)))) - (defn update-text [{:keys [current-chat-id] :as db} [_ text]] (let [suggestions (get-in db [:command-suggestions current-chat-id])] @@ -322,13 +247,6 @@ params #(dispatch [:command-handler! com %]))))) -(defn handle-commands - [{:keys [new-commands]}] - (doseq [{{content :content} :content - handler :handler} new-commands] - (when handler - (handler content)))) - (register-handler :send-chat-msg (-> prepare-message ((enrich prepare-staged-commans)) @@ -341,21 +259,7 @@ ((after save-commands-to-realm!)) ((after dispatch-responded-requests!)) ;; todo maybe it is better to track if it was handled or not - ((after invoke-commands-handlers!)) - ((after handle-commands)))) - -(register-handler :unstage-command - (fn [db [_ staged-command]] - (commands/unstage-command db staged-command))) - -(register-handler :set-chat-command - [(after invoke-suggestions-handler!) - (after #(dispatch [:command-edit-mode]))] - (fn [{:keys [current-chat-id] :as db} [_ command-key]] - (-> db - (commands/set-chat-command command-key) - (assoc-in [:chats current-chat-id :command-input :content] "c ") - (assoc :disable-input true)))) + ((after invoke-commands-handlers!)))) (register-handler :init-console-chat (fn [db [_]] @@ -552,6 +456,7 @@ (assoc-in db [:edit-mode current-chat-id] mode))) (register-handler :command-edit-mode + (after #(dispatch [:clear-validation-errors])) (edit-mode-handler :command)) (register-handler :text-edit-mode diff --git a/src/status_im/chat/handlers/animation.cljs b/src/status_im/chat/handlers/animation.cljs index c14a22b22d..fde8a8e457 100644 --- a/src/status_im/chat/handlers/animation.cljs +++ b/src/status_im/chat/handlers/animation.cljs @@ -47,7 +47,7 @@ input-height))) (register-handler :animate-show-response - [(after #(dispatch [:command-edit-mode]))] + ;[(after #(dispatch [:command-edit-mode]))] (fn [{:keys [current-chat-id] :as db}] (let [suggestions? (seq (get-in db [:suggestions current-chat-id])) height (if suggestions? diff --git a/src/status_im/chat/handlers/commands.cljs b/src/status_im/chat/handlers/commands.cljs new file mode 100644 index 0000000000..64f5128a9e --- /dev/null +++ b/src/status_im/chat/handlers/commands.cljs @@ -0,0 +1,186 @@ +(ns status-im.chat.handlers.commands + (:require [re-frame.core :refer [enrich after dispatch]] + [status-im.utils.handlers :refer [register-handler] :as u] + [status-im.components.jail :as j] + [status-im.models.commands :as commands] + [clojure.string :as str] + [status-im.commands.utils :as cu] + [status-im.utils.phone-number :as pn])) + +(def command-prefix "c ") + +(defn invoke-suggestions-handler! + [{:keys [current-chat-id canceled-command] :as db} _] + (when-not canceled-command + (let [{:keys [command content]} (get-in db [:chats current-chat-id :command-input]) + {:keys [name type]} command + path [(if (= :command type) :commands :responses) + name + :params + 0 + :suggestions] + params {:value content}] + (j/call current-chat-id + path + params + #(dispatch [:suggestions-handler {:command command + :content content + :chat-id current-chat-id} %]))))) + +(defn cancel-command! + [{:keys [canceled-command]}] + (when canceled-command + (dispatch [:start-cancel-command]))) + +(register-handler :set-chat-command-content + [(after invoke-suggestions-handler!) + (after cancel-command!) + (after #(dispatch [:clear-validation-errors]))] + (fn [{:keys [current-chat-id] :as db} [_ content]] + (let [starts-as-command? (str/starts-with? content command-prefix) + path [:chats current-chat-id :command-input :command :type] + command? (= :command (get-in db path))] + (as-> db db + (commands/set-chat-command-content db content) + (assoc-in db [:chats current-chat-id :input-text] nil) + (assoc db :canceled-command (and command? (not starts-as-command?))))))) + +(defn invoke-command-preview! + [{:keys [staged-command]} [_ chat-id]] + (let [{:keys [command content]} staged-command + {:keys [name type]} command + path [(if (= :command type) :commands :responses) + name + :preview] + params {:value content}] + (j/call chat-id + path + params + #(dispatch [:command-preview chat-id %])))) + +(defn content-by-command + [{:keys [type]} content] + (if (= :command type) + (subs content 2) + content)) + +(defn command-input + ([{:keys [current-chat-id] :as db}] + (command-input db current-chat-id)) + ([db chat-id] + (get-in db [:chats chat-id :command-input]))) + + +(register-handler ::validate! + (u/side-effect! + (fn [_ [_ {:keys [chat-id]} {:keys [error result]}]] + ;; todo handle error + (when-not error + (let [{:keys [errors validationHandler parameters]} result] + (cond errors + (dispatch [::add-validation-errors chat-id errors]) + + validationHandler + (dispatch [::validation-handler! + chat-id + validationHandler + parameters]) + + :else (dispatch [::finish-command-staging chat-id]))))))) + +(defn start-validate! [db] + (let [{:keys [content command chat-id] :as data} (::command db) + {:keys [name type]} command + path [(if (= :command type) :commands :responses) + name + :validator] + params {:value content}] + (j/call chat-id + path + params + #(dispatch [::validate! data %])))) + +(register-handler :stage-command + (after start-validate!) + (fn [{:keys [current-chat-id] :as db}] + (let [{:keys [command content]} (command-input db) + content' (content-by-command command content)] + (-> db + (assoc ::command {:content content' + :command command + :chat-id current-chat-id}) + (assoc-in [:disable-staging current-chat-id] true))))) + +(register-handler ::finish-command-staging + [(after #(dispatch [:start-cancel-command])) + (after invoke-command-preview!)] + (fn [db [_ chat-id]] + (let [db (assoc-in db [:chats chat-id :input-text] nil) + {:keys [command content to-msg-id]} (command-input db) + content' (content-by-command command content) + command-info {:command command + :content content' + :to-message to-msg-id}] + (-> db + (commands/stage-command command-info) + (assoc :staged-command command-info) + (assoc-in [:disable-staging chat-id] true))))) + +(register-handler :unstage-command + (fn [db [_ staged-command]] + (commands/unstage-command db staged-command))) + +(defn set-chat-command + [{:keys [current-chat-id] :as db} [_ command-key]] + (-> db + (commands/set-chat-command command-key) + (assoc-in [:chats current-chat-id :command-input :content] command-prefix) + (assoc :disable-input true))) + +(register-handler :set-chat-command + [(after invoke-suggestions-handler!) + (after #(dispatch [:command-edit-mode]))] + set-chat-command) + +(defn set-response-command [db [_ to-msg-id command-key]] + (-> db + (commands/set-response-chat-command to-msg-id command-key) + (assoc :canceled-command false))) + +(register-handler :set-response-chat-command + [(after invoke-suggestions-handler!) + (after #(dispatch [:command-edit-mode])) + (after #(dispatch [:set-chat-input-text ""]))] + set-response-command) + +(register-handler ::add-validation-errors + (fn [db [_ chat-id errors]] + (assoc-in db [:custom-validation-errors chat-id] + (map cu/generate-hiccup errors)))) + +(register-handler :clear-validation-errors + (fn [db] + (dissoc db :validation-errors :custom-validation-errors))) + +(def validation-handlers + {:phone (fn [chat-id [number]] + (if (pn/valid-mobile-number? number) + (dispatch [::finish-command-staging chat-id]) + (dispatch [::set-validation-error + chat-id + {:title "Phobe number" + :description "Wrong phone number"}])))}) + +(defn validator [name] + (validation-handlers (keyword name))) + +(register-handler ::validation-handler! + (u/side-effect! + (fn [_ [_ chat-id name params]] + (when-let [handler (validator name)] + (handler chat-id params))))) + + +(register-handler ::set-validation-error + (fn [db [_ chat-id error]] + (assoc-in db [:validation-errors chat-id] [error]))) diff --git a/src/status_im/chat/styles/command_validation.cljs b/src/status_im/chat/styles/command_validation.cljs new file mode 100644 index 0000000000..b38e8cc820 --- /dev/null +++ b/src/status_im/chat/styles/command_validation.cljs @@ -0,0 +1 @@ +(ns status-im.chat.styles.command-validation) diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index ae90e45bc0..634d2c8bf4 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -172,3 +172,13 @@ (fn [_ [_ message-id]] (let [requests (subscribe [:get-requests])] (reaction (not (some #(= message-id (:message-id %)) @requests)))))) + +(register-sub :validation-errors + (fn [db] + (let [current-chat-id (subscribe [:get-current-chat-id])] + (reaction (get-in @db [:validation-errors @current-chat-id]))))) + +(register-sub :custom-validation-errors + (fn [db] + (let [current-chat-id (subscribe [:get-current-chat-id])] + (reaction (get-in @db [:custom-validation-errors @current-chat-id]))))) diff --git a/src/status_im/chat/views/command.cljs b/src/status_im/chat/views/command.cljs index 49f5135b7e..3264e5d597 100644 --- a/src/status_im/chat/views/command.cljs +++ b/src/status_im/chat/views/command.cljs @@ -15,8 +15,7 @@ (dispatch [:set-chat-command-content message])) (defn send-command [] - (dispatch [:stage-command]) - (cancel-command-input)) + (dispatch [:stage-command])) (defn valid? [message validator] (if validator diff --git a/src/status_im/chat/views/command_validation.cljs b/src/status_im/chat/views/command_validation.cljs new file mode 100644 index 0000000000..4294e61ce2 --- /dev/null +++ b/src/status_im/chat/views/command_validation.cljs @@ -0,0 +1,30 @@ +(ns status-im.chat.views.command-validation + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [status-im.components.react :as c] + [status-im.chat.styles.command-validation :as st] + [status-im.utils.listview :as lw])) + +(defn error [{:keys [title description]}] + (c/list-item + [c/view + [c/text title] + [c/text description]])) + +(defn errors-list [errors] + [c/list-view {:renderRow error + :keyboardShouldPersistTaps true + :dataSource (lw/to-datasource errors)}]) + +(defview errors [] + [errors [:validation-errors] + custom-errors [:custom-validation-errors] + command? [:command?]] + (when (and command? + (or (seq errors) + (seq custom-errors))) + [c/scroll-view {:background-color :red} + (cond (seq custom-errors) + (vec (concat [c/view] custom-errors)) + + (seq errors) + [errors-list errors])])) diff --git a/src/status_im/chat/views/message_input.cljs b/src/status_im/chat/views/message_input.cljs index c96b36f22e..e45349b28f 100644 --- a/src/status_im/chat/views/message_input.cljs +++ b/src/status_im/chat/views/message_input.cljs @@ -2,6 +2,7 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe]] [status-im.components.react :refer [view + text animated-view icon touchable-highlight @@ -10,7 +11,8 @@ [status-im.chat.views.command :as command] [status-im.chat.styles.message-input :as st] [status-im.chat.styles.plain-message :as st-message] - [status-im.chat.styles.response :as st-response])) + [status-im.chat.styles.response :as st-response] + [status-im.chat.views.command-validation :as cv])) (defn send-button [{:keys [on-press accessibility-label]}] [touchable-highlight {:on-press on-press @@ -48,20 +50,20 @@ input-options) (if command? input-command input-message)]) -(defview plain-message-input-view [{:keys [input-options validator]}] +(defview plain-message-input-view [{:keys [input-options]}] [command? [:command?] {:keys [type] :as command} [:get-chat-command] input-command [:get-chat-command-content] - valid-plain-message? [:valid-plain-message?] - valid-command? [:valid-command? validator]] + valid-plain-message? [:valid-plain-message?]] [view st/input-container + [cv/errors] [view st/input-view [plain-message/commands-button] [message-input-container - [message-input input-options validator]] + [message-input input-options]] ;; TODO emoticons: not implemented [plain-message/smile-button] - (when (if command? valid-command? valid-plain-message?) + (when (or command? valid-plain-message?) (let [on-press (if command? command/send-command plain-message/send)] diff --git a/src/status_im/chat/views/new_message.cljs b/src/status_im/chat/views/new_message.cljs index 54bb81f2d7..84576ef5a3 100644 --- a/src/status_im/chat/views/new_message.cljs +++ b/src/status_im/chat/views/new_message.cljs @@ -18,8 +18,7 @@ (defn get-options [{:keys [type placeholder]} command-type] (let [options (case (keyword type) - :phone {:input-options {:keyboardType :phone-pad} - :validator valid-mobile-number?} + :phone {:input-options {:keyboardType :phone-pad}} :password {:input-options {:secureTextEntry true}} :number {:input-options {:keyboardType :numeric}} ;; todo maybe nil is fine for now :) diff --git a/src/status_im/models/commands.cljs b/src/status_im/models/commands.cljs index 1ebb70dccb..d7380a04c4 100644 --- a/src/status_im/models/commands.cljs +++ b/src/status_im/models/commands.cljs @@ -1,11 +1,5 @@ (ns status-im.models.commands - (:require [clojure.string :refer [join split]] - [clojure.walk :refer [stringify-keys keywordize-keys]] - [re-frame.core :refer [subscribe dispatch]] - [status-im.db :as db] - [status-im.components.animation :as anim] - [status-im.components.styles :refer [color-blue color-dark-mint]] - [status-im.i18n :refer [label]])) + (:require [status-im.db :as db])) (defn get-commands [{:keys [current-chat-id] :as db}] (or (get-in db [:chats current-chat-id :commands]) {})) diff --git a/src/status_im/utils/handlers.cljs b/src/status_im/utils/handlers.cljs index 083196375e..4d2777e335 100644 --- a/src/status_im/utils/handlers.cljs +++ b/src/status_im/utils/handlers.cljs @@ -11,4 +11,4 @@ (defn register-handler ([name handler] (register-handler name nil handler)) ([name middleware handler] - (re-core/register-handler name [#_debug middleware] handler))) + (re-core/register-handler name [debug middleware] handler)))