From 76a509ed22d0acdfd280e6c32948462d7bf6dada Mon Sep 17 00:00:00 2001 From: janherich Date: Tue, 17 Jul 2018 20:58:56 +0200 Subject: [PATCH] Cljs commands --- src/status_im/bots/events.cljs | 106 ----- src/status_im/bots/subs.cljs | 13 - src/status_im/chat/commands/core.cljs | 161 ++++++-- .../chat/commands/impl/transactions.cljs | 165 ++++---- src/status_im/chat/commands/protocol.cljs | 35 +- src/status_im/chat/commands/receiving.cljs | 25 ++ src/status_im/chat/commands/sending.cljs | 78 ++++ src/status_im/chat/commands/specs.cljs | 5 + src/status_im/chat/commands/validation.cljs | 10 - src/status_im/chat/console.cljs | 37 -- src/status_im/chat/constants.cljs | 4 - src/status_im/chat/events.cljs | 30 +- src/status_im/chat/events/commands.cljs | 83 ---- src/status_im/chat/events/console.cljs | 63 --- src/status_im/chat/events/input.cljs | 369 +++--------------- .../chat/events/receive_message.cljs | 77 +--- src/status_im/chat/events/send_message.cljs | 16 - src/status_im/chat/events/shortcuts.cljs | 45 --- src/status_im/chat/models/commands.cljs | 45 --- src/status_im/chat/models/input.cljs | 125 +----- src/status_im/chat/models/message.cljs | 260 ++---------- src/status_im/chat/specs.cljs | 3 +- .../chat/styles/input/result_box.cljs | 16 - .../chat/styles/message/message.cljs | 189 --------- src/status_im/chat/subs.cljs | 134 ++----- .../chat/views/api/choose_asset.cljs | 41 -- .../chat/views/api/choose_contact.cljs | 33 -- src/status_im/chat/views/api/styles.cljs | 35 -- src/status_im/chat/views/input/input.cljs | 6 +- .../chat/views/input/parameter_box.cljs | 15 +- .../chat/views/input/result_box.cljs | 14 - .../chat/views/input/send_button.cljs | 16 +- .../chat/views/input/suggestions.cljs | 25 +- .../chat/views/input/validation_messages.cljs | 17 +- src/status_im/chat/views/message/message.cljs | 121 +----- .../chat/views/message/request_message.cljs | 146 ------- src/status_im/commands/events/loading.cljs | 160 -------- src/status_im/commands/handlers/jail.cljs | 111 ------ src/status_im/commands/specs.cljs | 4 - src/status_im/commands/subs.cljs | 10 - src/status_im/commands/utils.cljs | 75 ---- src/status_im/native_module/impl/module.cljs | 2 +- src/status_im/ui/screens/db.cljs | 4 +- src/status_im/ui/screens/events.cljs | 62 +-- .../ui/screens/home/views/inner_item.cljs | 28 +- src/status_im/ui/screens/profile/events.cljs | 9 +- src/status_im/ui/screens/subs.cljs | 2 - .../wallet/choose_recipient/events.cljs | 5 +- .../ui/screens/wallet/request/events.cljs | 10 +- .../ui/screens/wallet/send/events.cljs | 19 +- src/status_im/utils/handlers_macro.cljs | 2 +- test/cljs/status_im/test/bots/events.cljs | 50 --- .../status_im/test/chat/commands/core.cljs | 150 +++++-- test/cljs/status_im/test/chat/events.cljs | 25 +- .../status_im/test/chat/models/input.cljs | 140 ------- test/cljs/status_im/test/chat/subs.cljs | 9 +- test/cljs/status_im/test/runner.cljs | 2 - 57 files changed, 720 insertions(+), 2722 deletions(-) delete mode 100644 src/status_im/bots/events.cljs delete mode 100644 src/status_im/bots/subs.cljs create mode 100644 src/status_im/chat/commands/receiving.cljs create mode 100644 src/status_im/chat/commands/sending.cljs create mode 100644 src/status_im/chat/commands/specs.cljs delete mode 100644 src/status_im/chat/commands/validation.cljs delete mode 100644 src/status_im/chat/console.cljs delete mode 100644 src/status_im/chat/events/commands.cljs delete mode 100644 src/status_im/chat/events/console.cljs delete mode 100644 src/status_im/chat/events/shortcuts.cljs delete mode 100644 src/status_im/chat/models/commands.cljs delete mode 100644 src/status_im/chat/styles/input/result_box.cljs delete mode 100644 src/status_im/chat/views/api/choose_asset.cljs delete mode 100644 src/status_im/chat/views/api/choose_contact.cljs delete mode 100644 src/status_im/chat/views/api/styles.cljs delete mode 100644 src/status_im/chat/views/input/result_box.cljs delete mode 100644 src/status_im/chat/views/message/request_message.cljs delete mode 100644 src/status_im/commands/events/loading.cljs delete mode 100644 src/status_im/commands/handlers/jail.cljs delete mode 100644 src/status_im/commands/specs.cljs delete mode 100644 src/status_im/commands/subs.cljs delete mode 100644 src/status_im/commands/utils.cljs delete mode 100644 test/cljs/status_im/test/bots/events.cljs diff --git a/src/status_im/bots/events.cljs b/src/status_im/bots/events.cljs deleted file mode 100644 index 778c63b74a..0000000000 --- a/src/status_im/bots/events.cljs +++ /dev/null @@ -1,106 +0,0 @@ -(ns status-im.bots.events - (:require [re-frame.core :as re-frame] - [status-im.utils.core :as utils] - [status-im.utils.handlers :as handlers] - [status-im.chat.models.input :as input-model] - [taoensso.timbre :as log])) - -;;;; Helper fns - -(defn- subscription-values [sub-params current-bot-db] - (reduce (fn [sub-values [sub-key sub-path]] - (assoc sub-values sub-key (get-in current-bot-db sub-path))) - {} - sub-params)) - -(defn- check-subscriptions-fx - [{:keys [bot-db] :contacts/keys [contacts] :as app-db} {:keys [bot path]}] - (when-let [subscriptions (and bot (get-in contacts (concat [bot :subscriptions] [path])))] - {:call-jail-function-n - (for [[sub-name sub-params] subscriptions] - {:chat-id bot - :function :subscription - :parameters {:name sub-name - :subscriptions (subscription-values sub-params (get bot-db bot))} - :callback-event-creator (fn [jail-response] - [::calculated-subscription - {:bot bot - :path [sub-name] - :result jail-response}])})})) - -(defn set-in-bot-db - "Associates value at specified path in bot-db and checks if there are any subscriptions - dependent on that path, if yes, adds effects for calling jail-functions to recalculate - relevant subscriptions." - [app-db {:keys [bot path value] :as params}] - (let [new-db (assoc-in app-db (concat [:bot-db bot] path) value)] - (merge {:db new-db} - (check-subscriptions-fx new-db params)))) - -(defn update-bot-db - [app-db {:keys [bot db]}] - (update-in app-db [:bot-db bot] merge db)) - -(defn clear-bot-db - [app-db bot-id] - (assoc-in app-db [:bot-db bot-id] nil)) - -(def ^:private keywordize-vector (partial mapv keyword)) - -(defn transform-bot-subscriptions - "Transforms bot subscriptions as returned from jail in the following format: - - `{:calculatedFee {:subscriptions {:value [\"sliderValue\"] - :tx [\"transaction\"]}} - :feeExplanation {:subscriptions {:value [\"sliderValue\"]}}}` - - into data-structure better suited for subscription lookups based on changes - in `:bot-db`: - - `{[:sliderValue] {:calculatedFee {:value [:sliderValue] - :tx [:transaction]} - :feeExplanation {:value [:sliderValue]}} - [:transaction] {:calculatedFee {:value [:sliderValue] - :tx [:transaction]}}}` - - In the resulting data-structure, the top level keys are the (keywordized) paths - in the `:bot-db` data structure, so it's quick and easy to look-up all the - subscriptions which must be recalculated when something in their path changes." - [bot-subscriptions] - (reduce-kv (fn [acc sub-key {:keys [subscriptions]}] - (reduce-kv (fn [acc sub-param-key sub-param-path] - (update acc - (keywordize-vector sub-param-path) - assoc sub-key - (utils/map-values subscriptions keywordize-vector))) - acc - subscriptions)) - {} - bot-subscriptions)) - -(defn calculated-subscription - [db {:keys [bot path] - {:keys [error result]} :result}] - (if error - db - (assoc-in db (concat [:bot-db bot] path) (:returned result)))) - -;;;; Handlers - -(handlers/register-handler-fx - :set-in-bot-db - [re-frame/trim-v] - (fn [{:keys [db]} [params]] - (set-in-bot-db db params))) - -(handlers/register-handler-db - :update-bot-db - [re-frame/trim-v] - (fn [db [params]] - (update-bot-db db params))) - -(handlers/register-handler-db - ::calculated-subscription - [re-frame/trim-v] - (fn [db [params]] - (calculated-subscription db params))) diff --git a/src/status_im/bots/subs.cljs b/src/status_im/bots/subs.cljs deleted file mode 100644 index 805d3cb2e3..0000000000 --- a/src/status_im/bots/subs.cljs +++ /dev/null @@ -1,13 +0,0 @@ -(ns status-im.bots.subs - (:require [re-frame.core :as re-frame] - [status-im.chat.models.input :as input-model])) - -(re-frame/reg-sub :get-bot-db :bot-db) - -(re-frame/reg-sub - :current-bot-db - :<- [:get-bot-db] - :<- [:selected-chat-command] - (fn [[bot-db command]] - (let [command-owner (get-in command [:command :owner-id])] - [command-owner (get bot-db command-owner)]))) diff --git a/src/status_im/chat/commands/core.cljs b/src/status_im/chat/commands/core.cljs index 769b35a6df..5f6759fc58 100644 --- a/src/status_im/chat/commands/core.cljs +++ b/src/status_im/chat/commands/core.cljs @@ -1,31 +1,50 @@ (ns status-im.chat.commands.core (:require [clojure.set :as set] + [clojure.string :as string] + [status-im.constants :as constants] + [status-im.chat.constants :as chat-constants] [status-im.chat.commands.protocol :as protocol] [status-im.chat.commands.impl.transactions :as transactions] - [status-im.chat.models.input :as input-model])) + [status-im.chat.models :as chat-model] + [status-im.chat.models.input :as input-model] + [status-im.chat.models.message :as message-model] + [status-im.utils.handlers-macro :as handlers-macro])) -(def ^:private arg-wrapping-char "\"") -(def ^:private command-char "/") -(def ^:private space-char " ") - -(def commands-register +(def register "Register of all commands. Whenever implementing a new command, provide the implementation in the `status-im.chat.commands.impl.*` ns, and add its instance here." - #{(transactions/PersonalSendCommand.)}) - -(defn validate-and-send - "Validates and sends command in current chat" - [command cofx] - nil) - -(defn send - "Sends command with given arguments in particular chat" - [command chat-id cofx] - nil) + #{(transactions/PersonalSendCommand.) + (transactions/PersonalRequestCommand.)}) (def command-id (juxt protocol/id protocol/scope)) +(defn command-name + "Given the command instance, returns command name as displayed in chat input, + with leading `/` character." + [type] + (str chat-constants/command-char (protocol/id type))) + +(defn command-description + "Returns description for the command." + [type] + (protocol/description type)) + +(defn accessibility-label + "Returns accessibility button label for command, derived from its id" + [type] + (keyword (str (protocol/id type) "-button"))) + +(defn generate-short-preview + "Returns short preview for command" + [{:keys [type]} command-message] + (protocol/short-preview type command-message)) + +(defn generate-preview + "Returns preview for command" + [{:keys [type]} command-message] + (protocol/preview type command-message)) + (defn- prepare-params "Prepares parameters sequence of command by providing suggestion components with selected-event injected with correct arg indexes and `last-arg?` flag." @@ -73,9 +92,10 @@ (protocol/scope type) protocol/or-scopes)] (reduce (fn [acc access-scope] - (assoc acc - access-scope - command-id)) + (update acc + access-scope + (fnil conj #{}) + command-id)) acc access-scopes))) {} @@ -84,11 +104,76 @@ :id->command id->command :access-scope->command-id access-scope->command-id)})) +(defn chat-commands + "Takes `id->command`, `access-scope->command-id` and `chat` parameters and returns + entries map of `id->command` map eligible for given chat. + Note that the result map is keyed just by `protocol/id` of command entries, + not the unique composite ids of the global `id->command` map. + That's because this function is already returning local commands for particular + chat and locally, they should always have unique `protocol/id`." + [id->command access-scope->command-id {:keys [chat-id group-chat public?]}] + (let [global-access-scope (cond-> #{} + (not group-chat) (conj :personal-chats) + (and group-chat (not public?)) (conj :group-chats) + public? (conj :public-chats)) + chat-access-scope #{chat-id}] + (reduce (fn [acc command-id] + (let [{:keys [type] :as command-props} (get id->command command-id)] + (assoc acc (protocol/id type) command-props))) + {} + (concat (get access-scope->command-id global-access-scope) + (get access-scope->command-id chat-access-scope))))) + +(defn- current-param-position [input-text selection] + (when selection + (when-let [subs-input-text (subs input-text 0 selection)] + (let [input-params (input-model/split-command-args subs-input-text) + param-index (dec (count input-params)) + wrapping-count (get (frequencies subs-input-text) chat-constants/arg-wrapping-char 0)] + (if (and (string/ends-with? subs-input-text chat-constants/spacing-char) + (even? wrapping-count)) + param-index + (dec param-index)))))) + +(defn- command-completion [input-params params] + (let [input-params-count (count input-params) + params-count (count params)] + (cond + (= input-params-count params-count) :complete + (< input-params-count params-count) :less-then-needed + (> input-params-count params-count) :more-than-needed))) + +(defn selected-chat-command + "Takes input text, text-selection and `protocol/id->command-props` map (result of + the `chat-commands` fn) and returns the corresponding `command-props` entry, + or nothing if input text doesn't match any available command. + Besides keys `:params` and `:type`, the returned map contains: + * `:input-params` - parsed parameters from the input text as map of `param-id->entered-value` + # `:current-param-position` - index of the parameter the user is currently focused on (cursor position + in relation to parameters), could be nil if the input is not selected + # `:command-completion` - indication of command completion, possible values are `:complete`, + `:less-then-needed` and `more-then-needed`" + [input-text text-selection id->command-props] + (when (input-model/starts-as-command? input-text) + (let [[command-name & input-params] (input-model/split-command-args input-text)] + (when-let [{:keys [params] :as command-props} (get id->command-props (subs command-name 1))] ;; trim leading `/` for lookup + command-props + (let [input-params (into {} + (keep-indexed (fn [idx input-value] + (when (not (string/blank? input-value)) + (when-let [param-name (get-in params [idx :id])] + [param-name input-value])))) + input-params)] + (assoc command-props + :input-params input-params + :current-param-position (current-param-position input-text text-selection) + :command-completion (command-completion input-params params))))))) + (defn set-command-parameter "Set value as command parameter for the current chat" [last-param? param-index value {:keys [db]}] - (let [{:keys [current-chat-id]} db - [command & params] (-> (get-in db [:chats current-chat-id :input-text]) + (let [{:keys [current-chat-id chats]} db + [command & params] (-> (get-in chats [current-chat-id :input-text]) input-model/split-command-args) param-count (count params) ;; put the new value at the right place in parameters array @@ -96,12 +181,36 @@ (< param-index param-count) (assoc param-index value) (>= param-index param-count) (conj value)) ;; if the parameter is not the last one for the command, add space - input-text (cond-> (str command space-char + input-text (cond-> (str command chat-constants/spacing-char (input-model/join-command-args new-params)) (and (not last-param?) (or (= 0 param-count) (= param-index (dec param-count)))) - (str space-char))] - {:db (assoc-in db [:chats current-chat-id :input-text] - (input-model/text->emoji input-text))})) + (str chat-constants/spacing-char))] + {:db (-> db + (chat-model/set-chat-ui-props {:validation-messages nil}) + (assoc-in [:chats current-chat-id :input-text] input-text))})) + +(defn select-chat-input-command + "Takes command and (optional) map of input-parameters map and sets it as current-chat input" + [{:keys [type params]} input-params {:keys [db]}] + (let [{:keys [current-chat-id chat-ui-props]} db] + {:db (-> db + (chat-model/set-chat-ui-props {:show-suggestions? false + :validation-messages nil}) + (assoc-in [:chats current-chat-id :input-text] + (str (command-name type) + chat-constants/spacing-char + (input-model/join-command-args input-params))))})) + +(defn parse-parameters + "Parses parameters from input for defined command params, + returns map of `param-name->param-value`" + [params input-text] + (let [input-params (->> (input-model/split-command-args input-text) rest (into []))] + (into {} + (keep-indexed (fn [idx {:keys [id]}] + (when-let [value (get input-params idx)] + [id value]))) + params))) diff --git a/src/status_im/chat/commands/impl/transactions.cljs b/src/status_im/chat/commands/impl/transactions.cljs index ede2e4ab6a..06bbc6a435 100644 --- a/src/status_im/chat/commands/impl/transactions.cljs +++ b/src/status_im/chat/commands/impl/transactions.cljs @@ -9,6 +9,7 @@ [status-im.ui.components.react :as react] [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.colors :as colors] + [status-im.ui.components.chat-preview :as chat-preview] [status-im.ui.components.list.views :as list] [status-im.ui.components.animation :as animation] [status-im.i18n :as i18n] @@ -16,8 +17,10 @@ [status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.tokens :as tokens] [status-im.utils.datetime :as datetime] - [status-im.ui.screens.wallet.send.events :as send.events] + [status-im.utils.money :as money] + [status-im.ui.screens.wallet.db :as wallet.db] [status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events] + [status-im.wallet.transactions :as wallet.transactions] [status-im.ui.screens.navigation :as navigation])) ;; common `send/request` functionality @@ -25,7 +28,7 @@ (defn- render-asset [selected-event-creator] (fn [{:keys [name symbol amount decimals] :as asset}] [react/touchable-highlight - {:on-press #(re-frame/dispatch (selected-event-creator symbol))} + {:on-press #(re-frame/dispatch (selected-event-creator (clojure.core/name symbol)))} [react/view transactions-styles/asset-container [react/view transactions-styles/asset-main [react/image {:source (-> asset :icon :source) @@ -50,14 +53,17 @@ :keyboardShouldPersistTaps :always :bounces false}]])) +(defn choose-asset-suggestion [selected-event-creator] + [choose-asset selected-event-creator]) + (defn personal-send-request-short-preview - [{:keys [content]}] - (let [parameters (:params content)] - [react/text {} - (str (i18n/label :command-sending) - (i18n/label-number (:amount parameters)) + [label-key {:keys [content]}] + (let [{:keys [amount asset]} (:params content)] + [chat-preview/text {} + (str (i18n/label label-key) + (i18n/label-number amount) " " - (:asset parameters))])) + asset)])) (def personal-send-request-params [{:id :asset @@ -66,7 +72,7 @@ ;; Suggestion components should be structured in such way that they will just take ;; one argument, event-creator fn used to construct event to fire whenever something ;; is selected. - :suggestions choose-asset} + :suggestions choose-asset-suggestion} {:id :amount :type :number :placeholder "Amount"}]) @@ -98,7 +104,7 @@ :else (let [sanitised-str (string/replace amount #"," ".") portions (string/split sanitised-str ".") - decimals (get portions 1) + decimals (count (get portions 1)) amount (js/parseFloat sanitised-str)] (cond @@ -173,57 +179,76 @@ :gas (ethereum/estimate-gas symbol) :from-chat? true))) +(defn- inject-network-price-info [{:keys [amount asset] :as parameters} {:keys [db]}] + (let [{:keys [chain prices]} db + currency (-> db + (get-in [:account/account :settings :wallet :currency] :usd) + name + string/upper-case)] + (assoc parameters + :network chain + ;; TODO(janherich) - shouldn't this be rather computed on the receiver side ? + :fiat-amount (money/fiat-amount-value amount + (keyword asset) + (keyword currency) + prices) + :currency currency))) + (deftype PersonalSendCommand [] protocol/Command - (id [_] - "send") - (scope [_] - #{:personal-chats}) - (parameters [_] - personal-send-request-params) + (id [_] "send") + (scope [_] #{:personal-chats}) + (description [_] "Send a payment") + (parameters [_] personal-send-request-params) (validate [_ parameters cofx] ;; Only superficial/formatting validation, "real validation" will be performed ;; by the wallet, where we yield control in the next step (personal-send-request-validation parameters cofx)) + (on-send [_ {:keys [chat-id]} {:keys [db]}] + (when-let [{:keys [responding-to]} (get-in db [:chats chat-id :input-metadata])] + {:db (update-in db [:chats chat-id :requests] dissoc responding-to) + :data-store/tx [(requests-store/mark-request-as-answered-tx chat-id responding-to)]})) + (on-receive [_ command-message cofx] + (when-let [tx-hash (get-in command-message [:content :params :tx-hash])] + (wallet.transactions/store-chat-transaction-hash tx-hash cofx))) + (short-preview [_ command-message] + (personal-send-request-short-preview :command-sending command-message)) + (preview [_ command-message] + (send-preview command-message)) + protocol/Yielding (yield-control [_ {:keys [amount asset]} {:keys [db]}] ;; Prefill wallet and navigate there - (let [recipient-contact (get-in db [:contacts/contacts (:current-chat-id db)]) - sender-account (:account/account db) - chain (keyword (:chain db)) - symbol (keyword asset) - {:keys [decimals]} (tokens/asset-for chain symbol)] - (merge {:db (-> db - (send.events/set-and-validate-amount-db amount symbol decimals) - (choose-recipient.events/fill-request-details - (transaction-details recipient-contact symbol)) - (update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?) - (navigation/navigate-to - (if (:wallet-set-up-passed? sender-account) - :wallet-send-transaction-chat - :wallet-onboarding-setup)))} - (send.events/update-gas-price db false)))) - (on-send [_ _ _ _] - ;; TODO(janherich) - remove this once periodic updates are implemented - {:dispatch [:update-transactions]}) - (on-receive [_ _ _] - ;; TODOD(janherich) - this just copyies the current logic but still seems super weird, - ;; remove/reconsider once periodic updates are implemented - {:dispatch [:update-transactions] - :dispatch-later [{:ms constants/command-send-status-update-interval-ms - :dispatch [:update-transactions]}]}) - (short-preview [_ command-message _] - (personal-send-request-short-preview command-message)) - (preview [_ command-message _] - (send-preview command-message))) + (let [recipient-contact (get-in db [:contacts/contacts (:current-chat-id db)]) + sender-account (:account/account db) + chain (keyword (:chain db)) + symbol (keyword asset) + {:keys [decimals]} (tokens/asset-for chain symbol) + {:keys [value error]} (wallet.db/parse-amount amount decimals)] + {:db (-> db + (assoc-in [:wallet :send-transaction :amount] (money/formatted->internal value symbol decimals)) + (assoc-in [:wallet :send-transaction :amount-text] amount) + (assoc-in [:wallet :send-transaction :amount-error] error) + (choose-recipient.events/fill-request-details + (transaction-details recipient-contact symbol)) + (update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?) + (navigation/navigate-to + (if (:wallet-set-up-passed? sender-account) + :wallet-send-transaction-chat + :wallet-onboarding-setup))) + ;; TODO(janherich) - refactor wallet send events, updating gas price + ;; is generic thing which shouldn't be defined in wallet.send, then + ;; we can include the utility helper without running into circ-dep problem + :update-gas-price {:web3 (:web3 db) + :success-event :wallet/update-gas-price-success + :edit? false}})) + protocol/EnhancedParameters + (enhance-parameters [_ parameters cofx] + (inject-network-price-info parameters cofx))) ;; `/request` command (def request-message-icon-scale-delay 600) -(defn set-chat-command [message-id command] - (let [metadata {:to-message-id message-id}] - (re-frame/dispatch [:select-chat-input-command command metadata]))) - (def min-scale 1) (def max-scale 1.3) @@ -278,19 +303,25 @@ [react/icon command-icon transactions-styles/command-request-image])]]))}))) (defview request-preview - [{:keys [message-id content outgoing timestamp timestamp-str group-chat]} {:keys [db]}] - (letsubs [answered? [:is-request-answered? message-id] + [{:keys [message-id content outgoing timestamp timestamp-str group-chat]}] + (letsubs [id->command [:get-id->command] + answered? [:is-request-answered? message-id] status-initialized? [:get :status-module-initialized?] - network [:network-name] - prices [:prices]] + network [:network-name] + prices [:prices]] (let [{:keys [amount asset fiat-amount currency] request-network :network} (:params content) recipient-name (get-in content [:params :bot-db :public :recipient]) network-mismatch? (and request-network (not= request-network network)) - command (get-in db [:id->command ["send" #{:personal-chats}] :type]) + command (get id->command ["send" #{:personal-chats}]) on-press-handler (cond - network-mismatch? nil + network-mismatch? + nil (and (not answered?) - status-initialized?) #(set-chat-command message-id command))] + status-initialized?) + #(re-frame/dispatch [:select-chat-input-command + command + [(or asset "ETH") amount] + {:responding-to message-id}]))] [react/view [react/touchable-highlight {:on-press on-press-handler} @@ -338,16 +369,13 @@ (deftype PersonalRequestCommand [] protocol/Command - (id [_] - "request") - (scope [_] - #{:personal-chats}) - (parameters [_] - personal-send-request-params) + (id [_] "request") + (scope [_] #{:personal-chats}) + (description [_] "Send a payment") + (parameters [_] personal-send-request-params) (validate [_ parameters cofx] (personal-send-request-validation parameters cofx)) - (yield-control [_ _ _]) - (on-send [_ _ _ _]) + (on-send [_ _ _]) (on-receive [_ {:keys [message-id chat-id]} {:keys [db]}] (let [request {:chat-id chat-id :message-id message-id @@ -355,7 +383,10 @@ :status "open"}] {:db (assoc-in db [:chats chat-id :requests message-id] request) :data-store/tx [(requests-store/save-request-tx request)]})) - (short-preview [_ command-message _] - (personal-send-request-short-preview command-message)) - (preview [_ command-message cofx] - (request-preview command-message cofx))) + (short-preview [_ command-message] + (personal-send-request-short-preview :command-requesting command-message)) + (preview [_ command-message] + (request-preview command-message)) + protocol/EnhancedParameters + (enhance-parameters [_ parameters cofx] + (inject-network-price-info parameters cofx))) diff --git a/src/status_im/chat/commands/protocol.cljs b/src/status_im/chat/commands/protocol.cljs index e659d6db88..e466bb0846 100644 --- a/src/status_im/chat/commands/protocol.cljs +++ b/src/status_im/chat/commands/protocol.cljs @@ -15,8 +15,8 @@ `id-of-the-any-chat` - command if available only for the specified chat `:personal-chats` - command is available for any personal 1-1 chat `:group-chats` - command is available for any group chat - `:public-chats` - command is available for any public chat - `:requested` - command is available only when there is an outstanding request") + `:public-chats` - command is available for any public chat ") + (description [this] "Description of the command") (parameters [this] "Ordered sequence of command parameter templates, where each parameter is defined as map consisting of mandatory `:id`, `:title` and `:type` keys, @@ -29,21 +29,36 @@ and `cofx` map as argument, returns either `nil` meaning that no errors were found and command send workflow can proceed, or one/more errors to display. Each error is represented by the map containing `:title` and `:description` keys.") - (yield-control [this parameters cofx] - "Optional function, which if implemented, can step out of the normal command - workflow (`validate-and-send`) and yield control back to application before sending. - Useful for cases where we want to use command input handling (parameters) and/or - validating, but we don't want to send message before yielding control elsewhere.") - (on-send [this message-id parameters cofx] + (on-send [this command-message cofx] "Function which can provide any extra effects to be produced in addition to normal message effects which happen whenever message is sent") (on-receive [this command-message cofx] "Function which can provide any extre effects to be produced in addition to normal message effects which happen when particular command message is received") - (short-preview [this command-message cofx] + (short-preview [this command-message] "Function rendering the short-preview of the command message, used when displaying the last message in list of chats on home tab. There is no argument names `parameters` anymore, as the message object contains everything needed for short-preview/preview to render.") - (preview [this command-message cofx] + (preview [this command-message] "Function rendering preview of the command message in message stream")) + +(defprotocol Yielding + "Protocol for defining commands yielding control back to application during the send flow" + (yield-control [this parameters cofx] + "Function, which steps out of the normal command workflow (`validate-and-send`) + and yields control back to application before sending. + Useful for cases where we want to use command input handling (parameters) and/or + validating, but we don't want to send message before yielding control elsewhere.")) + +(defprotocol EnhancedParameters + "Protocol for command messages which wish to modify/inject additional data into parameters, + other then those collected from the chat input. + Good example would be the `/send` and `/receive` commands - we would like to indicate + network selected in sender's device, but we of course don't want to force user to type + it in when it could be effortlessly looked up from context. + Another usage would be for example command where one of the input parameters will be + hashed after validation and we would want to avoid the original unhashed parameter + to be ever saved on the sender device, nor to be sent over the wire." + (enhance-parameters [this parameters cofx] + "Function which takes original parameters + cofx map and returns new map of parameters")) diff --git a/src/status_im/chat/commands/receiving.cljs b/src/status_im/chat/commands/receiving.cljs new file mode 100644 index 0000000000..ad565b3cbf --- /dev/null +++ b/src/status_im/chat/commands/receiving.cljs @@ -0,0 +1,25 @@ +(ns status-im.chat.commands.receiving + (:require [status-im.chat.commands.protocol :as protocol])) + +;; TODO(janherich) remove after couple of releases when danger of messages +;; with old command references will be low +(def ^:private old->new-path + {["transactor" :command 83 "send"] ["send" #{:personal-chats}] + ["transactor" :command 83 "request"] ["request" #{:personal-chats}]}) + +(defn lookup-command-by-ref + "Function which takes message object and looks up associated entry from the + `id->command` map if it can be found" + [{:keys [content]} id->command] + (when-let [path (or (:command-path content) + (:command-ref content))] + (or (get id->command path) + (get id->command (get old->new-path path))))) + +(defn receive + "Performs receive effects for command message. Does nothing + when message is not of the command type or command can't be found." + [message {:keys [db] :as cofx}] + (let [id->command (:id->command db)] + (when-let [{:keys [type]} (lookup-command-by-ref message id->command)] + (protocol/on-receive type message cofx)))) diff --git a/src/status_im/chat/commands/sending.cljs b/src/status_im/chat/commands/sending.cljs new file mode 100644 index 0000000000..ea442c3a5a --- /dev/null +++ b/src/status_im/chat/commands/sending.cljs @@ -0,0 +1,78 @@ +(ns status-im.chat.commands.sending + (:require [status-im.constants :as constants] + [status-im.chat.commands.protocol :as protocol] + [status-im.chat.commands.core :as commands] + [status-im.chat.models :as chat-model] + [status-im.chat.models.input :as input-model] + [status-im.chat.models.message :as message-model] + [status-im.utils.handlers-macro :as handlers-macro])) + +;; TODO(janherich) remove after couple of releases when danger of messages +;; with old command references will be low +(defn- new->old + [path parameter-map] + (get {["send" #{:personal-chats}] {:content {:command-ref ["transactor" :command 83 "send"] + :command "send" + :bot "transactor" + :command-scope-bitmask 83} + :content-type constants/content-type-command} + ["request" #{:personal-chats}] {:content {:command-ref ["transactor" :command 83 "request"] + :request-command-ref ["transactor" :command 83 "send"] + :command "request" + :request-command "send" + :bot "transactor" + :command-scope-bitmask 83 + :prefill [(get parameter-map :asset) + (get parameter-map :amount)]} + :content-type constants/content-type-command-request}} + path)) + +(defn- create-command-message + "Create message map from chat-id, command & input parameters" + [chat-id type parameter-map cofx] + (let [command-path (commands/command-id type) + ;; TODO(janherich) this is just for backward compatibility, can be removed later + {:keys [content content-type]} (new->old command-path parameter-map)] + {:chat-id chat-id + :content-type content-type + :content (merge {:command-path command-path + :params (if (satisfies? protocol/EnhancedParameters type) + (protocol/enhance-parameters type parameter-map cofx) + parameter-map)} + content)})) + +(defn validate-and-send + "Validates and sends command in current chat" + [input-text {:keys [type params] :as command} {:keys [db now random-id] :as cofx}] + (let [chat-id (:current-chat-id db) + parameter-map (commands/parse-parameters params input-text)] + (if-let [validation-error (protocol/validate type parameter-map cofx)] + ;; errors during validation + {:db (chat-model/set-chat-ui-props db {:validation-messages validation-error + :sending-in-progress? false})} + ;; no errors, define clean-up effects which will have to be performed in all cases + (let [cleanup-fx (handlers-macro/merge-fx cofx + {:db (chat-model/set-chat-ui-props + db {:sending-in-progress? false})} + (input-model/set-chat-input-text nil))] + (if (satisfies? protocol/Yielding type) + ;; yield control implemented, don't send the message + (handlers-macro/merge-fx cofx + cleanup-fx + (protocol/yield-control type parameter-map)) + ;; no yield control, proceed with sending the command message + (let [command-message (create-command-message chat-id type parameter-map cofx)] + (handlers-macro/merge-fx cofx + cleanup-fx + (protocol/on-send type command-message) + (input-model/set-chat-input-metadata nil) + (message-model/send-message command-message)))))))) + +(defn send + "Sends command with given parameters in particular chat" + [chat-id {:keys [type]} parameter-map cofx] + (let [command-message (create-command-message chat-id type parameter-map cofx)] + (handlers-macro/merge-fx cofx + (protocol/on-send type command-message) + (input-model/set-chat-input-metadata nil) + (message-model/send-message command-message)))) diff --git a/src/status_im/chat/commands/specs.cljs b/src/status_im/chat/commands/specs.cljs new file mode 100644 index 0000000000..25814e6ec2 --- /dev/null +++ b/src/status_im/chat/commands/specs.cljs @@ -0,0 +1,5 @@ +(ns status-im.chat.commands.specs + (:require [cljs.spec.alpha :as spec])) + +(spec/def :chat/id->command (spec/nilable map?)) +(spec/def :chat/access-scope->command-id (spec/nilable map?)) diff --git a/src/status_im/chat/commands/validation.cljs b/src/status_im/chat/commands/validation.cljs deleted file mode 100644 index 68bc9cd9dd..0000000000 --- a/src/status_im/chat/commands/validation.cljs +++ /dev/null @@ -1,10 +0,0 @@ -(ns status-im.chat.commands.validation - (:require [status-im.ui.components.react :as react] - [status-im.chat.commands.styles.validation :as styles])) - -(defn validation-message [{:keys [title description]}] - [react/view styles/message-container - [react/text {:style styles/message-title} - title] - [react/text {:style styles/message-description} - description]]) diff --git a/src/status_im/chat/console.cljs b/src/status_im/chat/console.cljs deleted file mode 100644 index 20bc69c520..0000000000 --- a/src/status_im/chat/console.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(ns status-im.chat.console - (:require [status-im.ui.components.styles :refer [default-chat-color]] - [status-im.constants :as constants] - [status-im.utils.clocks :as utils.clocks] - [status-im.i18n :as i18n])) - -(defn console-message [{:keys [timestamp message-id content content-type]}] - {:message-id message-id - :chat-id constants/console-chat-id - :from constants/console-chat-id - :to "me" - :timestamp timestamp - :clock-value (utils.clocks/send 0) - :content content - :content-type content-type - :show? true}) - -(def chat - {:chat-id constants/console-chat-id - :name (i18n/label :t/status-console) - :color default-chat-color - :group-chat false - :is-active true - :unremovable? true - :timestamp (.getTime (js/Date.)) - :photo-path (str "contacts://" constants/console-chat-id) - :contacts [constants/console-chat-id] - :last-clock-value 0}) - -(def contact - {:whisper-identity constants/console-chat-id - :name (i18n/label :t/status-console) - :photo-path (str "contacts://" constants/console-chat-id) - :dapp? true - :unremovable? true - :bot-url "local://console-bot" - :status (i18n/label :t/intro-status)}) diff --git a/src/status_im/chat/constants.cljs b/src/status_im/chat/constants.cljs index 232de47f7a..7f15175849 100644 --- a/src/status_im/chat/constants.cljs +++ b/src/status_im/chat/constants.cljs @@ -9,10 +9,6 @@ (def console-chat-id "console") -;; TODO(janherich): figure out something better then this -(def send-command-ref ["transactor" :command 83 "send"]) -(def request-command-ref ["transactor" :command 83 "request"]) - (def spam-message-frequency-threshold 4) (def spam-interval-ms 1000) (def default-cooldown-period-ms 10000) diff --git a/src/status_im/chat/events.cljs b/src/status_im/chat/events.cljs index 7a8c5dbde2..95d806eb1a 100644 --- a/src/status_im/chat/events.cljs +++ b/src/status_im/chat/events.cljs @@ -6,8 +6,7 @@ [status-im.i18n :as i18n] [status-im.chat.models :as models] [status-im.chat.models.message :as models.message] - [status-im.chat.console :as console] - [status-im.commands.events.loading :as events.loading] + [status-im.chat.commands.core :as commands] [status-im.ui.screens.navigation :as navigation] [status-im.utils.handlers :as handlers] [status-im.utils.handlers-macro :as handlers-macro] @@ -21,11 +20,10 @@ [status-im.data-store.messages :as messages-store] [status-im.data-store.user-statuses :as user-statuses-store] [status-im.data-store.contacts :as contacts-store] - [status-im.chat.events.commands :as events.commands] + status-im.chat.events.input status-im.chat.events.requests status-im.chat.events.send-message - status-im.chat.events.receive-message - status-im.chat.events.console)) + status-im.chat.events.receive-message)) ;;;; Effects @@ -109,14 +107,6 @@ db updated-statuses)}))) -(defn init-console-chat - [{:keys [db]}] - (when-not (get-in db [:chats constants/console-chat-id]) - {:db (-> db - (assoc :current-chat-id constants/console-chat-id) - (update :chats assoc constants/console-chat-id console/chat)) - :data-store/tx [(chats-store/save-chat-tx console/chat)]})) - (defn- add-default-contacts [{:keys [db default-contacts] :as cofx}] (let [new-contacts (-> {} @@ -134,16 +124,12 @@ :dapp-url (-> props :dapp-url :en) :bot-url (:bot-url props) :description (:description props)}]))) - default-contacts) - (assoc constants/console-chat-id console/contact)) + default-contacts)) existing-contacts (:contacts/contacts db) contacts-to-add (select-keys new-contacts (set/difference (set (keys new-contacts)) (set (keys existing-contacts))))] - (handlers-macro/merge-fx cofx - {:db (update db :contacts/contacts merge contacts-to-add) - :data-store/tx [(contacts-store/save-contacts-tx - (vals contacts-to-add))]} - (events.loading/load-commands)))) + {:db (update db :contacts/contacts merge contacts-to-add) + :data-store/tx [(contacts-store/save-contacts-tx (vals contacts-to-add))]})) (defn- group-chat-messages [{:keys [db]}] @@ -193,9 +179,9 @@ {:db (assoc db :chats chats :contacts/dapps default-dapps)} - (init-console-chat) (group-chat-messages) - (add-default-contacts))))) + (add-default-contacts) + (commands/index-commands commands/register))))) (defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}] (when (and (not (get-in db [:chats chat-id :public?])) diff --git a/src/status_im/chat/events/commands.cljs b/src/status_im/chat/events/commands.cljs deleted file mode 100644 index 918979e6b0..0000000000 --- a/src/status_im/chat/events/commands.cljs +++ /dev/null @@ -1,83 +0,0 @@ -(ns status-im.chat.events.commands - (:require [re-frame.core :as re-frame] - [taoensso.timbre :as log] - [status-im.chat.models.message :as models.message] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.handlers :as handlers] - [status-im.i18n :as i18n] - [status-im.utils.platform :as platform] - [status-im.chat.events.shortcuts :as shortcuts] - [status-im.utils.ethereum.tokens :as tokens])) - -;;;; Helper fns - -;;TODO(goranjovic): currently we only allow tokens which are enabled in Manage assets here -;; because balances are only fetched for them. Revisit this decision with regard to battery/network consequences -;; if we were to update all balances. -(defn- allowed-assets [chain account] - (let [visible-token-symbols (get-in account [:settings :wallet :visible-tokens (keyword chain)])] - (->> (tokens/tokens-for (keyword chain)) - (filter #(not (:nft? %))) - (filter #(contains? visible-token-symbols (:symbol %))) - (map #(vector (-> % :symbol clojure.core/name) - (:decimals %))) - (into {"ETH" 18})))) - -(defn- generate-context - "Generates context for jail call" - [account current-account-id chat-id group-chat? to chain] - (merge {:platform platform/os - :network chain - :from current-account-id - :to to - :allowed-assets (clj->js (allowed-assets chain account)) - :chat {:chat-id chat-id - :group-chat (boolean group-chat?)}} - i18n/delimeters)) - -(defn request-command-message-data - "Requests command message data from jail" - [{:contacts/keys [contacts] :account/keys [account] :keys [chain] :as db} - {{:keys [command command-scope-bitmask bot params type]} :content - :keys [chat-id group-id] :as message} - {:keys [data-type] :as opts}] - (if-not (get contacts bot) ;; bot is not even in contacts, do nothing - {:db db} - (if (get-in contacts [bot :jail-loaded?]) - (let [path [(if (= :response (keyword type)) :responses :commands) - [command command-scope-bitmask] - data-type] - to (get-in contacts [chat-id :address]) - address (get-in db [:account/account :address]) - jail-params {:parameters params - :context (generate-context account address chat-id (models.message/group-message? message) to chain)}] - {:db db - :call-jail [{:jail-id bot - :path path - :params jail-params - :callback-event-creator (fn [jail-response] - [::jail-command-data-response - jail-response message opts])}]}) - {:db (update-in db [:contacts/contacts bot :jail-loaded-events] - conj [:request-command-message-data message opts])}))) - -;;;; Handlers - -(handlers/register-handler-fx - ::jail-command-data-response - [re-frame/trim-v] - (fn [{:keys [db]} [{{:keys [returned]} :result} {:keys [chat-id]} {:keys [proceed-event-creator]}]] - (when proceed-event-creator - {:dispatch (proceed-event-creator returned)}))) - -(defn short-preview? [opts] - (= :short-preview (:data-type opts))) - -(handlers/register-handler-fx - :request-command-message-data - [re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)] - (fn [{:keys [db]} [message opts]] - (if (and (short-preview? opts) - (shortcuts/shortcut-override? message)) - (shortcuts/shortcut-override-fx db message) - (request-command-message-data db message opts)))) diff --git a/src/status_im/chat/events/console.cljs b/src/status_im/chat/events/console.cljs deleted file mode 100644 index 0309fbf9d3..0000000000 --- a/src/status_im/chat/events/console.cljs +++ /dev/null @@ -1,63 +0,0 @@ -(ns status-im.chat.events.console - (:require [status-im.constants :as constants] - [status-im.i18n :as i18n] - [status-im.chat.console :as console-chat] - [taoensso.timbre :as log] - [status-im.i18n :as i18n] - [goog.string :as gstring] - goog.string.format)) - -(defn console-respond-command-messages - [{:keys [name] :as command} handler-data {:keys [random-id-seq now]}] - (when-let [messages (case name - "js" (let [{:keys [err data messages]} handler-data - content (or err data) - message-events (mapv (fn [{:keys [message type]} id] - (console-chat/console-message - {:message-id id - :timestamp now - :content (str type ": " message) - :content-type constants/text-content-type})) - messages random-id-seq)] - (conj message-events - (console-chat/console-message - {:message-id (first random-id-seq) - :timestamp now - :content (str content) - :content-type constants/text-content-type}))) - (log/debug "ignoring command: " name))] - {:dispatch [:chat-received-message/add messages]})) - -(defn faucet-base-url->url [url] - (str url "/donate/0x%s")) - -(defn- faucet-response-event [now message-id content] - [:chat-received-message/add - [(console-chat/console-message - {:message-id message-id - :timestamp now - :content content - :content-type constants/text-content-type})]]) - -(def console-commands->fx - {"faucet" - (fn [{:keys [db random-id now]} {:keys [params]}] - (let [current-address (get-in db [:account/account :address]) - faucet-url (faucet-base-url->url (:url params))] - {:http-get {:url (gstring/format faucet-url current-address) - :success-event-creator (fn [_] - (faucet-response-event - now - random-id - (i18n/label :t/faucet-success))) - :failure-event-creator (fn [event] - (log/error "Faucet error" event) - (faucet-response-event - now - random-id - (i18n/label :t/faucet-error)))}}))}) - -(def commands-names (set (keys console-commands->fx))) - -(def commands-with-delivery-status - (disj commands-names "faucet")) diff --git a/src/status_im/chat/events/input.cljs b/src/status_im/chat/events/input.cljs index 720423e154..1f93f0a305 100644 --- a/src/status_im/chat/events/input.cljs +++ b/src/status_im/chat/events/input.cljs @@ -2,16 +2,16 @@ (:require [clojure.string :as string] [re-frame.core :as re-frame] [taoensso.timbre :as log] - [status-im.chat.constants :as constants] + [status-im.constants :as constants] + [status-im.chat.constants :as chat-constants] [status-im.chat.models :as model] [status-im.chat.models.input :as input-model] - [status-im.chat.models.commands :as commands-model] [status-im.chat.models.message :as message-model] - [status-im.chat.events.commands :as commands-events] - [status-im.bots.events :as bots-events] - [status-im.utils.ethereum.tokens :as tokens] + [status-im.chat.commands.core :as commands] + [status-im.chat.commands.sending :as commands-sending] [status-im.ui.components.react :as react-comp] - [status-im.utils.handlers :as handlers])) + [status-im.utils.handlers :as handlers] + [status-im.utils.handlers-macro :as handlers-macro])) ;;;; Effects @@ -43,260 +43,40 @@ ;;;; Helper functions -(defn set-chat-input-text - "Set input text for current-chat and updates suggestions relevant to current input. - Takes db, input text and `:append?` flag as arguments and returns new db. - When `:append?` is false or not provided, resets the current chat input with input text, - otherwise input text is appended to the current chat input." - [{:keys [current-chat-id] :as db} new-input & {:keys [append?]}] - (let [current-input (get-in db [:chats current-chat-id :input-text]) - {:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id]) - chat-text (if append? - (str current-input new-input) - new-input)] - (cond-> (model/set-chat-ui-props db {:validation-messages nil}) - true - (assoc-in [:chats current-chat-id :input-text] (input-model/text->emoji chat-text)) - - (and dapp? (string/blank? chat-text)) - (assoc-in [:chats current-chat-id :parameter-boxes :message] nil)))) - -;; TODO janherich: this is super fragile and won't work at all for group chats with bots. -;; The proper way how to do it is to check each chat participant and call `:on-message-input-change` -;; jail function in each participant's jail -(defn call-on-message-input-change - "Calls bot's `on-message-input-change` function" - [{:keys [current-chat-id chats local-storage] :as db}] - (let [chat-text (string/trim (or (get-in chats [current-chat-id :input-text]) "")) - {:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id]) - address (get-in db [:account/account :address])] - (cond-> {:db db} - (and dapp? (not (string/blank? chat-text))) - (assoc :call-jail-function {:chat-id current-chat-id - :function :on-message-input-change - :parameters {:message chat-text} - :context {:data (get local-storage current-chat-id) - :from address}})))) - -(defn set-chat-input-metadata - "Set input metadata for active chat. Takes db and metadata and returns updated db." - [{:keys [current-chat-id] :as db} metadata] - (assoc-in db [:chats current-chat-id :input-metadata] metadata)) - -(defn set-command-argument - "Sets command argument in active chat" - [{:keys [current-chat-id] :as db} index arg move-to-next?] - (let [command (-> (get-in db [:chats current-chat-id :input-text]) - (input-model/split-command-args))] - (let [arg (string/replace arg (re-pattern constants/arg-wrapping-char) "") - 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)) - input-text (str command-name - constants/spacing-char - (input-model/join-command-args command-args) - (when (and move-to-next? - (= index (dec (count command-args)))) - constants/spacing-char))] - (set-chat-input-text db input-text)))) - -(defn load-chat-parameter-box - "Returns fx for loading chat parameter box for active chat" - [{:keys [current-chat-id bot-db] :as db} - {:keys [name scope-bitmask type bot owner-id] :as command}] - (let [parameter-index (input-model/argument-position db)] - (when (and command (> parameter-index -1)) - (let [data (get-in db [:local-storage current-chat-id]) - bot-db (get bot-db owner-id) - path [(if (= :command type) :commands :responses) - [name scope-bitmask] - :params - parameter-index - :suggestions] - args (-> (get-in db [:chats current-chat-id :input-text]) - input-model/split-command-args - rest) - to (get-in db [:contacts/contacts current-chat-id :address]) - from (get-in db [:account/account :address]) - params {:parameters {:args args - :bot-db bot-db} - :context {:data data - :from from - :to to}}] - {:call-jail [{:jail-id owner-id - :path path - :params params - :callback-event-creator (fn [jail-response] - [:chat-received-message/bot-response - {:chat-id current-chat-id - :command command - :parameter-index parameter-index} - jail-response])}]})))) - (defn chat-input-focus "Returns fx for focusing on active chat input reference" - [{:keys [current-chat-id chat-ui-props]} ref] + [ref {{:keys [current-chat-id chat-ui-props]} :db}] (when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])] {::focus-rn-component cmp-ref})) -(defn update-text-selection - "Updates text selection in active chat input" - [db selection] - (let [command (input-model/selected-chat-command db) - new-db (model/set-chat-ui-props db {:selection selection}) - chat-parameter-box-fx (when command - (load-chat-parameter-box new-db (:command command)))] - (cond-> {:db new-db} - - chat-parameter-box-fx - (merge chat-parameter-box-fx)))) - -(defn select-chat-input-command - "Selects command + (optional) arguments as input for active chat" - [{:keys [prefill prefill-bot-db name owner-id] :as command} metadata prevent-auto-focus? {:keys [db]}] - (let [{:keys [current-chat-id chat-ui-props]} db - db' (-> db - (bots-events/clear-bot-db owner-id) - (model/set-chat-ui-props {:show-suggestions? false - :result-box nil}) - (set-chat-input-metadata metadata) - (set-chat-input-text (str (commands-model/command-name command) - constants/spacing-char - (input-model/join-command-args prefill)))) - fx (assoc (load-chat-parameter-box db' command) :db db')] - (cond-> fx - prefill-bot-db (update :db bots-events/update-bot-db {:db prefill-bot-db - :bot owner-id}) - - (not prevent-auto-focus?) - (merge (chat-input-focus (:db fx) :input-ref))))) - -;; TODO(goranjovic) - generalize setting something as a command argument -(defn set-contact-as-command-argument - "Sets contact as command argument for active chat" - [db {:keys [bot-db-key contact arg-index]}] - (let [name (string/replace (:name contact) (re-pattern constants/arg-wrapping-char) "") - contact (select-keys contact [:address :whisper-identity :name :photo-path :dapp?]) - command-owner (get-in (input-model/selected-chat-command db) [:command :owner-id])] - (-> db - (set-command-argument arg-index name true) - (bots-events/set-in-bot-db {:bot command-owner - :path [:public (keyword bot-db-key)] - :value contact}) - (as-> fx - (let [{:keys [current-chat-id] - :as new-db} (:db fx) - arg-position (input-model/argument-position new-db) - input-text (get-in new-db [:chats current-chat-id :input-text]) - command-args (cond-> (input-model/split-command-args input-text) - (input-model/text-ends-with-space? input-text) (conj "")) - new-selection (->> command-args - (take (+ 3 arg-position)) - (input-model/join-command-args) - count - (min (count input-text)))] - (merge fx (update-text-selection new-db new-selection))))))) - -;; TODO(goranjovic) - generalize setting something as a command argument -(defn set-asset-as-command-argument - "Sets asset as command argument for active chat" - [db {:keys [bot-db-key asset arg-index]}] - (let [name (string/replace (name (:symbol asset)) (re-pattern constants/arg-wrapping-char) "") - command-owner (get-in (input-model/selected-chat-command db) [:command :owner-id])] - (-> db - (set-command-argument arg-index name true) - (bots-events/set-in-bot-db {:bot command-owner - :path [:public (keyword bot-db-key)] - :value asset}) - (as-> fx - (let [{:keys [current-chat-id] - :as new-db} (:db fx) - arg-position (input-model/argument-position new-db) - input-text (get-in new-db [:chats current-chat-id :input-text]) - command-args (cond-> (input-model/split-command-args input-text) - (input-model/text-ends-with-space? input-text) (conj "")) - new-selection (->> command-args - (take (+ 3 arg-position)) - (input-model/join-command-args) - count - (min (count input-text)))] - (merge fx (update-text-selection new-db new-selection))))))) - -;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects -(defn- command->message - [{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}] - (message-model/add-message-type - {:chat-id current-chat-id - :content {:bot (:owner-id command) - :command (:name command) - :type (:type command) - :command-scope-bitmask (:scope-bitmask command) - :params (assoc (input-model/args->params command-params) - :bot-db (get bot-db (:owner-id command)))}} - (get chats current-chat-id))) - -(defn proceed-command - "Proceed with command processing by creating command message + setting up and executing chain of events: - 1. Params validation - 2. Short preview fetching - 3. Preview fetching" - [{:keys [bot-db current-chat-id chats] :as db} {:keys [command metadata] :as command-params} message-id current-time] - (let [message (command->message db command-params) - cmd-params {:command command - :params (get-in message [:content :params]) - :to-message (:to-message-id metadata) - :created-at current-time - :id message-id - :chat-id current-chat-id} - event-chain {:data-type :validator - :proceed-event-creator (fn [validation-response] - [::proceed-validation - validation-response - [[:request-command-message-data - message - {:data-type :short-preview - :proceed-event-creator (fn [short-preview] - [:request-command-message-data - message - {:data-type :preview - :proceed-event-creator (fn [preview] - [::send-command - (update cmd-params :command merge - {:short-preview short-preview - :preview preview})])}])}]]])}] - (commands-events/request-command-message-data db message event-chain))) - ;;;; Handlers (handlers/register-handler-fx :set-chat-input-text [re-frame/trim-v] - (fn [{:keys [db]} [text]] - (let [new-db (set-chat-input-text db text) - fx (call-on-message-input-change new-db)] - (if-let [{:keys [command]} (input-model/selected-chat-command new-db)] - (merge fx (load-chat-parameter-box new-db command)) - fx)))) + (fn [cofx [text]] + (input-model/set-chat-input-text text cofx))) (handlers/register-handler-fx :select-chat-input-command [re-frame/trim-v] - (fn [cofx [command metadata prevent-auto-focus?]] - (select-chat-input-command command metadata prevent-auto-focus? cofx))) + (fn [{:keys [db] :as cofx} [command params metadata]] + (handlers-macro/merge-fx cofx + (input-model/set-chat-input-metadata metadata) + (commands/select-chat-input-command command params) + (chat-input-focus :input-ref)))) -(handlers/register-handler-db - :set-command-argument +(handlers/register-handler-fx + :set-command-parameter [re-frame/trim-v] - (fn [db [[index arg move-to-next?]]] - (set-command-argument db index arg move-to-next?))) + (fn [cofx [last-param? index value]] + (commands/set-command-parameter last-param? index value cofx))) (handlers/register-handler-fx :chat-input-focus [re-frame/trim-v] - (fn [{:keys [db]} [ref]] - (chat-input-focus db ref))) + (fn [cofx [ref]] + (chat-input-focus ref cofx))) (handlers/register-handler-fx :chat-input-blur @@ -305,105 +85,56 @@ (when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])] {::blur-rn-component cmp-ref}))) -(handlers/register-handler-fx - ::proceed-validation - [re-frame/trim-v] - (fn [_ [{:keys [markup parameters]} proceed-events]] - (let [error-events-creator (fn [validator-result] - [[:set-chat-ui-props {:validation-messages validator-result - :sending-in-progress? false}]]) - events (if markup - (error-events-creator markup) - proceed-events)] - {:dispatch-n events}))) - -(defn cleanup-chat-command [db] - (-> (model/set-chat-ui-props db {:sending-in-progress? false}) - (set-chat-input-metadata nil) - (set-chat-input-text nil))) - -(handlers/register-handler-fx - :cleanup-chat-command - (fn [{:keys [db]}] - {:db (cleanup-chat-command db)})) - -(handlers/register-handler-fx - ::send-command - message-model/send-interceptors - (fn [{:keys [db] :as cofx} [command-message]] - (let [{:keys [current-chat-id current-public-key]} db - new-db (cleanup-chat-command db) - address (get-in db [:account/account :address])] - (merge {:db new-db} - (message-model/process-command (assoc cofx :db new-db) - {:message (get-in db [:chats current-chat-id :input-text]) - :command command-message - :chat-id current-chat-id - :identity current-public-key - :address address}))))) - -(defn command-complete? - [chat-command] - (= :complete (input-model/command-completion chat-command))) - (defn command-complete-fx - "command is complete, set `:sendint-in-progress?` flag and proceed with command processing" - [db chat-command message-id current-time] - (-> db - (model/set-chat-ui-props {:sending-in-progress? true}) - (proceed-command chat-command message-id current-time))) + "command is complete, set `:sending-in-progress?` flag and proceed with command processing" + [input-text command {:keys [db now random-id] :as cofx}] + (handlers-macro/merge-fx + cofx + {:db (model/set-chat-ui-props db {:sending-in-progress? true})} + (commands-sending/validate-and-send input-text command))) (defn command-not-complete-fx "command is not complete, just add space after command if necessary" - [db input-text] + [input-text current-chat-id {:keys [db]}] {:db (cond-> db (not (input-model/text-ends-with-space? input-text)) - (set-chat-input-text constants/spacing-char :append? true))}) + (assoc-in [:chats current-chat-id :input-text] + (str input-text chat-constants/spacing-char)))}) (defn plain-text-message-fx "no command detected, when not empty, proceed by sending text message without command processing" - [db cofx input-text current-chat-id current-public-key] + [input-text current-chat-id {:keys [db] :as cofx}] (when-not (string/blank? input-text) - (message-model/send-message (assoc cofx :db (-> db - (set-chat-input-metadata nil) - (set-chat-input-text nil))) - {:message-text input-text - :chat-id current-chat-id - :identity current-public-key}))) + (handlers-macro/merge-fx + cofx + (input-model/set-chat-input-text nil) + (input-model/set-chat-input-metadata nil) + (message-model/send-message {:chat-id current-chat-id + :content-type constants/text-content-type + :content input-text})))) (handlers/register-handler-fx :send-current-message message-model/send-interceptors - (fn [{{:keys [current-chat-id current-public-key] :as db} :db message-id :random-id current-time :now - :as cofx} _] + (fn [{{:keys [current-chat-id id->command access-scope->command-id] :as db} :db :as cofx} _] (when-not (get-in db [:chat-ui-props current-chat-id :sending-in-progress?]) (let [input-text (get-in db [:chats current-chat-id :input-text]) - chat-command (-> (input-model/selected-chat-command db) - (update :args (partial remove string/blank?)))] - (if (:command chat-command) + command (commands/selected-chat-command + input-text nil (commands/chat-commands id->command + access-scope->command-id + (get-in db [:chats current-chat-id])))] + (if command ;; Returns true if current input contains command - (if (command-complete? chat-command) - (command-complete-fx db chat-command message-id current-time) - (command-not-complete-fx db input-text)) - (plain-text-message-fx db cofx input-text current-chat-id current-public-key)))))) + (if (= :complete (:command-completion command)) + (command-complete-fx input-text command cofx) + (command-not-complete-fx input-text current-chat-id cofx)) + (plain-text-message-fx input-text current-chat-id cofx)))))) -(handlers/register-handler-fx +(handlers/register-handler-db :update-text-selection [re-frame/trim-v] - (fn [{:keys [db]} [selection]] - (update-text-selection db selection))) - -(handlers/register-handler-fx - :set-contact-as-command-argument - [re-frame/trim-v] - (fn [{:keys [db]} [params]] - (set-contact-as-command-argument db params))) - -(handlers/register-handler-fx - :set-asset-as-command-argument - [re-frame/trim-v] - (fn [{:keys [db]} [params]] - (set-asset-as-command-argument db params))) + (fn [db [selection]] + (model/set-chat-ui-props db {:selection selection}))) (handlers/register-handler-db :show-suggestions diff --git a/src/status_im/chat/events/receive_message.cljs b/src/status_im/chat/events/receive_message.cljs index ffb5679f72..9e0efc3b89 100644 --- a/src/status_im/chat/events/receive_message.cljs +++ b/src/status_im/chat/events/receive_message.cljs @@ -1,57 +1,18 @@ (ns status-im.chat.events.receive-message (:require [re-frame.core :as re-frame] [taoensso.timbre :as log] - [status-im.chat.events.commands :as commands-events] [status-im.chat.models.message :as message-model] - [status-im.constants :as constants] - [status-im.utils.clocks :as utils.clocks] [status-im.utils.handlers :as handlers] [status-im.utils.handlers-macro :as handlers-macro] [status-im.wallet.transactions :as wallet.transactions])) ;;;; Handlers -(handlers/register-handler-fx - ::received-message - message-model/receive-interceptors - (fn [cofx [message]] - (when (message-model/add-to-chat? cofx message) - (message-model/receive message cofx)))) - (re-frame.core/reg-fx :chat-received-message/add-fx (fn [messages] (re-frame/dispatch [:chat-received-message/add messages]))) -(defn- request-command-message-data [message {:keys [db]}] - (commands-events/request-command-message-data - db message - {:data-type :short-preview - :proceed-event-creator (fn [short-preview] - [:request-command-message-data - message - {:data-type :preview - :proceed-event-creator (fn [preview] - [::received-message - (update message :content merge - {:short-preview short-preview - :preview preview})])}])})) - -(defn add-message [{:keys [content] :as message} {:keys [db] :as cofx}] - (when (message-model/add-to-chat? cofx message) - (if (:command content) - ;; we are dealing with received command message, we can't add it right away, - ;; we first need to fetch short-preview + preview and add it only after we already have those. - ;; note that `request-command-message-data` implicitly wait till jail is ready and - ;; calls are made only after that - (let [{:keys [command params]} content - tx-hash (:tx-hash params)] - (handlers-macro/merge-fx cofx - (wallet.transactions/store-chat-transaction-hash tx-hash) - (request-command-message-data message))) - ;; regular non command message, we can add it right away - (message-model/receive message cofx)))) - (defn- filter-messages [messages cofx] (:accumulated (reduce (fn [{:keys [seen-ids] :as acc} {:keys [message-id] :as message}] @@ -65,45 +26,9 @@ :accumulated []} messages))) -(defn add-messages [messages {:keys [db] :as cofx}] - (let [messages-to-add (filter-messages messages cofx) - plain-messages (remove (comp :command :content) messages-to-add) - command-messages (filter (comp :command :content) messages-to-add)] - (handlers-macro/merge-effects (message-model/receive-many plain-messages cofx) - cofx - add-message - command-messages))) - (handlers/register-handler-fx :chat-received-message/add message-model/receive-interceptors (fn [cofx [messages]] - (add-messages messages cofx))) + (message-model/receive-many (filter-messages messages cofx) cofx))) -;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored -(handlers/register-handler-fx - :chat-received-message/bot-response - message-model/receive-interceptors - (fn [{:keys [random-id now]} [{:keys [chat-id] :as params} {:keys [result bot-id] :as data}]] - (let [{:keys [returned context]} result - {:keys [markup text-message err]} returned - {:keys [update-db default-db]} context - content (or err text-message)] - (when update-db - (re-frame/dispatch [:update-bot-db {:bot bot-id - :db update-db}])) - (re-frame/dispatch [:suggestions-handler (assoc params - :bot-id bot-id - :result data - :default-db default-db)]) - (when content - (re-frame/dispatch [:chat-received-message/add - [{:message-id random-id - :timestamp now - :content (str content) - :content-type constants/text-content-type - :clock-value (utils.clocks/send 0) - :chat-id chat-id - :from chat-id - :to "me" - :show? true}]]))))) diff --git a/src/status_im/chat/events/send_message.cljs b/src/status_im/chat/events/send_message.cljs index a2615f8072..8ad40fcd1e 100644 --- a/src/status_im/chat/events/send_message.cljs +++ b/src/status_im/chat/events/send_message.cljs @@ -13,19 +13,3 @@ tokens-json (types/clj->json tokens)] (log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json) (status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %))))) - -;;;; Handlers - -(handlers/register-handler-fx - :chat-send-message/send-command - message-model/send-interceptors - (fn [cofx [_ params]] - (message-model/send-command cofx params))) - -(handlers/register-handler-fx - :chat-send-message/from-jail - [re-frame/trim-v] - (fn [cofx [{:keys [chat-id message]}]] - (let [parsed-message (types/json->clj message)] - (message-model/handle-message-from-bot cofx {:message parsed-message - :chat-id chat-id})))) \ No newline at end of file diff --git a/src/status_im/chat/events/shortcuts.cljs b/src/status_im/chat/events/shortcuts.cljs deleted file mode 100644 index c49be4cc9f..0000000000 --- a/src/status_im/chat/events/shortcuts.cljs +++ /dev/null @@ -1,45 +0,0 @@ -(ns status-im.chat.events.shortcuts - (:require [status-im.ui.screens.wallet.send.events :as send.events] - [status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events] - [status-im.ui.screens.navigation :as navigation] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.ethereum.tokens :as tokens] - [taoensso.timbre :as log])) - -;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233 -(defn- transaction-details [contact symbol] - (-> contact - (select-keys [:name :address :whisper-identity]) - (assoc :symbol symbol - :gas (ethereum/estimate-gas symbol) - :from-chat? true))) - -(defn send-shortcut-fx [{:account/keys [account] :as db} contact params] - (let [chain (keyword (:chain db)) - symbol (-> params :asset keyword) - {:keys [decimals]} (tokens/asset-for chain symbol)] - (merge {:db (-> db - (send.events/set-and-validate-amount-db (:amount params) symbol decimals) - (choose-recipient.events/fill-request-details (transaction-details contact symbol)) - (update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?) - (navigation/navigate-to - (if (:wallet-set-up-passed? account) - :wallet-send-transaction-chat - :wallet-onboarding-setup)))} - (send.events/update-gas-price db false)))) - -(def shortcuts - {"send" send-shortcut-fx}) - -(defn shortcut-override? [message] - (get shortcuts (get-in message [:content :command]))) - -(defn shortcut-override-fx [db {:keys [chat-id content]}] - (let [command (:command content) - contact (get-in db [:contacts/contacts chat-id]) - shortcut-specific-fx (get shortcuts command)] - (-> db - (shortcut-specific-fx contact (:params content)) - ;; TODO(goranjovic) - replace this dispatch with a function call - ;; Need to refactor chat events namespaces for this to avoid circular dependecy - (assoc :dispatch [:cleanup-chat-command])))) diff --git a/src/status_im/chat/models/commands.cljs b/src/status_im/chat/models/commands.cljs deleted file mode 100644 index 80398c9035..0000000000 --- a/src/status_im/chat/models/commands.cljs +++ /dev/null @@ -1,45 +0,0 @@ -(ns status-im.chat.models.commands - (:require [status-im.chat.constants :as chat-consts] - [clojure.string :as str] - [taoensso.timbre :as log])) - -(defn- resolve-references - [contacts name->ref] - (reduce-kv (fn [acc name ref] - (assoc acc name (get-in contacts ref))) - {} - name->ref)) - -(defn- is-dapp? [all-contacts identity] - (get-in all-contacts [identity :dapp?])) - -(defn command-name [{:keys [name]}] - (str chat-consts/command-char name)) - -(defn commands-responses - "Returns map of commands/responses eligible for current chat." - [type access-scope->commands-responses {:keys [address]} {:keys [contacts group-chat public?]} all-contacts] - (let [dapps? (some (partial is-dapp? all-contacts) contacts) - humans? (some (comp not (partial is-dapp? all-contacts)) contacts) - basic-access-scope (cond-> #{} - group-chat (conj :group-chats) - (not group-chat) (conj :personal-chats) - address (conj :registered) - (not address) (conj :anonymous) - dapps? (conj :dapps) - humans? (conj :humans) - public? (conj :public-chats)) - global-access-scope (conj basic-access-scope :global) - member-access-scopes (into #{} (map (partial conj basic-access-scope)) contacts)] - (reduce (fn [acc access-scope] - (merge acc (resolve-references all-contacts - (get-in access-scope->commands-responses [access-scope type])))) - {} - (cons global-access-scope member-access-scopes)))) - -(defn requested-responses - "Returns map of requested command responses eligible for current chat." - [access-scope->commands-responses account chat contacts requests] - (let [requested-responses (map :response requests) - responses-map (commands-responses :response access-scope->commands-responses account chat contacts)] - (select-keys responses-map requested-responses))) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 38609ec514..4eea74a02b 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -3,11 +3,12 @@ [goog.object :as object] [status-im.chat.constants :as const] [status-im.chat.models :as chat-model] - [status-im.chat.models.commands :as commands-model] [status-im.utils.config :as config] [status-im.utils.datetime :as datetime] [status-im.js-dependencies :as dependencies])) +(def space-char " ") + (defn text->emoji "Replaces emojis in a specified `text`" [text] @@ -88,118 +89,18 @@ (str const/arg-wrapping-char arg const/arg-wrapping-char))))) (str/join const/spacing-char)))) -(defn selected-chat-command - "Takes whole db, or current chat and available commands/responses and returns a map - containing `:command`, `:metadata` and `:args` keys. Can also return `nil` if there is no selected command. +(defn set-chat-input-text + "Set input text for current-chat. Takes db and input text and cofx + as arguments and returns new fx. Always clear all validation messages." + [new-input {{:keys [current-chat-id] :as db} :db}] + {:db (-> (chat-model/set-chat-ui-props db {:validation-messages nil}) + (assoc-in [:chats current-chat-id :input-text] (text->emoji new-input)))}) - * `:command` key contains a map with all information about command. - * `:metadata` is also a map which contains some additional information, usually not visible by user. - For instance, we can add a `:to-message-id` key to this map, and this key will allow us to identity - the request we're responding to. - * `:args` contains all arguments provided by user." - ([{:keys [input-text input-metadata seq-arguments] :as current-chat} commands responses] - (let [[command-name :as command-args] (split-command-args input-text)] - (when-let [{{:keys [message-id]} :request :as command} (and command-name - (starts-as-command? command-name) - (get (merge commands - responses) - (subs command-name 1)))] - {:command command - :metadata (if (and message-id (not (:to-message-id input-metadata))) - (assoc input-metadata :to-message-id message-id) - input-metadata) - :args (into [] (if (empty? seq-arguments) - (rest command-args) - seq-arguments))}))) - ([{:keys [chats current-chat-id access-scope->commands-responses] - :contacts/keys [contacts] :as db}] - (let [chat (get chats current-chat-id) - account (:account/account db)] - (selected-chat-command chat - (commands-model/commands-responses :command - access-scope->commands-responses - account - chat - contacts) - (commands-model/requested-responses access-scope->commands-responses - account - chat - contacts - (vals (:requests chat))))))) - -(def *no-argument-error* -1) - -(defn current-chat-argument-position - "Returns the position of current argument. It's just an integer number from -1 to infinity. - -1 (`*no-argument-error*`) means error. It can happen if there is no selected command or selection." - [command input-text selection seq-arguments] - (if command - (if (get-in command [:command :sequential-params]) - (count seq-arguments) - (let [subs-input-text (subs input-text 0 selection)] - (if subs-input-text - (let [args (split-command-args subs-input-text) - argument-index (dec (count args)) - ends-with-space? (text-ends-with-space? subs-input-text) - arg-wrapping-count (-> (frequencies subs-input-text) - (get const/arg-wrapping-char) - (or 0))] - (if (and ends-with-space? - (even? arg-wrapping-count)) - argument-index - (dec argument-index))) - *no-argument-error*))) - *no-argument-error*)) - -(defn argument-position - "Returns the position of current argument. It's just an integer from -1 to infinity. - -1 (`*no-argument-error*`) means error. It can happen if there is no command or selection. - - This method is basically just another way of calling `current-chat-argument-position`." - [{:keys [current-chat-id] :as db}] - (let [input-text (get-in db [:chats current-chat-id :input-text]) - seq-arguments (get-in db [:chats current-chat-id :seq-arguments]) - selection (get-in db [:chat-ui-props current-chat-id :selection]) - chat-command (selected-chat-command db)] - (current-chat-argument-position chat-command input-text selection seq-arguments))) - -(defn command-completion - "Returns one of the following values indicating a command's completion status: - * `:complete` means that the command is complete and can be sent; - * `:less-than-needed` means that the command is not complete and additional arguments should be provided; - * `:more-than-needed` means that the command is more than complete and contains redundant arguments; - * `:no-command` means that there is no selected command." - [{:keys [args] :as chat-command}] - (let [args (remove str/blank? args) - params (get-in chat-command [:command :params]) - required-params (remove :optional params)] - (if chat-command - (cond - (or (= (count args) (count params)) - (= (count args) (count required-params))) - :complete - - (< (count args) (count required-params)) - :less-than-needed - - (> (count args) (count params)) - :more-than-needed - - :default - :no-command) - :no-command))) - -(defn args->params - "Uses `args` (which is a list or vector like ['Jarrad' '1.0']) and command's `params` - and returns a map that looks the following way: - {:recipient \"Jarrad\" :amount \"1.0\"}" - [{:keys [command args]}] - (let [params (:params command)] - (->> args - (map-indexed (fn [i value] - [(keyword (get-in params [i :name])) value])) - (remove #(nil? (first %))) - (into {})))) +(defn set-chat-input-metadata + "Sets user invisible chat input metadata for current-chat" + [metadata {:keys [db] :as cofx}] + (let [current-chat-id (:current-chat-id db)] + {:db (assoc-in db [:chats current-chat-id :input-metadata] metadata)})) (defn- start-cooldown [{:keys [db]} cooldowns] {:dispatch-later [{:dispatch [:disable-cooldown] diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index ba720a3e19..76549f07f8 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -4,11 +4,9 @@ [status-im.utils.core :as utils] [status-im.utils.ethereum.core :as ethereum] [status-im.utils.datetime :as time] - [status-im.chat.events.console :as console-events] - [status-im.chat.events.requests :as requests-events] [status-im.chat.models :as chat-model] - [status-im.chat.models.commands :as commands-model] [status-im.chat.models.input :as input] + [status-im.chat.commands.receiving :as commands-receiving] [status-im.utils.clocks :as utils.clocks] [status-im.utils.handlers-macro :as handlers-macro] [status-im.utils.money :as money] @@ -25,15 +23,6 @@ [(re-frame/inject-cofx :random-id) re-frame/trim-v]) -(defn- lookup-response-ref - [access-scope->commands-responses account chat contacts response-name] - (let [available-commands-responses (commands-model/commands-responses :response - access-scope->commands-responses - account - chat - contacts)] - (:ref (get available-commands-responses response-name)))) - (defn- emoji-only-content? [content] (and (string? content) (re-matches constants/regx-emoji content))) @@ -138,25 +127,10 @@ message (assoc message :clock-value (utils.clocks/send last-clock-value)))) -(defn add-command-request - [{:keys [content-type content] :as message} chat {:keys [db]}] - (let [request-command (:request-command content) - current-account (:account/account db) - command-request? (and (= content-type - constants/content-type-command-request) - request-command) - {:keys [access-scope->commands-responses] - :contacts/keys [contacts]} db] - (if command-request? - (assoc-in - message - [:content :request-command-ref] - (lookup-response-ref access-scope->commands-responses - current-account - chat - contacts - request-command)) - message))) +(defn- update-legacy-type [{:keys [content-type] :as message}] + (cond-> message + (= constants/content-type-command-request content-type) + (assoc :content-type constants/content-type-command))) (defn- add-received-message [batch? @@ -170,7 +144,8 @@ (ensure-clock-value chat) ;; TODO (cammellos): Refactor so it's not computed twice (add-outgoing-status cofx) - (add-command-request chat cofx))] + ;; TODO (janherich): Remove after couple of releases + update-legacy-type)] (handlers-macro/merge-fx cofx {:confirm-messages-processed [{:web3 web3 :js-obj js-obj}]} @@ -181,7 +156,7 @@ (add-own-status chat-id message-id (cond (:outgoing message) :sent current-chat? :seen :else :received)) - (requests-events/add-request chat-id message-id) + (commands-receiving/receive message) (send-message-seen chat-id message-id (and (not public?) current-chat? (not (chat-model/bot-only-chat? db chat-id)) @@ -251,47 +226,12 @@ (re-frame/inject-cofx :random-id-seq) re-frame/trim-v]) -(defn- handle-message-from-bot [{:keys [random-id] :as cofx} {:keys [message chat-id]}] - (when-let [message (cond - (string? message) - {:message-id random-id - :content (str message) - :content-type constants/text-content-type - :chat-id chat-id - :from chat-id - :to "me"} - - (= "request" (:type message)) - {:message-id random-id - :content (assoc (:content message) :bot chat-id) - :content-type constants/content-type-command-request - :chat-id chat-id - :from chat-id - :to "me"})] - (receive message cofx))) - -(defn- send-dapp-message! - [{:keys [db] :as cofx} chat-id {:keys [content-type] :as message}] - (if (= content-type constants/content-type-command) - (when-let [text-message (get-in message [:content :handler-data :text-message])] - (handle-message-from-bot cofx {:message text-message - :chat-id chat-id})) - (let [data (get-in db [:local-storage chat-id])] - {:call-jail-function {:chat-id chat-id - :function :on-message-send - :parameters {:message (:content message)} - :context {:data data - :from (get-in db [:account/account :address])}}}))) - (defn- send - [chat-id message-id send-record {{:contacts/keys [contacts] :keys [network-status current-public-key]} :db :as cofx}] - (let [{:keys [dapp?]} (get contacts chat-id)] - (if dapp? - (send-dapp-message! cofx chat-id send-record) - (if (= network-status :offline) - {:dispatch-later [{:ms 10000 - :dispatch [:update-message-status chat-id message-id current-public-key :not-sent]}]} - (transport/send send-record chat-id cofx))))) + [chat-id message-id send-record {{:keys [network-status current-public-key]} :db :as cofx}] + (if (= network-status :offline) + {:dispatch-later [{:ms 10000 + :dispatch [:update-message-status chat-id message-id current-public-key :not-sent]}]} + (transport/send send-record chat-id cofx))) (defn add-message-type [message {:keys [chat-id group-chat public?]}] (cond-> message @@ -302,17 +242,6 @@ (and group-chat (not public?)) (assoc :message-type :group-user-message))) -(defn- prepare-plain-message [chat-id {:keys [identity message-text]} - {:keys [last-clock-value] :as chat} now] - (add-message-type {:chat-id chat-id - :content message-text - :from identity - :content-type constants/text-content-type - :timestamp now - :clock-value (utils.clocks/send last-clock-value) - :show? true} - chat)) - (def ^:private transport-keys [:content :content-type :message-type :clock-value :timestamp]) (defn- upsert-and-send [{:keys [chat-id] :as message} {:keys [now] :as cofx}] @@ -374,155 +303,14 @@ :data-store/tx [(messages-store/delete-message-tx message-id)]} (remove-message-from-group chat-id (get-in db [:chats chat-id :messages message-id])))) -(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}] - (upsert-and-send (prepare-plain-message chat-id params (get-in db [:chats chat-id]) now) cofx)) - -(defn- prepare-command-message - [identity - {:keys [chat-id last-clock-value] :as chat} - now - {request-params :params - request-command :command - :keys [prefill prefillBotDb] - :as request} - {:keys [params command handler-data content-type]} - chain - currency - prices - tx-hash] - (let [content (if request - {:request-command request-command - ;; TODO janherich this is technically not correct, but works for now - :request-command-ref (:ref command) - :params (assoc request-params - :bot-db (:bot-db params) - :fiat-amount (money/fiat-amount-value (:amount request-params) - (-> request-params :asset keyword) - currency - prices) - :currency (name currency)) - :prefill prefill - :prefill-bot-db prefillBotDb} - {:params (cond-> params - (= (:name command) constants/command-send) - (assoc :network chain - :fiat-amount (money/fiat-amount-value (:amount params) - (-> params :asset keyword) - currency - prices) - :currency (name currency) - :tx-hash tx-hash))}) - content' (assoc content - :command (:name command) - :handler-data handler-data - :type (name (:type command)) - :command-scope-bitmask (:scope-bitmask command) - :command-ref (:ref command) - :preview (:preview command) - :short-preview (:short-preview command) - :bot (:owner-id command))] - (add-message-type {:chat-id chat-id - :from identity - :timestamp now - :content content' - :content-type (or content-type - (if request - constants/content-type-command-request - constants/content-type-command)) - :clock-value (utils.clocks/send last-clock-value) - :show? true} - chat))) - -(defn send-command - [{{:keys [current-public-key chats chain prices] :as db} :db :keys [now] :as cofx} params] - (let [{{:keys [handler-data to-message command] :as content} :command chat-id :chat-id} params - ;; We send commands to deleted chats as well, i.e. signed later transactions - chat (or (get chats chat-id) {:chat-id chat-id}) - request (:request handler-data) - command-name (:name command) - tx-hash (get-in db [:wallet :send-transaction :tx-hash]) - currency (-> (currency-settings/get-user-currency db) name string/upper-case keyword)] - (handlers-macro/merge-fx cofx - (upsert-and-send (prepare-command-message current-public-key chat now request content - chain currency prices tx-hash)) - (console-events/console-respond-command-messages command handler-data) - (requests-events/request-answered chat-id to-message)))) - -(defn invoke-console-command-handler - [{:keys [db] :as cofx} {:keys [command] :as command-params}] - (let [fx-fn (get console-events/console-commands->fx (-> command :command :name)) - fx (fx-fn cofx command)] - (let [command (send-command (assoc cofx :db (or (:db fx) db)) command-params) - dn (concat (:dispatch-n fx) (:dispatch-n command))] - ;; Make sure `dispatch-n` do not collide - ;; TODO (jeluard) Refactor to rely on merge-fx and reduce creation of `dispatch-n` - (merge {:dispatch-n dn} (dissoc fx :dispatch-n) (dissoc command :dispatch-n))))) - -(defn invoke-command-handlers - [{{:contacts/keys [contacts] :keys [chain] :as db} :db} - {{:keys [command params id]} :command - :keys [chat-id address] - :as orig-params}] - (let [{:keys [type name scope-bitmask bot owner-id]} command - handler-type (if (= :command type) :commands :responses) - to (get-in contacts [chat-id :address]) - identity (or owner-id bot chat-id) - ;; TODO what's actually semantic difference between `:parameters` and `:context` - ;; and do we have some clear API for both ? seems very messy and unorganized now - jail-params {:parameters params - :context (cond-> {:from address - :to to - :current-account (get db :account/account) - :network chain - :message-id id} - (:async-handler command) - (assoc :orig-params orig-params))}] - {:call-jail [{:jail-id identity - :path [handler-type [name scope-bitmask] :handler] - :params jail-params - :callback-event-creator (fn [jail-response] - (when-not (:async-handler command) - [:command-handler! chat-id orig-params jail-response]))}]})) - -(defn process-command - [cofx {:keys [command chat-id] :as params}] - (let [{:keys [command]} command] - (cond - (and (= constants/console-chat-id chat-id) - (console-events/commands-names (:name command))) - (invoke-console-command-handler cofx params) - - (:has-handler command) - (invoke-command-handlers cofx params) - - :else - (send-command cofx params)))) - -(defn custom-send-command-message [whisper-id address asset amount] - {:message nil, - :command {:command {:bot "transactor", - :color "#5fc48d", - :ref ["transactor" :command 83 "send"], - :name "send", - :type :command, - :async-handler false, - :icon "money_white", - :scope #{:global :personal-chats :registered :humans}, - :has-handler true, - :preview nil, - :short-preview nil, - :scope-bitmask 83, - :owner-id "transactor"}, - :params {:asset asset, - :amount amount}, - :to-message nil, - :created-at (datetime/timestamp), - :chat-id whisper-id, - :handler-data nil}, - :chat-id whisper-id, - :identity whisper-id, - :address address}) - -(defn send-custom-send-command [{:keys [whisper-identity address asset amount]} cofx] - (when whisper-identity - (send-command cofx (custom-send-command-message whisper-identity address asset amount)))) +(defn send-message [{:keys [chat-id] :as message} {:keys [db now] :as cofx}] + (let [{:keys [current-public-key chats]} db + {:keys [last-clock-value] :as chat} (get chats chat-id) + message-data (-> message + (assoc :from current-public-key + :timestamp now + :clock-value (utils.clocks/send + last-clock-value) + :show? true) + (add-message-type chat))] + (upsert-and-send message-data cofx))) diff --git a/src/status_im/chat/specs.cljs b/src/status_im/chat/specs.cljs index 94043e5d50..f3246d9bdd 100644 --- a/src/status_im/chat/specs.cljs +++ b/src/status_im/chat/specs.cljs @@ -1,5 +1,6 @@ (ns status-im.chat.specs - (:require [cljs.spec.alpha :as s])) + (:require [cljs.spec.alpha :as s] + status-im.chat.commands.specs)) (s/def :chat/chats (s/nilable map?)) ; {id (string) chat (map)} active chats on chat's tab (s/def :chat/current-chat-id (s/nilable string?)) ; current or last opened chat-id diff --git a/src/status_im/chat/styles/input/result_box.cljs b/src/status_im/chat/styles/input/result_box.cljs deleted file mode 100644 index 0ec535af1e..0000000000 --- a/src/status_im/chat/styles/input/result_box.cljs +++ /dev/null @@ -1,16 +0,0 @@ -(ns status-im.chat.styles.input.result-box - (:require [status-im.ui.components.styles :as common] - [status-im.ui.components.colors :as colors])) - -(defn root [height bottom] - {:background-color common/color-white - :border-top-color colors/gray-light - :border-top-width 1 - :flex-direction :column - :height height - :left 0 - :right 0 - :elevation 2 - :bottom bottom - :position :absolute}) - diff --git a/src/status_im/chat/styles/message/message.cljs b/src/status_im/chat/styles/message/message.cljs index 93f1c1153c..35cc61d1b8 100644 --- a/src/status_im/chat/styles/message/message.cljs +++ b/src/status_im/chat/styles/message/message.cljs @@ -162,195 +162,6 @@ :margin-bottom 4 :font-size 12}) -(defn command-request-message-view [outgoing] - {:border-radius 14 - :padding-vertical 4 - :background-color (if outgoing colors/hawkes-blue styles/color-white)}) - -(def command-request-from-text - (merge style-sub-text {:margin-bottom 2})) - -(defn command-request-image-touchable [] - {:position :absolute - :top 0 - :right -8 - :align-items :center - :justify-content :center - :width 48 - :height 48}) - -(defn command-request-image-view [command scale] - {:width 32 - :height 32 - :border-radius 16 - :background-color (:color command) - :transform [{:scale scale}]}) - -(def command-image-view - {:position :absolute - :top 0 - :right 0 - :width 24 - :height 24 - :align-items :center}) - -(def command-request-image - {:position :absolute - :top 9 - :left 10 - :width 12 - :height 13}) - -(def command-request-separator-line - {:background-color colors/gray-light - :height 1 - :border-radius 8 - :margin-top 10}) - -(def command-request-button - {:align-items :center - :padding-top 8}) - -(defn command-request-button-text [answered?] - {:font-size 15 - :color (if answered? colors/gray colors/blue)}) - -(def command-request-text-view - {:margin-top 4 - :height 14}) - -(defn command-request-header-text [outgoing] - {:font-size 12 - :color (if outgoing colors/wild-blue-yonder colors/gray)}) - -(def command-request-network-text - {:color colors/red}) - -(def command-request-row - {:flex-direction :row - :margin-top 6}) - -(def command-request-fiat-amount-row - {:margin-top 6}) - -(def command-request-fiat-amount-text - {:font-size 12 - :color colors/black}) - -(def command-request-timestamp-row - {:margin-top 6}) - -(defn command-request-timestamp-text [outgoing] - {:font-size 12 - :color (if outgoing colors/wild-blue-yonder colors/gray)}) - -(defstyle command-request-amount-text - {:font-size 22 - :ios {:letter-spacing -0.5} - :color colors/black}) - -(defn command-amount-currency-separator [outgoing] - {:opacity 0 - :color (if outgoing colors/hawkes-blue colors/white)}) - -(defn command-request-currency-text [outgoing] - {:font-size 22 - :letter-spacing 1 - :color (if outgoing colors/wild-blue-yonder colors/gray)}) - -(def command-request-recipient-text - {:color colors/blue - :font-size 14 - :line-height 18}) - -(def content-command-view - {:flex-direction :column - :align-items :flex-start}) - -(def command-container - {:flex-direction :row - :margin-top 4 - :margin-right 32}) - -(def command-image - {:margin-top 9 - :width 12 - :height 13 - :tint-color :#a9a9a9cc}) - -(def command-text - (merge style-message-text - {:margin-top 8 - :margin-horizontal 0})) - -(def command-send-message-view - {:flex-direction :column - :align-items :flex-start}) - -(def command-send-amount-row - {:flex-direction :row - :justify-content :space-between}) - -(def command-send-amount - {:flex-direction :column - :align-items :flex-end - :max-width 250}) - -(defstyle command-send-amount-text - {:font-size 22 - :color colors/blue - :ios {:letter-spacing -0.5}}) - -(def command-send-currency - {:flex-direction :column - :align-items :flex-end}) - -(defn command-send-currency-text [outgoing] - {:font-size 22 - :margin-left 4 - :letter-spacing 1 - :color (if outgoing colors/wild-blue-yonder colors/blue-transparent-40)}) - -(def command-send-fiat-amount - {:flex-direction :column - :justify-content :flex-end - :margin-top 6}) - -(def command-send-fiat-amount-text - {:font-size 12 - :color colors/black}) - -(def command-send-recipient-text - {:color colors/blue - :font-size 14 - :line-height 18}) - -(defn command-send-timestamp [outgoing] - {:color (if outgoing colors/wild-blue-yonder colors/gray) - :margin-top 6 - :font-size 12}) - -(def command-send-status-container - {:margin-top 6 - :flex-direction :row}) - -(defn command-send-status-icon [outgoing] - {:background-color (if outgoing - colors/blue-darker - colors/blue-transparent) - :width 24 - :height 24 - :border-radius 16 - :padding-top 4 - :padding-left 4}) - -(defstyle command-send-status-text - {:color colors/blue - :android {:margin-top 3} - :ios {:margin-top 4} - :margin-left 6 - :font-size 12}) - (def audio-container {:flex-direction :row :align-items :center}) diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index 345d0206d4..7f9a49dfc7 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -3,9 +3,7 @@ [re-frame.core :refer [reg-sub subscribe]] [status-im.chat.constants :as chat-constants] [status-im.chat.models.input :as input-model] - [status-im.chat.models.commands :as commands-model] - [status-im.chat.views.input.utils :as input-utils] - [status-im.commands.utils :as commands-utils] + [status-im.chat.commands.core :as commands] [status-im.utils.datetime :as time] [status-im.utils.platform :as platform] [status-im.utils.gfycat.core :as gfycat] @@ -19,6 +17,10 @@ (reg-sub :chat-ui-props :chat-ui-props) +(reg-sub :get-id->command :id->command) + +(reg-sub :get-access-scope->command-id :access-scope->command-id) + (reg-sub :get-current-chat-ui-props :<- [:chat-ui-props] @@ -63,13 +65,7 @@ :validation-messages :<- [:get-current-chat-ui-props] (fn [ui-props] - (some-> ui-props :validation-messages commands-utils/generate-hiccup))) - -(reg-sub - :result-box-markup - :<- [:get-current-chat-ui-props] - (fn [ui-props] - (some-> ui-props :result-box :markup commands-utils/generate-hiccup))) + (some-> ui-props :validation-messages))) (reg-sub :chat-input-margin @@ -80,18 +76,16 @@ platform/ios? kb-height :default 0))) -(defn- active-chat? [dev-mode? [_ chat]] +(defn- active-chat? [[_ chat]] (and (:is-active chat) - (or dev-mode? - (not= const/console-chat-id (:chat-id chat))))) + (not= const/console-chat-id (:chat-id chat)))) -(defn active-chats [[chats {:keys [dev-mode?]}]] - (into {} (filter (partial active-chat? dev-mode?) chats))) +(defn active-chats [chats] + (into {} (filter active-chat? chats))) (reg-sub :get-active-chats :<- [:get-chats] - :<- [:get-current-account] active-chats) (reg-sub @@ -230,133 +224,83 @@ (reg-sub :get-commands-for-chat - :<- [:get-commands-responses-by-access-scope] - :<- [:get-current-account] + :<- [:get-id->command] + :<- [:get-access-scope->command-id] :<- [:get-current-chat] - :<- [:get-contacts] - (fn [[commands-responses account chat contacts]] - (commands-model/commands-responses :command commands-responses account chat contacts))) - -(reg-sub - :get-responses-for-chat - :<- [:get-commands-responses-by-access-scope] - :<- [:get-current-account] - :<- [:get-current-chat] - :<- [:get-contacts] - (fn [[commands-responses account {:keys [requests] :as chat} contacts]] - (commands-model/requested-responses commands-responses account chat contacts (vals requests)))) + (fn [[id->command access-scope->command-id chat]] + (commands/chat-commands id->command access-scope->command-id chat))) (def ^:private map->sorted-seq (comp (partial map second) (partial sort-by first))) -(defn- available-commands-responses [[commands-responses {:keys [input-text]}]] - (->> commands-responses +(defn- available-commands [[commands {:keys [input-text]}]] + (->> commands map->sorted-seq - (filter (fn [item] + (filter (fn [{:keys [type]}] (when (input-model/starts-as-command? input-text) - (string/includes? (commands-model/command-name item) input-text)))))) + (string/includes? (commands/command-name type) input-text)))))) (reg-sub :get-available-commands :<- [:get-commands-for-chat] :<- [:get-current-chat] - available-commands-responses) + available-commands) (reg-sub - :get-available-responses - :<- [:get-responses-for-chat] - :<- [:get-current-chat] - available-commands-responses) - -(reg-sub - :get-available-commands-responses + :get-all-available-commands :<- [:get-commands-for-chat] - :<- [:get-responses-for-chat] - (fn [[commands responses]] - (map->sorted-seq (merge commands responses)))) + (fn [commands] + (map->sorted-seq commands))) (reg-sub :selected-chat-command :<- [:get-current-chat] + :<- [:get-current-chat-ui-prop :selection] :<- [:get-commands-for-chat] - :<- [:get-responses-for-chat] - (fn [[chat commands responses]] - (input-model/selected-chat-command chat commands responses))) + (fn [[{:keys [input-text]} selection commands]] + (commands/selected-chat-command input-text selection commands))) (reg-sub :chat-input-placeholder :<- [:get-current-chat] :<- [:selected-chat-command] - (fn [[{:keys [input-text]} command]] - (when (and (string/ends-with? (or input-text "") chat-constants/spacing-char) - (not (get-in command [:command :sequential-params]))) - (let [real-args (remove string/blank? (:args command))] - (cond - (and command (empty? real-args)) - (get-in command [:command :params 0 :placeholder]) - - (and command - (= (count real-args) 1)) - (get-in command [:command :params 1 :placeholder])))))) - -(reg-sub - :current-chat-argument-position - :<- [:selected-chat-command] - :<- [:get-current-chat] - :<- [:get-current-chat-ui-prop :selection] - (fn [[command {:keys [input-text seq-arguments]} selection]] - (input-model/current-chat-argument-position command input-text selection seq-arguments))) + (fn [[{:keys [input-text]} {:keys [params current-param-position]}]] + (when (string/ends-with? (or input-text "") chat-constants/spacing-char) + (get-in params [current-param-position :placeholder])))) (reg-sub :chat-parameter-box :<- [:get-current-chat] :<- [:selected-chat-command] - :<- [:current-chat-argument-position] - (fn [[current-chat selected-chat-command argument-position]] - (cond - (and selected-chat-command - (not= argument-position input-model/*no-argument-error*)) - (get-in current-chat [:parameter-boxes - (get-in selected-chat-command [:command :name]) - argument-position]) - - (not selected-chat-command) - (get-in current-chat [:parameter-boxes :message]) - - :default - nil))) + (fn [[current-chat {:keys [current-param-position params]}]] + (when (and params current-param-position) + (get-in params [current-param-position :suggestions])))) (reg-sub :show-parameter-box? :<- [:chat-parameter-box] :<- [:show-suggestions?] - :<- [:get-current-chat] :<- [:validation-messages] - (fn [[chat-parameter-box show-suggestions? {:keys [input-text]} validation-messages]] - (and (get chat-parameter-box :markup) + (fn [[chat-parameter-box show-suggestions? validation-messages]] + (and chat-parameter-box (not validation-messages) (not show-suggestions?)))) -(reg-sub - :command-completion - :<- [:selected-chat-command] - input-model/command-completion) - (reg-sub :show-suggestions-view? :<- [:get-current-chat-ui-prop :show-suggestions?] :<- [:get-current-chat] - :<- [:selected-chat-command] - :<- [:get-available-commands-responses] - (fn [[show-suggestions? {:keys [input-text]} selected-command commands-responses]] - (and (or show-suggestions? (input-model/starts-as-command? (string/trim (or input-text "")))) - (seq commands-responses)))) + :<- [:get-all-available-commands] + (fn [[show-suggestions? {:keys [input-text]} commands]] + (and (or show-suggestions? + (input-model/starts-as-command? (string/trim (or input-text "")))) + (seq commands)))) (reg-sub :show-suggestions? :<- [:show-suggestions-view?] :<- [:selected-chat-command] (fn [[show-suggestions-box? selected-command]] - (and show-suggestions-box? (not (:command selected-command))))) + (and show-suggestions-box? (not selected-command)))) (reg-sub :is-request-answered? diff --git a/src/status_im/chat/views/api/choose_asset.cljs b/src/status_im/chat/views/api/choose_asset.cljs deleted file mode 100644 index 7319506b28..0000000000 --- a/src/status_im/chat/views/api/choose_asset.cljs +++ /dev/null @@ -1,41 +0,0 @@ -(ns status-im.chat.views.api.choose-asset - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :as re-frame] - [status-im.ui.components.list.views :as list] - [status-im.ui.components.react :as react] - [status-im.utils.money :as money] - [status-im.chat.views.api.styles :as styles])) - -(defn clean-asset [asset] - (select-keys asset [:name :symbol :decimals :address])) - -(defn- render-asset [arg-index bot-db-key] - (fn [{:keys [name symbol amount decimals] :as asset}] - [react/touchable-highlight {:on-press #(re-frame/dispatch - [:set-asset-as-command-argument {:arg-index arg-index - :bot-db-key bot-db-key - :asset (clean-asset asset)}])} - [react/view styles/asset-container - [react/view styles/asset-main - [react/image {:source (-> asset :icon :source) - :style styles/asset-icon}] - [react/text {:style styles/asset-symbol} symbol] - [react/text {:style styles/asset-name} name]] - ;;TODO(goranjovic) : temporarily disabled to fix https://github.com/status-im/status-react/issues/4963 - ;;until the resolution of https://github.com/status-im/status-react/issues/4972 - #_[react/text {:style styles/asset-balance} - (str (money/internal->formatted amount symbol decimals))]]])) - -(def assets-separator [react/view styles/asset-separator]) - -(defview choose-asset-view [{arg-index :index - bot-db-key :bot-db-key}] - (letsubs [assets [:wallet/visible-assets-with-amount]] - [react/view - [list/flat-list {:data (filter #(not (:nft? %)) assets) - :key-fn (comp name :symbol) - :render-fn (render-asset arg-index bot-db-key) - :enableEmptySections true - :separator assets-separator - :keyboardShouldPersistTaps :always - :bounces false}]])) \ No newline at end of file diff --git a/src/status_im/chat/views/api/choose_contact.cljs b/src/status_im/chat/views/api/choose_contact.cljs deleted file mode 100644 index c4df1d1773..0000000000 --- a/src/status_im/chat/views/api/choose_contact.cljs +++ /dev/null @@ -1,33 +0,0 @@ -(ns status-im.chat.views.api.choose-contact - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :as re-frame] - [status-im.ui.components.contact.contact :refer [contact-view]] - [status-im.ui.components.list.views :as list] - [status-im.ui.components.react :as react])) - -(defn- render-contact [arg-index bot-db-key] - (fn [contact] - [contact-view {:contact contact - :on-press #(re-frame/dispatch - [:set-contact-as-command-argument {:arg-index arg-index - :bot-db-key bot-db-key - :contact contact}])}])) - -(defview choose-contact-view [{title :title - arg-index :index - bot-db-key :bot-db-key}] - (letsubs [contacts [:get-people-in-current-chat]] - [react/view - [react/text {:style {:font-size 14 - :color "rgb(147, 155, 161)" - :padding-top 12 - :padding-left 16 - :padding-right 16 - :padding-bottom 12}} - title] - [list/flat-list {:data contacts - :key-fn :address - :render-fn (render-contact arg-index bot-db-key) - :enableEmptySections true - :keyboardShouldPersistTaps :always - :bounces false}]])) diff --git a/src/status_im/chat/views/api/styles.cljs b/src/status_im/chat/views/api/styles.cljs deleted file mode 100644 index aef2b72fd3..0000000000 --- a/src/status_im/chat/views/api/styles.cljs +++ /dev/null @@ -1,35 +0,0 @@ -(ns status-im.chat.views.api.styles - (:require [status-im.ui.components.colors :as colors])) - -(def asset-container - {:flex-direction :row - :align-items :center - :justify-content :space-between - :padding-vertical 11}) - -(def asset-main - {:flex 1 - :flex-direction :row - :align-items :center}) - -(def asset-icon - {:width 30 - :height 30 - :margin-left 14 - :margin-right 12}) - -(def asset-symbol - {:color colors/black}) - -(def asset-name - {:color colors/gray - :padding-left 4}) - -(def asset-balance - {:color colors/gray - :padding-right 14}) - -(def asset-separator - {:height 1 - :background-color colors/gray-light - :margin-left 56}) diff --git a/src/status_im/chat/views/input/input.cljs b/src/status_im/chat/views/input/input.cljs index 45d43b57d4..21d0738420 100644 --- a/src/status_im/chat/views/input/input.cljs +++ b/src/status_im/chat/views/input/input.cljs @@ -6,7 +6,6 @@ [status-im.chat.constants :as constants] [status-im.chat.styles.input.input :as style] [status-im.chat.views.input.parameter-box :as parameter-box] - [status-im.chat.views.input.result-box :as result-box] [status-im.chat.views.input.send-button :as send-button] [status-im.chat.views.input.suggestions :as suggestions] [status-im.chat.views.input.validation-messages :as validation-messages] @@ -93,8 +92,8 @@ [input-helper {:width width}]]]))) (defview commands-button [] - (letsubs [commands-responses [:get-available-commands-responses]] - (when (seq commands-responses) + (letsubs [commands [:get-all-available-commands]] + (when (seq commands) [react/touchable-highlight {:on-press #(do (re-frame/dispatch [:set-chat-input-text constants/command-char]) (re-frame/dispatch [:chat-input-focus :input-ref])) @@ -123,7 +122,6 @@ (defn container [] [react/view [parameter-box/parameter-box-view] - [result-box/result-box-view] [suggestions/suggestions-view] [validation-messages/validation-messages-view] [input-container]]) diff --git a/src/status_im/chat/views/input/parameter_box.cljs b/src/status_im/chat/views/input/parameter_box.cljs index c8c4963888..0afffb7099 100644 --- a/src/status_im/chat/views/input/parameter_box.cljs +++ b/src/status_im/chat/views/input/parameter_box.cljs @@ -2,19 +2,18 @@ (:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [status-im.chat.views.input.animations.expandable :as expandable] [status-im.chat.styles.input.parameter-box :as style] - [status-im.commands.utils :as command-utils] [status-im.ui.components.react :as react] [taoensso.timbre :as log])) (defview parameter-box-container [] - [{:keys [markup]} [:chat-parameter-box] - bot-id-bot-db [:current-bot-db]] - (when markup - [react/view style/root - (command-utils/generate-hiccup markup (first bot-id-bot-db) (second bot-id-bot-db))])) + (letsubs [parameter-box [:chat-parameter-box]] + (when parameter-box + [react/view style/root + [parameter-box]]))) (defview parameter-box-view [] - (letsubs [show-parameter-box? [:show-parameter-box?]] - (when show-parameter-box? + (letsubs [show-box? [:show-parameter-box?]] + (when show-box? + [react/view] [expandable/expandable-view {:key :parameter-box} [parameter-box-container]]))) diff --git a/src/status_im/chat/views/input/result_box.cljs b/src/status_im/chat/views/input/result_box.cljs deleted file mode 100644 index 99abb9b25e..0000000000 --- a/src/status_im/chat/views/input/result_box.cljs +++ /dev/null @@ -1,14 +0,0 @@ -(ns status-im.chat.views.input.result-box - (:require-macros [status-im.utils.views :refer [defview]]) - (:require [status-im.ui.components.react :as react] - [status-im.chat.views.input.animations.expandable :as expandable] - [status-im.ui.components.connectivity.view :as connectivity] - [status-im.ui.components.styles :as common-styles])) - -(defview result-box-view [] - [markup [:result-box-markup]] - (when markup - [expandable/expandable-view {:key :result-box} - [react/view common-styles/flex - markup] - [connectivity/error-view]])) diff --git a/src/status_im/chat/views/input/send_button.cljs b/src/status_im/chat/views/input/send_button.cljs index a2b8cf77d6..8aa501b8bb 100644 --- a/src/status_im/chat/views/input/send_button.cljs +++ b/src/status_im/chat/views/input/send_button.cljs @@ -10,7 +10,7 @@ (defn send-button-view-on-update [{:keys [spin-value command-completion]}] (fn [_] - (let [to-spin-value (if (some #{:complete :no-command} [@command-completion]) 1 0)] + (let [to-spin-value (if (#{:complete :no-command} command-completion) 1 0)] (animation/start (animation/timing spin-value {:toValue to-spin-value :duration 300}))))) @@ -20,16 +20,14 @@ (not (or (string/blank? trimmed) (= trimmed "/"))))) (defview send-button-view [] - (letsubs [command-completion [:command-completion] - selected-command [:selected-chat-command] + (letsubs [{:keys [command-completion]} [:selected-chat-command] {:keys [input-text seq-arg-input-text]} [:get-current-chat] - spin-value (animation/create-value 1) - on-update (send-button-view-on-update {:spin-value spin-value - :command-completion command-completion})] - {:component-did-update on-update} + spin-value (animation/create-value 1)] + {:component-did-update (send-button-view-on-update {:spin-value spin-value + :command-completion command-completion})} (when (and (sendable? input-text) - (or (not selected-command) - (some #{:complete :less-than-needed} [command-completion]))) + (or (not command-completion) + (#{:complete :less-than-needed} command-completion))) [react/touchable-highlight {:on-press #(re-frame/dispatch [:send-current-message])} (let [spin (.interpolate spin-value (clj->js {:inputRange [0 1] :outputRange ["0deg" "90deg"]}))] diff --git a/src/status_im/chat/views/input/suggestions.cljs b/src/status_im/chat/views/input/suggestions.cljs index 1c10cda2dc..f6552cca2c 100644 --- a/src/status_im/chat/views/input/suggestions.cljs +++ b/src/status_im/chat/views/input/suggestions.cljs @@ -4,7 +4,7 @@ [status-im.ui.components.react :as react] [status-im.chat.styles.input.suggestions :as style] [status-im.chat.views.input.animations.expandable :as expandable] - [status-im.chat.models.commands :as commands-model] + [status-im.chat.commands.core :as commands] [status-im.i18n :as i18n] [taoensso.timbre :as log])) @@ -19,19 +19,18 @@ description]]]) (defview suggestions-view [] - (letsubs [commands [:get-available-commands]] + (letsubs [available-commands [:get-available-commands]] [expandable/expandable-view {:key :suggestions} [react/view [react/scroll-view {:keyboard-should-persist-taps :always :bounces false} - (when (seq commands) - (for [[i {:keys [description] :as command}] (map-indexed vector commands)] - ^{:key i} - [suggestion-item {:on-press #(re-frame/dispatch [:select-chat-input-command command nil]) - :name (commands-model/command-name command) - :description description - :last? (= i (dec (count commands))) - :accessibility-label (case (:name command) - "send" :send-payment-button - "request" :request-payment-button - nil)}]))]]])) + (when (seq available-commands) + (map-indexed + (fn [i {:keys [type] :as command}] + ^{:key i} + [suggestion-item {:on-press #(re-frame/dispatch [:select-chat-input-command command nil]) + :name (commands/command-name type) + :description (commands/command-description type) + :last? (= i (dec (count available-commands))) + :accessibility-label (commands/accessibility-label type)}]) + available-commands))]]])) diff --git a/src/status_im/chat/views/input/validation_messages.cljs b/src/status_im/chat/views/input/validation_messages.cljs index e7f9f24fdf..c04b71f605 100644 --- a/src/status_im/chat/views/input/validation_messages.cljs +++ b/src/status_im/chat/views/input/validation_messages.cljs @@ -18,11 +18,12 @@ (defview validation-messages-view [] (letsubs [chat-input-margin [:chat-input-margin] - input-height [:get-current-chat-ui-prop :input-height] - messages [:validation-messages]] - (when messages - [react/view (style/root (+ input-height chat-input-margin)) - (if (string? messages) - [messages-list [validation-message {:title (i18n/label :t/error) - :description messages}]] - [messages-list messages])]))) + input-height [:get-current-chat-ui-prop :input-height] + validation-result [:validation-messages]] + (when validation-result + (let [message (if (string? validation-result) + {:title (i18n/label :t/error) + :description validation-result} + validation-result)] + [react/view (style/root (+ input-height chat-input-margin)) + [messages-list [validation-message message]]])))) diff --git a/src/status_im/chat/views/message/message.cljs b/src/status_im/chat/views/message/message.cljs index ed3451b738..79633ec387 100644 --- a/src/status_im/chat/views/message/message.cljs +++ b/src/status_im/chat/views/message/message.cljs @@ -7,12 +7,10 @@ [status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.action-sheet :as action-sheet] - [status-im.commands.utils :as commands.utils] - [status-im.chat.models.commands :as models.commands] - [status-im.chat.models.message :as models.message] + [status-im.chat.commands.core :as commands] + [status-im.chat.commands.receiving :as commands-receiving] [status-im.chat.styles.message.message :as style] [status-im.chat.styles.message.command-pill :as pill-style] - [status-im.chat.views.message.request-message :as request-message] [status-im.chat.views.photos :as photos] [status-im.constants :as constants] [status-im.ui.components.chat-icon.screen :as chat-icon.screen] @@ -22,8 +20,7 @@ [status-im.utils.platform :as platform] [status-im.i18n :as i18n] [status-im.ui.components.colors :as colors] - [clojure.string :as string] - [status-im.chat.events.console :as console])) + [clojure.string :as string])) (defview message-content-status [] (letsubs [{:keys [chat-id group-id name color public-key]} [:get-current-chat] @@ -56,90 +53,11 @@ :font :default} "03:39"]]]) -(defview send-command-status [tx-hash outgoing] - (letsubs [confirmed? [:transaction-confirmed? tx-hash] - tx-exists? [:wallet-transaction-exists? tx-hash]] - [react/touchable-highlight {:on-press #(when tx-exists? - (re-frame/dispatch [:show-transaction-details tx-hash]))} - [react/view style/command-send-status-container - [vector-icons/icon (if confirmed? :icons/check :icons/dots) - {:color colors/blue - :container-style (style/command-send-status-icon outgoing)}] - [react/view - [react/text {:style style/command-send-status-text} - (i18n/label (cond - confirmed? :status-confirmed - tx-exists? :status-pending - :else :status-tx-not-found))]]]])) - -(defview message-content-command-send - [{:keys [content timestamp-str outgoing group-chat]}] - (letsubs [network [:network-name]] - (let [{{:keys [amount fiat-amount tx-hash asset currency] send-network :network} :params} content - recipient-name (get-in content [:params :bot-db :public :recipient]) - amount-text-long? (< 10 (count amount)) - network-mismatch? (and (seq send-network) (not= network send-network))] - [react/view style/command-send-message-view - [react/view - [react/view style/command-send-amount-row - [react/view style/command-send-amount - [react/text {:style style/command-send-amount-text - :font :medium} - amount - [react/text {:style (style/command-amount-currency-separator outgoing)} - (if amount-text-long? "\n" ".")] - [react/text {:style (style/command-send-currency-text outgoing) - :font :default} - asset]]]] - (when fiat-amount - [react/view style/command-send-fiat-amount - [react/text {:style style/command-send-fiat-amount-text} - (str "~ " fiat-amount " " (or currency (i18n/label :usd-currency)))]]) - (when (and group-chat - recipient-name) - [react/text {:style style/command-send-recipient-text} - (str - (i18n/label :send-sending-to) - " " - recipient-name)]) - [react/view - [react/text {:style (style/command-send-timestamp outgoing)} - (str (i18n/label :sent-at) " " timestamp-str)]] - [send-command-status tx-hash outgoing] - (when network-mismatch? - [react/text send-network])]]))) - -;; Used for command messages with markup generated on JS side -(defview message-content-command-with-markup - [{:keys [content params]}] - (letsubs [command [:get-command (:command-ref content)]] - (let [preview (:preview content) - {:keys [color] icon-path :icon} command] - [react/view style/content-command-view - (when color - [react/view style/command-container - [react/view (pill-style/pill command) - [react/text {:style pill-style/pill-text - :font :default} - (models.commands/command-name command)]]]) - (when icon-path - [react/view style/command-image-view - [react/icon icon-path style/command-image]]) - (if (:markup preview) - ;; Markup was defined for command in jail, generate hiccup and render it - (commands.utils/generate-hiccup (:markup preview)) - ;; Display preview if it's defined (as a string), in worst case, render params - [react/text {:style style/command-text - :font :default} - (or preview (str params))])]))) - -(defn message-content-command - [message] - (let [{{:keys [command preview]} :content} message] - (if (and (= command constants/command-send) - (nil? preview)) - [message-content-command-send message] - [message-content-command-with-markup message]))) +(defview message-content-command + [command-message] + (letsubs [id->command [:get-id->command]] + (when-let [command (commands-receiving/lookup-command-by-ref command-message id->command)] + (commands/generate-preview command command-message)))) (def rtl-characters-regex #"[^\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]*?[\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]") @@ -156,7 +74,9 @@ [{:keys [timestamp-str outgoing content] :as message} message-content {:keys [justify-timestamp?]}] [react/view (style/message-view message) message-content - [message-timestamp timestamp-str justify-timestamp? outgoing (get-in message [:content :command]) content]]) + [message-timestamp timestamp-str justify-timestamp? outgoing (or (get content :command-path) + (get content :command-ref)) + content]]) (def replacements {"\\*[^*]+\\*" {:font-weight :bold} @@ -286,11 +206,6 @@ (defmulti message-content (fn [_ message _] (message :content-type))) -(defmethod message-content constants/content-type-command-request - [wrapper message] - [wrapper message - [message-view message [request-message/message-content-command-request message]]]) - (defmethod message-content constants/text-content-type [wrapper message] [wrapper message [text-message message]]) @@ -303,6 +218,13 @@ [_ _] [message-content-status]) +;; TODO(janherich) in the future, `content-type-command-request` will be deprecated +;; as it's the same thing as command +(defmethod message-content constants/content-type-command-request + [wrapper message] + [wrapper message + [message-view message [message-content-command message]]]) + (defmethod message-content constants/content-type-command [wrapper message] [wrapper message @@ -387,12 +309,7 @@ [{:keys [chat-id message-id current-public-key user-statuses content last-outgoing? outgoing message-type] :as message}] (let [outgoing-status (or (get-in user-statuses [current-public-key :status]) :not-sent) delivery-status (get-in user-statuses [chat-id :status]) - status (cond (and (= constants/console-chat-id chat-id) - (not (console/commands-with-delivery-status (:command content)))) - :seen - - :else - (or delivery-status outgoing-status))] + status (or delivery-status outgoing-status)] (case status :sending [message-activity-indicator] :not-sent [message-not-sent-text chat-id message-id] diff --git a/src/status_im/chat/views/message/request_message.cljs b/src/status_im/chat/views/message/request_message.cljs deleted file mode 100644 index b0c91db5e0..0000000000 --- a/src/status_im/chat/views/message/request_message.cljs +++ /dev/null @@ -1,146 +0,0 @@ -(ns status-im.chat.views.message.request-message - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :refer [subscribe dispatch]] - [reagent.core :as r] - [status-im.i18n :as i18n] - [status-im.ui.components.react :refer [view - animated-view - text - image - icon - touchable-highlight]] - [status-im.chat.styles.message.message :as st] - [status-im.chat.models.commands :as commands] - [status-im.utils.datetime :as datetime] - [status-im.utils.money :as money] - [status-im.commands.utils :as commands-utils] - [status-im.ui.components.animation :as anim] - [taoensso.timbre :as log])) - -(def request-message-icon-scale-delay 600) - -(defn set-chat-command [message-id command] - (let [metadata {:to-message-id message-id}] - (dispatch [:select-chat-input-command command metadata]))) - -(def min-scale 1) -(def max-scale 1.3) - -(defn button-animation [val to-value loop? answered?] - (anim/anim-sequence - [(anim/anim-delay - (if (and @loop? (not @answered?)) - request-message-icon-scale-delay - 0)) - (anim/spring val {:toValue to-value - :useNativeDriver true})])) - -(defn request-button-animation-logic - [{:keys [to-value val loop? answered?] :as context}] - (anim/start - (button-animation val to-value loop? answered?) - #(if (and @loop? (not @answered?)) - (let [new-value (if (= to-value min-scale) max-scale min-scale) - context' (assoc context :to-value new-value)] - (request-button-animation-logic context')) - (anim/start - (button-animation val min-scale loop? answered?))))) - -(defn request-button-label - "The request button label will be in the form of `request-the-command-name`" - [command-name] - (keyword (str "request-" (name command-name)))) - -(defn request-button [message-id _ on-press-handler] - (let [scale-anim-val (anim/create-value min-scale) - answered? (subscribe [:is-request-answered? message-id]) - loop? (r/atom true) - context {:to-value max-scale - :val scale-anim-val - :answered? answered? - :loop? loop?}] - (r/create-class - {:display-name "request-button" - :component-did-mount - (if (or (nil? on-press-handler) @answered?) (fn []) #(request-button-animation-logic context)) - :component-will-unmount - #(reset! loop? false) - :reagent-render - (fn [message-id {command-icon :icon :as command} on-press-handler] - (when command - [touchable-highlight - {:on-press on-press-handler - :style (st/command-request-image-touchable) - :accessibility-label (request-button-label (:name command))} - [animated-view {:style (st/command-request-image-view command scale-anim-val)} - (when command-icon - [icon command-icon st/command-request-image])]]))}))) - -(defview message-content-command-request - [{:keys [message-id content outgoing timestamp timestamp-str group-chat]}] - (letsubs [command [:get-command (:request-command-ref content)] - answered? [:is-request-answered? message-id] - status-initialized? [:get :status-module-initialized?] - network [:network-name] - prices [:prices]] - (let [{:keys [prefill prefill-bot-db prefillBotDb params preview] - text-content :text} content - command (if (and params command) - (merge command {:prefill prefill - :prefill-bot-db (or prefill-bot-db prefillBotDb)}) - command) - {:keys [amount asset fiat-amount currency] request-network :network} params - recipient-name (get-in params [:bot-db :public :recipient]) - network-mismatch? (and request-network (not= request-network network)) - on-press-handler (cond - network-mismatch? nil - (and (not answered?) status-initialized?) #(set-chat-command message-id command))] - [view - [touchable-highlight - {:on-press on-press-handler} - [view (st/command-request-message-view outgoing) - (if (:markup preview) - [view (commands-utils/generate-hiccup (:markup preview)) - (when network-mismatch? - [text request-network])] - [view - [view - [text {:style (st/command-request-header-text outgoing)} - (i18n/label :transaction-request)]] - [view st/command-request-row - [text {:style st/command-request-amount-text - :font :medium} - amount - [text {:style (st/command-amount-currency-separator outgoing)} - "."] - [text {:style (st/command-request-currency-text outgoing) - :font :default} - asset]]] - [view st/command-request-fiat-amount-row - [text {:style st/command-request-fiat-amount-text} - (str "~ " fiat-amount " " (or currency (i18n/label :usd-currency)))]] - (when (and group-chat - recipient-name) - [text {:style st/command-request-recipient-text} - (str - (i18n/label :request-requesting-from) - " " - recipient-name)]) - (when network-mismatch? - [text {:style st/command-request-network-text} - (str (i18n/label :on) " " request-network)]) - [view st/command-request-timestamp-row - [text {:style (st/command-request-timestamp-text outgoing)} - (str - (datetime/timestamp->mini-date timestamp) - " " - (i18n/label :at) - " " - timestamp-str)]] - (when-not outgoing - [view - [view st/command-request-separator-line] - [view st/command-request-button - [text {:style (st/command-request-button-text answered?) - :on-press on-press-handler} - (i18n/label (if answered? :command-button-sent :command-button-send))]]])])]]]))) diff --git a/src/status_im/commands/events/loading.cljs b/src/status_im/commands/events/loading.cljs deleted file mode 100644 index d39db58e0e..0000000000 --- a/src/status_im/commands/events/loading.cljs +++ /dev/null @@ -1,160 +0,0 @@ -(ns status-im.commands.events.loading - (:require [clojure.string :as string] - [clojure.set :as s] - [re-frame.core :as re-frame] - [status-im.utils.handlers :as handlers] - [status-im.utils.js-resources :as js-resources] - [status-im.utils.types :as types] - [status-im.utils.utils :as utils] - [status-im.native-module.core :as status] - [status-im.bots.events :as bots-events] - [taoensso.timbre :as log])) - -;; FX -(re-frame/reg-fx - ::evaluate-jail-n - (fn [jail-data] - (doseq [{:keys [jail-id jail-resource]} jail-data] - (status/parse-jail - jail-id jail-resource - (fn [jail-response] - (let [converted (types/json->clj jail-response)] - (re-frame/dispatch [::proceed-loading jail-id (update converted :result types/json->clj)]))))))) - -(re-frame/reg-fx - ::show-popup - (fn [{:keys [title msg]}] - (utils/show-popup title msg))) - -;; Handlers -(defn- valid-network-resource? - [response] - (some-> (.. response -headers) - (get "Content-type") - (string/includes? "application/javascript"))) - -(defn- evaluate-commands-in-jail - [{:keys [db get-local-storage-data]} commands-resource whisper-identity] - (let [data (get-local-storage-data whisper-identity) - local-storage-snippet (js-resources/local-storage-data data) - network-id (get-in db [:account/account :networks (:network db) :config :NetworkId]) - ethereum-id-snippet (js-resources/network-id network-id) - commands-snippet (str local-storage-snippet ethereum-id-snippet commands-resource)] - {::evaluate-jail-n [{:jail-id whisper-identity - :jail-resource commands-snippet}]})) - -(defn load-commands-for-bot - "This function takes coeffects, effects, bot contact and adds effects - for loading all commands/responses/subscriptions." - [cofx fx {:keys [whisper-identity bot-url]}] - (if-let [commands-resource (js-resources/get-resource bot-url)] - (merge-with into fx (evaluate-commands-in-jail cofx commands-resource whisper-identity)) - (update fx :http-get-n conj {:url bot-url - :response-validator valid-network-resource? - :success-event-creator (fn [commands-resource] - [::evaluate-commands-in-jail commands-resource whisper-identity]) - :failure-event-creator (fn [error-response] - [::proceed-loading whisper-identity {:error error-response}])}))) - -(defn load-commands - "This function takes coeffects and produces effects - for loading all commands/responses/subscriptions for existing bot contacts. - - It's currently working only for bots, eq we are not evaluating - dapp resources in jail at all." - [{:keys [db] :as cofx}] - (transduce (comp (map second) - (filter :bot-url)) - (completing (partial load-commands-for-bot cofx)) - {} - (:contacts/contacts db))) - -(defn- add-exclusive-choices [initial-scope exclusive-choices] - (reduce (fn [scopes-set exclusive-choices] - (reduce (fn [scopes-set scope] - (let [exclusive-match (s/intersection scope exclusive-choices)] - (if (seq exclusive-match) - (reduce conj - (disj scopes-set scope) - (map (partial conj (s/difference scope exclusive-match)) - exclusive-match)) - scopes-set))) - scopes-set - scopes-set)) - #{initial-scope} - exclusive-choices)) - -(defn- create-access-scopes - "Based on command owner and command scope, create set of access-scopes which can be used to directly - look up any commands/subscriptions relevant for actual context (type of chat opened, registered user - or not, etc.)" - [jail-id scope] - (let [member-scope (cond-> scope - (not (scope :global)) (conj jail-id))] - (add-exclusive-choices member-scope [#{:personal-chats :group-chats} - #{:anonymous :registered} - #{:dapps :humans :public-chats}]))) - -(defn- index-by-access-scope-type - [init jail-id items] - (reduce (fn [acc {:keys [scope name type ref]}] - (let [access-scopes (create-access-scopes jail-id scope)] - (reduce (fn [acc access-scope] - (assoc-in acc [access-scope type name] ref)) - acc - access-scopes))) - init - items)) - -(defn- enrich - [jail-id type [_ {:keys [scope-bitmask scope name] :as props}]] - (-> props - (assoc :scope (into #{} (map keyword) scope) - :owner-id jail-id - :bot jail-id - :type type - :ref [jail-id type scope-bitmask name]))) - -(defn add-jail-result - "This function add commands/responses/subscriptions from jail-evaluated resource - into the database" - [db jail-id {:keys [commands responses subscriptions]}] - (let [enriched-commands (map (partial enrich jail-id :command) commands) - enriched-responses (map (partial enrich jail-id :response) responses) - new-db (reduce (fn [acc {:keys [ref] :as props}] - (assoc-in acc (into [:contacts/contacts] ref) props)) - db - (concat enriched-commands enriched-responses))] - (-> new-db - (update :access-scope->commands-responses (fn [acc] - (-> (or acc {}) - (index-by-access-scope-type jail-id enriched-commands) - (index-by-access-scope-type jail-id enriched-responses)))) - (update-in [:contacts/contacts jail-id] assoc - :subscriptions (bots-events/transform-bot-subscriptions subscriptions) - :jail-loaded? true)))) - -(handlers/register-handler-fx - ::evaluate-commands-in-jail - [re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)] - (fn [cofx [commands-resource whisper-identity]] - (evaluate-commands-in-jail cofx commands-resource whisper-identity))) - -(handlers/register-handler-fx - ::proceed-loading - [re-frame/trim-v] - (fn [{:keys [db]} [jail-id {:keys [error result]}]] - (if error - (let [message (string/join "\n" ["bot.js loading failed" - jail-id - error])] - {::show-popup {:title "Error" - :msg message}}) - (let [jail-loaded-events (get-in db [:contacts/contacts jail-id :jail-loaded-events])] - (cond-> {:db (add-jail-result db jail-id result) - :call-jail-function {:chat-id jail-id - :function :init :context - {:from (get-in db [:account/account :address])}}} - (seq jail-loaded-events) - (-> (assoc :dispatch-n jail-loaded-events) - (update-in [:db :contacts/contacts jail-id] dissoc :jail-loaded-events))))))) diff --git a/src/status_im/commands/handlers/jail.cljs b/src/status_im/commands/handlers/jail.cljs deleted file mode 100644 index 488ff64786..0000000000 --- a/src/status_im/commands/handlers/jail.cljs +++ /dev/null @@ -1,111 +0,0 @@ -(ns status-im.commands.handlers.jail - (:require [clojure.string :as string] - [re-frame.core :refer [after dispatch subscribe trim-v debug]] - [status-im.utils.handlers :as handlers] - [status-im.utils.utils :refer [show-popup]] - [status-im.utils.types :as types] - [status-im.commands.utils :refer [reg-handler]] - [status-im.constants :refer [console-chat-id]] - [status-im.i18n :refer [get-contact-translated]] - [taoensso.timbre :as log] - [status-im.data-store.local-storage :as local-storage-store])) - -(defn command-handler! - [_ [chat-id - params - {:keys [result error]}]] - (let [{:keys [returned]} result - {handler-error :error} returned] - (cond - handler-error - (when-let [markup (:markup handler-error)] - (dispatch [:set-chat-ui-props {:validation-messages markup}])) - - result - (dispatch [:chat-send-message/send-command chat-id (assoc-in params [:command :handler-data] returned)]) - - (not (or error handler-error)) - (dispatch [:chat-send-message/send-command chat-id params]) - - :else nil))) - -(defn suggestions-handler! - [{:keys [chats] :as db} - [{:keys [chat-id bot-id default-db command parameter-index result]}]] - (let [{:keys [markup] :as returned} (get-in result [:result :returned]) - contains-markup? (contains? returned :markup) - current-input (get-in chats [chat-id :input-text]) - path (if command - [:chats chat-id :parameter-boxes (:name command) parameter-index] - (when-not (string/blank? current-input) - [:chats chat-id :parameter-boxes :message]))] - (when (and contains-markup? path (not= (get-in db path) markup)) - (dispatch [:set-in path returned]) - (when default-db - (dispatch [:update-bot-db {:bot bot-id - :db default-db}]))))) - -(defn suggestions-events-handler! - [{:keys [bot-db]} [bot-id [n & data] val]] - (log/debug "Suggestion event: " n (first data) val) - (case (keyword n) - :set-command-argument - (let [[index value move-to-next?] (first data)] - (dispatch [:set-command-argument [index value move-to-next?]])) - :set-value - (dispatch [:set-chat-input-text (first data)]) - :set - (let [opts {:bot bot-id - :path (mapv keyword data) - :value val}] - (dispatch [:set-in-bot-db opts])) - :set-command-argument-from-db - (let [[index arg move-to-next?] (first data) - path (keyword arg) - value (str (get-in bot-db [bot-id path]))] - (dispatch [:set-command-argument [index value move-to-next?]])) - :set-value-from-db - (let [path (keyword (first data)) - value (str (get-in bot-db [bot-id path]))] - (dispatch [:set-chat-input-text value])) - :focus-input - (dispatch [:chat-input-focus :input-ref]) - nil)) - -(defn print-error-message! [message] - (fn [_ params] - (when (:error (last params)) - (show-popup "Error" (string/join "\n" [message params])) - (log/debug message params)))) - -;; TODO(alwx): rewrite -(reg-handler :command-handler! - (after (print-error-message! "Error on command handling")) - (handlers/side-effect! command-handler!)) - -(reg-handler - :suggestions-handler - [(after (print-error-message! "Error on param suggestions"))] - (handlers/side-effect! suggestions-handler!)) - -(reg-handler - :suggestions-event! - (handlers/side-effect! suggestions-events-handler!)) - -(reg-handler - :show-suggestions-from-jail - (handlers/side-effect! - (fn [_ [_ {:keys [chat-id markup]}]] - (let [markup' (types/json->clj markup) - result (assoc-in {} [:result :returned :markup] markup')] - (dispatch [:suggestions-handler - {:result result - :chat-id chat-id}]))))) - -(handlers/register-handler-fx - :set-local-storage - [trim-v] - (fn [_ [{:keys [data chat-id]}]] - {:data-store/tx [(local-storage-store/set-local-storage-data-tx - {:chat-id chat-id - :data data})]})) diff --git a/src/status_im/commands/specs.cljs b/src/status_im/commands/specs.cljs deleted file mode 100644 index ea136ed1c3..0000000000 --- a/src/status_im/commands/specs.cljs +++ /dev/null @@ -1,4 +0,0 @@ -(ns status-im.commands.specs - (:require [cljs.spec.alpha :as spec])) - -(spec/def :commands/access-scope->commands-responses (spec/nilable map?)) diff --git a/src/status_im/commands/subs.cljs b/src/status_im/commands/subs.cljs deleted file mode 100644 index e933d05ade..0000000000 --- a/src/status_im/commands/subs.cljs +++ /dev/null @@ -1,10 +0,0 @@ -(ns status-im.commands.subs - (:require [re-frame.core :refer [reg-sub]])) - -(reg-sub :get-commands-responses-by-access-scope :access-scope->commands-responses) - -(reg-sub - :get-command - :<- [:get-contacts] - (fn [contacts [_ ref]] - (some->> ref (get-in contacts)))) diff --git a/src/status_im/commands/utils.cljs b/src/status_im/commands/utils.cljs deleted file mode 100644 index 828c348d70..0000000000 --- a/src/status_im/commands/utils.cljs +++ /dev/null @@ -1,75 +0,0 @@ -(ns status-im.commands.utils - (:require [clojure.set :as set] - [clojure.walk :as w] - [re-frame.core :refer [dispatch trim-v]] - [status-im.ui.components.react :as components] - [status-im.chat.views.input.validation-messages :as chat-validation-messages] - [status-im.chat.views.api.choose-contact :as choose-contact] - [status-im.chat.views.api.choose-asset :as choose-asset] - [status-im.ui.components.qr-code-viewer.views :as qr-code-viewer] - [status-im.ui.components.chat-preview :as chat-preview] - [status-im.utils.handlers :refer [register-handler]] - [taoensso.timbre :as log])) - -(defn json->clj [json] - (when-not (= json "undefined") - (js->clj (.parse js/JSON json) :keywordize-keys true))) - -(defn parameter-box-separator [] - [components/view {:height 1 :background-color "#c1c7cbb7" :opacity 0.5}]) - -(def elements - {:view components/view - :text components/text - :text-input components/text-input - :chat-preview-text chat-preview/text - :image components/image - :qr-code qr-code-viewer/qr-code - :linking components/linking - :slider components/slider - :scroll-view components/scroll-view - :web-view components/web-view - :touchable components/touchable-highlight - :activity-indicator components/activity-indicator - :validation-message chat-validation-messages/validation-message - :choose-contact choose-contact/choose-contact-view - :choose-asset choose-asset/choose-asset-view - :separator parameter-box-separator}) - -(defn get-element [n] - (elements (keyword (.toLowerCase n)))) - -(def events #{:onPress :onValueChange :onSlidingComplete}) - -(defn wrap-event [[_ event] bot-id] - #(dispatch [:suggestions-event! bot-id (update event 0 keyword) %])) - -(defn check-events [m bot-id] - (let [ks (set (keys m)) - evs (set/intersection ks events)] - (reduce #(update %1 %2 wrap-event bot-id) m evs))) - -(defn generate-hiccup - ([markup] - (generate-hiccup markup nil {})) - ([markup bot-id bot-db] - (w/prewalk - (fn [el] - (cond - - (and (vector? el) (= "subscribe" (first el))) - (let [path (mapv keyword (second el))] - (get-in bot-db path)) - - (and (vector? el) (string? (first el))) - (-> el - (update 0 get-element) - (update 1 check-events bot-id)) - - :else el)) - markup))) - -(defn reg-handler - ([name handler] (reg-handler name nil handler)) - ([name middleware handler] - (register-handler name [trim-v middleware] handler))) diff --git a/src/status_im/native_module/impl/module.cljs b/src/status_im/native_module/impl/module.cljs index d1939f98f2..719fa38c7c 100644 --- a/src/status_im/native_module/impl/module.cljs +++ b/src/status_im/native_module/impl/module.cljs @@ -216,7 +216,7 @@ {:jail-id chat-id :path path :params params - :callback (or callback #(dispatch [:chat-received-message/bot-response {:chat-id chat-id} %]))}))) + :callback callback}))) (defn set-soft-input-mode [mode] (when status diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 09271f4fb1..c03e119134 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -10,7 +10,6 @@ status-im.ui.screens.qr-scanner.db status-im.ui.screens.group.db status-im.chat.specs - status-im.commands.specs status-im.ui.screens.profile.db status-im.ui.screens.network-settings.db status-im.ui.screens.offline-messaging-settings.db @@ -264,7 +263,8 @@ :chat/last-clock-value :chat/loaded-chats :chat/bot-db - :commands/access-scope->commands-responses + :chat/id->command + :chat/access-scope->command-id :discoveries/discoveries :discoveries/discover-search-tags :discoveries/discover-current-dapp diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 36fba521f1..c46c0f6574 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -1,8 +1,5 @@ (ns status-im.ui.screens.events - (:require status-im.bots.events - status-im.chat.events - status-im.commands.handlers.jail - status-im.commands.events.loading + (:require status-im.chat.events status-im.network.events [status-im.transport.handlers :as transport.handlers] status-im.protocol.handlers @@ -16,7 +13,6 @@ [status-im.utils.universal-links.core :as universal-links] [status-im.utils.dimensions :as dimensions] status-im.utils.universal-links.events - [status-im.chat.commands.core :as commands] status-im.ui.screens.add-new.new-chat.navigation status-im.ui.screens.network-settings.events status-im.ui.screens.profile.events @@ -73,11 +69,7 @@ :path path :params params :callback (fn [jail-response] - (when-let [event (if callback-event-creator - (callback-event-creator jail-response) - [:chat-received-message/bot-response - {:chat-id chat-id} - jail-response])] + (when-let [event (callback-event-creator jail-response)] (re-frame/dispatch event)))}))) ;;;; COFX @@ -99,28 +91,6 @@ ;;;; FX -(re-frame/reg-fx - :call-jail - (fn [args] - (doseq [{:keys [callback-event-creator] :as opts} args] - (status/call-jail - (-> opts - (dissoc :callback-event-creator) - (assoc :callback - (fn [jail-response] - (when-let [event (callback-event-creator jail-response)] - (re-frame/dispatch event))))))))) - -(re-frame/reg-fx - :call-jail-function - call-jail-function) - -(re-frame/reg-fx - :call-jail-function-n - (fn [opts-seq] - (doseq [opts opts-seq] - (call-jail-function opts)))) - (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}] (let [on-success #(re-frame/dispatch (success-event-creator %)) on-error #(re-frame/dispatch (failure-event-creator %)) @@ -336,7 +306,6 @@ :initialize-account-db (fn [{:keys [accounts/accounts accounts/create contacts/contacts networks/networks network network-status peers-count peers-summary view-id navigation-stack - access-scope->commands-responses status-module-initialized? status-node-started? device-UUID] :or [network (get app-db :network)]} [_ address]] (let [console-contact (get contacts constants/console-chat-id) @@ -344,7 +313,6 @@ account-network-id (get current-account :network network) account-network (get-in current-account [:networks account-network-id])] (cond-> (assoc app-db - :access-scope->commands-responses access-scope->commands-responses :current-public-key (:public-key current-account) :view-id view-id :navigation-stack navigation-stack @@ -414,31 +382,6 @@ (fn [_ _] {::get-fcm-token-fx nil})) -;; Because we send command to jail in params and command `:ref` is a lookup vector with -;; keyword in it (for example `["transactor" :command 51 "send"]`), we lose that keyword -;; information in the process of converting to/from JSON, and we need to restore it -(defn- restore-command-ref-keyword - [orig-params] - (if [(get-in orig-params [:command :command :ref])] - (update-in orig-params [:command :command :ref 1] keyword) - orig-params)) - -(defn handle-jail-signal [{:keys [chat_id data]}] - (let [{:keys [event data]} (types/json->clj data)] - (case event - "local-storage" [:set-local-storage {:chat-id chat_id - :data data}] - "show-suggestions" [:show-suggestions-from-jail {:chat-id chat_id - :markup data}] - "send-message" [:chat-send-message/from-jail {:chat-id chat_id - :message data}] - "handler-result" (let [orig-params (:origParams data)] - ;; TODO(janherich): figure out and fix chat_id from event - [:command-handler! (:chat-id orig-params) - (restore-command-ref-keyword orig-params) - {:result {:returned (dissoc data :origParams)}}]) - (log/debug "Unknown jail signal " event)))) - (handlers/register-handler-fx :discovery/summary (fn [{:keys [db] :as cofx} [_ peers-summary]] @@ -463,7 +406,6 @@ "node.started" [:status-node-started] "node.stopped" [:status-node-stopped] "module.initialized" [:status-module-initialized] - "jail.signal" (handle-jail-signal event) "envelope.sent" [:signals/envelope-status (:hash event) :sent] "envelope.expired" [:signals/envelope-status (:hash event) :not-sent] "discovery.summary" [:discovery/summary event] diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index 3c968f2844..78476ce996 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -3,11 +3,12 @@ (:require [re-frame.core :as re-frame] [clojure.string :as str] [status-im.constants :as constants] + [status-im.chat.commands.core :as commands] + [status-im.chat.commands.receiving :as commands-receiving] [status-im.ui.components.react :as react] [status-im.ui.screens.home.styles :as styles] [status-im.ui.components.styles :as component.styles] [status-im.utils.core :as utils] - [status-im.commands.utils :as commands-utils] [status-im.i18n :as i18n] [status-im.utils.datetime :as time] [status-im.utils.gfycat.core :as gfycat] @@ -19,18 +20,12 @@ [status-im.ui.components.common.common :as components.common] [status-im.models.browser :as model])) -(defn command-short-preview - [{:keys [command] {:keys [amount asset]} :params}] - [chat-preview/text {} - (str - (i18n/label (if (= command constants/command-request) - :command-requesting - :command-sending)) - (i18n/label-number amount) - " " - asset)]) +(defview command-short-preview [message] + (letsubs [id->command [:get-id->command]] + (when-let [command (commands-receiving/lookup-command-by-ref message id->command)] + (commands/generate-short-preview command message)))) -(defn message-content-text [{:keys [content] :as message}] +(defn message-content-text [{:keys [content content-type] :as message}] [react/view styles/last-message-container (cond @@ -49,11 +44,10 @@ :accessibility-label :chat-message-text} (:content content)] - (and (:command content) (-> content :short-preview :markup)) - (commands-utils/generate-hiccup (-> content :short-preview :markup)) - - (contains? #{constants/command-request constants/command-send} (:command content)) - [command-short-preview content] + (contains? #{constants/content-type-command + constants/content-type-command-request} + content-type) + [command-short-preview message] :else [react/text {:style styles/last-message-text diff --git a/src/status_im/ui/screens/profile/events.cljs b/src/status_im/ui/screens/profile/events.cljs index 56c81908b0..1050c22a51 100644 --- a/src/status_im/ui/screens/profile/events.cljs +++ b/src/status_im/ui/screens/profile/events.cljs @@ -2,11 +2,10 @@ (:require [clojure.spec.alpha :as spec] [re-frame.core :as re-frame] [status-im.ui.components.react :refer [show-image-picker]] - [status-im.chat.constants :as chat-const] [status-im.ui.screens.profile.navigation] [status-im.ui.screens.accounts.utils :as accounts.utils] [status-im.chat.events :as chat-events] - [status-im.chat.events.input :as input-events] + [status-im.chat.commands.core :as commands] [status-im.utils.handlers :as handlers] [status-im.utils.handlers-macro :as handlers-macro] [status-im.utils.image-processing :refer [img->base64]] @@ -29,11 +28,11 @@ (handlers/register-handler-fx :profile/send-transaction [re-frame/trim-v] - (fn [{{:contacts/keys [contacts]} :db :as cofx} [chat-id]] - (let [send-command (get-in contacts chat-const/send-command-ref)] + (fn [{:keys [db] :as cofx} [chat-id]] + (let [send-command (get-in db [:id->command ["send" #{:personal-chats}]])] (handlers-macro/merge-fx cofx (chat-events/start-chat chat-id {:navigation-replace? true}) - (input-events/select-chat-input-command send-command nil true))))) + (commands/select-chat-input-command send-command nil))))) (defn valid-name? [name] (spec/valid? :profile/name name)) diff --git a/src/status_im/ui/screens/subs.cljs b/src/status_im/ui/screens/subs.cljs index dc154cf2e9..10e44dbdc8 100644 --- a/src/status_im/ui/screens/subs.cljs +++ b/src/status_im/ui/screens/subs.cljs @@ -2,7 +2,6 @@ (:require [re-frame.core :refer [reg-sub subscribe]] [status-im.utils.ethereum.core :as ethereum] status-im.chat.subs - status-im.commands.subs status-im.ui.screens.accounts.subs status-im.ui.screens.home.subs status-im.ui.screens.contacts.subs @@ -17,7 +16,6 @@ status-im.ui.screens.bootnodes-settings.subs status-im.ui.screens.currency-settings.subs status-im.ui.screens.browser.subs - status-im.bots.subs status-im.ui.screens.add-new.new-chat.subs status-im.ui.screens.profile.subs)) diff --git a/src/status_im/ui/screens/wallet/choose_recipient/events.cljs b/src/status_im/ui/screens/wallet/choose_recipient/events.cljs index b8c988abcd..5cb73c328b 100644 --- a/src/status_im/ui/screens/wallet/choose_recipient/events.cljs +++ b/src/status_im/ui/screens/wallet/choose_recipient/events.cljs @@ -1,7 +1,6 @@ (ns status-im.ui.screens.wallet.choose-recipient.events (:require [status-im.constants :as constants] [status-im.i18n :as i18n] - [status-im.ui.screens.wallet.send.events :as send.events] [status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.eip681 :as eip681] [status-im.utils.handlers :as handlers] @@ -42,7 +41,9 @@ ;; NOTE(janherich) - whenever changing assets, we want to clear the previusly set amount/amount-text (defn changed-asset [{:keys [db] :as fx} old-symbol new-symbol] (-> fx - (merge (send.events/update-gas-price db)) + (merge {:update-gas-price {:web3 (:web3 db) + :success-event :wallet/update-gas-price-success + :edit? false}}) (assoc-in [:db :wallet :send-transaction :amount] nil) (assoc-in [:db :wallet :send-transaction :amount-text] nil) (assoc-in [:db :wallet :send-transaction :asset-error] diff --git a/src/status_im/ui/screens/wallet/request/events.cljs b/src/status_im/ui/screens/wallet/request/events.cljs index 0955bda810..af48893387 100644 --- a/src/status_im/ui/screens/wallet/request/events.cljs +++ b/src/status_im/ui/screens/wallet/request/events.cljs @@ -3,24 +3,24 @@ [status-im.ui.screens.wallet.db :as wallet-db] [status-im.utils.handlers :as handlers] [status-im.utils.handlers-macro :as handlers-macro] - [status-im.chat.constants :as chat-const] - [status-im.chat.events.input :as input-events] + [status-im.chat.commands.core :as commands] [status-im.utils.money :as money])) (handlers/register-handler-fx ::wallet-send-chat-request [re-frame/trim-v] - (fn [{{:contacts/keys [contacts]} :db :as cofx} [asset amount]] + (fn [{:keys [db] :as cofx} [asset amount]] (handlers-macro/merge-fx cofx {:dispatch [:send-current-message]} - (input-events/select-chat-input-command - (assoc (get-in contacts chat-const/request-command-ref) :prefill [asset amount]) nil true)))) + (commands/select-chat-input-command + (get-in db [:id->command ["request" #{:personal-chats}]]) [asset amount])))) (handlers/register-handler-fx :wallet-send-request [re-frame/trim-v] (fn [_ [whisper-identity amount symbol decimals]] (assert whisper-identity) + ;; TODO(janherich) remove this dispatch sequence, there is absolutely no need for that :/ {:dispatch-n [[:navigate-back] [:navigate-to-clean :home] [:add-chat-loaded-event whisper-identity diff --git a/src/status_im/ui/screens/wallet/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index 15feb7e5fb..98843ebd6f 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -17,6 +17,7 @@ [status-im.utils.utils :as utils] [status-im.models.wallet :as models.wallet] [status-im.chat.models.message :as models.message] + [status-im.chat.commands.sending :as commands-sending] [status-im.constants :as constants] [status-im.transport.utils :as transport.utils] [taoensso.timbre :as log] @@ -304,10 +305,11 @@ :send-transaction-message (concat models.message/send-interceptors navigation/navigation-interceptors) - (fn [cofx [{:keys [view-id] :as params}]] - (handlers-macro/merge-fx cofx - (models.message/send-custom-send-command params) - (navigation/replace-view view-id)))) + (fn [{:keys [db] :as cofx} [chat-id params]] + (when-let [send-command (get-in db [:id->command ["send" #{:personal-chats}]])] + (handlers-macro/merge-fx cofx + (commands-sending/send chat-id send-command params) + (navigation/replace-view :wallet-transaction-sent))))) (handlers/register-handler-fx ::transaction-completed @@ -331,11 +333,10 @@ (= method constants/web3-send-transaction) (assoc :dispatch-later [{:ms 400 :dispatch [:navigate-to-modal :wallet-transaction-sent-modal]}])) - {:dispatch [:send-transaction-message {:view-id :wallet-transaction-sent - :whisper-identity whisper-identity - :address to - :asset (name symbol) - :amount amount-text}]})))))) + {:dispatch [:send-transaction-message whisper-identity {:address to + :asset (name symbol) + :amount amount-text + :tx-hash hash}]})))))) (defn on-transactions-modal-completed [raw-results] (let [result (types/json->clj raw-results)] diff --git a/src/status_im/utils/handlers_macro.cljs b/src/status_im/utils/handlers_macro.cljs index cb077f60cc..84d3d3e3ee 100644 --- a/src/status_im/utils/handlers_macro.cljs +++ b/src/status_im/utils/handlers_macro.cljs @@ -10,7 +10,7 @@ (def ^:private mergable-keys #{:data-store/tx :data-store/base-tx :chat-received-message/add-fx :shh/add-new-sym-keys :shh/get-new-sym-keys :shh/post - :confirm-messages-processed :call-jail}) + :confirm-messages-processed}) (defn safe-merge [fx new-fx] (if (:merging-fx-with-common-keys fx) diff --git a/test/cljs/status_im/test/bots/events.cljs b/test/cljs/status_im/test/bots/events.cljs deleted file mode 100644 index ecce328b96..0000000000 --- a/test/cljs/status_im/test/bots/events.cljs +++ /dev/null @@ -1,50 +0,0 @@ -(ns status-im.test.bots.events - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.bots.events :as bots-events])) - -(def ^:private initial-db - {:bot-db {} - :contacts/contacts - {"bot1" {:subscriptions - {:feeExplanation - {:subscriptions {:fee ["sliderValue"] - :tx ["transaction"]}}}} - "bot2" {:subscriptions - {:roundedValue - {:subscriptions {:amount ["input"]}}}} - "bot3" {:subscriptions - {:warning - {:subscriptions {:amount ["input"]}}}}}}) - -(deftest add-active-bot-subscriptions-test - (testing "That active bot subscriptions are correctly transformed and added to db" - (let [db (update-in initial-db [:contacts/contacts "bot1" :subscriptions] - bots-events/transform-bot-subscriptions)] - (is (= {[:sliderValue] {:feeExplanation {:fee [:sliderValue] - :tx [:transaction]}} - [:transaction] {:feeExplanation {:fee [:sliderValue] - :tx [:transaction]}}} - (get-in db [:contacts/contacts "bot1" :subscriptions])))))) - -(defn- fake-subscription-call - [db {:keys [chat-id parameters]}] - (let [{:keys [name subscriptions]} parameters - simulated-jail-response {:result {:returned {:sub-call-arg-map subscriptions}}}] - (bots-events/calculated-subscription db {:bot chat-id - :path [name] - :result simulated-jail-response}))) - -(deftest set-in-bot-db-test - (let [{:keys [db call-jail-function-n]} (-> initial-db - (update-in [:contacts/contacts "bot1" :subscriptions] - bots-events/transform-bot-subscriptions) - (bots-events/set-in-bot-db {:bot "bot1" - :path [:sliderValue] - :value 2})) - new-db (reduce fake-subscription-call db call-jail-function-n)] - (testing "That setting in value in bot-db correctly updates bot-db" - (is (= 2 (get-in new-db [:bot-db "bot1" :sliderValue])))) - (testing "That subscriptions are fired-off" - (is (= {:sub-call-arg-map {:fee 2 - :tx nil}} - (get-in new-db [:bot-db "bot1" :feeExplanation])))))) diff --git a/test/cljs/status_im/test/chat/commands/core.cljs b/test/cljs/status_im/test/chat/commands/core.cljs index eda0706e1d..b081323535 100644 --- a/test/cljs/status_im/test/chat/commands/core.cljs +++ b/test/cljs/status_im/test/chat/commands/core.cljs @@ -7,44 +7,65 @@ [selected-event-creator value] (selected-event-creator value)) +(def test-command-parameters + [{:id :first-param + :type :text + ;; pass function as mock-up for suggestions component, so we can + ;; just test the correct injection of `:set-command-parameter` event + :suggestions fake-suggestion} + {:id :second-param + :type :text} + {:id :last-param + :type :text + :suggestions fake-suggestion}]) + (deftype TestCommand [] protocol/Command - (id [_] - :test-command) - (scope [_] - #{:personal-chats :group-chats :public-chats :requested}) - (parameters [_] - [{:id :first-param - :type :text - ;; pass function as mock-up for suggestions component, so we can - ;; just test the correct injection of `:set-command-parameter` event - :suggestions fake-suggestion} - {:id :second-param - :type :text} - {:id :last-param - :type :text - :suggestions fake-suggestion}]) + (id [_] "test-command") + (scope [_] #{:personal-chats :group-chats :public-chats}) + (description [_] "Another test command") + (parameters [_] test-command-parameters) (validate [_ parameters _] (when-not (every? (comp string? second) parameters) - "Not all parameters are filled and of the correc type")) - (yield-control [_ _ _] - nil) - (on-send [_ _ _ _] - nil) - (on-receive [_ _ _] - nil) - (short-preview [_ command-message _] + "Not all parameters are filled and of the correct type")) + (on-send [_ _ _]) + (on-receive [_ _ _]) + (short-preview [_ command-message] [:text (str "Test-command, first-param: " (get-in command-message [:content :params :first-param]))]) - (preview [_ command-message _] + (preview [_ command-message] [:text (str "Test-command, params: " (apply str (map [:first-param :second-param :last-param] (get-in command-message [:content :params]))))])) +(def another-test-command-parameters + [{:id :first-param + :type :text}]) + +(deftype AnotherTestCommand [] + protocol/Command + (id [_] "another-test-command") + (scope [_] #{:public-chats}) + (description [_] "Another test command") + (parameters [_] another-test-command-parameters) + (validate [_ parameters _] + (when-not (every? (comp string? second) parameters) + "Not all parameters are filled and of the correct type")) + (on-send [_ _ _]) + (on-receive [_ _ _]) + (short-preview [_ command-message] + [:text (str "Test-command, first-param: " + (get-in command-message [:content :params :first-param]))]) + (preview [_ command-message] + [:text (str "Test-command, params: " + (apply str (map [:first-param] + (get-in command-message [:content :params]))))])) + (def TestCommandInstance (TestCommand.)) +(def AnotherTestCommandInstance (AnotherTestCommand.)) (deftest index-commands-test - (let [fx (core/index-commands #{TestCommandInstance} {:db {}})] + (let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})] (testing "Primary composite key index for command is correctly created" (is (= TestCommandInstance (get-in fx [:db :id->command @@ -61,12 +82,63 @@ 2 :suggestions]) "last-value")))) (testing "Access scope indexes are correctly created" - (is (= (get-in fx [:db :access-scope->command-id #{:personal-chats :requested}]) - (core/command-id TestCommandInstance))) - (is (= (get-in fx [:db :access-scope->command-id #{:group-chats :requested}]) - (core/command-id TestCommandInstance))) - (is (= (get-in fx [:db :access-scope->command-id #{:public-chats :requested}]) - (core/command-id TestCommandInstance)))))) + (is (contains? (get-in fx [:db :access-scope->command-id #{:personal-chats}]) + (core/command-id TestCommandInstance))) + (is (not (contains? (get-in fx [:db :access-scope->command-id #{:personal-chats}]) + (core/command-id AnotherTestCommandInstance)))) + (is (contains? (get-in fx [:db :access-scope->command-id #{:group-chats}]) + (core/command-id TestCommandInstance))) + (is (contains? (get-in fx [:db :access-scope->command-id #{:public-chats}]) + (core/command-id TestCommandInstance))) + (is (contains? (get-in fx [:db :access-scope->command-id #{:public-chats}]) + (core/command-id AnotherTestCommandInstance)))))) + +(deftest chat-commands-test + (let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})] + (testing "That relevant commands are looked up for chat" + (is (= #{TestCommandInstance AnotherTestCommandInstance} + (into #{} + (map (comp :type second)) + (core/chat-commands (get-in fx [:db :id->command]) + (get-in fx [:db :access-scope->command-id]) + {:chat-id "topic" + :group-chat true + :public? true})))) + (is (= #{TestCommandInstance} + (into #{} + (map (comp :type second)) + (core/chat-commands (get-in fx [:db :id->command]) + (get-in fx [:db :access-scope->command-id]) + {:chat-id "group" + :group-chat true})))) + (is (= #{TestCommandInstance} + (into #{} + (map (comp :type second)) + (core/chat-commands (get-in fx [:db :id->command]) + (get-in fx [:db :access-scope->command-id]) + {:chat-id "contact"}))))))) + +(deftest selected-chat-command-test + (let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}}) + commands (core/chat-commands (get-in fx [:db :id->command]) + (get-in fx [:db :access-scope->command-id]) + {:chat-id "contact"})] + (testing "Text not beggining with the command special charactes `/` is recognised" + (is (not (core/selected-chat-command "test-command 1" nil commands)))) + (testing "Command not matching any available commands is not recognised as well" + (is (not (core/selected-chat-command "/another-test-command" nil commands)))) + (testing "Available correctly entered command is recognised" + (is (= TestCommandInstance + (get (core/selected-chat-command "/test-command" nil commands) :type)))) + (testing "Command completion and param position are determined as well" + (let [{:keys [current-param-position command-completion]} + (core/selected-chat-command "/test-command 1 " 17 commands)] + (is (= 1 current-param-position)) + (is (= :less-then-needed command-completion))) + (let [{:keys [current-param-position command-completion]} + (core/selected-chat-command "/test-command 1 2 3" 20 commands)] + (is (= 2 current-param-position)) + (is (= :complete command-completion)))))) (deftest set-command-parameter-test (testing "Setting command parameter correctly updates the text input" @@ -88,3 +160,19 @@ false 2 "last value" (create-cofx "/test-command first-value second-value")) [:db :chats "test" :input-text])))))) + +(deftest parse-parameters-test + (testing "testing that parse-parameters work correctly" + (is (= {:first-param "1" + :second-param "2" + :last-param "3"} + (core/parse-parameters test-command-parameters "/test-command 1 2 3"))) + (is (= {:first-param "1" + :second-param "2 2" + :last-param "3"} + (core/parse-parameters test-command-parameters "/test-command 1 \"2 2\" 3"))) + (is (= {:first-param "1" + :second-param "2"} + (core/parse-parameters test-command-parameters "/test-command 1 2"))) + (is (= {} + (core/parse-parameters test-command-parameters "/test-command "))))) diff --git a/test/cljs/status_im/test/chat/events.cljs b/test/cljs/status_im/test/chat/events.cljs index d1b36c675a..47ed12d1e7 100644 --- a/test/cljs/status_im/test/chat/events.cljs +++ b/test/cljs/status_im/test/chat/events.cljs @@ -4,13 +4,11 @@ [re-frame.core :as rf] [day8.re-frame.test :refer [run-test-sync]] [status-im.constants :as const] - [status-im.chat.console :as console-chat] [status-im.chat.events :as chat-events])) (def test-db {:current-public-key "me" - :chats {const/console-chat-id console-chat/chat - "status" {:public? true + :chats {"status" {:public? true :unviewed-messages #{"6" "5" "4" "3" "2" "1"} :message-statuses {"6" {"me" {:message-id "6" :chat-id "status" @@ -43,27 +41,6 @@ :whisper-identity "me" :status :received}}}}}}) -(deftest init-console-chat - (testing "initialising console if console is already added to chats, should not modify anything" - (let [fx (chat-events/init-console-chat {:db test-db})] - (is (not fx)))) - - (testing "initialising console without existing account and console chat not initialisated" - (let [fresh-db {:chats {}} - {:keys [db dispatch-n]} (chat-events/init-console-chat {:db fresh-db})] - (is (= (:current-chat-id db) - (:chat-id console-chat/chat))) - (is (= (:current-chat-id db) - const/console-chat-id)))) - - (testing "initialising console with existing account and console chat not initialisated" - (let [fresh-db {:chats {}} - {:keys [db dispatch-n]} (chat-events/init-console-chat {:db fresh-db})] - (is (= (:current-chat-id db) - (:chat-id console-chat/chat))) - (is (= (:current-chat-id db) - const/console-chat-id))))) - (deftest mark-messages-seen (testing "Marking messages seen correctly marks loaded messages as seen and updates absolute unviewed set" (let [fx (chat-events/mark-messages-seen "status" {:db test-db}) diff --git a/test/cljs/status_im/test/chat/models/input.cljs b/test/cljs/status_im/test/chat/models/input.cljs index 2ef4a37a32..2e10b0adee 100644 --- a/test/cljs/status_im/test/chat/models/input.cljs +++ b/test/cljs/status_im/test/chat/models/input.cljs @@ -5,41 +5,6 @@ [status-im.utils.datetime :as datetime] [status-im.chat.models.input :as input])) -(def fake-db - {:access-scope->commands-responses {#{:global :personal-chats :anonymous :dapps} {:command {"global-command1" ["0x1" :command 0 "global-command1"]}} - #{"0x1" :personal-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 2 "command2"]}} - #{"0x1" :group-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 4 "command2"]}} - #{"0x2" :personal-chats :anonymous :dapps} {:command {"command3" ["0x2" :command 2 "command3"]}} - #{"0x2" :group-chats :anonymous :dapps} {:response {"response1" ["0x2" :response 4 "response1"]}}} - :chats {"test1" {:contacts ["0x1"] - :requests nil - :seq-arguments ["arg1" "arg2"]} - "test2" {:contacts ["0x1" "0x2"] - :group-chat true - :requests {"id1" {:message-id "id1" - :response "response1"}}} - "test3" {:contacts ["0x1"] - :requests {"id1" {:message-id "id1" - :response "request1"}}} - "test4" {:contacts ["0x1" "0x2"] - :group-chat true - :requests {"id2" {:message-id "id2" - :response "response1"}} - :input-metadata {:meta-k "meta-v"}}} - :contacts/contacts {"0x1" {:dapp? true - :command {0 {"global-command1" {:name "global-command1" - :ref ["0x1" :command 0 "global-command1"]}} - 2 {"command2" {:name "command2" - :ref ["0x1" :command 2 "command2"]}} - 4 {"command2" {:name "command2" - :ref ["0x1" :command 4 "command2"]}}} - :response {}} - "0x2" {:dapp? true - :command {2 {"command3" {:name "command3" - :ref ["0x2" :command 2 "command3"]}}} - :response {4 {"response1" {:name "response1" - :ref ["0x2" :response 4 "response1"]}}}}}}) - (deftest text->emoji (is (nil? (input/text->emoji nil))) (is (= "" (input/text->emoji ""))) @@ -65,111 +30,6 @@ (is (= "" (input/join-command-args [""]))) (is (= "/send 1.0 \"John Doe\"" (input/join-command-args ["/send" "1.0" "John Doe"])))) -(deftest selected-chat-command - (is (= (input/selected-chat-command (-> fake-db - (assoc :current-chat-id "test1") - (assoc-in [:chats "test1" :input-text] "/global-command1"))) - {:command {:name "global-command1" - :ref ["0x1" :command 0 "global-command1"]} - :metadata nil - :args ["arg1" "arg2"]})) - (is (= (input/selected-chat-command (-> fake-db - (assoc :current-chat-id "test2") - (assoc-in [:chats "test2" :input-text] "/command2"))) - {:command {:name "command2" - :ref ["0x1" :command 4 "command2"]} - :metadata nil - :args []})) - (is (nil? (input/selected-chat-command (-> fake-db - (assoc :current-chat-id "test1") - (assoc-in [:chats "test1" :input-text] "/command3"))))) - (is (= (input/selected-chat-command (-> fake-db - (assoc :current-chat-id "test1") - (assoc-in [:chats "test1" :input-text] "/command2"))) - {:command {:name "command2" - :ref ["0x1" :command 2 "command2"]} - :metadata nil - :args ["arg1" "arg2"]})) - (is (= (input/selected-chat-command (-> fake-db - (assoc :current-chat-id "test2") - (assoc-in [:chats "test2" :input-text] "/response1 arg1"))) - {:command {:name "response1" - :ref ["0x2" :response 4 "response1"]} - :metadata nil - :args ["arg1"]})) - (is (= (input/selected-chat-command (-> fake-db - (assoc :current-chat-id "test4") - (assoc-in [:chats "test4" :input-text] "/command2 arg1"))) - {:command {:name "command2" - :ref ["0x1" :command 4 "command2"]} - :metadata {:meta-k "meta-v"} - :args ["arg1"]}))) - -(deftest current-chat-argument-position - (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 arg1 arg2 " 0 nil) -1)) - (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 9 nil) -1)) - (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 10 nil) 0)) - (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 19 nil) 0)) - (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 20 nil) 1)) - (is (= (input/current-chat-argument-position - {:name "command2"} "/command2 \"a r g u m e n t 1\" argument2" 30 nil) 1)) - (is (= (input/current-chat-argument-position - {:name "command3" :command {:sequential-params true}} "/command3" 0 ["test1" "test2"]) 2))) - -(deftest argument-position - "Doesn't require a separate test because it simply calls `current-chat-argument-position") - -(deftest command-completion - (is (= (input/command-completion {:args ["p1" "p2"] - :command {:params [{:optional false} {:optional false}]}}) - :complete)) - (is (= (input/command-completion {:args ["p1"] - :command {:params [{:optional false} {:optional false}]}}) - :less-than-needed)) - (is (= (input/command-completion {:args ["p1" "p2" "p3"] - :command {:params [{:optional false} {:optional false}]}}) - :more-than-needed)) - (is (= (input/command-completion {:args ["p1" "p2"] - :command {:params [{:optional false} {:optional false} {:optional true}]}}) - :complete)) - (is (= (input/command-completion {:args ["p1" "p2" "p3"] - :command {:params [{:optional false} {:optional false} {:optional true}]}}) - :complete)) - (is (= (input/command-completion {:args ["p1" "p2" "p3" "p4"] - :command {:params [{:optional false} {:optional false} {:optional true}]}}) - :more-than-needed)) - (is (= (input/command-completion {:command {:params [{:optional false}]}}) - :less-than-needed)) - (is (= (input/command-completion {:command {}}) - :complete)) - (is (= (input/command-completion nil) - :no-command))) - -(deftest args->params - (is (= {} (input/args->params nil))) - (is (= {} (input/args->params {}))) - (is (= {} (input/args->params {:args ["1.0"]}))) - (is (= {:amount "1.0"} - (input/args->params {:command {:params [{:name "amount"}]} - :args ["1.0"]}))) - (is (= {:amount "1.0"} - (input/args->params {:command {:params [{:name "amount"}]} - :args ["1.0" "2.0" "3.0"]}))) - (is (= {:amount "1.0"} - (input/args->params {:command {:params [{:name "amount"} {:name "recipient"}]} - :args ["1.0"]}))) - (is (= {:amount "1.0" :recipient "John Doe"} - (input/args->params {:command {:params [{:name "amount"} {:name "recipient"}]} - :args ["1.0" "John Doe"]})))) - -(deftest modified-db-after-change - "Just a combination of db modifications. Can be skipped now") - (deftest process-cooldown-fx (let [db {:current-chat-id "chat" :chats {"chat" {:public? true}} diff --git a/test/cljs/status_im/test/chat/subs.cljs b/test/cljs/status_im/test/chat/subs.cljs index 4cc228991a..5a54ea3da9 100644 --- a/test/cljs/status_im/test/chat/subs.cljs +++ b/test/cljs/status_im/test/chat/subs.cljs @@ -118,12 +118,7 @@ 2 active-chat-2 3 {:is-active false :chat-id 3} const/console-chat-id console}] - (testing "in normal it returns only chats with is-active, without console" + (testing "it returns only chats with is-active, without console" (is (= {1 active-chat-1 2 active-chat-2} - (s/active-chats [chats {:dev-mode? false}])))) - (testing "in dev-mode it returns only chats with is-active, keeping console" - (is (= {1 active-chat-1 - 2 active-chat-2 - const/console-chat-id console} - (s/active-chats [chats {:dev-mode? true}])))))) + (s/active-chats chats)))))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 7ed578925a..fa47a69d4f 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -10,7 +10,6 @@ [status-im.test.wallet.transactions.subs] [status-im.test.wallet.transactions.views] [status-im.test.profile.events] - [status-im.test.bots.events] [status-im.test.models.mailserver] [status-im.test.models.bootnode] [status-im.test.models.account] @@ -73,7 +72,6 @@ 'status-im.test.models.contact 'status-im.test.models.network 'status-im.test.models.wallet - 'status-im.test.bots.events 'status-im.test.wallet.subs 'status-im.test.wallet.transactions.subs 'status-im.test.wallet.transactions.views