Refactoring chat events to shorten event chains

This commit is contained in:
Herich 2017-08-15 22:56:44 +02:00 committed by Roman Volosovskyi
parent 5a49926ad0
commit d9db548e57
36 changed files with 1950 additions and 1584 deletions

View File

@ -0,0 +1,98 @@
(ns status-im.bots.events
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]))
;;;; Helper fns
(defn- chats-with-bot [chats bot]
(reduce (fn [acc [_ {:keys [chat-id contacts]}]]
(let [contacts (map :identity contacts)]
(if (some #{bot} contacts)
(conj acc chat-id)
acc)))
[]
chats))
(defn- subscription-values [subscriptions current-bot-db]
(reduce (fn [sub-values [sub-name sub-path]]
(assoc sub-values sub-name (get-in current-bot-db sub-path)))
{}
subscriptions))
;; TODO(janherich): optimze this, for sure we don't need to re-calculate all bot subscriptions every time something in bot db changes
(defn- check-subscriptions-fx
[db {:keys [bot path value]}]
(let [{:keys [bot-db chats]} db
subscriptions (get-in db [:bot-subscriptions path])]
{:call-jail-function-n
(for [{:keys [bot subscriptions name]} subscriptions
:let [subs-values (subscription-values subscriptions (get bot-db bot))]]
{:chat-id bot
:function :subscription
:parameters {:name name
:subscriptions subs-values}
:callback-events-creator (fn [jail-response]
(into [[::calculated-subscription
{:bot bot
:path [name]
:result jail-response}]]
(map (fn [chat-id]
[::calculated-subscription
{:bot chat-id
:path [name]
:result jail-response}])
(chats-with-bot chats bot))))})}))
(defn set-in-bot-db
[{:keys [current-chat-id] :as app-db} {:keys [bot path value] :as params}]
(let [bot (or bot current-chat-id)
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
[{:keys [current-chat-id] :as app-db} {:keys [bot db]}]
(let [bot (or bot current-chat-id)]
(update-in app-db [:bot-db bot] merge db)))
(defn clear-bot-db
[{:keys [current-chat-id] :as app-db}]
(assoc-in app-db [:bot-db current-chat-id] nil))
;;;; 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
:register-bot-subscription
[re-frame/trim-v]
(fn [db [{:keys [bot subscriptions] :as opts}]]
(reduce
(fn [db [sub-name sub-path]]
(let [keywordized-sub-path (mapv keyword
(if (coll? sub-path)
sub-path
[sub-path]))]
(update-in db [:bot-subscriptions keywordized-sub-path] conj
(assoc-in opts [:subscriptions sub-name] keywordized-sub-path))))
db
subscriptions)))
(handlers/register-handler-db
::calculated-subscription
[re-frame/trim-v]
(fn [db [{:keys [bot path]
{:keys [error result]} :result}]]
(if error
db
(assoc-in db (concat [:bot-db bot] path) (:returned result)))))

View File

@ -1,77 +0,0 @@
(ns status-im.bots.handlers
(:require [re-frame.core :as re-frame]
[status-im.native-module.core :as status]
[status-im.utils.handlers :as u]))
(defn chats-with-bot [chats bot]
(reduce (fn [acc [_ {:keys [chat-id contacts]}]]
(let [contacts (map :identity contacts)]
(if (some #{bot} contacts)
(conj acc chat-id)
acc)))
[]
chats))
(defn check-subscriptions
[{:keys [bot-db chats] :as db} [handler {:keys [path key bot]}]]
(let [path' (or path [key])
subscriptions (get-in db [:bot-subscriptions path'])
current-bot-db (get bot-db bot)]
(doseq [{:keys [bot subscriptions name]} subscriptions]
(let [subs-values (reduce (fn [res [sub-name sub-path]]
(assoc res sub-name (get-in current-bot-db sub-path)))
{} subscriptions)]
(status/call-function!
{:chat-id bot
:function :subscription
:parameters {:name name
:subscriptions subs-values}
:callback #(do
(re-frame/dispatch
[::calculated-subscription {:bot bot
:path [name]
:result %}])
(doseq [chat-id (chats-with-bot chats bot)]
(re-frame/dispatch
[::calculated-subscription {:bot chat-id
:path [name]
:result %}])))})))))
(u/register-handler :set-in-bot-db
(re-frame/after check-subscriptions)
(fn [{:keys [current-chat-id] :as db} [_ {:keys [bot path value]}]]
(let [bot (or bot current-chat-id)]
(assoc-in db (concat [:bot-db bot] path) value))))
(u/register-handler :register-bot-subscription
(fn [db [_ {:keys [bot subscriptions] :as opts}]]
(reduce
(fn [db [sub-name sub-path]]
(let [sub-path' (if (coll? sub-path) sub-path [sub-path])
sub-path'' (mapv keyword sub-path')]
(update-in db [:bot-subscriptions sub-path''] conj
(assoc-in opts [:subscriptions sub-name] sub-path''))))
db
subscriptions)))
(u/register-handler ::calculated-subscription
(u/side-effect!
(fn [_ [_ {:keys [bot path]
{:keys [error result]} :result
:as data}]]
(when-not error
(let [returned (:returned result)
opts {:bot bot
:path path
:value returned}]
(re-frame/dispatch [:set-in-bot-db opts]))))))
(u/register-handler :update-bot-db
(fn [{:keys [current-chat-id] :as app-db} [_ {:keys [bot db]}]]
(let [bot (or bot current-chat-id)]
(update-in app-db [:bot-db bot] merge db))))
(u/register-handler :clear-bot-db
(fn [{:keys [current-chat-id] :as app-db} [_ {:keys [bot]}]]
(let [bot (or bot current-chat-id)]
(assoc-in app-db [:bot-db bot] nil))))

View File

@ -0,0 +1,253 @@
(ns status-im.chat.events
(:require [re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.utils.handlers :as handlers]
[status-im.chat.models :as model]
[status-im.chat.models.unviewed-messages :as unviewed-messages-model]
[status-im.chat.sign-up :as sign-up]
[status-im.chat.constants :as chat-const]
[status-im.data-store.handler-data :as handler-data]
[status-im.data-store.messages :as msg-store]
[status-im.data-store.contacts :as contacts-store]
[status-im.data-store.chats :as chats-store]
[status-im.protocol.core :as protocol]
[status-im.constants :as const]
status-im.chat.events.input
status-im.chat.events.commands
status-im.chat.events.animation
status-im.chat.events.receive-message
status-im.chat.events.sign-up
status-im.chat.events.console))
;;;; Coeffects
(re-frame/reg-cofx
:stored-unviewed-messages
(fn [cofx _]
(assoc cofx :stored-unviewed-messages (msg-store/get-unviewed))))
(re-frame/reg-cofx
:get-stored-message
(fn [cofx _]
(assoc cofx :get-stored-message msg-store/get-by-id)))
(re-frame/reg-cofx
:get-stored-messages
(fn [cofx _]
(assoc cofx :get-stored-messages msg-store/get-by-chat-id)))
(re-frame/reg-cofx
:get-last-stored-message
(fn [cofx _]
(assoc cofx :get-last-stored-message msg-store/get-last-message)))
(re-frame/reg-cofx
:get-message-previews
(fn [cofx _]
(assoc cofx :message-previews (msg-store/get-previews))))
(re-frame/reg-cofx
:all-stored-chats
(fn [cofx _]
(assoc cofx :all-stored-chats (chats-store/get-all))))
;;;; Effects
(re-frame/reg-fx
:update-message
(fn [message]
(msg-store/update message)))
(re-frame/reg-fx
:save-message
(fn [{:keys [chat-id] :as message}]
(msg-store/save chat-id message)))
(re-frame/reg-fx
:save-chat
(fn [chat]
(chats-store/save chat)))
(re-frame/reg-fx
:save-all-contacts
(fn [contacts]
(contacts-store/save-all contacts)))
(re-frame/reg-fx
:protocol-send-seen
(fn [params]
(protocol/send-seen! params)))
;;;; Helper fns
(defn init-console-chat
[{:keys [chats] :accounts/keys [current-account-id] :as db} existing-account?]
(if (chats const/console-chat-id)
{:db db}
(cond-> {:db (-> db
(assoc :new-chat sign-up/console-chat)
(update :chats assoc const/console-chat-id sign-up/console-chat)
(assoc :current-chat-id const/console-chat-id))
:dispatch-n [[:add-contacts [sign-up/console-contact]]]
:save-chat sign-up/console-chat
:save-all-contacts [sign-up/console-contact]}
(not current-account-id)
(update :dispatch-n concat sign-up/intro-events)
existing-account?
(update :dispatch-n concat sign-up/start-signup-events))))
;;;; Handlers
(handlers/register-handler-db
:set-layout-height
[re-frame/trim-v]
(fn [db [height]]
(assoc db :layout-height height)))
(handlers/register-handler-db
:set-chat-ui-props
[re-frame/trim-v]
(fn [db [kvs]]
(model/set-chat-ui-props db kvs)))
(handlers/register-handler-db
:toggle-chat-ui-props
[re-frame/trim-v]
(fn [db [ui-element]]
(model/toggle-chat-ui-prop db ui-element)))
(handlers/register-handler-db
:show-message-details
[re-frame/trim-v]
(fn [db [details]]
(model/set-chat-ui-props db {:show-bottom-info? true
:show-emoji? false
:bottom-info details})))
(handlers/register-handler-fx
:load-more-messages
[(re-frame/inject-cofx :get-stored-messages)]
(fn [{{:keys [current-chat-id loading-allowed] :as db} :db
get-stored-messages :get-stored-messages} _]
(let [all-loaded? (get-in db [:chats current-chat-id :all-loaded?])]
(if (and loading-allowed (not all-loaded?))
(let [messages-path [:chats current-chat-id :messages]
messages (get-in db messages-path)
chat-messages (filter #(= current-chat-id (:chat-id %)) messages)
new-messages (get-stored-messages current-chat-id (count chat-messages))
all-loaded? (> const/default-number-of-messages (count new-messages))]
{:db (-> db
(assoc :loading-allowed false)
(update-in messages-path concat new-messages)
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?))
;; we permit loading more messages again after 400ms
:dispatch-later [{:ms 400 :dispatch [:set :loading-allowed true]}]})
{:db db}))))
(handlers/register-handler-db
:set-message-shown
[re-frame/trim-v]
(fn [db [{:keys [chat-id message-id]}]]
(update-in db
[:chats chat-id :messages]
(fn [messages]
(map (fn [message]
(if (= message-id (:message-id message))
(assoc message :new? false)
message))
messages)))))
(handlers/register-handler-fx
:init-console-chat
(fn [{:keys [db]} _]
(init-console-chat db false)))
(handlers/register-handler-fx
:initialize-chats
[(re-frame/inject-cofx :all-stored-chats)
(re-frame/inject-cofx :stored-unviewed-messages)
(re-frame/inject-cofx :get-last-stored-message)
(re-frame/inject-cofx :get-message-previews)]
(fn [{:keys [db all-stored-chats stored-unviewed-messages get-last-stored-message message-previews]} _]
(let [{:accounts/keys [account-creation?]} db
new-db (unviewed-messages-model/load-unviewed-messages db stored-unviewed-messages)
event [:load-default-contacts!]]
(if account-creation?
{:db new-db
:dispatch-n [event]}
(let [chats (->> all-stored-chats
(map (fn [{:keys [chat-id] :as chat}]
[chat-id (assoc chat :last-message (get-last-stored-message chat-id))]))
(into {}))]
(-> new-db
(assoc-in [:message-data :preview] message-previews)
(assoc :handler-data (handler-data/get-all))
(assoc :chats chats)
(init-console-chat true)
(update :dispatch-n conj event)))))))
(handlers/register-handler-fx
:reload-chats
[(re-frame/inject-cofx :all-stored-chats) (re-frame/inject-cofx :get-last-stored-message)]
(fn [{:keys [db all-stored-chats get-last-stored-message]} _]
(let [updated-chats (->> all-stored-chats
(map (fn [{:keys [chat-id] :as chat}]
(let [prev-chat (get (:chats db) chat-id)
updated-chat (assoc chat :last-message (get-last-stored-message chat-id))]
[chat-id (merge prev-chat updated-chat)])))
(into {}))]
(-> (assoc db :chats updated-chats)
(init-console-chat true)))))
(handlers/register-handler-fx
:send-seen!
[re-frame/trim-v]
(fn [{:keys [db]} [{:keys [message-id chat-id from]}]]
(let [{:keys [web3 current-public-key chats]
:contacts/keys [contacts]} db
{:keys [group-chat public?]} (get chats chat-id)]
(cond-> {:db (unviewed-messages-model/remove-unviewed-messages db chat-id)
:update-message {:message-id message-id
:message-status :seen}}
(and (not (get-in contacts [chat-id] :dapp?))
(not public?))
(assoc :protocol-send-seen
{:web3 web3
:message (cond-> {:from current-public-key
:to from
:message-id message-id}
group-chat (assoc :group-id chat-id))})))))
(handlers/register-handler-fx
:show-mnemonic
[(re-frame/inject-cofx :get-stored-message) re-frame/trim-v]
(fn [{:keys [get-stored-message]} [mnemonic signing-phrase]]
(let [crazy-math-message? (get-stored-message chat-const/crazy-math-message-id)]
{:dispatch-n (sign-up/passphrase-messages-events mnemonic
signing-phrase
crazy-math-message?)})))
(handlers/register-handler-fx
:account-generation-message
[(re-frame/inject-cofx :get-stored-message)]
(fn [{:keys [get-stored-message]} _]
(when-not (get-stored-message chat-const/passphrase-message-id)
{:dispatch sign-up/account-generation-event})))
(handlers/register-handler-fx
:move-to-internal-failure-message
[(re-frame/inject-cofx :get-stored-message)]
(fn [{:keys [get-stored-message]} _]
(when-not (get-stored-message chat-const/move-to-internal-failure-message-id)
{:dispatch sign-up/move-to-internal-failure-event})))
(comment
(handlers/register-handler-fx
:init-chat
[(re-frame/inject-cofx :get-stored-messages)]
(fn [{:keys [db get-stored-messages]} _]
(let [current-chat-id (:current-chat-id db)]
{:db (assoc-in [:chats current-chat-id :messages] (get-stored-messages current-chat-id))
;; TODO(janherich): make this dispatch into fn call once commands loading is refactored
:dispatch [:load-commands! current-chat-id]}))))

View File

@ -0,0 +1,75 @@
(ns status-im.chat.events.animation
(:require [re-frame.core :as re-frame]
[status-im.chat.views.input.utils :as input-utils]
[status-im.utils.handlers :as handlers]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]))
;;;; Helper fns
(defn set-expandable-height
[{:keys [current-chat-id] :as db} key value]
(-> db
(assoc-in [:chat-animations current-chat-id key :height] value)
(update-in [:chat-animations current-chat-id key :changes-counter] inc)))
(defn choose-predefined-expandable-height
[{:keys [current-chat-id chat-ui-props layout-height] :as db} key preset]
(if (= preset :max)
(set-expandable-height db key :max)
(let [input-height (get-in chat-ui-props [current-chat-id :input-height])
chat-input-margin (if platform/ios?
(get db :keyboard-height)
0)
bottom (+ input-height chat-input-margin)
height (case preset
:min input-utils/min-height
(input-utils/default-container-area-height bottom layout-height))]
(set-expandable-height db key height))))
;;;; Handlers
(handlers/register-handler-db
:set-expandable-height
[re-frame/trim-v]
(fn [db [key value]]
(set-expandable-height db key value)))
(handlers/register-handler-db
:choose-predefined-expandable-height
[re-frame/trim-v]
(fn [db [key preset]]
(choose-predefined-expandable-height db key preset)))
(handlers/register-handler-db
:fix-expandable-height
[re-frame/trim-v]
(fn [{:keys [current-chat-id chats chat-ui-props layout-height] :as db} [vy current key]]
(let [input-height (get-in chat-ui-props [current-chat-id :input-height])
chat-input-margin (if platform/ios?
(get db :keyboard-height)
0)
bottom (+ input-height chat-input-margin)
min-height input-utils/min-height
max-height (input-utils/max-container-area-height bottom layout-height)
default-height (input-utils/default-container-area-height bottom layout-height)
possible-values [min-height default-height max-height]
moving-down? (pos? vy)
closest-index (->> possible-values
(map-indexed vector)
(sort-by (fn [[i v]] (Math/abs (- v current))))
(ffirst))
height (cond (and moving-down? (not= closest-index 0))
(get possible-values (dec closest-index))
(and (not moving-down?) (not= closest-index 2))
(get possible-values (inc closest-index))
moving-down?
min-height
(not moving-down?)
max-height)]
(set-expandable-height db key height))))

View File

@ -1,17 +1,15 @@
(ns status-im.chat.events.commands
(:require [cljs.reader :as reader]
[clojure.string :as str]
[re-frame.core :refer [reg-fx reg-cofx inject-cofx dispatch trim-v]]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.data-store.messages :as msg-store]
[status-im.utils.handlers :refer [register-handler-fx]]
[status-im.native-module.core :as status]
[status-im.utils.handlers :as handlers]
[status-im.i18n :as i18n]
[status-im.utils.platform :as platform]))
;;;; Helper fns
(defn generate-context
(defn- generate-context
"Generates context for jail call"
[{:keys [chats] :accounts/keys [current-account-id]} chat-id to group-id]
(merge {:platform platform/platform
@ -22,55 +20,12 @@
(not (nil? group-id)))}}
i18n/delimeters))
;;;; Coeffects
(reg-cofx
::get-persisted-message
(fn [coeffects _]
(assoc coeffects :get-persisted-message msg-store/get-by-id)))
;;;; Effects
(reg-fx
::update-persisted-message
(fn [message]
(msg-store/update message)))
(reg-fx
:chat-fx/call-jail
(fn [{:keys [callback-events-creator] :as opts}]
(status/call-jail
(-> opts
(dissoc :callback-events-creator)
(assoc :callback
(fn [jail-response]
(doseq [event (callback-events-creator jail-response)]
(dispatch event))))))))
;;;; Handlers
(register-handler-fx
::jail-command-data-response
[trim-v]
(fn [{:keys [db]} [{{:keys [returned]} :result} {:keys [message-id on-requested]} data-type]]
(cond-> {}
returned
(assoc :db (assoc-in db [:message-data data-type message-id] returned))
(and returned
(= :preview data-type))
(assoc ::update-persisted-message {:message-id message-id
:preview (prn-str returned)})
on-requested
(assoc :dispatch (on-requested returned)))))
(register-handler-fx
:request-command-data
[trim-v]
(fn [{:keys [db]}
[{{:keys [command content-command params type]} :content
:keys [chat-id jail-id group-id message-id handler-data] :as message}
data-type]]
(defn request-command-message-data
"Requests command message data from jail"
[db
{{:keys [command content-command params type]} :content
:keys [chat-id jail-id group-id message-id] :as message}
data-type]
(let [{:keys [chats]
:accounts/keys [current-account-id]
:contacts/keys [contacts]} db
@ -85,38 +40,60 @@
to (get-in contacts [chat-id :address])
jail-params {:parameters params
:context (generate-context db chat-id to group-id)}]
{:chat-fx/call-jail {:jail-id jail-id
{:call-jail {:jail-id jail-id
:path path
:params jail-params
:callback-events-creator (fn [jail-response]
[[::jail-command-data-response
jail-response message data-type]])}})
{:dispatch-n [[:add-commands-loading-callback jail-id
#(dispatch [:request-command-data message data-type])]
[:load-commands! jail-id]]}))))
#(re-frame/dispatch [:request-command-message-data message data-type])]
[:load-commands! jail-id]]})))
(register-handler-fx
;;;; Handlers
(handlers/register-handler-fx
::jail-command-data-response
[re-frame/trim-v]
(fn [{:keys [db]} [{{:keys [returned]} :result} {:keys [message-id on-requested]} data-type]]
(cond-> {}
returned
(assoc :db (assoc-in db [:message-data data-type message-id] returned))
(and returned
(= :preview data-type))
(assoc :update-message {:message-id message-id
:preview (prn-str returned)})
on-requested
(assoc :dispatch (on-requested returned)))))
(handlers/register-handler-fx
:request-command-message-data
[re-frame/trim-v]
(fn [{:keys [db]} [message data-type]]
(request-command-message-data db message data-type)))
(handlers/register-handler-fx
:execute-command-immediately
[trim-v]
[re-frame/trim-v]
(fn [_ [{command-name :name :as command}]]
(case (keyword command-name)
:grant-permissions
{:dispatch [:request-permissions
[:read-external-storage]
#(dispatch [:initialize-geth])]}
#(re-frame/dispatch [:initialize-geth])]}
(log/debug "ignoring command: " command))))
(register-handler-fx
(handlers/register-handler-fx
:request-command-preview
[trim-v (inject-cofx ::get-persisted-message)]
(fn [{:keys [db get-persisted-message]} [{:keys [message-id] :as message}]]
[re-frame/trim-v (re-frame/inject-cofx :get-stored-message)]
(fn [{:keys [db get-stored-message]} [{:keys [message-id] :as message}]]
(let [previews (get-in db [:message-data :preview])]
(when-not (contains? previews message-id)
(let [{serialized-preview :preview} (get-persisted-message message-id)]
(let [{serialized-preview :preview} (get-stored-message message-id)]
;; if preview is already cached in db, do not request it from jail
;; and write it directly to message-data path
(if serialized-preview
{:db (assoc-in db
[:message-data :preview message-id]
(reader/read-string serialized-preview))}
{:dispatch [:request-command-data message :preview]}))))))
(request-command-message-data db message :preview)))))))

View File

@ -0,0 +1,129 @@
(ns status-im.chat.events.console
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.constants :as const]
[status-im.i18n :as i18n]
[status-im.chat.events.sign-up :as sign-up-events]
[status-im.ui.screens.accounts.events :as accounts-events]
[taoensso.timbre :as log]
[status-im.i18n :as i18n]
[goog.string :as gstring]
goog.string.format))
;;;; Helper fns
(defn console-respond-command-events
[command random-id-seq]
(let [{:keys [command handler-data]} command]
(when command
(let [{:keys [name]} command]
(case name
"js" (let [{:keys [err data messages]} handler-data
content (or err data)
message-events (mapv (fn [{:keys [message type]} id]
[:received-message
{:message-id id
:content (str type ": " message)
:content-type const/text-content-type
:outgoing false
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}])
messages random-id-seq)]
(conj message-events
[:received-message
{:message-id (first random-id-seq)
:content (str content)
:content-type const/text-content-type
:outgoing false
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]))
(log/debug "ignoring command: " command))))))
(def faucet-base-url->url
{"http://faucet.ropsten.be:3001" "http://faucet.ropsten.be:3001/donate/0x%s"
"http://46.101.129.137:3001" "http://46.101.129.137:3001/donate/0x%s"})
(defn- faucet-response-event [message-id content]
[:received-message
{:message-id message-id
:content content
:content-type const/text-content-type
:outgoing false
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}])
(def console-commands->fx
{"password"
(fn [{:keys [db]} {:keys [params]}]
(accounts-events/create-account db (:password params)))
"phone"
(fn [{:keys [db]} {:keys [params id]}]
(-> db
(sign-up-events/sign-up (:phone params) id)
(as-> fx
(assoc fx :dispatch-n [(:dispatch fx)]))
(dissoc :dispatch)))
"confirmation-code"
(fn [{:keys [db]} {:keys [params id]}]
(sign-up-events/sign-up-confirm db (:code params) id))
"faucet"
(fn [{:keys [db random-id]} {:keys [params id]}]
(let [{:accounts/keys [accounts current-account-id]} db
current-address (get-in accounts [current-account-id :address])
faucet-url (get faucet-base-url->url (:url params))]
{:http-get {:url (gstring/format faucet-url current-address)
:success-event-creator (fn [_]
(faucet-response-event
random-id
(i18n/label :t/faucet-success)))
:failure-event-creator (fn [_]
(faucet-response-event
random-id
(i18n/label :t/faucet-error)))}}))
"debug"
(fn [{:keys [random-id] :as cofx} {:keys [params id]}]
(let [debug? (= "On" (:mode params))
fx (accounts-events/account-update cofx {:debug? debug?})]
(assoc fx :dispatch-n (if debug?
[[:debug-server-start]
[:received-message
{:message-id random-id
:content (i18n/label :t/debug-enabled)
:content-type const/text-content-type
:outgoing false
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]]
[[:debug-server-stop]]))))})
(def commands-names (set (keys console-commands->fx)))
(def commands-with-delivery-status
(disj commands-names "password" "faucet" "debug"))
;;;; Handlers
;; TODO(janherich) remove this once send-message events are refactored
(handlers/register-handler-fx
:invoke-console-command-handler!
[re-frame/trim-v (re-frame/inject-cofx :random-id) (re-frame/inject-cofx :now)]
(fn [cofx [{:keys [chat-id command] :as command-params}]]
(let [fx-fn (get console-commands->fx (-> command :command :name))]
(-> cofx
(fx-fn command)
(update :dispatch-n (fnil conj []) [:prepare-command! chat-id command-params])))))
;; TODO(janherich) remove this once send-message events are refactored
(handlers/register-handler-fx
:console-respond-command
[(re-frame/inject-cofx :random-id-seq) re-frame/trim-v]
(fn [{:keys [random-id-seq]} [command]]
(when-let [events (console-respond-command-events command random-id-seq)]
{:dispatch-n events})))

View File

@ -1,33 +1,24 @@
(ns status-im.chat.events.input
(:require [clojure.string :as str]
[re-frame.core :refer [reg-fx reg-cofx inject-cofx dispatch trim-v]]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.chat.constants :as const]
[status-im.chat.utils :as chat-utils]
[status-im.chat.models :as model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.suggestions :as suggestions]
[status-im.chat.models.suggestions :as suggestions-model]
[status-im.chat.events.commands :as commands-events]
[status-im.chat.events.animation :as animation-events]
[status-im.bots.events :as bots-events]
[status-im.components.react :as react-comp]
[status-im.native-module.core :as status]
[status-im.utils.datetime :as time]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
[status-im.utils.handlers :as handlers]
[status-im.utils.random :as random]
[status-im.i18n :as i18n]))
;;;; Coeffects
(reg-cofx
:now
(fn [coeffects _]
(assoc coeffects :now (time/now-ms))))
(reg-cofx
:random-id
(fn [coeffects _]
(assoc coeffects :random-id (random/id))))
;;;; Effects
(reg-fx
(re-frame/reg-fx
::focus-rn-component
(fn [ref]
(try
@ -35,7 +26,7 @@
(catch :default e
(log/debug "Cannot focus the reference")))))
(reg-fx
(re-frame/reg-fx
::blur-rn-component
(fn [ref]
(try
@ -43,169 +34,110 @@
(catch :default e
(log/debug "Cannot blur the reference")))))
(reg-fx
(re-frame/reg-fx
::dismiss-keyboard
(fn [_]
(react-comp/dismiss-keyboard!)))
(reg-fx
(re-frame/reg-fx
::set-native-props
(fn [{:keys [ref props]}]
(.setNativeProps ref (clj->js props))))
(reg-fx
:chat-fx/call-jail-function
(fn [{:keys [chat-id function callback-events-creator] :as opts}]
(let [path [:functions function]
params (select-keys opts [:parameters :context])]
(status/call-jail
{:jail-id chat-id
:path path
:params params
:callback (fn [jail-response]
(doseq [event (if callback-events-creator
(callback-events-creator jail-response)
[[:received-bot-response
{:chat-id chat-id}
jail-response]])
:when event]
(dispatch event)))}))))
;;;; Helper functions
;;;; Handlers
(defn update-suggestions
"Update suggestions for current chat input, takes db as the only argument
and returns map with keys :db (new db with up-to-date suggestions) and (optionally)
:call-jail-function with jail function call params, if request to jail needs
to be made as a result of suggestions update."
[{:keys [chats current-chat-id current-account-id local-storage] :as db}]
(let [chat-text (str/trim (or (get-in chats [current-chat-id :input-text]) ""))
requests (->> (suggestions-model/get-request-suggestions db chat-text)
(remove (fn [{:keys [type]}]
(= type :grant-permissions))))
commands (suggestions-model/get-command-suggestions db chat-text)
global-commands (suggestions-model/get-global-command-suggestions db chat-text)
all-commands (->> (into global-commands commands)
(remove (fn [[k {:keys [hidden?]}]] hidden?))
(into {}))
{:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id])
new-db (cond-> db
true (assoc-in [:chats current-chat-id :request-suggestions] requests)
true (assoc-in [:chats current-chat-id :command-suggestions] all-commands)
(and dapp?
(str/blank? chat-text))
(assoc-in [:chats current-chat-id :parameter-boxes :message] nil))]
(cond-> {:db new-db}
(and dapp?
(not (str/blank? chat-text))
(every? empty? [requests commands]))
(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 current-account-id}}))))
(register-handler-db
:update-input-data
(fn [db]
(input-model/modified-db-after-change db)))
(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])]
(assoc-in db
[:chats current-chat-id :input-text]
(input-model/text->emoji (if append?
(str current-input new-input)
new-input)))))
(register-handler-fx
:set-chat-input-text
[trim-v]
(fn [{{:keys [current-chat-id chats chat-ui-props] :as db} :db} [text chat-id]]
(let [chat-id (or chat-id current-chat-id)]
{:db (assoc-in db
[:chats chat-id :input-text]
(input-model/text->emoji text))
:dispatch [:update-suggestions chat-id text]})))
(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))
(register-handler-fx
:add-to-chat-input-text
[trim-v]
(fn [{{:keys [chats current-chat-id]} :db} [text-to-add]]
(let [input-text (get-in chats [current-chat-id :input-text])]
{:dispatch [:set-chat-input-text (str input-text text-to-add)]})))
(defn set-chat-seq-arg-input-text
"Sets input text for current sequential argument in active chat"
[{:keys [current-chat-id] :as db} text]
(assoc-in db [:chats current-chat-id :seq-argument-input-text] text))
(register-handler-fx
:select-chat-input-command
[trim-v]
(fn [{{:keys [current-chat-id chat-ui-props]} :db}
[{:keys [prefill prefill-bot-db sequential-params name] :as command} metadata prevent-auto-focus?]]
(cond-> {:dispatch-n [[:set-chat-input-text (str (chat-utils/command-name command)
const/spacing-char
(when-not sequential-params
(input-model/join-command-args prefill)))]
[:clear-bot-db]
[:set-chat-input-metadata metadata]
[:set-chat-ui-props {:show-suggestions? false
:show-emoji? false
:result-box nil
:validation-messages nil
:prev-command name}]
[:load-chat-parameter-box command 0]]}
(defn clear-seq-arguments
"Clears sequential arguments for current-chat"
[{:keys [current-chat-id chats] :as db}]
(-> db
(assoc-in [:chats current-chat-id :seq-arguments] [])
(assoc-in [:chats current-chat-id :seq-argument-input-text] nil)))
prefill-bot-db
(update :dispatch-n conj [:update-bot-db {:bot current-chat-id
:db prefill-bot-db}])
(not (and sequential-params
prevent-auto-focus?))
(update :dispatch-n conj [:chat-input-focus :input-ref])
sequential-params
(assoc :dispatch-later (cond-> [{:ms 100 :dispatch [:set-chat-seq-arg-input-text
(str/join const/spacing-char prefill)]}]
(not prevent-auto-focus?)
(conj {:ms 100 :dispatch [:chat-input-focus :seq-input-ref]}))))))
(register-handler-db
:set-chat-input-metadata
[trim-v]
(fn [{:keys [current-chat-id] :as db} [data chat-id]]
(let [chat-id (or chat-id current-chat-id)]
(assoc-in db [:chats chat-id :input-metadata] data))))
(register-handler-fx
:set-command-argument
[trim-v]
(fn [{{:keys [current-chat-id] :as db} :db} [[index arg move-to-next?]]]
(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))
seq-params? (-> (input-model/selected-chat-command db current-chat-id)
(get-in [:command :sequential-params]))
event-to-dispatch (if seq-params?
[:set-chat-seq-arg-input-text arg]
(get-in [:command :sequential-params]))]
(if seq-params?
{:db (set-chat-seq-arg-input-text db arg)}
(let [arg (str/replace arg (re-pattern const/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))]
[:set-chat-input-text (str command-name
(conj command-args arg))
input-text (str command-name
const/spacing-char
(input-model/join-command-args command-args)
(when (and move-to-next?
(= index (dec (count command-args))))
const/spacing-char))]))]
{:dispatch event-to-dispatch})))
const/spacing-char))]
(-> db
(set-chat-input-text input-text)
update-suggestions)))))
(register-handler-fx
:chat-input-focus
[trim-v]
(fn [{{:keys [current-chat-id chat-ui-props]} :db} [ref]]
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::focus-rn-component cmp-ref})))
(register-handler-fx
:chat-input-blur
[trim-v]
(fn [{{:keys [current-chat-id chat-ui-props]} :db} [ref]]
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::blur-rn-component cmp-ref})))
(register-handler-fx
:update-suggestions
[trim-v]
(fn [{{:keys [current-chat-id] :as db} :db} [chat-id text]]
(let [chat-id (or chat-id current-chat-id)
chat-text (str/trim (or text (get-in db [:chats chat-id :input-text]) ""))
requests (->> (suggestions/get-request-suggestions db chat-text)
(remove (fn [{:keys [type]}]
(= type :grant-permissions))))
commands (suggestions/get-command-suggestions db chat-text)
global-commands (suggestions/get-global-command-suggestions db chat-text)
all-commands (->> (into global-commands commands)
(remove (fn [[k {:keys [hidden?]}]] hidden?))
(into {}))
{:keys [dapp?]} (get-in db [:contacts/contacts chat-id])
new-db (cond-> db
true (assoc-in [:chats chat-id :request-suggestions] requests)
true (assoc-in [:chats chat-id :command-suggestions] all-commands)
(and dapp?
(str/blank? chat-text))
(assoc-in [:chats chat-id :parameter-boxes :message] nil))
new-event (when (and dapp?
(not (str/blank? chat-text))
(every? empty? [requests commands]))
[::check-dapp-suggestions chat-id chat-text])]
(cond-> {:db new-db}
new-event (assoc :dispatch new-event)))))
(register-handler-fx
:load-chat-parameter-box
[trim-v]
(fn [{{:keys [current-chat-id bot-db] :accounts/keys [current-account-id] :as db} :db}
[{:keys [name type bot owner-id] :as command}]]
(let [parameter-index (input-model/argument-position db current-chat-id)]
(defn load-chat-parameter-box
"Returns fx for loading chat parameter box for active chat"
[{:keys [current-chat-id bot-db] :accounts/keys [current-account-id] :as db}
{:keys [name 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 (or bot current-chat-id))
@ -215,8 +147,8 @@
parameter-index
:suggestions]
args (-> (get-in db [:chats current-chat-id :input-text])
(input-model/split-command-args)
(rest))
input-model/split-command-args
rest)
seq-arg (get-in db [:chats current-chat-id :seq-argument-input-text])
to (get-in db [:contacts/contacts current-chat-id :address])
params {:parameters {:args args
@ -226,7 +158,7 @@
:from current-account-id
:to to}
(input-model/command-dependent-context-params current-chat-id command))}]
{:chat-fx/call-jail {:jail-id (or bot owner-id current-chat-id)
{:call-jail {:jail-id (or bot owner-id current-chat-id)
:path path
:params params
:callback-events-creator (fn [jail-response]
@ -234,104 +166,100 @@
{:chat-id current-chat-id
:command command
:parameter-index parameter-index}
jail-response]])}})))))
jail-response]])}}))))
(register-handler-fx
::send-message
[trim-v]
(fn [{{:keys [current-public-key] :accounts/keys [current-account-id] :as db} :db} [command chat-id]]
(let [text (get-in db [:chats chat-id :input-text])
data {:message text
:command command
:chat-id chat-id
:identity current-public-key
:address current-account-id}
events [[:set-chat-input-text nil chat-id]
[:set-chat-input-metadata nil chat-id]
[:set-chat-ui-props {:sending-in-progress? false}]]]
{:dispatch-n (if command
(conj events [:check-commands-handlers! data])
(if (str/blank? text)
events
(conj events [:prepare-message data])))})))
(defn chat-input-focus
"Returns fx for focusing on active chat input reference"
[{:keys [current-chat-id chat-ui-props]} ref]
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::focus-rn-component cmp-ref}))
(register-handler-fx
:proceed-command
[trim-v]
(fn [_ [{{:keys [bot]} :command :as content} chat-id]]
(let [params {:content content
:chat-id chat-id
:jail-id (or bot chat-id)}
on-send-params (merge params
{:data-type :on-send
:event-after-creator (fn [_ jail-response]
[::send-command jail-response content chat-id])})
after-validation-events [[::request-command-data on-send-params]]
validation-params (merge params
{:data-type :validator
:event-after-creator (fn [_ jail-response]
[::proceed-validation
jail-response
after-validation-events])})]
{:dispatch [::request-command-data validation-params]})))
(defn update-text-selection
"Updates text selection in active chat input"
[{:keys [current-chat-id] :as db} selection]
(let [input-text (get-in db [:chats current-chat-id :input-text])
command (input-model/selected-chat-command db current-chat-id input-text)
new-db (model/set-chat-ui-props db {:selection selection})
chat-parameter-box-fx (load-chat-parameter-box new-db (:command command))]
(cond-> {:db new-db}
(register-handler-fx
::proceed-validation
[trim-v]
(fn [_ [{:keys [markup validationHandler parameters]} proceed-events]]
(let [error-events-creator (fn [validator-result]
[[:set-chat-ui-props {:validation-messages validator-result
:sending-in-progress? false}]])
events (cond
markup
(error-events-creator markup)
chat-parameter-box-fx
(merge chat-parameter-box-fx)
validationHandler
[[::execute-validation-handler
validationHandler parameters error-events-creator proceed-events]
[:set-chat-ui-props {:sending-in-progress? false}]]
(and (= selection (+ (count const/command-char)
(count (get-in command [:command :name]))
(count const/spacing-char)))
(get-in command [:command :sequential-params]))
(merge (chat-input-focus new-db :seq-input-ref)))))
:default
proceed-events)]
{:dispatch-n events})))
(defn select-chat-input-command
"Selects command + (optional) arguments as input for active chat"
[{:keys [current-chat-id chat-ui-props] :as db}
{:keys [prefill prefill-bot-db sequential-params name] :as command} metadata prevent-auto-focus?]
(let [fx (-> db
bots-events/clear-bot-db
clear-seq-arguments
(model/set-chat-ui-props {:show-suggestions? false
:show-emoji? false
:result-box nil
:validation-messages nil
:prev-command name})
(set-chat-input-metadata metadata)
(set-chat-input-text (str (chat-utils/command-name command)
const/spacing-char
(when-not sequential-params
(input-model/join-command-args prefill))))
update-suggestions
(as-> fx'
(merge fx' (load-chat-parameter-box (:db fx') command))))]
(cond-> fx
prefill-bot-db (update :db bots-events/update-bot-db {:db prefill-bot-db})
(register-handler-fx
::execute-validation-handler
[trim-v]
(fn [_ [validation-handler-name params error-events-creator proceed-events]]
(let [error-events (when-let [validator (input-model/validation-handler validation-handler-name)]
(validator params error-events-creator))]
{:dispatch-n (or error-events proceed-events)})))
(not (and sequential-params
prevent-auto-focus?))
(merge (chat-input-focus (:db fx) :input-ref))
(register-handler-fx
::send-command
[trim-v]
(fn [_ [on-send {{:keys [fullscreen bot]} :command :as content} chat-id]]
(if on-send
{:dispatch-n (cond-> [[:set-chat-ui-props {:result-box on-send
:sending-in-progress? false}]]
fullscreen
(conj [:choose-predefined-expandable-height :result-box :max]))
::dismiss-keyboard nil}
{:dispatch [::request-command-data
{:content content
:chat-id chat-id
:jail-id (or bot chat-id)
:data-type :preview
:event-after-creator (fn [command-message _]
[::send-message command-message chat-id])}]})))
sequential-params
(as-> fx'
(cond-> (update fx' :db
set-chat-seq-arg-input-text
(str/join const/spacing-char prefill))
(not prevent-auto-focus?)
(merge fx' (chat-input-focus (:db fx') :seq-input-ref)))))))
(register-handler-fx
::request-command-data
[trim-v (inject-cofx :random-id) (inject-cofx :now)]
(fn [{{:keys [bot-db] :contacts/keys [contacts]} :db
message-id :random-id
current-time :now}
[{{:keys [command
(defn set-contact-as-command-argument
"Sets contact as command argument for active chat"
[db {:keys [bot-db-key contact arg-index]}]
(let [name (str/replace (:name contact) (re-pattern const/arg-wrapping-char) "")
contact (select-keys contact [:address :whisper-identity :name :photo-path :dapp?])]
(-> (set-command-argument db arg-index name true)
(as-> fx
(merge fx (bots-events/set-in-bot-db
(:db fx)
{: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)))))))
(defn- request-command-data
"Requests command data from jail"
[{:keys [bot-db] :contacts/keys [contacts] :as db}
{{:keys [command
metadata
args]
:as content} :content
:keys [chat-id jail-id data-type event-after-creator]}]]
:keys [chat-id jail-id data-type event-after-creator message-id current-time]}]
(let [{:keys [dapp? dapp-url name]} (get contacts chat-id)
metadata (merge metadata
(when dapp?
@ -359,53 +287,228 @@
:type (:type command)}
:on-requested (fn [jail-response]
(event-after-creator command-message jail-response))}]
{:dispatch [:request-command-data request-data data-type]})))
(commands-events/request-command-message-data db request-data data-type)))
(register-handler-fx
:send-current-message
(fn [{{:keys [current-chat-id] :as db} :db} _]
(let [chat-command (input-model/selected-chat-command db current-chat-id)
seq-command? (get-in chat-command [:command :sequential-params])
chat-command (if seq-command?
(let [args (get-in db [:chats current-chat-id :seq-arguments])]
(assoc chat-command :args args))
(update chat-command :args #(remove str/blank? %)))
set-chat-ui-props-event [:set-chat-ui-props {:sending-in-progress? true}]
additional-events (if (:command chat-command)
(if (= :complete (input-model/command-completion chat-command))
[[:proceed-command chat-command current-chat-id]
[:clear-seq-arguments current-chat-id]]
(let [text (get-in db [:chats current-chat-id :input-text])]
[[:set-chat-ui-props {:sending-in-progress? false}]
(when-not (input-model/text-ends-with-space? text)
[:set-chat-input-text (str text const/spacing-char)])]))
[[::send-message nil current-chat-id]])]
{:dispatch-n (into [set-chat-ui-props-event]
(remove nil? additional-events))})))
(defn proceed-command
"Proceed with command processing by setting up execution chain of events:
(register-handler-fx
::check-dapp-suggestions
[trim-v]
(fn [{{:accounts/keys [current-account-id] :as db} :db} [chat-id text]]
(let [data (get-in db [:local-storage chat-id])]
{:chat-fx/call-jail-function {:chat-id chat-id
:function :on-message-input-change
:parameters {:message text}
:context {:data data
:from current-account-id}}})))
1. Command validation
2. Checking if command defined `onSend`
(register-handler-db
:clear-seq-arguments
[trim-v]
(fn [{:keys [current-chat-id chats] :as db} [chat-id]]
(let [chat-id (or chat-id current-chat-id)]
Second check is important because commands defining `onSend` are actually not
'sendable' and can't be treated as normal command messages, instead of actually
sending them, returned markup is displayed in the chat result box and command processing
ends there.
If it's normal command instead (determined by nil response to `:on-send` jail request),
processing continues by requesting command preview before actually sending the command
message."
[{:keys [current-chat-id] :as db} {{:keys [bot]} :command :as content} message-id current-time]
(let [params-template {:content content
:chat-id current-chat-id
:jail-id (or bot current-chat-id)
:message-id message-id
:current-time current-time}
on-send-params (merge params-template
{:data-type :on-send
:event-after-creator (fn [_ jail-response]
[::check-command-type
jail-response
params-template])})
after-validation-events [[::request-command-data on-send-params]]
validation-params (merge params-template
{:data-type :validator
:event-after-creator (fn [_ jail-response]
[::proceed-validation
jail-response
after-validation-events])})]
(request-command-data db validation-params)))
;;;; Handlers
(handlers/register-handler-db
:update-input-data
(fn [db]
(input-model/modified-db-after-change db)))
(handlers/register-handler-fx
:set-chat-input-text
[re-frame/trim-v]
(fn [{:keys [db]} [text]]
(-> db
(assoc-in [:chats chat-id :seq-arguments] [])
(assoc-in [:chats chat-id :seq-argument-input-text] nil)))))
(set-chat-input-text text)
update-suggestions)))
(register-handler-db
(handlers/register-handler-fx
:add-to-chat-input-text
[re-frame/trim-v]
(fn [{:keys [db]} [text-to-add]]
(-> db
(set-chat-input-text text-to-add :append? true)
update-suggestions)))
(handlers/register-handler-fx
:select-chat-input-command
[re-frame/trim-v]
(fn [{:keys [db]} [command metadata prevent-auto-focus?]]
(select-chat-input-command db command metadata prevent-auto-focus?)))
(handlers/register-handler-db
:set-chat-input-metadata
[re-frame/trim-v]
(fn [db [data]]
(set-chat-input-metadata db data)))
(handlers/register-handler-fx
:set-command-argument
[re-frame/trim-v]
(fn [{:keys [db]} [[index arg move-to-next?]]]
(set-command-argument db index arg move-to-next?)))
(handlers/register-handler-fx
:chat-input-focus
[re-frame/trim-v]
(fn [{:keys [db]} [ref]]
(chat-input-focus db ref)))
(handlers/register-handler-fx
:chat-input-blur
[re-frame/trim-v]
(fn [{{:keys [current-chat-id chat-ui-props]} :db} [ref]]
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::blur-rn-component cmp-ref})))
(handlers/register-handler-fx
:update-suggestions
(fn [{:keys [db]} _]
(update-suggestions db)))
(handlers/register-handler-fx
:load-chat-parameter-box
[re-frame/trim-v]
(fn [{:keys [db]} [command]]
(load-chat-parameter-box db command)))
(handlers/register-handler-fx
:proceed-command
[re-frame/trim-v (re-frame/inject-cofx :random-id) (re-frame/inject-cofx :now)]
(fn [{:keys [db random-id now]} [content]]
(proceed-command db content random-id now)))
(handlers/register-handler-fx
::proceed-validation
[re-frame/trim-v]
(fn [_ [{:keys [markup validationHandler parameters]} proceed-events]]
(let [error-events-creator (fn [validator-result]
[[:set-chat-ui-props {:validation-messages validator-result
:sending-in-progress? false}]])
events (cond
markup
(error-events-creator markup)
validationHandler
[[::execute-validation-handler
validationHandler parameters error-events-creator proceed-events]
[:set-chat-ui-props {:sending-in-progress? false}]]
:default
proceed-events)]
{:dispatch-n events})))
(handlers/register-handler-fx
::execute-validation-handler
[re-frame/trim-v]
(fn [_ [validation-handler-name params error-events-creator proceed-events]]
(let [error-events (when-let [validator (input-model/validation-handler validation-handler-name)]
(validator params error-events-creator))]
{:dispatch-n (or error-events proceed-events)})))
(handlers/register-handler-fx
::request-command-data
[re-frame/trim-v]
(fn [{:keys [db]} [command-params]]
(request-command-data db command-params)))
(handlers/register-handler-fx
::send-command
[re-frame/trim-v]
(fn [{{:keys [current-public-key current-chat-id]
:accounts/keys [current-account-id] :as db} :db} [{:keys [command] :as command-message}]]
(-> db
clear-seq-arguments
(set-chat-input-metadata nil)
(set-chat-input-text nil)
(model/set-chat-ui-props {:sending-in-progress? false})
update-suggestions
;; TODO: refactor send-message.cljs to use atomic pure handlers and get rid of this dispatch
(assoc :dispatch [:check-commands-handlers! {:message (get-in db [:chats current-chat-id :input-text])
:command command-message
:chat-id current-chat-id
:identity current-public-key
:address current-account-id}]))))
(handlers/register-handler-fx
::check-command-type
[re-frame/trim-v]
(fn [{{:keys [current-chat-id] :as db} :db} [on-send-jail-response params-template]]
(if on-send-jail-response
;; `onSend` is defined, we have non-sendable command here, like `@browse`
{:db (-> db
(model/set-chat-ui-props {:result-box on-send-jail-response
:sending-in-progress? false})
(animation-events/choose-predefined-expandable-height :result-box :max))
::dismiss-keyboard nil}
;; regular command message, we need to fetch preview before sending the command message
(request-command-data db (merge params-template
{:data-type :preview
:event-after-creator (fn [command-message _]
[::send-command command-message])})))))
(handlers/register-handler-fx
:send-current-message
[(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :now)]
(fn [{{:keys [current-chat-id current-public-key] :as db} :db message-id :random-id current-time :now} _]
(let [input-text (get-in db [:chats current-chat-id :input-text])
chat-command (-> db
(input-model/selected-chat-command current-chat-id input-text)
(as-> selected-command
(if (get-in selected-command [:command :sequential-params])
(assoc selected-command :args
(get-in db [:chats current-chat-id :seq-arguments]))
(update selected-command :args (partial remove str/blank?)))))]
(if (:command chat-command)
;; current input contains command
(if (= :complete (input-model/command-completion chat-command))
;; command is complete, clear sequential arguments and proceed with command processing
(-> db
clear-seq-arguments
(model/set-chat-ui-props {:sending-in-progress? true})
(proceed-command chat-command message-id current-time))
;; command is not complete, just add space after command if necessary
{:db (cond-> db
(not (input-model/text-ends-with-space? input-text))
(set-chat-input-text const/spacing-char :append? true))})
;; no command detected, when not empty, proceed by sending text message without command processing
(if (str/blank? input-text)
{:db db}
(-> db
(set-chat-input-metadata nil)
(set-chat-input-text nil)
update-suggestions
;; TODO: refactor send-message.cljs to use atomic pure handlers and get rid of this dispatch
(assoc :dispatch [:prepare-message {:message input-text
:chat-id current-chat-id
:identity current-public-key
:address (:accounts/current-account-id db)}])))))))
;; TODO: remove this handler and leave only helper fn once all invocations are refactored
(handlers/register-handler-db
:clear-seq-arguments
(fn [db]
(clear-seq-arguments db)))
(handlers/register-handler-db
::update-seq-arguments
[trim-v]
[re-frame/trim-v]
(fn [{:keys [current-chat-id chats] :as db} [chat-id]]
(let [chat-id (or chat-id current-chat-id)
text (get-in chats [chat-id :seq-argument-input-text])]
@ -413,58 +516,46 @@
(update-in [:chats chat-id :seq-arguments] #(into [] (conj % text)))
(assoc-in [:chats chat-id :seq-argument-input-text] nil)))))
(register-handler-fx
(handlers/register-handler-fx
:send-seq-argument
[trim-v]
(fn [{{:keys [current-chat-id chats] :as db} :db} [chat-id]]
(let [chat-id (or chat-id current-chat-id)
text (get-in chats [chat-id :seq-argument-input-text])
seq-arguments (get-in chats [chat-id :seq-arguments])
command (-> (input-model/selected-chat-command db chat-id)
(fn [{{:keys [current-chat-id chats] :as db} :db} _]
(let [text (get-in chats [current-chat-id :seq-argument-input-text])
seq-arguments (get-in chats [current-chat-id :seq-arguments])
command (-> (input-model/selected-chat-command db current-chat-id)
(assoc :args (into [] (conj seq-arguments text))))]
{:dispatch [::request-command-data
{:content command
:chat-id chat-id
:jail-id (or (get-in command [:command :bot]) chat-id)
(request-command-data db {:content command
:chat-id current-chat-id
:jail-id (or (get-in command [:command :bot]) current-chat-id)
:data-type :validator
:event-after-creator (fn [_ jail-response]
[::proceed-validation
jail-response
[[::update-seq-arguments chat-id]
[:send-current-message]]])}]})))
[[::update-seq-arguments current-chat-id]
[:send-current-message]]])}))))
(register-handler-db
(handlers/register-handler-db
:set-chat-seq-arg-input-text
[trim-v]
(fn [{:keys [current-chat-id] :as db} [text chat-id]]
(let [chat-id (or chat-id current-chat-id)]
(assoc-in db [:chats chat-id :seq-argument-input-text] text))))
[re-frame/trim-v]
(fn [db [text]]
(set-chat-seq-arg-input-text db text)))
(register-handler-fx
(handlers/register-handler-fx
:update-text-selection
[trim-v]
(fn [{{:keys [current-chat-id] :as db} :db} [selection]]
(let [input-text (get-in db [:chats current-chat-id :input-text])
command (input-model/selected-chat-command db current-chat-id input-text)]
(cond-> {:dispatch-n [[:set-chat-ui-props {:selection selection}]
[:load-chat-parameter-box (:command command)]]}
[re-frame/trim-v]
(fn [{:keys [db]} [selection]]
(update-text-selection db selection)))
(and (= selection (+ (count const/command-char)
(count (get-in command [:command :name]))
(count const/spacing-char)))
(get-in command [:command :sequential-params]))
(update :dispatch-n conj [:chat-input-focus :seq-input-ref])))))
(register-handler-fx
(handlers/register-handler-fx
:select-prev-argument
(fn [{{:keys [chat-ui-props current-chat-id] :as db} :db} _]
(let [input-text (get-in db [:chats current-chat-id :input-text])
command (input-model/selected-chat-command db current-chat-id input-text)]
(if (get-in command [:command :sequential-params])
{:dispatch-n [[:set-command-argument [0 "" false]]
[:set-chat-seq-arg-input-text ""]
[:load-chat-parameter-box (:command command)]]}
(let [arg-pos (input-model/argument-position db current-chat-id)]
(-> (set-command-argument db 0 "" false)
(update :db set-chat-seq-arg-input-text "")
(as-> fx
(merge fx (load-chat-parameter-box (:db fx) (:command command)))))
(let [arg-pos (input-model/argument-position db)]
(when (pos? arg-pos)
(let [input-text (get-in db [:chats current-chat-id :input-text])
new-sel (->> (input-model/split-command-args input-text)
@ -472,23 +563,22 @@
(input-model/join-command-args)
(count))
ref (get-in chat-ui-props [current-chat-id :input-ref])]
{::set-native-props {:ref ref
:props {:selection {:start new-sel :end new-sel}}}
:dispatch [:update-text-selection new-sel]})))))))
(-> db
(update-text-selection new-sel)
(assoc ::set-native-props
{:ref ref
:props {:selection {:start new-sel :end new-sel}}})))))))))
(register-handler-fx
:select-next-argument
(fn [{{:keys [chat-ui-props current-chat-id] :as db} :db} _]
(let [arg-pos (input-model/argument-position db current-chat-id)]
(let [input-text (get-in 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-sel (->> command-args
(take (+ 3 arg-pos))
(input-model/join-command-args)
count
(min (count input-text)))
ref (get-in chat-ui-props [current-chat-id :input-ref])]
{::set-native-props {:ref ref
:props {:selection {:start new-sel :end new-sel}}}
:dispatch [:update-text-selection new-sel]}))))
(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
:show-suggestions
(fn [{:keys [db]} _]
(-> db
(model/toggle-chat-ui-prop :show-suggestions?)
(model/set-chat-ui-props {:validation-messages nil})
update-suggestions)))

View File

@ -0,0 +1,123 @@
(ns status-im.chat.events.receive-message
(:require [re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.utils.handlers :as handlers]
[status-im.utils.random :as random]
[status-im.utils.clocks :as clocks]
[status-im.constants :as const]
[status-im.chat.utils :as chat-utils]
[status-im.chat.models.unviewed-messages :as unviewed-messages-model]
[status-im.data-store.chats :as chat-store]
[status-im.data-store.messages :as msg-store]))
(re-frame/reg-cofx
:pop-up-chat?
(fn [cofx]
(assoc cofx :pop-up-chat? (fn [chat-id]
(or (not (chat-store/exists? chat-id))
(chat-store/is-active? chat-id))))))
(re-frame/reg-cofx
:get-last-clock-value
(fn [cofx]
(assoc cofx :get-last-clock-value msg-store/get-last-clock-value)))
(re-frame/reg-cofx
:current-timestamp
(fn [cofx]
;; TODO (janherich) why is actual timestmap generation in random namespace ?
(assoc cofx :current-timestamp (random/timestamp))))
(defn- get-current-identity
[{:accounts/keys [accounts current-account-id]}]
(get-in accounts [current-account-id :public-key]))
(defn- wallet-message
[{:keys [content-type] :as message}]
(log/debug "Wallet msg")
(let [wallet-ct (if (= content-type const/content-type-command)
const/content-type-wallet-command
const/content-type-wallet-request)]
(-> message
(assoc :clock-value 0
:chat-id const/wallet-chat-id
:content-type wallet-ct)
(dissoc :group-id))))
(defn add-message
[{:keys [db get-stored-message get-last-stored-message pop-up-chat?
get-last-clock-value current-timestamp random-id]}
{:keys [from group-id chat-id content-type
message-id timestamp clock-value]
:as message
:or {clock-value 0}}]
(let [chat-identifier (or group-id chat-id from)
current-identity (get-current-identity db)]
;; proceed with adding message if message is not already stored in realm,
;; it's not from current user (outgoing message) and it's for relevant chat
;; (either current active chat or new chat not existing yet)
(if (and (not (get-stored-message chat-identifier))
(not= from current-identity)
(pop-up-chat? chat-identifier))
(let [group-chat? (not (nil? group-id))
enriched-message (assoc (chat-utils/check-author-direction
(get-last-stored-message chat-identifier)
message)
:chat-id chat-identifier
:timestamp (or timestamp current-timestamp)
:clock-value (clocks/receive
clock-value
(get-last-clock-value chat-identifier)))]
(cond-> {:db (-> db
(chat-utils/add-message-to-db chat-identifier chat-identifier enriched-message
(:new? enriched-message))
(unviewed-messages-model/add-unviewed-message chat-identifier message-id)
(assoc-in [:chats chat-identifier :last-message] message))
:dispatch-n [[:upsert-chat! {:chat-id chat-identifier
:group-chat group-chat?}]
[:request-command-message-data enriched-message :short-preview]]
:save-message (dissoc enriched-message :new?)}
(get-in enriched-message [:content :command])
(update :dispatch-n conj [:request-command-preview enriched-message])
(= (:content-type enriched-message) const/content-type-command-request)
(update :dispatch-n conj [:add-request chat-identifier enriched-message])
;; TODO(janherich) refactor this ugly special treatment of wallet send commands for logged in user
(and (= (get-in message [:content :params :bot-db :public :recipient :whisper-identity])
current-identity)
(= content-type const/content-type-command)
(not= chat-identifier const/wallet-chat-id)
(= "send" (get-in message [:content :command])))
(update :dispatch-n conj [:received-message (wallet-message (assoc message :message-id random-id))])))
{:db db})))
(def ^:private receive-interceptors
[(re-frame/inject-cofx :get-stored-message) (re-frame/inject-cofx :get-last-stored-message)
(re-frame/inject-cofx :pop-up-chat?) (re-frame/inject-cofx :get-last-clock-value)
(re-frame/inject-cofx :current-timestamp) (re-frame/inject-cofx :random-id)
re-frame/trim-v])
(handlers/register-handler-fx
:received-protocol-message!
receive-interceptors
(fn [cofx [{:keys [from to payload]}]]
(add-message cofx (merge payload
{:from from
:to to
:chat-id from}))))
(handlers/register-handler-fx
:received-message
receive-interceptors
(fn [cofx [message]]
(add-message cofx message)))
(handlers/register-handler-fx
:received-message-when-commands-loaded
receive-interceptors
(fn [{:keys [db] :as cofx} [chat-id message]]
(if (and (:status-node-started? db)
(get-in db [:contacts/contacts chat-id :commands-loaded?]))
(add-message cofx message)
{:dispatch-later [{:ms 400 :dispatch [:received-message-when-commands-loaded chat-id message]}]})))

View File

@ -0,0 +1,123 @@
(ns status-im.chat.events.sign-up
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.utils.phone-number :as phone-number-util]
[status-im.constants :as const]
[status-im.chat.sign-up :as sign-up]
[status-im.ui.screens.accounts.events :as accounts-events]
[status-im.ui.screens.contacts.events :as contacts-events]))
;;;; Helpers fns
(defn sign-up
"Creates effects for signing up"
[db phone-number message-id]
(let [current-account-id (:accounts/current-account-id db)
{:keys [public-key address]} (get-in db [:accounts/accounts current-account-id])]
{:dispatch sign-up/start-listening-confirmation-code-sms-event
:http-post {:action "sign-up"
:data {:phone-number (phone-number-util/format-phone-number phone-number)
:whisper-identity public-key
:address address}
:success-event-creator (fn [_]
[::sign-up-success message-id])
:failure-event-creator (fn [_]
[::http-request-failure [::sign-up phone-number message-id]])}}))
(defn sign-up-confirm
"Creates effects for sign-up confirmation"
[db confirmation-code message-id]
{:http-post {:action "sign-up-confirm"
:data {:code confirmation-code}
:success-event-creator (fn [body]
[::sign-up-confirm-response body message-id])
:failure-event-creator (fn [_]
[::http-request-failure [::sign-up-confirm confirmation-code message-id]])}})
;;;; Handlers
(handlers/register-handler-fx
::sign-up
[re-frame/trim-v]
(fn [{:keys [db]} [phone-number message-id]]
(sign-up db phone-number message-id)))
(defn- message-seen [{:keys [db] :as fx} message-id]
(merge fx
{:db (assoc-in db [:message-data :statuses message-id] {:status :seen})
:update-message {:message-id message-id
:message-status :seen}}))
(handlers/register-handler-fx
::sign-up-success
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [{:keys [db random-id]} message-id]
(-> {:db db
:dispatch-n [;; create manual way for entering confirmation code
(sign-up/enter-confirmation-code-event random-id)
;; create automatic way for receiving confirmation code
sign-up/start-listening-confirmation-code-sms-event]}
(message-seen message-id))))
(handlers/register-handler-fx
:start-listening-confirmation-code-sms
[re-frame/trim-v]
(fn [{:keys [db]} [sms-listener]]
{:db (if-not (:confirmation-code-sms-listener db)
(assoc db :confirmation-code-sms-listener sms-listener)
db)}))
(handlers/register-handler-fx
::sign-up-confirm
(fn [{:keys [db]} [confirmation-code message-id]]
(sign-up-confirm db confirmation-code message-id)))
(defn- sign-up-confirmed [{:keys [db] :as fx}]
(cond-> (update fx :dispatch-n conj [:request-permissions
[:read-contacts]
#(re-frame/dispatch [:sync-contacts (fn [contacts]
[::contacts-synced contacts])])])
(:confirmation-code-sms-listener db)
(merge {:db (dissoc db :confirmation-code-sms-listener)
:remove-sms-listener (:confirmation-code-sms-listener db)})))
(handlers/register-handler-fx
::sign-up-confirm-response
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [{:keys [db random-id]} [{:keys [message status]} message-id]]
(cond-> {:db db
:dispatch-n [[:received-message
{:message-id random-id
:content message
:content-type const/text-content-type
:outgoing false
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]]}
message-id
(message-seen message-id)
(= "confirmed" status)
sign-up-confirmed
(= "failed" status)
(update :dispatch-n conj (sign-up/incorrect-confirmation-code-event random-id)))))
(handlers/register-handler-fx
::contacts-synced
[re-frame/trim-v (re-frame/inject-cofx :random-id) (re-frame/inject-cofx :now)]
(fn [{:keys [db random-id] :as cofx} [contacts]]
(-> db
(contacts-events/add-contacts contacts)
(as-> fx
(merge fx
(accounts-events/account-update (assoc cofx :db (:db fx)) {:signed-up? true})
{:dispatch (sign-up/contacts-synchronised-event random-id)})))))
(handlers/register-handler-fx
::http-request-failure
[re-frame/trim-v]
(fn [_ [original-event-vector]]
;; TODO(janherich): in case of http request failure, we will try to hit http endpoint in loop forever,
;; maybe it's better to cut it after N tries and display error message with explanation to user
{:dispatch-later [{:ms 1000 :dispatch original-event-vector}]}))

View File

@ -10,179 +10,29 @@
[status-im.data-store.chats :as chats]
[status-im.data-store.contacts :as contacts]
[status-im.data-store.messages :as messages]
[status-im.data-store.handler-data :as handler-data]
[status-im.data-store.pending-messages :as pending-messages]
[status-im.constants :refer [text-content-type
content-type-command
content-type-command-request
default-number-of-messages
console-chat-id
wallet-chat-id]]
[status-im.utils.random :as random]
[status-im.chat.sign-up :as sign-up-service]
[status-im.ui.screens.navigation :as nav]
[status-im.utils.handlers :refer [register-handler register-handler-fx] :as u]
[status-im.handlers.server :as server]
[status-im.utils.phone-number :refer [format-phone-number
valid-mobile-number?]]
[status-im.native-module.core :as status]
[status-im.utils.types :refer [json->clj]]
[status-im.chat.utils :refer [console? not-console? safe-trim]]
[status-im.utils.gfycat.core :refer [generate-gfy]]
status-im.chat.events.input
status-im.chat.events.commands
status-im.chat.handlers.animation
status-im.chat.events
status-im.chat.handlers.requests
status-im.chat.handlers.unviewed-messages
status-im.chat.handlers.send-message
status-im.chat.handlers.receive-message
status-im.chat.handlers.faucet
[cljs.core.async :as a]
status-im.chat.handlers.webview-bridge
status-im.chat.handlers.console
[taoensso.timbre :as log]))
(register-handler :set-layout-height
(fn [db [_ height]]
(assoc db :layout-height height)))
(register-handler :set-chat-ui-props
(fn [{:keys [current-chat-id] :as db} [_ kvs]]
(update-in db [:chat-ui-props current-chat-id] merge kvs)))
(register-handler :toggle-chat-ui-props
(fn [{:keys [current-chat-id chat-ui-props] :as db} [_ ui-element chat-id]]
(let [chat-id (or chat-id current-chat-id)]
(update-in db [:chat-ui-props chat-id ui-element] not))))
(register-handler :show-message-details
(u/side-effect!
(fn [_ [_ details]]
(dispatch [:set-chat-ui-props {:show-bottom-info? true
:show-emoji? false
:bottom-info details}]))))
(register-handler :load-more-messages
(fn [{:keys [current-chat-id loading-allowed] :as db} _]
(let [all-loaded? (get-in db [:chats current-chat-id :all-loaded?])]
(if loading-allowed
(do (am/go
(<! (a/timeout 400))
(dispatch [:set :loading-allowed true]))
(if all-loaded?
db
(let [messages-path [:chats current-chat-id :messages]
messages (get-in db messages-path)
chat-messages (filter #(= current-chat-id (:chat-id %)) messages)
new-messages (messages/get-by-chat-id current-chat-id (count chat-messages))
all-loaded? (> default-number-of-messages (count new-messages))]
(-> db
(assoc :loading-allowed false)
(update-in messages-path concat new-messages)
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?)))))
db))))
(defn set-message-shown
[db chat-id message-id]
(update-in db
[:chats chat-id :messages]
(fn [messages]
(map (fn [message]
(if (= message-id (:message-id message))
(assoc message :new? false)
message))
messages))))
(register-handler :set-message-shown
(fn [db [_ {:keys [chat-id message-id]}]]
(set-message-shown db chat-id message-id)))
(register-handler :cancel-command
(fn [{:keys [current-chat-id] :as db} _]
(-> db
(assoc-in [:chats current-chat-id :command-input] {})
(update-in [:chats current-chat-id :input-text] safe-trim))))
(defn init-console-chat
([{:keys [chats] :accounts/keys [current-account-id] :as db} existing-account?]
(let [new-chat sign-up-service/console-chat]
(if (chats console-chat-id)
db
(do
(dispatch [:add-contacts [sign-up-service/console-contact]])
(chats/save new-chat)
(contacts/save-all [sign-up-service/console-contact])
(when-not current-account-id
(sign-up-service/intro))
(when existing-account?
(sign-up-service/start-signup))
(-> db
(assoc :new-chat new-chat)
(update :chats assoc console-chat-id new-chat)
(assoc :current-chat-id console-chat-id)))))))
(register-handler :init-console-chat
(fn [db _]
(init-console-chat db false)))
(register-handler :account-generation-message
(u/side-effect!
(fn [_]
(when-not (messages/get-by-id chat-consts/passphrase-message-id)
(sign-up-service/account-generation-message)))))
(register-handler :move-to-internal-failure-message
(u/side-effect!
(fn [_]
(when-not (messages/get-by-id chat-consts/move-to-internal-failure-message-id)
(sign-up-service/move-to-internal-failure-message)))))
(register-handler :show-mnemonic
(u/side-effect!
(fn [_ [_ mnemonic signing-phrase]]
(let [crazy-math-message? (messages/get-by-id chat-consts/crazy-math-message-id)]
(sign-up-service/passphrase-messages mnemonic signing-phrase crazy-math-message?)))))
(defn- handle-sms [{body :body}]
(when-let [matches (re-matches #"(\d{4})" body)]
(dispatch [:sign-up-confirm (second matches)])))
(register-handler :sign-up
(after (fn [_ [_ phone-number]]
(dispatch [:account-update {:phone phone-number}])))
(fn [db [_ phone-number message-id]]
(sign-up-service/start-listening-confirmation-code-sms)
(let [formatted (format-phone-number phone-number)]
(server/sign-up db
formatted
message-id
sign-up-service/on-sign-up-response))))
(register-handler :start-listening-confirmation-code-sms
(fn [db [_ listener]]
(if-not (:confirmation-code-sms-listener db)
(assoc db :confirmation-code-sms-listener listener)
db)))
(register-handler :stop-listening-confirmation-code-sms
(fn [db]
(if (:confirmation-code-sms-listener db)
(sign-up-service/stop-listening-confirmation-code-sms db)
db)))
(register-handler :sign-up-confirm
(u/side-effect!
(fn [_ [_ confirmation-code message-id]]
(server/sign-up-confirm
confirmation-code
message-id
sign-up-service/on-send-code-response))))
(register-handler :set-signed-up
(u/side-effect!
(fn [_ [_ signed-up]]
(dispatch [:account-update {:signed-up? signed-up}]))))
(defn load-messages!
([db] (load-messages! db nil))
([{:keys [current-chat-id] :as db} _]
@ -208,47 +58,6 @@
init-chat
load-commands!))
(defn initialize-chats
[{:keys [loaded-chats chats] :accounts/keys [account-creation?] :as db} _]
(let [chats' (if account-creation?
chats
(->> loaded-chats
(map (fn [{:keys [chat-id] :as chat}]
(let [last-message (messages/get-last-message chat-id)]
[chat-id (assoc chat :last-message last-message)])))
(into {})))]
(-> db
(assoc :chats chats')
(assoc :handler-data (handler-data/get-all))
(dissoc :loaded-chats)
(init-console-chat true))))
(defn load-chats!
[{:accounts/keys [account-creation?] :as db} _]
(if account-creation?
db
(assoc db :loaded-chats (chats/get-all))))
(register-handler :initialize-chats
[(after #(dispatch [:load-unviewed-messages!]))
(after #(dispatch [:load-default-contacts!]))]
(u/handlers->
load-chats!
initialize-chats))
(register-handler :reload-chats
(fn [{:keys [chats] :as db} _]
(let [chats' (->> (chats/get-all)
(map (fn [{:keys [chat-id] :as chat}]
(let [last-message (messages/get-last-message chat-id)
prev-chat (get chats chat-id)
new-chat (assoc chat :last-message last-message)]
[chat-id (merge prev-chat new-chat)])))
(into {}))]
(-> (assoc db :chats chats')
(init-console-chat true)))))
(defmethod nav/preload-data! :chat
[{:keys [current-chat-id] :accounts/keys [current-account-id] :as db} [_ _ id]]
(let [chat-id (or id current-chat-id)
@ -427,27 +236,6 @@
remove-pending-messages!
delete-chat!))
(defn send-seen!
[{:keys [web3 current-public-key chats]
:contacts/keys [contacts]}
[_ {:keys [from chat-id message-id]}]]
(when-not (get-in contacts [chat-id :dapp?])
(let [{:keys [group-chat public?]} (chats chat-id)]
(when-not public?
(protocol/send-seen! {:web3 web3
:message {:from current-public-key
:to from
:group-id (when group-chat chat-id)
:message-id message-id}})))))
(register-handler :send-seen!
[(after (fn [_ [_ {:keys [message-id]}]]
(messages/update {:message-id message-id
:message-status :seen})))
(after (fn [_ [_ {:keys [chat-id]}]]
(dispatch [:remove-unviewed-messages chat-id])))]
(u/side-effect! send-seen!))
(register-handler :check-and-open-dapp!
(u/side-effect!
(fn [{:keys [current-chat-id global-commands]

View File

@ -1,65 +0,0 @@
(ns status-im.chat.handlers.animation
(:require [re-frame.core :refer [after dispatch subscribe debug path]]
[status-im.chat.views.input.utils :as input-utils]
[status-im.utils.handlers :as handlers]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]))
(handlers/register-handler :set-expandable-height
(fn [{:keys [current-chat-id] :as db} [_ key value]]
(-> db
(assoc-in [:chat-animations current-chat-id key :height] value)
(update-in [:chat-animations current-chat-id key :changes-counter] inc))))
(handlers/register-handler :hide-expandable
(handlers/side-effect!
(fn [_ [_ key]]
(dispatch [:set-expandable-height key 1]))))
(handlers/register-handler :choose-predefined-expandable-height
(handlers/side-effect!
(fn [{:keys [current-chat-id chat-ui-props layout-height] :as db} [_ key preset]]
(if (= preset :max)
(dispatch [:set-expandable-height key :max])
(let [input-height (get-in chat-ui-props [current-chat-id :input-height])
chat-input-margin (if platform/ios?
(get db :keyboard-height)
0)
bottom (+ input-height chat-input-margin)
height (case preset
:min input-utils/min-height
(input-utils/default-container-area-height bottom layout-height))]
(dispatch [:set-expandable-height key height]))))))
(handlers/register-handler :fix-expandable-height
(handlers/side-effect!
(fn [{:keys [current-chat-id chats chat-ui-props layout-height] :as db} [_ vy current key]]
(let [input-height (get-in chat-ui-props [current-chat-id :input-height])
chat-input-margin (if platform/ios?
(get db :keyboard-height)
0)
bottom (+ input-height chat-input-margin)
min-height input-utils/min-height
max-height (input-utils/max-container-area-height bottom layout-height)
default-height (input-utils/default-container-area-height bottom layout-height)
possible-values [min-height default-height max-height]
moving-down? (pos? vy)
closest-index (->> possible-values
(map-indexed vector)
(sort-by (fn [[i v]] (Math/abs (- v current))))
(ffirst))
height (cond (and moving-down? (not= closest-index 0))
(get possible-values (dec closest-index))
(and (not moving-down?) (not= closest-index 2))
(get possible-values (inc closest-index))
moving-down?
min-height
(not moving-down?)
max-height)]
(dispatch [:set-expandable-height key height])))))

View File

@ -1,100 +0,0 @@
(ns status-im.chat.handlers.console
(:require [re-frame.core :refer [dispatch dispatch-sync after]]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.constants :refer [console-chat-id
text-content-type]]
[status-im.data-store.messages :as messages]
[status-im.i18n :refer [label]]
[status-im.utils.random :as random]
[taoensso.timbre :as log]))
(def console-commands
{:password
(fn [{:keys [password]} _]
(dispatch [:create-account password]))
:phone
(fn [{:keys [phone]} id]
(dispatch [:sign-up phone id]))
:confirmation-code
(fn [{:keys [code]} id]
(dispatch [:sign-up-confirm code id]))
:faucet
(fn [{:keys [url]} id]
(dispatch [:open-faucet url id]))
:debug
(fn [{:keys [mode]} id]
(let [debug-on? (= mode "On")]
(dispatch [:account-update {:debug? debug-on?}])
(if debug-on?
(do
(dispatch [:debug-server-start])
(dispatch [:received-message
{:message-id (random/id)
:content (label :t/debug-enabled)
:content-type text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}]))
(dispatch [:debug-server-stop]))))})
(def commands-names (set (keys console-commands)))
(def commands-with-delivery-status
(disj commands-names :password :faucet :debug))
(register-handler :invoke-console-command-handler!
(u/side-effect!
(fn [_ [_ {{:keys [command
params
id]
:as content} :command
chat-id :chat-id
:as all-params}]]
(let [{:keys [name]} command]
(dispatch [:prepare-command! chat-id all-params])
((console-commands (keyword name)) params id)))))
(register-handler :set-message-status
(after
(fn [_ [_ message-id status]]
(messages/update {:message-id message-id
:message-status status})))
(fn [db [_ message-id status]]
(assoc-in db [:message-data :statuses message-id] {:status status})))
(register-handler :console-respond-command
(u/side-effect!
(fn [_ [_ {:keys [command]}]]
(let [{:keys [command handler-data]} command]
(when command
(let [{:keys [name]} command]
(case name
"js" (let [{:keys [err data messages]} handler-data
content (or err data)]
(doseq [message messages]
(let [{:keys [message type]} message]
(dispatch [:received-message
{:message-id (random/id)
:content (str type ": " message)
:content-type text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])))
(when content
(dispatch [:received-message
{:message-id (random/id)
:content (str content)
:content-type text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])))
(log/debug "ignoring command: " command))))))))

View File

@ -1,49 +0,0 @@
(ns status-im.chat.handlers.faucet
(:require [re-frame.core :refer [dispatch]]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.utils.utils :refer [http-get]]
[status-im.utils.random :as random]
[status-im.constants :refer [console-chat-id
text-content-type]]
[status-im.i18n :refer [label]]
[goog.string :as gstring]
goog.string.format))
(def faucets
[{:name "http://faucet.ropsten.be:3001"
:type :api
:api-url "http://faucet.ropsten.be:3001/donate/0x%s"}
{:name "http://46.101.129.137:3001"
:type :api
:api-url "http://46.101.129.137:3001/donate/0x%s"}])
(defn faucet-by-name [faucet-name]
(->> faucets
(filter #(= (:name %) faucet-name))
(first)))
(defn received-message [content]
(dispatch [:received-message
{:message-id (random/id)
:content content
:content-type text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}]))
(defmulti open-faucet (fn [_ _ {:keys [type]}] type))
(defmethod open-faucet :api
[_ current-address {:keys [api-url]}]
(let [api-url (gstring/format api-url current-address)]
(http-get api-url
#(received-message (label :t/faucet-success))
#(received-message (label :t/faucet-error)))))
(register-handler :open-faucet
(u/side-effect!
(fn [{:accounts/keys [accounts current-account-id]} [_ faucet-name _]]
(if-let [faucet (faucet-by-name faucet-name)]
(let [current-address (get-in accounts [current-account-id :address])]
(open-faucet faucet-name current-address faucet))))))

View File

@ -1,112 +0,0 @@
(ns status-im.chat.handlers.receive-message
(:require [status-im.utils.handlers :refer [register-handler] :as u]
[re-frame.core :refer [enrich after debug dispatch path]]
[status-im.data-store.messages :as messages]
[status-im.chat.utils :as cu]
[status-im.utils.random :as random]
[status-im.constants :refer [wallet-chat-id
content-type-command
content-type-command-request]
:as c]
[cljs.reader :refer [read-string]]
[status-im.data-store.chats :as chats]
[status-im.utils.scheduler :as s]
[taoensso.timbre :as log]
[status-im.utils.clocks :as clocks]))
(defn store-message [{chat-id :chat-id :as message}]
(messages/save chat-id (dissoc message :new?)))
(defn get-current-identity
[{:accounts/keys [accounts current-account-id]}]
(get-in accounts [current-account-id :public-key]))
(declare add-message-to-wallet)
(defn add-message
[db {:keys [from group-id chat-id
message-id timestamp clock-value]
:as message
:or {clock-value 0}}]
(let [same-message (messages/get-by-id message-id)
current-identity (get-current-identity db)
chat-id' (or group-id chat-id from)
exists? (chats/exists? chat-id')
active? (chats/is-active? chat-id')
local-clock (messages/get-last-clock-value chat-id')
clock-new (clocks/receive clock-value local-clock)
recipient-pub-key (get-in message [:content :params :bot-db :public :recipient :whisper-identity])]
(when (and (not same-message)
(not= from current-identity)
(or (not exists?) active?))
(let [group-chat? (not (nil? group-id))
previous-message (messages/get-last-message chat-id')
message' (assoc (cu/check-author-direction previous-message message)
:chat-id chat-id'
:timestamp (or timestamp (random/timestamp))
:clock-value clock-new)]
(store-message message')
(dispatch [:upsert-chat! {:chat-id chat-id'
:group-chat group-chat?}])
(when (get-in message [:content :command])
(dispatch [:request-command-preview message]))
(dispatch [::add-message chat-id' message'])
(dispatch [::set-last-message message'])
(when (= (:content-type message') content-type-command-request)
(dispatch [:add-request chat-id' message']))
(dispatch [:add-unviewed-message chat-id' message-id]))
(if (and
(= recipient-pub-key current-identity)
(= (:content-type message) content-type-command)
(not= chat-id' wallet-chat-id)
(= "send" (get-in message [:content :command])))
(add-message-to-wallet db message)))))
(defn add-message-to-wallet [db {:keys [content-type] :as message}]
(let [ct (if (= content-type c/content-type-command)
c/content-type-wallet-command
c/content-type-wallet-request)
message' (-> (assoc message :clock-value 0
:message-id (random/id)
:chat-id wallet-chat-id
:content-type ct)
(dissoc :group-id))]
(add-message db message')))
(register-handler :received-protocol-message!
(u/side-effect!
(fn [_ [_ {:keys [from to payload]}]]
(dispatch [:received-message (merge payload
{:from from
:to to
:chat-id from})]))))
(register-handler :received-message
(after #(dispatch [:update-suggestions]))
(u/side-effect!
(fn [db [_ message]]
(add-message db message))))
(register-handler ::add-message
(fn [db [_ add-to-chat-id {:keys [chat-id new?] :as message}]]
(cu/add-message-to-db db add-to-chat-id chat-id message new?)))
(register-handler ::set-last-message
(fn [{:keys [chats] :as db} [_ {:keys [chat-id] :as message}]]
(dispatch [:request-command-data message :short-preview])
(assoc-in db [:chats chat-id :last-message] message)))
(defn commands-loaded? [db chat-id]
(get-in db [:contacts/contacts chat-id :commands-loaded?]))
(def timeout 400)
(register-handler :received-message-when-commands-loaded
(u/side-effect!
(fn [{:keys [status-node-started?] :as db} [_ chat-id message]]
(if (and status-node-started? (commands-loaded? db chat-id))
(dispatch [:received-message message])
(s/execute-later
#(dispatch [:received-message-when-commands-loaded chat-id message])
timeout)))))

View File

@ -19,7 +19,7 @@
[status-im.utils.datetime :as datetime]
[status-im.protocol.core :as protocol]
[taoensso.timbre :refer-macros [debug] :as log]
[status-im.chat.handlers.console :as console]
[status-im.chat.events.console :as console]
[status-im.utils.types :as types]
[status-im.utils.config :as config]
[status-im.utils.clocks :as clocks]))
@ -60,7 +60,7 @@
(defn console-command? [chat-id command-name]
(and (= console-chat-id chat-id)
(console/commands-names (keyword command-name))))
(console/commands-names command-name)))
(register-handler :check-commands-handlers!
(u/side-effect!
@ -124,10 +124,12 @@
(register-handler ::save-command!
(u/side-effect!
(fn [_ [_ chat-id {:keys [command]} hidden-params]]
(let [command (-> command
(fn [db [_ chat-id {:keys [command]} hidden-params]]
(let [preview (get-in db [:message-data :preview (:message-id command)])
command (cond-> (-> command
(update-in [:content :params] #(apply dissoc % hidden-params))
(dissoc :to-message :has-handler))]
(dissoc :to-message :has-handler :raw-input))
preview (assoc :preview (pr-str preview)))]
(messages/save chat-id command)))))
(register-handler ::dispatch-responded-requests!

View File

@ -1,37 +0,0 @@
(ns status-im.chat.handlers.unviewed-messages
(:require [re-frame.core :refer [after enrich path dispatch]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.data-store.messages :as messages]))
(defn set-unviewed-messages [db]
(let [messages (->> (:raw-unviewed-messages db)
(group-by :chat-id)
(map (fn [[id messages]]
[id {:messages-ids (map :message-id messages)
:count (count messages)}]))
(into {}))]
(-> db
(assoc :unviewed-messages messages)
(dissoc :raw-unviewed-messages))))
(defn load-messages! [db]
(let [messages (messages/get-unviewed)]
(assoc db :raw-unviewed-messages messages)))
(register-handler ::set-unviewed-messages set-unviewed-messages)
(register-handler :load-unviewed-messages!
(after #(dispatch [::set-unviewed-messages]))
load-messages!)
(register-handler :add-unviewed-message
(path :unviewed-messages)
(fn [db [_ chat-id message-id]]
(-> db
(update-in [chat-id :messages-ids] conj message-id)
(update-in [chat-id :count] inc))))
(register-handler :remove-unviewed-messages
(path :unviewed-messages)
(fn [db [_ chat-id]]
(dissoc db chat-id)))

View File

@ -68,8 +68,7 @@
(dispatch [:proceed-command
{:command command,
:metadata nil,
:args [(get contact :name) amount]}
current-chat-id])))))
:args [(get contact :name) amount]}])))))
(defn chat-with-command
[_ [_ whisper-identity command-key params]]

View File

@ -0,0 +1,11 @@
(ns status-im.chat.models)
(defn set-chat-ui-props
"Updates ui-props in active chat by merging provided kvs into them"
[{:keys [current-chat-id] :as db} kvs]
(update-in db [:chat-ui-props current-chat-id] merge kvs))
(defn toggle-chat-ui-prop
"Toggles chat ui prop in active chat"
[{:keys [current-chat-id] :as db} ui-element]
(update-in db [:chat-ui-props current-chat-id ui-element] not))

View File

@ -95,7 +95,7 @@
[[] false])
(first)))))
(defn join-command-args [args]
(defn join-command-args
"Transforms a list of args to a string. The opposite of `split-command-args`.
Examples:
@ -107,6 +107,7 @@
Input: ['/send' 'Complex name with space in between' '1.0']
Output: '/send \"Complex name with space in between\" 1.0'"
[args]
(when args
(->> args
(map (fn [arg]
@ -178,12 +179,11 @@
-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} chat-id]
(let [chat-id (or chat-id current-chat-id)
input-text (get-in db [:chats chat-id :input-text])
seq-arguments (get-in db [:chats chat-id :seq-arguments])
selection (get-in db [:chat-ui-props chat-id :selection])
chat-command (selected-chat-command db chat-id)]
[{: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-id)]
(current-chat-argument-position chat-command input-text selection seq-arguments)))
(defn command-completion

View File

@ -0,0 +1,18 @@
(ns status-im.chat.models.unviewed-messages)
(defn load-unviewed-messages [db raw-unviewed-messages]
(assoc db :unviewed-messages
(->> raw-unviewed-messages
(group-by :chat-id)
(map (fn [[id messages]]
[id {:messages-ids (map :message-id messages)
:count (count messages)}]))
(into {}))))
(defn add-unviewed-message [db chat-id message-id]
(-> db
(update-in [:unviewed-messages chat-id :messages-ids] conj message-id)
(update-in [:unviewed-messages chat-id :count] inc)))
(defn remove-unviewed-messages [db chat-id]
(update db :unviewed-messages dissoc chat-id))

View File

@ -1,25 +1,20 @@
(ns status-im.chat.sign-up
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[status-im.components.styles :refer [default-chat-color]]
[status-im.utils.utils :refer [http-post]]
[status-im.utils.random :as random]
[status-im.utils.sms-listener :refer [add-sms-listener
remove-sms-listener]]
[status-im.constants :refer [console-chat-id
text-content-type
content-type-command
content-type-command-request
content-type-status]]
[status-im.chat.constants :as const]
[status-im.constants :as const]
[status-im.chat.constants :as chat-const]
[status-im.i18n :refer [label]]
[clojure.string :as s]))
(defn send-console-message [text]
{:message-id (random/id)
:from "me"
:to console-chat-id
:to const/console-chat-id
:content text
:content-type text-content-type
:content-type const/text-content-type
:outgoing true})
;; todo fn name is not too smart, but...
@ -29,192 +24,165 @@
:content content})
;; -- Send phone number ----------------------------------------
(defn on-sign-up-response [& [message]]
(let [message-id (random/id)]
(dispatch [:received-message
(defn- confirmation-code-event [correct? message-id]
[:received-message
{:message-id message-id
:content (command-content
:confirmation-code
(or message (label :t/confirmation-code)))
:content-type content-type-command-request
(if correct?
(label :t/confirmation-code)
(label :t/incorrect-code)))
:content-type const/content-type-command-request
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])))
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}])
(defn start-listening-confirmation-code-sms []
(dispatch [:request-permissions
(def enter-confirmation-code-event (partial confirmation-code-event true))
(def incorrect-confirmation-code-event (partial confirmation-code-event false))
(defn- sms-receive-handler [{confirmation-code :body}]
(when-let [matches (re-matches #"(\d{4})" confirmation-code)]
(dispatch [:sign-up-confirm (second matches)])))
(def start-listening-confirmation-code-sms-event
[:request-permissions
[:receive-sms]
(fn []
(let [listener (add-sms-listener
(fn [{body :body}]
(when-let [matches (re-matches #"(\d{4})" body)]
(dispatch [:sign-up-confirm (second matches)]))))]
(dispatch [:start-listening-confirmation-code-sms listener])))]))
(defn stop-listening-confirmation-code-sms [db]
(when-let [listener (:confirmation-code-sms-listener db)]
(remove-sms-listener listener)
(dissoc db :confirmation-code-sms-listener)))
(let [listener (add-sms-listener sms-receive-handler)]
(dispatch [:start-listening-confirmation-code-sms listener])))])
;; -- Send confirmation code and synchronize contacts---------------------------
(defn on-sync-contacts []
(dispatch [:received-message
{:message-id (random/id)
:content (label :t/contacts-syncronized)
:content-type text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])
(dispatch [:set-signed-up true]))
(defn sync-contacts []
;; TODO 'on-sync-contacts' is never called
(dispatch [:request-permissions
[:read-contacts]
#(dispatch [:sync-contacts on-sync-contacts])]))
(defn on-send-code-response [body]
(dispatch [:received-message
{:message-id (random/id)
:content (:message body)
:content-type text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])
(let [status (keyword (:status body))]
(when (= :confirmed status)
(dispatch [:stop-listening-confirmation-code-sms])
(sync-contacts)
;; TODO should be called after sync-contacts?
(dispatch [:set-signed-up true]))
(when (= :failed status)
(on-sign-up-response (label :t/incorrect-code)))))
(defn start-signup []
(let [message-id (random/id)]
(dispatch [:received-message
(defn contacts-synchronised-event [message-id]
[:received-message
{:message-id message-id
:content (label :t/contacts-syncronized)
:content-type const/text-content-type
:outgoing false
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}])
(def start-signup-events
[[:received-message
{:message-id (random/id)
:content (command-content
:phone
(label :t/phone-number-required))
:content-type content-type-command-request
:content-type const/content-type-command-request
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}]))
(dispatch [:received-message
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]
[:received-message
{:message-id (random/id)
:content (label :t/shake-your-phone)
:content-type text-content-type
:content-type const/text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}]))
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]])
;; -- Saving password ----------------------------------------
(defn account-generation-message []
(dispatch [:received-message
{:message-id const/crazy-math-message-id
(def account-generation-event
[:received-message
{:message-id chat-const/crazy-math-message-id
:content (label :t/account-generation-message)
:content-type text-content-type
:content-type const/text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}]))
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}])
(defn move-to-internal-failure-message []
(dispatch [:received-message
{:message-id const/move-to-internal-failure-message-id
(def move-to-internal-failure-event
[:received-message
{:message-id chat-const/move-to-internal-failure-message-id
:content (command-content
:grant-permissions
(label :t/move-to-internal-failure-message))
:content-type content-type-command-request
:content-type const/content-type-command-request
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}]))
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}])
(defn passphrase-messages [mnemonic signing-phrase crazy-math-message?]
(dispatch [:received-message
{:message-id const/passphrase-message-id
(defn passphrase-messages-events [mnemonic signing-phrase crazy-math-message?]
(into [[:received-message
{:message-id chat-const/passphrase-message-id
:content (if crazy-math-message?
(label :t/phew-here-is-your-passphrase)
(label :t/here-is-your-passphrase))
:content-type text-content-type
:content-type const/text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])
(dispatch [:received-message
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]
[:received-message
{:message-id (random/id)
:content mnemonic
:content-type text-content-type
:content-type const/text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])
(dispatch [:received-message
{:message-id const/signing-phrase-message-id
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]
[:received-message
{:message-id chat-const/signing-phrase-message-id
:content (label :t/here-is-your-signing-phrase)
:content-type text-content-type
:content-type const/text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])
(dispatch [:received-message
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]
[:received-message
{:message-id (random/id)
:content signing-phrase
:content-type text-content-type
:content-type const/text-content-type
:outgoing false
:chat-id console-chat-id
:from console-chat-id
:to "me"}])
(start-signup))
:chat-id const/console-chat-id
:from const/console-chat-id
:to "me"}]]
start-signup-events))
(def intro-status
{:message-id const/intro-status-message-id
{:message-id chat-const/intro-status-message-id
:content (label :t/intro-status)
:from console-chat-id
:chat-id console-chat-id
:content-type content-type-status
:from const/console-chat-id
:chat-id const/console-chat-id
:content-type const/content-type-status
:outgoing false
:to "me"})
(defn intro []
(dispatch [:received-message intro-status])
(dispatch [:received-message-when-commands-loaded
console-chat-id
{:chat-id console-chat-id
:message-id const/intro-message1-id
(def intro-events
[[:received-message intro-status]
[:received-message-when-commands-loaded
const/console-chat-id
{:chat-id const/console-chat-id
:message-id chat-const/intro-message1-id
:content (command-content
:password
(label :t/intro-message1))
:content-type content-type-command-request
:content-type const/content-type-command-request
:outgoing false
:from console-chat-id
:to "me"}]))
:from const/console-chat-id
:to "me"}]])
(def console-chat
{:chat-id console-chat-id
:name (s/capitalize console-chat-id)
{:chat-id const/console-chat-id
:name (s/capitalize const/console-chat-id)
:color default-chat-color
:group-chat false
:is-active true
:unremovable? true
:timestamp (.getTime (js/Date.))
:photo-path console-chat-id
:contacts [{:identity console-chat-id
:photo-path const/console-chat-id
:contacts [{:identity const/console-chat-id
:text-color "#FFFFFF"
:background-color "#AB7967"}]})
(def console-contact
{:whisper-identity console-chat-id
:name (s/capitalize console-chat-id)
:photo-path console-chat-id
{:whisper-identity const/console-chat-id
:name (s/capitalize const/console-chat-id)
:photo-path const/console-chat-id
:dapp? true
:unremovable? true
:bot-url "local://console-bot"

View File

@ -2,8 +2,6 @@
(:require-macros [status-im.utils.views :refer [defview]])
(:require [reagent.core :as r]
[re-frame.core :refer [dispatch subscribe]]
[clojure.string :as str]
[status-im.chat.constants :as const]
[status-im.components.react :refer [view
text
list-view
@ -12,20 +10,15 @@
[status-im.components.renderers.renderers :as renderers]
[status-im.utils.listview :as lw]))
(defn- select-contact [arg-index bot-db-key {:keys [name] :as contact}]
(let [contact (select-keys contact [:address :whisper-identity :name :photo-path :dapp?])
name (str/replace name (re-pattern const/arg-wrapping-char) "")]
(dispatch [:set-command-argument [arg-index name true]])
(dispatch [:set-in-bot-db {:path [:public (keyword bot-db-key)]
:value contact}])
(dispatch [:select-next-argument])))
(defn render-row [arg-index bot-db-key]
(fn [contact _ _]
(list-item
^{:key contact}
[contact-view {:contact contact
:on-press #(select-contact arg-index bot-db-key contact)}])))
:on-press #(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

View File

@ -41,9 +41,7 @@
show-suggestions? [:show-suggestions?]]
[view style/commands-root
[view style/command-list-icon-container
[touchable-highlight {:on-press #(do (dispatch [:toggle-chat-ui-props :show-suggestions?])
(dispatch [:set-chat-ui-props {:validation-messages nil}])
(dispatch [:update-suggestions]))}
[touchable-highlight {:on-press #(dispatch [:show-suggestions])}
[view style/commands-list-icon
(if show-suggestions?
[vi/icon :icons/close]

View File

@ -39,7 +39,7 @@
get-contact-translated]]
[status-im.chat.utils :as cu]
[clojure.string :as str]
[status-im.chat.handlers.console :as console]
[status-im.chat.events.console :as console]
[taoensso.timbre :as log]))
(def window-width (:width (get-dimensions "window")))
@ -300,8 +300,7 @@
[{:keys [message-id chat-id message-status user-statuses content]}]
[app-db-message-status-value [:get-in [:message-data :statuses message-id :status]]]
(let [delivery-status (get-in user-statuses [chat-id :status])
command-name (keyword (:command content))
status (cond (and (not (console/commands-with-delivery-status command-name))
status (cond (and (not (console/commands-with-delivery-status (:command content)))
(cu/console? chat-id))
:seen

View File

@ -97,6 +97,14 @@
[]
(data-store/get-unviewed))
(defn get-previews
[]
(->> (data-store/get-all-as-list)
(filter :preview)
(reduce (fn [acc {:keys [message-id preview]}]
(assoc acc message-id (read-string preview)))
{})))
(defn save
;; todo remove chat-id parameter
[chat-id {:keys [message-id content]
@ -120,4 +128,3 @@
(defn delete-by-chat-id [chat-id]
(data-store/delete-by-chat-id chat-id))

View File

@ -4,7 +4,7 @@
status-im.ui.screens.accounts.recover.events
[status-im.data-store.accounts :as accounts-store]
[re-frame.core :refer [reg-cofx reg-fx dispatch inject-cofx]]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.protocol.core :as protocol]
[status-im.native-module.core :as status]
@ -13,26 +13,35 @@
[status-im.utils.random :as random]
[clojure.string :as str]
[status-im.utils.datetime :as time]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx get-hashtags] :as handlers]
[status-im.utils.handlers :as handlers]
[status-im.ui.screens.accounts.statuses :as statuses]
[status-im.utils.signing-phrase.core :as signing-phrase]
[status-im.utils.gfycat.core :refer [generate-gfy]]))
;;;; Helper fns
(defn create-account
"Takes db and password, creates map of effects describing account creation"
[db password]
{:db (assoc db :accounts/creating-account? true)
::create-account password
:dispatch-later [{:ms 400 :dispatch [:account-generation-message]}]})
;;;; COFX
(reg-cofx
(re-frame/reg-cofx
:get-new-keypair!
(fn [coeffects _]
(assoc coeffects :keypair (protocol/new-keypair!))))
(reg-cofx
(re-frame/reg-cofx
::get-all-accounts
(fn [coeffects _]
(assoc coeffects :all-accounts (accounts-store/get-all))))
;;;; FX
(reg-fx
(re-frame/reg-fx
::save-account
(fn [account]
(accounts-store/save account true)))
@ -55,17 +64,17 @@
:signing-phrase phrase}]
(log/debug "account-created")
(when-not (str/blank? public-key)
(dispatch [:show-mnemonic mnemonic phrase])
(dispatch [:add-account account password]))))
(re-frame/dispatch [:show-mnemonic mnemonic phrase])
(re-frame/dispatch [:add-account account password]))))
(reg-fx
(re-frame/reg-fx
::create-account
(fn [password]
(status/create-account
password
#(account-created % password))))
(reg-fx
(re-frame/reg-fx
::broadcast-account-update
(fn [{:keys [current-public-key web3 name photo-path status
updates-public-key updates-private-key]}]
@ -80,7 +89,7 @@
:status status
:profile-image photo-path}}}}))))
(reg-fx
(re-frame/reg-fx
::send-keys-update
(fn [{:keys [web3 current-public-key contacts
updates-public-key updates-private-key]}]
@ -94,7 +103,7 @@
:private updates-private-key}}}}))))
;;;; Handlers
(register-handler-fx
(handlers/register-handler-fx
:add-account
(fn [{{:keys [network]
:networks/keys [networks]
@ -107,23 +116,16 @@
(when password
{:dispatch-later [{:ms 400 :dispatch [:login-account address password true]}]})))))
(register-handler-fx
:create-account
(fn [{db :db} [_ password]]
{:db (assoc db :accounts/creating-account? true)
::create-account password
:dispatch-later [{:ms 400 :dispatch [:account-generation-message]}]}))
(register-handler-fx
(handlers/register-handler-fx
:create-new-account-handler
(fn [_ _]
{:dispatch-n [[:initialize-db]
[:load-accounts]
[:check-console-chat true]]}))
(register-handler-fx
(handlers/register-handler-fx
:load-accounts
[(inject-cofx ::get-all-accounts)]
[(re-frame/inject-cofx ::get-all-accounts)]
(fn [{:keys [db all-accounts]} _]
(let [accounts (->> all-accounts
(map (fn [{:keys [address] :as account}]
@ -136,7 +138,7 @@
(when-not (empty? events)
{:dispatch-n events})))))
(register-handler-fx
(handlers/register-handler-fx
:account-update-networks
(fn [{{:accounts/keys [accounts] :networks/keys [networks] :as db} :db} [_ id]]
(let [current-account (get accounts id)
@ -144,41 +146,51 @@
{:db (assoc-in db [:accounts/accounts id] new-account)
::save-account new-account})))
(register-handler-fx
(handlers/register-handler-fx
:check-status-change
(fn [{{:accounts/keys [accounts current-account-id]} :db} [_ status]]
(let [{old-status :status} (get accounts current-account-id)
status-updated? (and (not= status nil)
(not= status old-status))]
(when status-updated?
(let [hashtags (get-hashtags status)]
(let [hashtags (handlers/get-hashtags status)]
(when (seq hashtags)
{:dispatch [:broadcast-status status hashtags]}))))))
(defn update-account [current-account new-fields]
(merge current-account (assoc new-fields :last-updated (time/now-ms))))
(defn- update-account [current-account new-fields now]
(merge current-account (assoc new-fields :last-updated now)))
(register-handler-fx
:account-update
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db} [_ new-account-fields]]
(defn account-update
"Takes map of coeffects containing `:db` and `:now` keys + new account fields,
returns all effects necessary for account update."
[{{:keys [network]
:accounts/keys [accounts current-account-id] :as db} :db now :now} new-account-fields]
(let [current-account (get accounts current-account-id)
new-account (update-account current-account new-account-fields)]
new-account (update-account current-account new-account-fields now)]
{:db (assoc-in db [:accounts/accounts current-account-id] new-account)
::save-account new-account
::broadcast-account-update (merge
(select-keys db [:current-public-key :web3])
(select-keys new-account [:name :photo-path :status
:updates-public-key :updates-private-key]))})))
:updates-public-key :updates-private-key]))}))
(register-handler-fx
;; TODO(janherich): remove this event once it's not used anymore
(handlers/register-handler-fx
:account-update
[(re-frame/inject-cofx :now) re-frame/trim-v]
(fn [cofx [new-account-fields]]
(account-update cofx new-account-fields)))
(handlers/register-handler-fx
:account-update-keys
[(inject-cofx :get-new-keypair!)]
(fn [{:keys [db keypair]} _]
[(re-frame/inject-cofx :get-new-keypair!) (re-frame/inject-cofx :now)]
(fn [{:keys [db keypair now]} _]
(let [{:accounts/keys [accounts current-account-id]} db
{:keys [public private]} keypair
current-account (get accounts current-account-id)
new-account (update-account current-account {:updates-public-key public
:updates-private-key private})]
:updates-private-key private}
now)]
{:db (assoc-in db [:accounts/accounts current-account-id] new-account)
::save-account new-account
::send-keys-update (merge
@ -186,17 +198,19 @@
(select-keys new-account [:updates-public-key
:updates-private-key]))})))
(register-handler-fx
(handlers/register-handler-fx
:send-account-update-if-needed
(fn [{{:accounts/keys [accounts current-account-id]} :db} _]
[(re-frame/inject-cofx :now)]
(fn [{{:accounts/keys [accounts current-account-id]} :db now :now :as cofx} _]
(let [{:keys [last-updated]} (get accounts current-account-id)
now (time/now-ms)
needs-update? (> (- now last-updated) time/week)]
(log/info "Need to send account-update: " needs-update?)
(when needs-update?
(dispatch [:account-update])))))
;; TODO(janherich): this is very strange and misleading, need to figure out why it'd necessary to update
;; account with network update when last update was more then week ago
(account-update cofx nil)))))
(register-handler-db
(handlers/register-handler-db
:set-current-account
(fn [{:accounts/keys [accounts] :as db} [_ address]]
(let [key (:public-key (accounts address))]

View File

@ -27,7 +27,7 @@
(fn []
(when (and (get-in @message [:content :command])
(not @preview))
(dispatch [:request-command-data @message :short-preview])))
(dispatch [:request-command-message-data @message :short-preview])))
:reagent-render
(fn [_]

View File

@ -1,5 +1,5 @@
(ns status-im.ui.screens.contacts.events
(:require [re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
(:require [re-frame.core :refer [dispatch trim-v reg-fx reg-cofx inject-cofx]]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
[status-im.data-store.contacts :as contacts]
[status-im.utils.crypt :refer [encrypt]]
@ -102,13 +102,13 @@
(reg-fx
::fetch-contacts-from-phone!
(fn [_]
(fn [on-contacts-event-creator]
(.getAll rn-dependencies/contacts
(fn [error contacts]
(if error
(log/debug :error-on-fetching-loading error)
(let [contacts' (normalize-phone-contacts contacts)]
(dispatch [::get-contacts-identities contacts'])))))))
(dispatch [::get-contacts-identities contacts' on-contacts-event-creator])))))))
(defn- get-contacts-by-hash [contacts]
(->> contacts
@ -131,13 +131,12 @@
(reg-fx
::request-stored-contacts
(fn [contacts]
(fn [{:keys [contacts on-contacts-event-creator]}]
(let [contacts-by-hash (get-contacts-by-hash contacts)
data (or (keys contacts-by-hash) '())]
(http-post "get-contacts" {:phone-number-hashes data}
(fn [{:keys [contacts]}]
(let [contacts' (add-identity contacts-by-hash contacts)]
(dispatch [:add-contacts contacts'])))))))
(dispatch (on-contacts-event-creator (add-identity contacts-by-hash contacts))))))))
(reg-fx
::request-contacts-by-address
@ -159,8 +158,16 @@
(register-handler-fx
::get-contacts-identities
(fn [_ [_ contacts]]
{::request-stored-contacts contacts}))
[trim-v]
(fn [_ [contacts on-contacts-event-creator]]
{::request-stored-contacts {:contacts contacts
:on-contacts-event-creator on-contacts-event-creator}}))
(register-handler-fx
:sync-contacts
[trim-v]
(fn [_ [on-contacts-event-creator]]
{::fetch-contacts-from-phone! on-contacts-event-creator}))
(register-handler-fx
:watch-contact
@ -177,11 +184,6 @@
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
::save-contact contact})))
(register-handler-fx
:sync-contacts
(fn [_ _]
{::fetch-contacts-from-phone! nil}))
(defn- update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending?
:as old-contact} (get old-contacts whisper-identity)
@ -281,9 +283,9 @@
:global-commands global-commands)
:dispatch-n (mapv (fn [_ contact] [:watch-contact contact]) contacts)})))
(register-handler-fx
:add-contacts
(fn [{:keys [db]} [_ new-contacts]]
(defn add-contacts
"Creates effects map for adding contacts"
[db new-contacts]
(let [{:contacts/keys [contacts]} db
identities (set (keys contacts))
new-contacts' (->> new-contacts
@ -301,7 +303,13 @@
{:db (-> db
(update :global-commands merge global-commands)
(update :contacts/contacts merge new-contacts'))
::save-contacts! (vals new-contacts')})))
::save-contacts! (vals new-contacts')}))
(register-handler-fx
:add-contacts
[trim-v]
(fn [{:keys [db]} [new-contacts]]
(add-contacts db new-contacts)))
(register-handler-db
:remove-contacts-click-handler

View File

@ -1,5 +1,5 @@
(ns status-im.ui.screens.events
(:require status-im.bots.handlers
(:require status-im.bots.events
status-im.chat.handlers
status-im.commands.handlers.jail
status-im.commands.handlers.loading
@ -17,7 +17,7 @@
status-im.ui.screens.profile.events
status-im.ui.screens.qr-scanner.events
status-im.ui.screens.wallet.events
[re-frame.core :refer [dispatch reg-fx]]
[re-frame.core :refer [dispatch reg-fx reg-cofx]]
[status-im.native-module.core :as status]
[status-im.components.permissions :as permissions]
[status-im.constants :refer [console-chat-id]]
@ -25,6 +25,9 @@
[status-im.i18n :as i18n]
[status-im.js-dependencies :as dependencies]
[status-im.ui.screens.db :refer [app-db]]
[status-im.utils.sms-listener :as sms-listener-util]
[status-im.utils.datetime :as time]
[status-im.utils.random :as random]
[status-im.utils.config :as config]
[status-im.utils.crypt :as crypt]
[status-im.utils.notifications :as notifications]
@ -35,10 +38,87 @@
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]))
;;;; Helper fns
(defn- call-jail-function
[{:keys [chat-id function callback-events-creator] :as opts}]
(let [path [:functions function]
params (select-keys opts [:parameters :context])]
(status/call-jail
{:jail-id chat-id
:path path
:params params
:callback (fn [jail-response]
(doseq [event (if callback-events-creator
(callback-events-creator jail-response)
[[:received-bot-response
{:chat-id chat-id}
jail-response]])
:when event]
(dispatch event)))})))
;;;; COFX
(reg-cofx
:now
(fn [coeffects _]
(assoc coeffects :now (time/now-ms))))
(reg-cofx
:random-id
(fn [coeffects _]
(assoc coeffects :random-id (random/id))))
(reg-cofx
:random-id-seq
(fn [coeffects _]
(assoc coeffects :random-id-seq
((fn rand-id-seq []
(cons (random/id) (lazy-seq (rand-id-seq))))))))
;;;; FX
(reg-fx
:call-jail
(fn [{:keys [callback-events-creator] :as opts}]
(status/call-jail
(-> opts
(dissoc :callback-events-creator)
(assoc :callback
(fn [jail-response]
(doseq [event (callback-events-creator jail-response)]
(dispatch event))))))))
(reg-fx
:call-jail-function
call-jail-function)
(reg-fx
:call-jail-function-n
(fn [opts-seq]
(doseq [opts opts-seq]
(call-jail-function opts))))
(reg-fx
:http-post
(fn [{:keys [action data success-event-creator failure-event-creator]}]
(utils/http-post action
data
#(dispatch (success-event-creator %))
#(dispatch (failure-event-creator %)))))
(reg-fx
:http-get
(fn [{:keys [url success-event-creator failure-event-creator]}]
(utils/http-get url
#(dispatch (success-event-creator %))
#(dispatch (failure-event-creator %)))))
(reg-fx
:remove-sms-listener
(fn [subscription]
(sms-listener-util/remove-sms-listener subscription)))
(reg-fx
::init-store
(fn []

View File

@ -1,7 +1,7 @@
(ns status-im.ui.screens.profile.events
(:require [clojure.spec.alpha :as spec]
[clojure.string :as string]
[re-frame.core :as re-frame :refer [reg-fx]]
[re-frame.core :as re-frame :refer [reg-fx trim-v]]
[status-im.components.react :refer [show-image-picker]]
[status-im.constants :refer [console-chat-id]]
[status-im.ui.screens.profile.db :as db]
@ -28,11 +28,13 @@
(handlers/register-handler-fx
:profile/send-transaction
(fn [_ [_ chat-id]]
{:dispatch-n [[:clear-seq-arguments]
[:navigate-to :chat chat-id]
;; TODO https://github.com/status-im/status-react/issues/1621
[:select-chat-input-command {:name "send"}]]}))
[trim-v]
(fn [{:keys [db]} [chat-id contact-id]]
{:dispatch-n [[:navigate-to :chat chat-id]
[:select-chat-input-command {:name "send"}]
[:set-contact-as-command-argument {:arg-index 0
:bot-db-key "recipient"
:contact (get-in db [:contacts/contacts contact-id])}]]}))
(handlers/register-handler-fx
:profile/send-message

View File

@ -75,7 +75,7 @@
[action-button {:label (label :t/send-transaction)
:icon :icons/arrow-right
:icon-opts {:color :blue}
:on-press #(dispatch [:profile/send-transaction chat-id])}]])])
:on-press #(dispatch [:profile/send-transaction chat-id whisper-identity])}]])])
(defn profile-info-item [{:keys [label value options text-mode empty-value? accessibility-label]}]
[react/view styles/profile-setting-item

View File

@ -1,16 +1,17 @@
(ns status-im.utils.sms-listener
(:require [status-im.utils.platform :refer [android?]]
(:require [re-frame.core :as re-frame]
[status-im.utils.platform :refer [android?]]
[status-im.react-native.js-dependencies :as rn-dependencies]))
;; Only android is supported!
(defn add-sms-listener
"Message format: {:originatingAddress string, :body string}. Returns
cancelable subscription."
[listen-fn]
[listen-event-creator]
(when android?
(.addListener rn-dependencies/android-sms-listener
(fn [message]
(listen-fn (js->clj message :keywordize-keys true))))))
(re-frame/dispatch (listen-event-creator (js->clj message :keywordize-keys true)))))))
(defn remove-sms-listener [subscription]
(when android?

View File

@ -0,0 +1,48 @@
(ns status-im.test.chat.events
(:require [cljs.test :refer [deftest is testing]]
reagent.core
[re-frame.core :as rf]
[day8.re-frame.test :refer [run-test-sync]]
[status-im.constants :as const]
[status-im.chat.sign-up :as sign-up]
[status-im.chat.events :as chat-events]))
(def contact
{:address "c296367a939e0957500a25ca89b70bd64b03004e"
:whisper-identity "0x04f5722fba79eb36d73263417531007f43d13af76c6233573a8e3e60f667710611feba0785d751b50609bfc0b7cef35448875c5392c0a91948c95798a0ce600847"
:name "testuser"
:photo-path "contacts://testuser"
:dapp? false})
(deftest init-console-chat
(testing "initialising console if console is already added to chats, should not modify anything"
(let [db {:chats {const/console-chat-id sign-up/console-chat}}
fx (chat-events/init-console-chat db false)]
(is (= db (:db fx)))
(is (= #{:db} (-> fx keys set)))))
(testing "initialising console without existing account and console chat not initialisated"
(let [fresh-db {:chats {}
:accounts/current-account-id nil}
{:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db false)]
(is (= (:current-chat-id db)
(:chat-id sign-up/console-chat)))
(is (= (:new-chat db)
sign-up/console-chat))
(is (= (:current-chat-id db)
const/console-chat-id))
(is (= dispatch-n
(concat [[:add-contacts [sign-up/console-contact]]]
sign-up/intro-events)))))
(testing "initialising console with existing account and console chat not initialisated"
(let [fresh-db {:chats {}
:accounts/current-account-id (:whisper-identity contact)}
{:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db false)]
(is (= (:current-chat-id db)
(:chat-id sign-up/console-chat)))
(is (= (:new-chat db)
sign-up/console-chat))
(is (= (:current-chat-id db)
const/console-chat-id))
(is (= dispatch-n [[:add-contacts [sign-up/console-contact]]])))))

View File

@ -1,5 +1,6 @@
(ns status-im.test.runner
(:require [doo.runner :refer-macros [doo-tests]]
[status-im.test.chat.events]
[status-im.test.contacts.events]
[status-im.test.accounts.events]
[status-im.test.wallet.events]
@ -24,6 +25,7 @@
(set! goog.DEBUG false)
(doo-tests
'status-im.test.chat.events
'status-im.test.accounts.events
'status-im.test.contacts.events
'status-im.test.profile.events