Chat refactoring

Move chat views to ui.screens
This commit is contained in:
janherich 2018-09-14 12:36:15 +02:00
parent 195b70fcdd
commit 8913dee762
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
67 changed files with 574 additions and 668 deletions

View File

@ -1,17 +1,12 @@
(ns status-im.chat.commands.core
(:require [re-frame.core :as re-frame]
[clojure.set :as set]
[clojure.string :as string]
[pluto.host :as host]
[status-im.constants :as constants]
[status-im.chat.constants :as chat-constants]
[status-im.chat.commands.protocol :as protocol]
[status-im.chat.commands.impl.transactions :as transactions]
[status-im.chat.models :as chat-model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.message :as message-model]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]))
[status-im.utils.handlers :as handlers]))
(def register
"Register of all commands. Whenever implementing a new command,
@ -183,94 +178,3 @@
{}
(concat (get access-scope->command-id global-access-scope)
(get access-scope->command-id chat-access-scope)))))
(defn- current-param-position [input-text selection]
(when selection
(when-let [subs-input-text (subs input-text 0 selection)]
(let [input-params (input-model/split-command-args subs-input-text)
param-index (dec (count input-params))
wrapping-count (get (frequencies subs-input-text) chat-constants/arg-wrapping-char 0)]
(if (and (string/ends-with? subs-input-text chat-constants/spacing-char)
(even? wrapping-count))
param-index
(dec param-index))))))
(defn- command-completion [input-params params]
(let [input-params-count (count input-params)
params-count (count params)]
(cond
(= input-params-count params-count) :complete
(< input-params-count params-count) :less-then-needed
(> input-params-count params-count) :more-than-needed)))
(defn selected-chat-command
"Takes input text, text-selection and `protocol/id->command-props` map (result of
the `chat-commands` fn) and returns the corresponding `command-props` entry,
or nothing if input text doesn't match any available command.
Besides keys `:params` and `:type`, the returned map contains:
* `:input-params` - parsed parameters from the input text as map of `param-id->entered-value`
# `:current-param-position` - index of the parameter the user is currently focused on (cursor position
in relation to parameters), could be nil if the input is not selected
# `:command-completion` - indication of command completion, possible values are `:complete`,
`:less-then-needed` and `more-then-needed`"
[input-text text-selection id->command-props]
(when (input-model/starts-as-command? input-text)
(let [[command-name & input-params] (input-model/split-command-args input-text)]
(when-let [{:keys [params] :as command-props} (get id->command-props (subs command-name 1))] ;; trim leading `/` for lookup
command-props
(let [input-params (into {}
(keep-indexed (fn [idx input-value]
(when (not (string/blank? input-value))
(when-let [param-name (get-in params [idx :id])]
[param-name input-value]))))
input-params)]
(assoc command-props
:input-params input-params
:current-param-position (current-param-position input-text text-selection)
:command-completion (command-completion input-params params)))))))
(defn set-command-parameter
"Set value as command parameter for the current chat"
[last-param? param-index value {:keys [db]}]
(let [{:keys [current-chat-id chats]} db
[command & params] (-> (get-in chats [current-chat-id :input-text])
input-model/split-command-args)
param-count (count params)
;; put the new value at the right place in parameters array
new-params (cond-> (into [] params)
(< param-index param-count) (assoc param-index value)
(>= param-index param-count) (conj value))
;; if the parameter is not the last one for the command, add space
input-text (cond-> (str command chat-constants/spacing-char
(input-model/join-command-args
new-params))
(and (not last-param?)
(or (= 0 param-count)
(= param-index (dec param-count))))
(str chat-constants/spacing-char))]
{:db (-> db
(chat-model/set-chat-ui-props {:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text] input-text))}))
(defn select-chat-input-command
"Takes command and (optional) map of input-parameters map and sets it as current-chat input"
[{:keys [type params]} input-params {:keys [db]}]
(let [{:keys [current-chat-id chat-ui-props]} db]
{:db (-> db
(chat-model/set-chat-ui-props {:show-suggestions? false
:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text]
(str (command-name type)
chat-constants/spacing-char
(input-model/join-command-args input-params))))}))
(defn parse-parameters
"Parses parameters from input for defined command params,
returns map of `param-name->param-value`"
[params input-text]
(let [input-params (->> (input-model/split-command-args input-text) rest (into []))]
(into {}
(keep-indexed (fn [idx {:keys [id]}]
(when-let [value (get input-params idx)]
[id value])))
params)))

View File

@ -0,0 +1,97 @@
(ns status-im.chat.commands.input
(:require [clojure.string :as string]
[status-im.chat.commands.core :as commands]
[status-im.chat.constants :as chat-constants]
[status-im.chat.models :as chat-model]
[status-im.chat.models.input :as input-model]))
(defn- current-param-position [input-text selection]
(when selection
(when-let [subs-input-text (subs input-text 0 selection)]
(let [input-params (input-model/split-command-args subs-input-text)
param-index (dec (count input-params))
wrapping-count (get (frequencies subs-input-text) chat-constants/arg-wrapping-char 0)]
(if (and (string/ends-with? subs-input-text chat-constants/spacing-char)
(even? wrapping-count))
param-index
(dec param-index))))))
(defn- command-completion [input-params params]
(let [input-params-count (count input-params)
params-count (count params)]
(cond
(= input-params-count params-count) :complete
(< input-params-count params-count) :less-then-needed
(> input-params-count params-count) :more-than-needed)))
(defn selected-chat-command
"Takes input text, text-selection and `protocol/id->command-props` map (result of
the `chat-commands` fn) and returns the corresponding `command-props` entry,
or nothing if input text doesn't match any available command.
Besides keys `:params` and `:type`, the returned map contains:
* `:input-params` - parsed parameters from the input text as map of `param-id->entered-value`
# `:current-param-position` - index of the parameter the user is currently focused on (cursor position
in relation to parameters), could be nil if the input is not selected
# `:command-completion` - indication of command completion, possible values are `:complete`,
`:less-then-needed` and `more-then-needed`"
[input-text text-selection id->command-props]
(when (input-model/starts-as-command? input-text)
(let [[command-name & input-params] (input-model/split-command-args input-text)]
(when-let [{:keys [params] :as command-props} (get id->command-props (subs command-name 1))] ;; trim leading `/` for lookup
command-props
(let [input-params (into {}
(keep-indexed (fn [idx input-value]
(when (not (string/blank? input-value))
(when-let [param-name (get-in params [idx :id])]
[param-name input-value]))))
input-params)]
(assoc command-props
:input-params input-params
:current-param-position (current-param-position input-text text-selection)
:command-completion (command-completion input-params params)))))))
(defn set-command-parameter
"Set value as command parameter for the current chat"
[last-param? param-index value {:keys [db]}]
(let [{:keys [current-chat-id chats]} db
[command & params] (-> (get-in chats [current-chat-id :input-text])
input-model/split-command-args)
param-count (count params)
;; put the new value at the right place in parameters array
new-params (cond-> (into [] params)
(< param-index param-count) (assoc param-index value)
(>= param-index param-count) (conj value))
;; if the parameter is not the last one for the command, add space
input-text (cond-> (str command chat-constants/spacing-char
(input-model/join-command-args
new-params))
(and (not last-param?)
(or (= 0 param-count)
(= param-index (dec param-count))))
(str chat-constants/spacing-char))]
{:db (-> db
(chat-model/set-chat-ui-props {:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text] input-text))}))
(defn select-chat-input-command
"Takes command and (optional) map of input-parameters map and sets it as current-chat input"
[{:keys [type params]} input-params {:keys [db]}]
(let [{:keys [current-chat-id chat-ui-props]} db]
{:db (-> db
(chat-model/set-chat-ui-props {:show-suggestions? false
:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text]
(str (commands/command-name type)
chat-constants/spacing-char
(input-model/join-command-args input-params))))}))
(defn parse-parameters
"Parses parameters from input for defined command params,
returns map of `param-name->param-value`"
[params input-text]
(let [input-params (->> (input-model/split-command-args input-text) rest (into []))]
(into {}
(keep-indexed (fn [idx {:keys [id]}]
(when-let [value (get input-params idx)]
[id value])))
params)))

View File

@ -2,6 +2,7 @@
(:require [status-im.constants :as constants]
[status-im.chat.commands.protocol :as protocol]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.input :as commands-input]
[status-im.chat.models :as chat-model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.message :as message-model]
@ -46,7 +47,7 @@
"Validates and sends command in current chat"
[input-text {:keys [type params] :as command} {:keys [db now random-id] :as cofx}]
(let [chat-id (:current-chat-id db)
parameter-map (commands/parse-parameters params input-text)]
parameter-map (commands-input/parse-parameters params input-text)]
(if-let [validation-error (protocol/validate type parameter-map cofx)]
;; errors during validation
{:db (chat-model/set-chat-ui-props db {:validation-messages validation-error

View File

@ -7,8 +7,6 @@
(def input-height 56)
(def input-spacing-top 16)
(def console-chat-id "console")
(def spam-message-frequency-threshold 4)
(def spam-interval-ms 1000)
(def default-cooldown-period-ms 10000)

View File

@ -5,13 +5,13 @@
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.models :as models]
[status-im.chat.models.loading :as chat-loading]
[status-im.chat.models.message :as models.message]
[status-im.constants :as constants]
[status-im.data-store.user-statuses :as user-statuses-store]
[status-im.i18n :as i18n]
[status-im.transport.message.core :as transport.message]
[status-im.transport.message.v1.group-chat :as group-chat]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.message.v1.public-chat :as public-chat]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.handlers :as handlers]
@ -36,12 +36,6 @@
(fn [db [kvs]]
(models/set-chat-ui-props db kvs)))
(handlers/register-handler-db
:toggle-chat-ui-props
[re-frame/trim-v]
(fn [db [ui-element]]
(models/toggle-chat-ui-prop db ui-element)))
(handlers/register-handler-db
:show-message-details
[re-frame/trim-v]
@ -56,14 +50,6 @@
(models/set-chat-ui-props db {:show-message-options? true
:message-options options})))
(def index-messages (partial into {} (map (juxt :message-id identity))))
(handlers/register-handler-db
:message-appeared
[re-frame/trim-v]
(fn [db [{:keys [chat-id message-id]}]]
(update-in db [:chats chat-id :messages message-id] assoc :appearing? false)))
(handlers/register-handler-fx
:update-message-status
[re-frame/trim-v]
@ -77,130 +63,24 @@
new-status)
:data-store/tx [(user-statuses-store/save-status-tx new-status)]})))
(defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}]
(when (and (not (get-in db [:chats chat-id :public?]))
(not (models/bot-only-chat? db chat-id)))
(transport.message/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
;; TODO (janherich) - ressurect `constants/system` messages for group chats in the future
(defn mark-messages-seen
[chat-id {:keys [db] :as cofx}]
(when-let [all-unviewed-ids (seq (get-in db [:chats chat-id :unviewed-messages]))]
(let [me (:current-public-key db)
updated-statuses (keep (fn [message-id]
(some-> db
(get-in [:chats chat-id :message-statuses
message-id me])
(assoc :status :seen)))
all-unviewed-ids)
loaded-unviewed-ids (map :message-id updated-statuses)]
(when (seq loaded-unviewed-ids)
(handlers-macro/merge-fx
cofx
{:db (-> (reduce (fn [acc {:keys [message-id status]}]
(assoc-in acc [:chats chat-id :message-statuses
message-id me :status]
status))
db
updated-statuses)
(update-in [:chats chat-id :unviewed-messages]
#(apply disj % loaded-unviewed-ids)))
:data-store/tx [(user-statuses-store/save-statuses-tx updated-statuses)]}
(send-messages-seen chat-id loaded-unviewed-ids))))))
(defn- fire-off-chat-loaded-event
[chat-id {:keys [db]}]
(when-let [event (get-in db [:chats chat-id :chat-loaded-event])]
{:db (update-in db [:chats chat-id] dissoc :chat-loaded-event)
:dispatch event}))
(defn- preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
[chat-id {:keys [db] :as cofx}]
(handlers-macro/merge-fx cofx
{:db (-> (assoc db :current-chat-id chat-id)
(models/set-chat-ui-props {:validation-messages nil}))}
(fire-off-chat-loaded-event chat-id)
(mark-messages-seen chat-id)))
(handlers/register-handler-fx
:add-chat-loaded-event
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [chat-id event]]
(if (get (:chats db) chat-id)
{:db (assoc-in db [:chats chat-id :chat-loaded-event] event)}
(-> (models/upsert-chat {:chat-id chat-id} cofx) ; chat not created yet, we have to create it
(assoc-in [:db :chats chat-id :chat-loaded-event] event)))))
(defn- navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
[chat-id {:keys [navigation-replace?]} cofx]
(if navigation-replace?
(handlers-macro/merge-fx
cofx
(navigation/navigate-reset
{:index 1
:actions [{:routeName :home}
{:routeName :chat}]})
(preload-chat-data chat-id))
(handlers-macro/merge-fx
cofx
(navigation/navigate-to-cofx :chat {})
(preload-chat-data chat-id))))
(handlers/register-handler-fx
:navigate-to-chat
[re-frame/trim-v]
(fn [cofx [chat-id opts]]
(navigate-to-chat chat-id opts cofx)))
(models/navigate-to-chat chat-id opts cofx)))
(handlers/register-handler-fx
:load-more-messages
[(re-frame/inject-cofx :data-store/get-messages)
(re-frame/inject-cofx :data-store/get-user-statuses)]
(fn [{{:keys [current-chat-id] :as db} :db
get-stored-messages :get-stored-messages
get-stored-user-statuses :get-stored-user-statuses :as cofx} _]
(when-not (get-in db [:chats current-chat-id :all-loaded?])
(let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
new-messages (get-stored-messages current-chat-id loaded-count)
indexed-messages (index-messages new-messages)
new-message-ids (keys indexed-messages)
new-statuses (get-stored-user-statuses current-chat-id new-message-ids)]
(handlers-macro/merge-fx
cofx
{:db (-> db
(update-in [:chats current-chat-id :messages] merge indexed-messages)
(update-in [:chats current-chat-id :message-statuses] merge new-statuses)
(update-in [:chats current-chat-id :not-loaded-message-ids]
#(apply disj % new-message-ids))
(assoc-in [:chats current-chat-id :all-loaded?]
(> constants/default-number-of-messages (count new-messages))))}
(models.message/group-messages current-chat-id new-messages)
(mark-messages-seen current-chat-id))))))
(defn start-chat
"Start a chat, making sure it exists"
[chat-id opts {:keys [db] :as cofx}]
;; don't allow to open chat with yourself
(when (not= (:current-public-key db) chat-id)
(handlers-macro/merge-fx cofx
(models/upsert-chat {:chat-id chat-id
:is-active true})
(navigate-to-chat chat-id opts))))
(fn [cofx _]
(chat-loading/load-more-messages cofx)))
(handlers/register-handler-fx
:start-chat
[re-frame/trim-v]
(fn [cofx [contact-id opts]]
(start-chat contact-id opts cofx)))
;; TODO(janherich): remove this unnecessary event in the future (only model function `update-chat` will stay)
(handlers/register-handler-fx
:update-chat!
[re-frame/trim-v]
(fn [cofx [chat]]
(models/upsert-chat chat cofx)))
(models/start-chat contact-id opts cofx)))
(defn remove-chat-and-navigate-home [cofx [chat-id]]
(handlers-macro/merge-fx cofx
@ -235,11 +115,10 @@
:on-accept #(re-frame/dispatch [:clear-history])}}))
(defn create-new-public-chat [topic cofx]
(handlers-macro/merge-fx
cofx
(models/add-public-chat topic)
(navigate-to-chat topic {:navigation-replace? true})
(public-chat/join-public-chat topic)))
(handlers-macro/merge-fx cofx
(models/add-public-chat topic)
(models/navigate-to-chat topic {:navigation-replace? true})
(public-chat/join-public-chat topic)))
(handlers/register-handler-fx
:create-new-public-chat
@ -268,12 +147,11 @@
{:db (assoc db :group/selected-contacts #{})}
(models/add-group-chat random-id chat-name (:current-public-key db) selected-contacts)
(navigation/navigate-to-cofx :home nil)
(navigate-to-chat random-id {})
(models/navigate-to-chat random-id {})
(transport.message/send (group-chat/GroupAdminUpdate. chat-name selected-contacts) random-id)))))
(defn show-profile [identity {:keys [db]}]
(navigation/navigate-to-cofx
:profile nil {:db (assoc db :contacts/identity identity)}))
(navigation/navigate-to-cofx :profile nil {:db (assoc db :contacts/identity identity)}))
(handlers/register-handler-fx
:show-profile

View File

@ -8,6 +8,7 @@
[status-im.chat.models.input :as input-model]
[status-im.chat.models.message :as message-model]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.input :as commands-input]
[status-im.chat.commands.sending :as commands-sending]
[status-im.ui.components.react :as react-comp]
[status-im.utils.handlers :as handlers]
@ -63,14 +64,14 @@
(fn [{:keys [db] :as cofx} [command params metadata]]
(handlers-macro/merge-fx cofx
(input-model/set-chat-input-metadata metadata)
(commands/select-chat-input-command command params)
(commands-input/select-chat-input-command command params)
(chat-input-focus :input-ref))))
(handlers/register-handler-fx
:set-command-parameter
[re-frame/trim-v]
(fn [cofx [last-param? index value]]
(commands/set-command-parameter last-param? index value cofx)))
(commands-input/set-command-parameter last-param? index value cofx)))
(handlers/register-handler-fx
:chat-input-focus
@ -119,7 +120,7 @@
(fn [{{:keys [current-chat-id id->command access-scope->command-id] :as db} :db :as cofx} _]
(when-not (get-in db [:chat-ui-props current-chat-id :sending-in-progress?])
(let [input-text (get-in db [:chats current-chat-id :input-text])
command (commands/selected-chat-command
command (commands-input/selected-chat-command
input-text nil (commands/chat-commands id->command
access-scope->command-id
(get-in db [:chats current-chat-id])))]

View File

@ -1,14 +1,17 @@
(ns status-im.chat.models
(:require [status-im.data-store.chats :as chats-store]
[status-im.data-store.messages :as messages-store]
[status-im.data-store.user-statuses :as user-statuses-store]
[status-im.transport.message.core :as transport.message]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.message.v1.group-chat :as transport.group-chat]
[status-im.transport.utils :as transport.utils]
[status-im.ui.components.styles :as styles]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.clocks :as utils.clocks]
[status-im.utils.datetime :as time]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.platform :as platform]))
[status-im.utils.handlers-macro :as handlers-macro]))
(defn multi-user-chat? [chat-id cofx]
(get-in cofx [:db :chats chat-id :group-chat]))
@ -76,7 +79,9 @@
:group-admin admin
:contacts participants} cofx))
(defn clear-history [chat-id {:keys [db] :as cofx}]
(defn clear-history
"Clears history of the particular chat"
[chat-id {:keys [db] :as cofx}]
(let [{:keys [messages
deleted-at-clock-value]} (get-in db [:chats chat-id])
last-message-clock-value (or (->> messages
@ -102,11 +107,10 @@
(transport.utils/unsubscribe-from-chat chat-id cofx)))
(defn- deactivate-chat [chat-id {:keys [db now] :as cofx}]
(cond-> (assoc-in {:db db
:data-store/tx [(chats-store/deactivate-chat-tx chat-id now)]}
[:db :chats chat-id :is-active] false)
platform/desktop?
(assoc-in [:db :current-chat-id] nil)))
{:db (-> db
(assoc-in [:chats chat-id :is-active] false)
(assoc-in [:current-chat-id] nil))
:data-store/tx [(chats-store/deactivate-chat-tx chat-id now)]})
;; TODO: There's a race condition here, as the removal of the filter (async)
;; is done at the same time as the removal of the chat, so a message
@ -114,7 +118,9 @@
;; (remove chat only after the filter has been removed, probably the safest,
;; flag the chat to ignore new messages, change receive method for public/group chats)
;; For now to keep the code simplier and avoid significant changes, best to leave as it is.
(defn remove-chat [chat-id {:keys [db now] :as cofx}]
(defn remove-chat
"Removes chat completely from app, producing all necessary effects for that"
[chat-id {:keys [db now] :as cofx}]
(letfn [(remove-transport-fx [chat-id cofx]
(when (multi-user-chat? chat-id cofx)
(remove-transport chat-id cofx)))]
@ -124,7 +130,64 @@
(deactivate-chat chat-id)
(clear-history chat-id))))
(defn bot-only-chat? [db chat-id]
(let [{:keys [group-chat contacts]} (get-in db [:chats chat-id])]
(and (not group-chat)
(get-in db [:contacts/contacts (first contacts) :dapp?]))))
(defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}]
(when (not (get-in db [:chats chat-id :public?]))
(transport.message/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
;; TODO (janherich) - ressurect `constants/system` messages for group chats in the future
(defn mark-messages-seen
"Marks all unviewed loaded messages as seen in particular chat"
[chat-id {:keys [db] :as cofx}]
(when-let [all-unviewed-ids (seq (get-in db [:chats chat-id :unviewed-messages]))]
(let [me (:current-public-key db)
updated-statuses (keep (fn [message-id]
(some-> db
(get-in [:chats chat-id :message-statuses
message-id me])
(assoc :status :seen)))
all-unviewed-ids)
loaded-unviewed-ids (map :message-id updated-statuses)]
(when (seq loaded-unviewed-ids)
(handlers-macro/merge-fx
cofx
{:db (-> (reduce (fn [acc {:keys [message-id status]}]
(assoc-in acc [:chats chat-id :message-statuses
message-id me :status]
status))
db
updated-statuses)
(update-in [:chats chat-id :unviewed-messages]
#(apply disj % loaded-unviewed-ids)))
:data-store/tx [(user-statuses-store/save-statuses-tx updated-statuses)]}
(send-messages-seen chat-id loaded-unviewed-ids))))))
(defn- preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
[chat-id {:keys [db] :as cofx}]
(handlers-macro/merge-fx cofx
{:db (-> (assoc db :current-chat-id chat-id)
(set-chat-ui-props {:validation-messages nil}))}
(mark-messages-seen chat-id)))
(defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
[chat-id {:keys [navigation-replace?]} cofx]
(if navigation-replace?
(handlers-macro/merge-fx cofx
(navigation/navigate-reset {:index 1
:actions [{:routeName :home}
{:routeName :chat}]})
(preload-chat-data chat-id))
(handlers-macro/merge-fx cofx
(navigation/navigate-to-cofx :chat {})
(preload-chat-data chat-id))))
(defn start-chat
"Start a chat, making sure it exists"
[chat-id opts {:keys [db] :as cofx}]
;; don't allow to open chat with yourself
(when (not= (:current-public-key db) chat-id)
(handlers-macro/merge-fx cofx
(upsert-chat {:chat-id chat-id
:is-active true})
(navigate-to-chat chat-id opts))))

View File

@ -1,10 +1,12 @@
(ns status-im.models.chat
(ns status-im.chat.models.loading
(:require [clojure.set :as set]
[status-im.chat.commands.core :as commands]
[status-im.chat.models.message :as models.message]
[status-im.constants :as constants]
[status-im.data-store.contacts :as contacts-store]
[status-im.data-store.user-statuses :as user-statuses-store]
[status-im.utils.contacts :as utils.contacts]
[status-im.chat.commands.core :as commands]
[status-im.chat.models :as chat-model]
[status-im.utils.datetime :as time]
[status-im.utils.handlers-macro :as handlers-macro]))
(def index-messages (partial into {} (map (juxt :message-id identity))))
@ -33,20 +35,42 @@
{:db (update db :contacts/contacts merge contacts-to-add)
:data-store/tx [(contacts-store/save-contacts-tx (vals contacts-to-add))]}))
(defn- group-chat-messages
(defn- sort-references
"Sorts message-references sequence primary by clock value,
breaking ties by `:message-id`"
[messages message-references]
(sort-by (juxt (comp :clock-value (partial get messages) :message-id)
:message-id)
message-references))
(defn group-chat-messages
"Takes chat-id, new messages + cofx and properly groups them
into the `:message-groups`index in db"
[chat-id messages {:keys [db]}]
{:db (reduce (fn [db [datemark grouped-messages]]
(update-in db [:chats chat-id :message-groups datemark]
(fn [message-references]
(->> grouped-messages
(map (fn [{:keys [message-id timestamp]}]
{:message-id message-id
:timestamp-str (time/timestamp->time timestamp)}))
(into (or message-references '()))
(sort-references (get-in db [:chats chat-id :messages]))))))
db
(group-by (comp time/day-relative :timestamp)
(filter :show? messages)))})
(defn- group-messages
[{:keys [db]}]
(reduce-kv (fn [fx chat-id {:keys [messages]}]
(models.message/group-messages chat-id (vals messages) fx))
(group-chat-messages chat-id (vals messages) fx))
{:db db}
(:chats db)))
(defn initialize-chats [{:keys [db
default-dapps
all-stored-chats
get-stored-messages
get-stored-user-statuses
get-stored-unviewed-messages
stored-message-ids] :as cofx}]
(defn initialize-chats
"Initialize all persisted chats on startup"
[{:keys [db default-dapps all-stored-chats get-stored-messages get-stored-user-statuses
get-stored-unviewed-messages stored-message-ids] :as cofx}]
(let [stored-unviewed-messages (get-stored-unviewed-messages (:current-public-key db))
chats (reduce (fn [acc {:keys [chat-id] :as chat}]
(let [chat-messages (index-messages (get-stored-messages chat-id))
@ -65,11 +89,11 @@
{:db (assoc db
:chats chats
:contacts/dapps default-dapps)}
(group-chat-messages)
(group-messages)
(add-default-contacts)
(commands/load-commands commands/register))))
(defn process-pending-messages
(defn initialize-pending-messages
"Change status of own messages which are still in `sending` status to `not-sent`
(If signal from status-go has not been received)"
[{:keys [db]}]
@ -89,3 +113,26 @@
status))
db
updated-statuses)}))
(defn load-more-messages
"Loads more messages for current chat"
[{{:keys [current-chat-id] :as db} :db
get-stored-messages :get-stored-messages
get-stored-user-statuses :get-stored-user-statuses :as cofx}]
(when-not (get-in db [:chats current-chat-id :all-loaded?])
(let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
new-messages (get-stored-messages current-chat-id loaded-count)
indexed-messages (index-messages new-messages)
new-message-ids (keys indexed-messages)
new-statuses (get-stored-user-statuses current-chat-id new-message-ids)]
(handlers-macro/merge-fx
cofx
{:db (-> db
(update-in [:chats current-chat-id :messages] merge indexed-messages)
(update-in [:chats current-chat-id :message-statuses] merge new-statuses)
(update-in [:chats current-chat-id :not-loaded-message-ids]
#(apply disj % new-message-ids))
(assoc-in [:chats current-chat-id :all-loaded?]
(> constants/default-number-of-messages (count new-messages))))}
(group-chat-messages current-chat-id new-messages)
(chat-model/mark-messages-seen current-chat-id)))))

View File

@ -7,6 +7,7 @@
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.datetime :as time]
[status-im.chat.models :as chat-model]
[status-im.chat.models.loading :as chat-loading]
[status-im.chat.models.input :as input]
[status-im.chat.commands.receiving :as commands-receiving]
[status-im.utils.clocks :as utils.clocks]
@ -18,7 +19,6 @@
[status-im.transport.message.v1.protocol :as protocol]
[status-im.data-store.messages :as messages-store]
[status-im.data-store.user-statuses :as user-statuses-store]
[status-im.utils.datetime :as datetime]
[clojure.string :as string]))
(def receive-interceptors
@ -57,31 +57,6 @@
(assoc groups new-datemark message-refs))))
{}))}))
(defn- sort-references
"Sorts message-references sequence primary by clock value,
breaking ties by `:message-id`"
[messages message-references]
(sort-by (juxt (comp :clock-value (partial get messages) :message-id)
:message-id)
message-references))
(defn- group-messages
"Takes chat-id, new messages + cofx and properly groups them
into the `:message-groups`index in db"
[chat-id messages {:keys [db]}]
{:db (reduce (fn [db [datemark grouped-messages]]
(update-in db [:chats chat-id :message-groups datemark]
(fn [message-references]
(->> grouped-messages
(map (fn [{:keys [message-id timestamp]}]
{:message-id message-id
:timestamp-str (time/timestamp->time timestamp)}))
(into (or message-references '()))
(sort-references (get-in db [:chats chat-id :messages]))))))
db
(group-by (comp time/day-relative :timestamp)
(filter :show? messages)))})
(defn- add-own-status
[chat-id message-id status {:keys [db]}]
(let [me (:current-public-key db)
@ -115,7 +90,7 @@
(handlers-macro/merge-fx cofx
fx
(re-index-message-groups chat-id)
(group-messages chat-id [message]))))))
(chat-loading/group-chat-messages chat-id [message]))))))
(def ^:private- add-single-message (partial add-message false))
(def ^:private- add-batch-message (partial add-message true))
@ -175,7 +150,6 @@
(display-notification chat-id)
(send-message-seen chat-id message-id (and (not public?)
current-chat?
(not (chat-model/bot-only-chat? db chat-id))
(not (= constants/system from))
(not (:outgoing message)))))))
@ -211,7 +185,7 @@
(fn [chat-id cofx]
(handlers-macro/merge-fx cofx
(re-index-message-groups chat-id)
(group-messages chat-id (get chat->message chat-id))))
(chat-loading/group-chat-messages chat-id (get chat->message chat-id))))
chat-ids)))
(defn system-message [chat-id message-id timestamp content]

View File

@ -11,7 +11,6 @@
(s/def :chat/chat-list-ui-props (s/nilable map?))
(s/def :chat/layout-height (s/nilable number?)) ; height of chat's view layout
(s/def :chat/selected-participants (s/nilable set?))
(s/def :chat/chat-loaded-callbacks (s/nilable map?))
(s/def :chat/public-group-topic (s/nilable string?))
(s/def :chat/public-group-topic-error (s/nilable string?))
(s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id

View File

@ -4,11 +4,11 @@
[status-im.chat.constants :as chat-constants]
[status-im.chat.models.input :as input-model]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.input :as commands-input]
[status-im.utils.datetime :as time]
[status-im.utils.platform :as platform]
[status-im.utils.gfycat.core :as gfycat]
[status-im.i18n :as i18n]
[status-im.constants :as const]
[status-im.models.transactions :as transactions]))
(reg-sub :get-chats :chats)
@ -76,12 +76,8 @@
platform/ios? kb-height
:default 0)))
(defn- active-chat? [[_ chat]]
(and (:is-active chat)
(not= const/console-chat-id (:chat-id chat))))
(defn active-chats [chats]
(into {} (filter active-chat? chats)))
(into {} (filter (comp :is-active second) chats)))
(reg-sub
:get-active-chats
@ -257,7 +253,7 @@
:<- [:get-current-chat-ui-prop :selection]
:<- [:get-commands-for-chat]
(fn [[{:keys [input-text]} selection commands]]
(commands/selected-chat-command input-text selection commands)))
(commands-input/selected-chat-command input-text selection commands)))
(reg-sub
:chat-input-placeholder

View File

@ -1,10 +0,0 @@
(ns status-im.chat.views.message.datemark
(:require [status-im.ui.components.react :as react]
[clojure.string :as str]
[status-im.chat.styles.message.datemark :as st]))
(defn chat-datemark [value]
[react/view st/datemark-wrapper
[react/view st/datemark
[react/text {:style st/datemark-text}
(str/capitalize value)]]])

View File

@ -27,8 +27,6 @@
(def default-number-of-messages 20)
(def blocks-per-hour 120)
(def console-chat-id "console")
(def inbox-password "status-offline-inbox")
(def default-network config/default-network)

View File

@ -1,5 +1,6 @@
(ns status-im.init.core
(:require [re-frame.core :as re-frame]
[status-im.chat.models.loading :as chat-loading]
[status-im.accounts.login.core :as accounts.login]
[status-im.accounts.update.core :as accounts.update]
[status-im.constants :as constants]
@ -7,7 +8,6 @@
[status-im.data-store.realm.core :as realm]
[status-im.i18n :as i18n]
[status-im.models.browser :as browser]
[status-im.models.chat :as chat]
[status-im.models.contacts :as models.contacts]
[status-im.models.dev-server :as models.dev-server]
[status-im.protocol.core :as protocol]
@ -143,7 +143,6 @@
network network-status peers-count peers-summary view-id navigation-stack
status-module-initialized? status-node-started? device-UUID semaphores]
:or {network (get app-db :network)}} db
console-contact (get contacts constants/console-chat-id)
current-account (get accounts address)
account-network-id (get current-account :network network)
account-network (get-in current-account [:networks account-network-id])]
@ -166,9 +165,7 @@
:semaphores semaphores
:web3 web3)
(= view-id :create-account)
(assoc-in [:accounts/create :step] :enter-name)
console-contact
(assoc :contacts/contacts {constants/console-chat-id console-contact}))}))
(assoc-in [:accounts/create :step] :enter-name))}))
(defn initialize-wallet [cofx]
(when-not platform/desktop?
@ -196,8 +193,8 @@
(protocol/initialize-protocol address)
(models.contacts/load-contacts)
(models.dev-server/start-if-needed)
(chat/initialize-chats)
(chat/process-pending-messages)
(chat-loading/initialize-chats)
(chat-loading/initialize-pending-messages)
(browser/initialize-browsers)
(browser/initialize-dapp-permissions)
(initialize-wallet)

View File

@ -3,7 +3,9 @@
[re-frame.core :as re-frame]
[status-im.react-native.js-dependencies :as rn]
[taoensso.timbre :as log]
[status-im.utils.platform :as platform]))
[status-im.chat.models :as chat-model]
[status-im.utils.platform :as platform]
[status-im.utils.handlers-macro :as handlers-macro]))
;; Work in progress namespace responsible for push notifications and interacting
;; with Firebase Cloud Messaging.
@ -86,8 +88,9 @@
;; TODO(yenda) why do we ignore the notification if
;; it is not for the current account ?
(when (= to current-public-key)
{:db (update db :push-notifications/stored dissoc to)
:dispatch [:navigate-to-chat from]})
(handlers-macro/merge-fx cofx
{:db (update db :push-notifications/stored dissoc to)}
(chat-model/navigate-to-chat from nil)))
(store-event event cofx))))
(defn parse-notification-payload [s]

View File

@ -5,7 +5,7 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.chat-icon.styles :as styles]
[status-im.ui.components.styles :as components.styles]
[status-im.chat.views.photos :as photos]
[status-im.ui.screens.chat.photos :as photos]
[status-im.react-native.resources :as resources]))
(defn default-chat-icon [name styles]

View File

@ -13,7 +13,7 @@
[status-im.i18n :as i18n]
[status-im.ui.components.react :as components]
[status-im.ui.components.common.common :as components.common]
[status-im.chat.views.photos :as photos]
[status-im.ui.screens.chat.photos :as photos]
[re-frame.core :as re-frame]
[cljs.spec.alpha :as spec]
[status-im.utils.platform :as platform]

View File

@ -2,7 +2,7 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.views.photos :as photos]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.accounts.styles :as styles]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.status-bar.view :as status-bar]

View File

@ -1,19 +1,10 @@
(ns status-im.ui.screens.add-new.new-public-chat.db
(:require [clojure.string :as string]
[cljs.spec.alpha :as spec]
[status-im.constants :as constants]
[status-im.utils.homoglyph :as utils]
status-im.utils.db))
(defn- legal-name? [username]
(let [username (some-> username string/trim)]
(not (utils/matches username constants/console-chat-id))))
(spec/def ::legal-name legal-name?)
(spec/def ::name (spec/and :global/not-empty-string
::legal-name))
(spec/def ::name :global/not-empty-string)
(spec/def ::topic (spec/and :global/not-empty-string
::legal-name
(partial re-matches #"[a-z0-9\-]+")))

View File

@ -1,4 +1,4 @@
(ns status-im.chat.views.actions
(ns status-im.ui.screens.chat.actions
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]))

View File

@ -1,14 +1,14 @@
(ns status-im.chat.views.bottom-info
(ns status-im.ui.screens.chat.bottom-info
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[clojure.string :as string]
[status-im.ui.components.react :as react]
[status-im.chat.styles.screen :as styles]
[status-im.ui.screens.chat.styles.main :as styles]
[status-im.i18n :as i18n]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.list.views :as list]
[status-im.chat.views.photos :as photos]
[status-im.ui.screens.chat.photos :as photos]
[status-im.utils.core :as utils]
[status-im.utils.identicon :as identicon]))

View File

@ -1,10 +1,10 @@
(ns status-im.chat.views.input.animations.expandable
(ns status-im.ui.screens.chat.input.animations.expandable
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [reagent.core :as reagent]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.react :as react]
[status-im.chat.styles.animations :as style]
[status-im.chat.styles.input.input :as input-style]
[status-im.ui.screens.chat.styles.animations :as style]
[status-im.ui.screens.chat.styles.input.input :as input-style]
[status-im.utils.platform :as platform]))
(def top-offset 100)

View File

@ -1,14 +1,14 @@
(ns status-im.chat.views.input.input
(ns status-im.ui.screens.chat.input.input
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[status-im.chat.constants :as constants]
[status-im.chat.styles.input.input :as style]
[status-im.chat.views.input.parameter-box :as parameter-box]
[status-im.chat.views.input.send-button :as send-button]
[status-im.chat.views.input.suggestions :as suggestions]
[status-im.chat.views.input.validation-messages :as validation-messages]
[status-im.ui.screens.chat.styles.input.input :as style]
[status-im.ui.screens.chat.input.parameter-box :as parameter-box]
[status-im.ui.screens.chat.input.send-button :as send-button]
[status-im.ui.screens.chat.input.suggestions :as suggestions]
[status-im.ui.screens.chat.input.validation-messages :as validation-messages]
[status-im.i18n :as i18n]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.colors :as colors]

View File

@ -1,9 +1,8 @@
(ns status-im.chat.views.input.parameter-box
(ns status-im.ui.screens.chat.input.parameter-box
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.chat.views.input.animations.expandable :as expandable]
[status-im.chat.styles.input.parameter-box :as style]
[status-im.ui.components.react :as react]
[taoensso.timbre :as log]))
(:require [status-im.ui.screens.chat.input.animations.expandable :as expandable]
[status-im.ui.screens.chat.styles.input.parameter-box :as style]
[status-im.ui.components.react :as react]))
(defview parameter-box-container []
(letsubs [parameter-box [:chat-parameter-box]]

View File

@ -1,12 +1,11 @@
(ns status-im.chat.views.input.send-button
(ns status-im.ui.screens.chat.input.send-button
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.styles.input.send-button :as style]
[status-im.ui.screens.chat.styles.input.send-button :as style]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vi]
[status-im.utils.utils :as utils]))
[status-im.ui.components.icons.vector-icons :as vi]))
(defn send-button-view-on-update [{:keys [spin-value command-completion]}]
(fn [_]

View File

@ -1,12 +1,10 @@
(ns status-im.chat.views.input.suggestions
(ns status-im.ui.screens.chat.input.suggestions
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[status-im.chat.styles.input.suggestions :as style]
[status-im.chat.views.input.animations.expandable :as expandable]
[status-im.chat.commands.core :as commands]
[status-im.i18n :as i18n]
[taoensso.timbre :as log]))
[status-im.ui.screens.chat.styles.input.suggestions :as style]
[status-im.ui.screens.chat.input.animations.expandable :as expandable]
[status-im.chat.commands.core :as commands]))
(defn suggestion-item [{:keys [on-press name description last? accessibility-label]}]
[react/touchable-highlight (cond-> {:on-press on-press}

View File

@ -1,4 +1,4 @@
(ns status-im.chat.views.input.utils
(ns status-im.ui.screens.chat.input.utils
(:require [taoensso.timbre :as log]
[status-im.ui.components.toolbar.styles :as toolbar-st]
[status-im.utils.platform :as p]))

View File

@ -1,8 +1,8 @@
(ns status-im.chat.views.input.validation-messages
(ns status-im.ui.screens.chat.input.validation-messages
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[status-im.chat.styles.input.validation-message :as style]
[status-im.ui.screens.chat.styles.input.validation-message :as style]
[status-im.i18n :as i18n]))
(defn validation-message [{:keys [title description]}]

View File

@ -0,0 +1,10 @@
(ns status-im.ui.screens.chat.message.datemark
(:require [status-im.ui.components.react :as react]
[clojure.string :as string]
[status-im.ui.screens.chat.styles.message.datemark :as style]))
(defn chat-datemark [value]
[react/view style/datemark-wrapper
[react/view style/datemark
[react/text {:style style/datemark-text}
(string/capitalize value)]]])

View File

@ -1,4 +1,4 @@
(ns status-im.chat.views.message.message
(ns status-im.ui.screens.chat.message.message
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
@ -9,9 +9,9 @@
[status-im.ui.components.action-sheet :as action-sheet]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.receiving :as commands-receiving]
[status-im.chat.styles.message.message :as style]
[status-im.chat.styles.message.command-pill :as pill-style]
[status-im.chat.views.photos :as photos]
[status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.styles.message.command-pill :as pill-style]
[status-im.ui.screens.chat.photos :as photos]
[status-im.constants :as constants]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.utils.core :as utils]

View File

@ -1,18 +1,11 @@
(ns status-im.chat.views.message.options
(ns status-im.ui.screens.chat.message.options
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[clojure.string :as string]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]
[status-im.chat.views.bottom-info :as bottom-info]
[status-im.chat.styles.screen :as styles]
[status-im.chat.styles.message.options :as options.styles]
[status-im.i18n :as i18n]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.list.views :as list]
[status-im.utils.core :as utils]
[status-im.utils.identicon :as identicon]
[status-im.ui.screens.chat.bottom-info :as bottom-info]
[status-im.ui.screens.chat.styles.main :as styles]
[status-im.ui.screens.chat.styles.message.options :as options.styles]
[status-im.ui.components.icons.vector-icons :as vector-icons]))
(defn action-item [{:keys [label icon style on-press]}]

View File

@ -1,7 +1,7 @@
(ns status-im.chat.views.photos
(ns status-im.ui.screens.chat.photos
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react]
[status-im.chat.styles.photos :as style]
[status-im.ui.screens.chat.styles.photos :as style]
[status-im.utils.identicon :as identicon]
[clojure.string :as string]
[status-im.react-native.resources :as resources]))

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.animations
(ns status-im.ui.screens.chat.styles.animations
(:require [status-im.ui.components.styles :as common]))
(def header-draggable-icon "rgba(73, 84, 93, 0.23)")

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.input.input
(ns status-im.ui.screens.chat.styles.input.input
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.colors :as colors]))

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.input.parameter-box
(ns status-im.ui.screens.chat.styles.input.parameter-box
(:require [status-im.ui.components.styles :as common]
[status-im.ui.components.colors :as colors]))

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.input.send-button
(ns status-im.ui.screens.chat.styles.input.send-button
(:require [status-im.ui.components.colors :as colors]))
(defn send-message-container [rotation]

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.input.suggestions
(ns status-im.ui.screens.chat.styles.input.suggestions
(:require-macros [status-im.utils.styles :refer [defnstyle]])
(:require [status-im.ui.components.styles :as common]
[status-im.ui.components.colors :as colors]

View File

@ -1,6 +1,5 @@
(ns status-im.chat.styles.input.validation-message
(:require [status-im.chat.constants :as constants]
[status-im.ui.components.styles :as common]))
(ns status-im.ui.screens.chat.styles.input.validation-message
(:require [status-im.ui.components.styles :as common]))
(defn root [bottom]
{:flex-direction :column
@ -20,4 +19,4 @@
(def message-description
{:color common/color-white
:font-size 12
:opacity 0.9})
:opacity 0.9})

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.screen
(ns status-im.ui.screens.chat.styles.main
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.styles :as component.styles]
[status-im.ui.components.colors :as colors]))

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.message.command-pill
(ns status-im.ui.screens.chat.styles.message.command-pill
(:require [status-im.utils.platform :as p]
[status-im.ui.components.styles :refer [color-white]]))

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.message.datemark
(ns status-im.ui.screens.chat.styles.message.datemark
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.ui.components.styles :as common]))

View File

@ -1,8 +1,8 @@
(ns status-im.chat.styles.message.message
(ns status-im.ui.screens.chat.styles.message.message
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.styles :as styles]
[status-im.chat.styles.photos :as photos]
[status-im.ui.components.colors :as colors]
[status-im.ui.screens.chat.styles.photos :as photos]
[status-im.utils.platform :as platform]
[status-im.constants :as constants]))

View File

@ -1,8 +1,7 @@
(ns status-im.chat.styles.message.options
(ns status-im.ui.screens.chat.styles.message.options
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.styles :as styles]
[status-im.ui.components.colors :as colors]
[status-im.constants :as constants]))
[status-im.ui.components.colors :as colors]))
(defstyle row
{:flex-direction :row

View File

@ -1,4 +1,4 @@
(ns status-im.chat.styles.photos
(ns status-im.ui.screens.chat.styles.photos
(:require [status-im.ui.components.colors :as colors]))
(def default-size 36)

View File

@ -1,31 +1,26 @@
(ns status-im.chat.views.toolbar-content
(ns status-im.ui.screens.chat.toolbar-content
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[cljs-time.core :as t]
[status-im.ui.components.react :as react]
[status-im.i18n :as i18n]
[status-im.chat.views.photos :as photos]
[status-im.chat.styles.screen :as st]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.styles.main :as st]
[status-im.utils.datetime :as time]
[status-im.utils.platform :refer [platform-specific]]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.constants :refer [console-chat-id]]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.components.styles :as common.styles]))
(defn- online-text [contact chat-id]
(cond
(= console-chat-id chat-id) (i18n/label :t/available)
contact (let [last-online (get contact :last-online)
last-online-date (time/to-date last-online)
now-date (t/now)]
(if (and (pos? last-online)
(<= last-online-date now-date))
(time/time-ago last-online-date)
(i18n/label :t/active-unknown)))
:else (i18n/label :t/active-unknown)))
(if contact
(let [last-online (get contact :last-online)
last-online-date (time/to-date last-online)
now-date (t/now)]
(if (and (pos? last-online)
(<= last-online-date now-date))
(time/time-ago last-online-date)
(i18n/label :t/active-unknown)))
(i18n/label :t/active-unknown)))
(defn- in-progress-text [{:keys [highestBlock currentBlock startBlock]}]
(let [total (- highestBlock startBlock)

View File

@ -1,23 +1,18 @@
(ns status-im.chat.screen
(ns status-im.ui.screens.chat.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.chat.models :as models.chat]
[status-im.models.contact :as models.contact]
[status-im.chat.styles.screen :as style]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.utils.platform :as platform]
[status-im.chat.views.toolbar-content :as toolbar-content]
[status-im.chat.views.message.message :as message]
[status-im.chat.views.message.datemark :as message-datemark]
[status-im.chat.views.input.input :as input]
[status-im.chat.views.actions :as actions]
[status-im.chat.views.bottom-info :as bottom-info]
[status-im.chat.views.message.options :as message-options]
[status-im.chat.views.message.datemark :as message-datemark]
[status-im.chat.views.message.message :as message]
[status-im.chat.views.toolbar-content :as toolbar-content]
[status-im.ui.screens.chat.input.input :as input]
[status-im.ui.screens.chat.actions :as actions]
[status-im.ui.screens.chat.bottom-info :as bottom-info]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.message.options :as message-options]
[status-im.ui.screens.chat.message.datemark :as message-datemark]
[status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.list-selection :as list-selection]
@ -56,19 +51,17 @@
{:keys [group-chat chat-id contacts]} [:get-current-chat]]
[react/view
[status-bar/status-bar]
(if (= chat-id constants/console-chat-id)
[toolbar/simple-toolbar name]
[toolbar/platform-agnostic-toolbar {}
(toolbar/nav-back-count {:home? true})
[toolbar-content/toolbar-content-view]
[toolbar/actions [{:icon :icons/wallet
:icon-opts {:color :black
:accessibility-label :wallet-modal-button}
:handler #(re-frame/dispatch [:navigate-to :wallet-modal])}
{:icon :icons/options
:icon-opts {:color :black
:accessibility-label :chat-menu-button}
:handler #(on-options chat-id name group-chat public?)}]]])
[toolbar/platform-agnostic-toolbar {}
(toolbar/nav-back-count {:home? true})
[toolbar-content/toolbar-content-view]
[toolbar/actions [{:icon :icons/wallet
:icon-opts {:color :black
:accessibility-label :wallet-modal-button}
:handler #(re-frame/dispatch [:navigate-to :wallet-modal])}
{:icon :icons/options
:icon-opts {:color :black
:accessibility-label :chat-menu-button}
:handler #(on-options chat-id name group-chat public?)}]]]
(when-not (or public? group-chat) [add-contact-bar (first contacts)])]))
(defmulti message-row (fn [{{:keys [type]} :row}] type))
@ -113,16 +106,10 @@
(when one-to-one
[vector-icons/icon :icons/lock])
[react/text {:style style/empty-chat-text}
(cond
(= chat-id constants/console-chat-id)
(i18n/label :t/empty-chat-description-console)
one-to-one
(if one-to-one
[react/text style/empty-chat-container-one-to-one
(i18n/label :t/empty-chat-description-one-to-one)
[react/text {:style style/empty-chat-text-name} (:name contact)]]
:else
(i18n/label :t/empty-chat-description))]])))
(defview messages-view [group-chat]

View File

@ -1,6 +1,6 @@
(ns status-im.ui.screens.contacts.events
(:require [re-frame.core :as re-frame]
[status-im.chat.events :as chat.events]
[status-im.chat.models :as chat.models]
[status-im.i18n :as i18n]
[status-im.models.contact :as models.contact]
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db]
@ -15,7 +15,7 @@
(handlers-macro/merge-fx
cofx
(models.contact/add-contact whisper-id)
(chat.events/start-chat whisper-id {:navigation-replace? true})))
(chat.models/start-chat whisper-id {:navigation-replace? true})))
(re-frame/reg-cofx
:get-default-contacts

View File

@ -272,7 +272,6 @@
:chat/message-data
:chat/message-status
:chat/selected-participants
:chat/chat-loaded-callbacks
:chat/public-group-topic
:chat/public-group-topic-error
:chat/messages

View File

@ -3,8 +3,8 @@
(:require [re-frame.core :as re-frame]
[status-im.ui.components.icons.vector-icons :as icons]
[clojure.string :as string]
[status-im.chat.styles.message.message :as message.style]
[status-im.chat.views.message.message :as message]
[status-im.ui.screens.chat.styles.message.message :as message.style]
[status-im.ui.screens.chat.message.message :as message]
[status-im.utils.gfycat.core :as gfycat.core]
[taoensso.timbre :as log]
[reagent.core :as reagent]
@ -16,7 +16,7 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.colors :as colors]
[status-im.chat.views.message.datemark :as message.datemark]
[status-im.ui.screens.chat.message.datemark :as message.datemark]
[status-im.ui.screens.desktop.main.tabs.profile.views :as profile.views]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.screens.desktop.main.chat.styles :as styles]

View File

@ -2,14 +2,12 @@
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.chat.models.message :as models.message]
[status-im.chat.models :as models.chat]
[status-im.ui.screens.navigation :as navigation]
[status-im.transport.message.v1.group-chat :as group-chat]
[status-im.transport.message.core :as transport]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.data-store.chats :as chats-store]
[status-im.data-store.messages :as messages-store]))
[status-im.data-store.chats :as chats-store]))
(handlers/register-handler-fx
:show-group-chat-profile

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.home.views
(:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
@ -44,20 +43,18 @@
inner-item-view (if (:chat-id home-item)
inner-item/home-list-chat-item-inner-view
inner-item/home-list-browser-item-inner-view)
is-deletable? (not= (:chat-id home-item) constants/console-chat-id)
offset-x (animation/create-value (if (and is-deletable? swiped?) styles/delete-button-width 0))
offset-x (animation/create-value (if swiped? styles/delete-button-width 0))
swipe-pan-responder (responder/swipe-pan-responder offset-x styles/delete-button-width home-item-id swiped?)
swipe-pan-handler (if is-deletable? (responder/pan-handlers swipe-pan-responder) {})]
swipe-pan-handler (responder/pan-handlers swipe-pan-responder)]
[react/view (assoc swipe-pan-handler :accessibility-label :chat-item)
[react/animated-view {:style {:flex 1 :right offset-x}}
[inner-item-view home-item]
(when is-deletable?
[react/touchable-highlight {:style styles/delete-icon-highlight
:on-press #(do
(re-frame/dispatch [:set-swipe-position home-item-id false])
(re-frame/dispatch [delete-action home-item-id]))}
[react/view {:style styles/delete-icon-container}
[vector-icons/icon :icons/delete {:color colors/red}]]])]])))
[react/touchable-highlight {:style styles/delete-icon-highlight
:on-press #(do
(re-frame/dispatch [:set-swipe-position home-item-id false])
(re-frame/dispatch [delete-action home-item-id]))}
[react/view {:style styles/delete-icon-container}
[vector-icons/icon :icons/delete {:color colors/red}]]]]])))
;;do not remove view-id and will-update or will-unmount handlers, this is how it works
(views/defview welcome [view-id]

View File

@ -90,12 +90,11 @@
timestamp]}]
(letsubs [last-message [:get-last-message chat-id]
chat-name [:get-chat-name chat-id]]
(let [hide-dapp? (= chat-id const/console-chat-id)
truncated-chat-name (utils/truncate-str chat-name 30)]
(let [truncated-chat-name (utils/truncate-str chat-name 30)]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to-chat chat-id])}
[react/view styles/chat-container
[react/view styles/chat-icon-container
[chat-icon.screen/chat-icon-view-chat-list chat-id group-chat truncated-chat-name color online hide-dapp?]]
[chat-icon.screen/chat-icon-view-chat-list chat-id group-chat truncated-chat-name color online false]]
[react/view styles/chat-info-container
[react/view styles/item-upper-container
[chat-list-item-name truncated-chat-name group-chat public? public-key]

View File

@ -2,14 +2,12 @@
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[status-im.chat.constants :as chat.constants]
[status-im.constants :as constants]
[status-im.utils.homoglyph :as homoglyph]))
(defn correct-name? [username]
(when-let [username (some-> username (string/trim))]
(every? false?
[(string/blank? username)
(homoglyph/matches username constants/console-chat-id)
(string/includes? username chat.constants/command-char)])))
(defn base64-encoded-image-path? [photo-path]

View File

@ -4,8 +4,8 @@
[status-im.ui.components.react :as react]
[status-im.ui.screens.profile.navigation]
[status-im.accounts.update.core :as accounts.update]
[status-im.chat.events :as chat-events]
[status-im.chat.commands.core :as commands]
[status-im.chat.models :as chat-models]
[status-im.chat.commands.input :as commands-input]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.image-processing :as image-processing]
[taoensso.timbre :as log]))
@ -24,8 +24,8 @@
(defn send-transaction [chat-id {:keys [db] :as cofx}]
(let [send-command (get-in db [:id->command ["send" #{:personal-chats}]])]
(handlers-macro/merge-fx cofx
(chat-events/start-chat chat-id {:navigation-replace? true})
(commands/select-chat-input-command send-command nil))))
(chat-models/start-chat chat-id {:navigation-replace? true})
(commands-input/select-chat-input-command send-command nil))))
(defn- valid-name? [name]
(spec/valid? :profile/name name))

View File

@ -13,7 +13,7 @@
[status-im.ui.screens.progress.views :refer [progress]]
[status-im.chat.screen :refer [chat]]
[status-im.ui.screens.chat.views :refer [chat]]
[status-im.ui.screens.add-new.views :refer [add-new]]
[status-im.ui.screens.add-new.new-chat.views :refer [new-chat]]
[status-im.ui.screens.add-new.new-public-chat.view :refer [new-public-chat]]

View File

@ -6,7 +6,7 @@
[status-im.i18n :as i18n]
[status-im.ui.components.bottom-buttons.view :as bottom-buttons]
[status-im.ui.components.button.view :as button]
[status-im.chat.views.photos :as photos]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.list.styles :as list.styles]
[status-im.ui.components.list-selection :as list-selection]

View File

@ -1,31 +1,25 @@
(ns status-im.ui.screens.wallet.request.events
(:require [re-frame.core :as re-frame]
[status-im.ui.screens.wallet.db :as wallet-db]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.chat.commands.core :as commands]
[status-im.utils.money :as money]))
(handlers/register-handler-fx
::wallet-send-chat-request
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [asset amount]]
(handlers-macro/merge-fx cofx
{:dispatch [:send-current-message]}
(commands/select-chat-input-command
(get-in db [:id->command ["request" #{:personal-chats}]]) [asset amount]))))
[status-im.chat.models :as chat-model]
[status-im.chat.commands.sending :as commands-sending]
[status-im.utils.money :as money]
[status-im.utils.handlers-macro :as handlers-macro]))
(handlers/register-handler-fx
:wallet-send-request
[re-frame/trim-v]
(fn [_ [whisper-identity amount symbol decimals]]
(fn [{:keys [db] :as cofx} [_ whisper-identity amount symbol decimals]]
(assert whisper-identity)
;; TODO(janherich) remove this dispatch sequence, there is absolutely no need for that :/
{:dispatch-n [[:navigate-back]
[:navigate-to :home]
[:add-chat-loaded-event whisper-identity
[::wallet-send-chat-request (name symbol) (str (money/internal->formatted amount symbol decimals))]]
[:start-chat whisper-identity]]}))
(let [request-command (get-in db [:id->command ["request" #{:personal-chats}]])]
(handlers-macro/merge-fx cofx
(navigation/navigate-to-clean :home)
(chat-model/start-chat whisper-identity nil)
(commands-sending/send whisper-identity
request-command
{:asset (name symbol)
:amount (str (money/internal->formatted amount symbol decimals))})))))
(handlers/register-handler-fx
:wallet.request/set-and-validate-amount

View File

@ -117,62 +117,3 @@
(core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "contact"})))))))
(deftest selected-chat-command-test
(let [fx (core/load-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})
commands (core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "contact"})]
(testing "Text not beggining with the command special charactes `/` is recognised"
(is (not (core/selected-chat-command "test-command 1" nil commands))))
(testing "Command not matching any available commands is not recognised as well"
(is (not (core/selected-chat-command "/another-test-command" nil commands))))
(testing "Available correctly entered command is recognised"
(is (= TestCommandInstance
(get (core/selected-chat-command "/test-command" nil commands) :type))))
(testing "Command completion and param position are determined as well"
(let [{:keys [current-param-position command-completion]}
(core/selected-chat-command "/test-command 1 " 17 commands)]
(is (= 1 current-param-position))
(is (= :less-then-needed command-completion)))
(let [{:keys [current-param-position command-completion]}
(core/selected-chat-command "/test-command 1 2 3" 20 commands)]
(is (= 2 current-param-position))
(is (= :complete command-completion))))))
(deftest set-command-parameter-test
(testing "Setting command parameter correctly updates the text input"
(let [create-cofx (fn [input-text]
{:db {:chats {"test" {:input-text input-text}}
:current-chat-id "test"}})]
(is (= "/test-command first-value "
(get-in (core/set-command-parameter
false 0 "first-value"
(create-cofx "/test-command"))
[:db :chats "test" :input-text])))
(is (= "/test-command first-value second-value \"last value\""
(get-in (core/set-command-parameter
false 1 "second-value"
(create-cofx "/test-command first-value edited \"last value\""))
[:db :chats "test" :input-text])))
(is (= "/test-command first-value second-value \"last value\""
(get-in (core/set-command-parameter
false 2 "last value"
(create-cofx "/test-command first-value second-value"))
[:db :chats "test" :input-text]))))))
(deftest parse-parameters-test
(testing "testing that parse-parameters work correctly"
(is (= {:first-param "1"
:second-param "2"
:last-param "3"}
(core/parse-parameters test-command-parameters "/test-command 1 2 3")))
(is (= {:first-param "1"
:second-param "2 2"
:last-param "3"}
(core/parse-parameters test-command-parameters "/test-command 1 \"2 2\" 3")))
(is (= {:first-param "1"
:second-param "2"}
(core/parse-parameters test-command-parameters "/test-command 1 2")))
(is (= {}
(core/parse-parameters test-command-parameters "/test-command ")))))

View File

@ -0,0 +1,64 @@
(ns status-im.test.chat.commands.input
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.test.chat.commands.core :as test-core]
[status-im.chat.commands.core :as core]
[status-im.chat.commands.input :as input]))
(deftest selected-chat-command-test
(let [fx (core/load-commands #{test-core/TestCommandInstance test-core/AnotherTestCommandInstance} {:db {}})
commands (core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "contact"})]
(testing "Text not beggining with the command special charactes `/` is recognised"
(is (not (input/selected-chat-command "test-command 1" nil commands))))
(testing "Command not matching any available commands is not recognised as well"
(is (not (input/selected-chat-command "/another-test-command" nil commands))))
(testing "Available correctly entered command is recognised"
(is (= test-core/TestCommandInstance
(get (input/selected-chat-command "/test-command" nil commands) :type))))
(testing "Command completion and param position are determined as well"
(let [{:keys [current-param-position command-completion]}
(input/selected-chat-command "/test-command 1 " 17 commands)]
(is (= 1 current-param-position))
(is (= :less-then-needed command-completion)))
(let [{:keys [current-param-position command-completion]}
(input/selected-chat-command "/test-command 1 2 3" 20 commands)]
(is (= 2 current-param-position))
(is (= :complete command-completion))))))
(deftest set-command-parameter-test
(testing "Setting command parameter correctly updates the text input"
(let [create-cofx (fn [input-text]
{:db {:chats {"test" {:input-text input-text}}
:current-chat-id "test"}})]
(is (= "/test-command first-value "
(get-in (input/set-command-parameter
false 0 "first-value"
(create-cofx "/test-command"))
[:db :chats "test" :input-text])))
(is (= "/test-command first-value second-value \"last value\""
(get-in (input/set-command-parameter
false 1 "second-value"
(create-cofx "/test-command first-value edited \"last value\""))
[:db :chats "test" :input-text])))
(is (= "/test-command first-value second-value \"last value\""
(get-in (input/set-command-parameter
false 2 "last value"
(create-cofx "/test-command first-value second-value"))
[:db :chats "test" :input-text]))))))
(deftest parse-parameters-test
(testing "testing that parse-parameters work correctly"
(is (= {:first-param "1"
:second-param "2"
:last-param "3"}
(input/parse-parameters test-core/test-command-parameters "/test-command 1 2 3")))
(is (= {:first-param "1"
:second-param "2 2"
:last-param "3"}
(input/parse-parameters test-core/test-command-parameters "/test-command 1 \"2 2\" 3")))
(is (= {:first-param "1"
:second-param "2"}
(input/parse-parameters test-core/test-command-parameters "/test-command 1 2")))
(is (= {}
(input/parse-parameters test-core/test-command-parameters "/test-command ")))))

View File

@ -1,66 +1,7 @@
(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.events :as chat-events]))
(def test-db
{:current-public-key "me"
:chats {"status" {:public? true
:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"5" {"me" {:message-id "5"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"4" {"me" {:message-id "4"
:chat-id "status"
:whisper-identity "me"
:status :received}}}}
"opened" {:unviewed-messages #{}
:message-statuses {"1" {"me" {:message-id "1"
:chat-id "opened"
:whisper-identity "me"
:status :seen}}}}
"1-1" {:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"5" {"me" {:message-id "5"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"4" {"me" {:message-id "4"
:chat-id "status"
:whisper-identity "me"
:status :received}}}}}})
(deftest mark-messages-seen
(testing "Marking messages seen correctly marks loaded messages as seen and updates absolute unviewed set"
(let [fx (chat-events/mark-messages-seen "status" {:db test-db})
me (:current-public-key test-db)]
(is (= '(:seen :seen :seen)
(map (fn [[_ v]]
(get-in v [me :status]))
(get-in fx [:db :chats "status" :message-statuses]))))
(is (= 1 (count (:data-store/tx fx))))
(is (= nil (:shh/post fx))) ;; for public chats, no confirmation is sent out
(is (= #{"3" "2" "1"} (get-in fx [:db :chats "status" :unviewed-messages])))))
(testing "With empty unviewed set, no effects are produced"
(is (= nil (chat-events/mark-messages-seen "opened" {:db test-db}))))
(testing "For 1-1 chat, we send seen messages confirmation to the recipient as well"
(is (= #{"4" "5" "6"}
(set (get-in (chat-events/mark-messages-seen "1-1" {:db test-db})
[:shh/post 0 :message :payload :message-ids]))))))
(deftest show-profile-test
(testing "Dafault behaviour: navigate to profile"
(let [{:keys [db]} (chat-events/show-profile

View File

@ -210,3 +210,58 @@
(testing "it returns false if it's a 1-to-1 chat"
(let [cofx {:db {:chats {chat-id {}}}}]
(is (not (chat/group-chat? chat-id cofx)))))))
(def test-db
{:current-public-key "me"
:chats {"status" {:public? true
:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"5" {"me" {:message-id "5"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"4" {"me" {:message-id "4"
:chat-id "status"
:whisper-identity "me"
:status :received}}}}
"opened" {:unviewed-messages #{}
:message-statuses {"1" {"me" {:message-id "1"
:chat-id "opened"
:whisper-identity "me"
:status :seen}}}}
"1-1" {:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"5" {"me" {:message-id "5"
:chat-id "status"
:whisper-identity "me"
:status :received}}
"4" {"me" {:message-id "4"
:chat-id "status"
:whisper-identity "me"
:status :received}}}}}})
(deftest mark-messages-seen
(testing "Marking messages seen correctly marks loaded messages as seen and updates absolute unviewed set"
(let [fx (chat/mark-messages-seen "status" {:db test-db})
me (:current-public-key test-db)]
(is (= '(:seen :seen :seen)
(map (fn [[_ v]]
(get-in v [me :status]))
(get-in fx [:db :chats "status" :message-statuses]))))
(is (= 1 (count (:data-store/tx fx))))
(is (= nil (:shh/post fx))) ;; for public chats, no confirmation is sent out
(is (= #{"3" "2" "1"} (get-in fx [:db :chats "status" :unviewed-messages])))))
(testing "With empty unviewed set, no effects are produced"
(is (= nil (chat/mark-messages-seen "opened" {:db test-db}))))
(testing "For 1-1 chat, we send seen messages confirmation to the recipient as well"
(is (= #{"4" "5" "6"}
(set (get-in (chat/mark-messages-seen "1-1" {:db test-db})
[:shh/post 0 :message :payload :message-ids]))))))

View File

@ -0,0 +1,43 @@
(ns status-im.test.chat.models.loading
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.models.loading :as loading]))
(deftest group-chat-messages
(let [cofx {:db {:chats {"chat-id" {:messages {0 {:message-id 0
:content "a"
:clock-value 0
:timestamp 0}
1 {:message-id 1
:content "b"
:clock-value 1
:timestamp 1}
2 {:message-id 2
:content "c"
:clock-value 2
:timestamp 2}
3 {:message-id 3
:content "d"
:clock-value 3
:timestamp 3}}}}}}
new-messages [{:message-id 1
:content "b"
:clock-value 1
:timestamp 1
:show? false}
{:message-id 2
:content "c"
:clock-value 2
:timestamp 2
:show? true}
{:message-id 3
:content "d"
:clock-value 3
:timestamp 3
:show? true}]]
(testing "New messages are grouped/sorted correctly, hidden messages are not grouped"
(is (= '(2 3)
(map :message-id
(-> (get-in (loading/group-chat-messages "chat-id" new-messages cofx)
[:db :chats "chat-id" :message-groups])
first
second)))))))

View File

@ -101,46 +101,6 @@
message
(assoc-in db [:db :view-id] :home))))))))
(deftest group-messages
(let [cofx {:db {:chats {"chat-id" {:messages {0 {:message-id 0
:content "a"
:clock-value 0
:timestamp 0}
1 {:message-id 1
:content "b"
:clock-value 1
:timestamp 1}
2 {:message-id 2
:content "c"
:clock-value 2
:timestamp 2}
3 {:message-id 3
:content "d"
:clock-value 3
:timestamp 3}}}}}}
new-messages [{:message-id 1
:content "b"
:clock-value 1
:timestamp 1
:show? false}
{:message-id 2
:content "c"
:clock-value 2
:timestamp 2
:show? true}
{:message-id 3
:content "d"
:clock-value 3
:timestamp 3
:show? true}]]
(testing "New messages are grouped/sorted correctly, hidden messages are not grouped"
(is (= '(2 3)
(map :message-id
(-> (get-in (message/group-messages "chat-id" new-messages cofx)
[:db :chats "chat-id" :message-groups])
first
second)))))))
(deftest delete-message
(let [timestamp (time/now)
cofx1 {:db {:chats {"chat-id" {:messages {0 {:message-id 0

View File

@ -1,6 +1,5 @@
(ns status-im.test.chat.subs
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.constants :as const]
[status-im.chat.subs :as s]))
(deftest chat-name
@ -113,12 +112,10 @@
(deftest active-chats-test
(let [active-chat-1 {:is-active true :chat-id 1}
active-chat-2 {:is-active true :chat-id 2}
console {:is-active true :chat-id const/console-chat-id}
chats {1 active-chat-1
2 active-chat-2
3 {:is-active false :chat-id 3}
const/console-chat-id console}]
(testing "it returns only chats with is-active, without console"
3 {:is-active false :chat-id 3}}]
(testing "it returns only chats with is-active"
(is (= {1 active-chat-1
2 active-chat-2}
(s/active-chats chats))))))

View File

@ -1,6 +1,6 @@
(ns status-im.test.chat.views.message
(:require [cljs.test :refer [deftest is]]
[status-im.chat.views.message.message :as message]))
[status-im.ui.screens.chat.message.message :as message]))
(deftest parse-url
(is (= (lazy-seq [{:text "" :url? false}

View File

@ -1,7 +1,7 @@
(ns status-im.test.chat.views.photos
(:require [cljs.test :refer [deftest testing is]]
[status-im.react-native.resources :as resources]
[status-im.chat.views.photos :as photos]))
[status-im.ui.screens.chat.photos :as photos]))
(deftest photos-test
(testing "a normal string"

View File

@ -19,11 +19,13 @@
[status-im.test.transport.handlers]
[status-im.test.chat.models]
[status-im.test.chat.models.input]
[status-im.test.chat.models.loading]
[status-im.test.chat.models.message]
[status-im.test.chat.subs]
[status-im.test.chat.views.message]
[status-im.test.chat.views.photos]
[status-im.test.chat.commands.core]
[status-im.test.chat.commands.input]
[status-im.test.chat.commands.impl.transactions]
[status-im.test.i18n]
[status-im.test.protocol.web3.inbox]
@ -78,11 +80,13 @@
'status-im.test.wallet.subs
'status-im.test.wallet.transactions.subs
'status-im.test.wallet.transactions.views
'status-im.test.chat.models.loading
'status-im.test.chat.models.input
'status-im.test.chat.models.message
'status-im.test.chat.views.message
'status-im.test.chat.views.photos
'status-im.test.chat.commands.core
'status-im.test.chat.commands.input
'status-im.test.chat.commands.impl.transactions
'status-im.test.i18n
'status-im.test.transport.core