Cljs commands
This commit is contained in:
parent
d1bd86c894
commit
76a509ed22
|
@ -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)))
|
|
@ -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)])))
|
|
@ -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)))
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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))))
|
|
@ -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))))
|
|
@ -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?))
|
|
@ -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]])
|
|
@ -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)})
|
|
@ -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)
|
||||
|
|
|
@ -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?]))
|
||||
|
|
|
@ -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))))
|
|
@ -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"))
|
|
@ -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
|
||||
|
|
|
@ -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}]])))))
|
||||
|
|
|
@ -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}))))
|
|
@ -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]))))
|
|
@ -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)))
|
|
@ -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]
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
@ -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})
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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}]]))
|
|
@ -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}]]))
|
|
@ -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})
|
|
@ -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]])
|
||||
|
|
|
@ -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]])))
|
||||
|
|
|
@ -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]]))
|
|
@ -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"]}))]
|
||||
|
|
|
@ -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))]]]))
|
||||
|
|
|
@ -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]]]))))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))]]])])]]])))
|
|
@ -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)))))))
|
|
@ -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})]}))
|
|
@ -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?))
|
|
@ -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))))
|
|
@ -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)))
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]))))))
|
|
@ -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 ")))))
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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))))))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue