[refactor] move chat.subs fns to chat.db

Signed-off-by: yenda <eric@status.im>
This commit is contained in:
yenda 2018-11-13 13:48:28 +01:00
parent 15369325c0
commit 77e9aea755
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
5 changed files with 216 additions and 206 deletions

152
src/status_im/chat/db.cljs Normal file
View File

@ -0,0 +1,152 @@
(ns status-im.chat.db
(:require [clojure.set :as clojure.set]
[clojure.string :as string]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.input :as commands.input]
[status-im.utils.config :as utils.config]
[status-im.utils.gfycat.core :as gfycat]))
(defn chat-name
[{:keys [group-chat
chat-id
public?
name]}
{contact-name :name}]
(cond
public? (str "#" name)
group-chat name
:else (or contact-name
(gfycat/generate-gfy chat-id))))
(defn active-chats
[contacts chats {:keys [dev-mode?]}]
(reduce (fn [acc [chat-id {:keys [group-chat public? is-active] :as chat}]]
(if (and is-active
;; not a group chat
(or (not (and group-chat (not public?)))
;; if it's a group chat
(utils.config/group-chats-enabled? dev-mode?)))
(assoc acc chat-id (if-let [contact (get contacts chat-id)]
(-> chat
(assoc :name (:name contact))
(assoc :random-name (gfycat/generate-gfy (:public-key contact)))
(update :tags clojure.set/union (:tags contact)))
chat))
acc))
{}
chats))
(defn sort-message-groups
"Sorts message groups according to timestamp of first message in group"
[message-groups messages]
(sort-by
(comp unchecked-negate :timestamp (partial get messages) :message-id first second)
message-groups))
(defn quoted-message-data
"Selects certain data from quoted message which must be available in the view"
[message-id messages referenced-messages]
(when-let [{:keys [from content]} (get messages message-id
(get referenced-messages message-id))]
{:from from
:text (:text content)}))
(defn messages-with-datemarks-and-statuses
"Converts message groups into sequence of messages interspersed with datemarks,
with correct user statuses associated into message"
[message-groups messages message-statuses referenced-messages]
(mapcat (fn [[datemark message-references]]
(into (list {:value datemark
:type :datemark})
(map (fn [{:keys [message-id timestamp-str]}]
(let [{:keys [content] :as message} (get messages message-id)
quote (some-> (:response-to content)
(quoted-message-data messages referenced-messages))]
(cond-> (-> message
(update :content dissoc :response-to)
(assoc :datemark datemark
:timestamp-str timestamp-str
:user-statuses (get message-statuses message-id)))
quote ;; quoted message reference
(assoc-in [:content :response-to] quote)))))
message-references))
message-groups))
(defn- set-previous-message-info [stream]
(let [{:keys [display-photo? message-type] :as previous-message} (peek stream)]
(conj (pop stream) (assoc previous-message
:display-username? (and display-photo?
(not= :system-message message-type))
:first-in-group? true))))
(defn display-photo? [{:keys [outgoing message-type]}]
(or (= :system-message message-type)
(and (not outgoing)
(not (= :user-message message-type)))))
; any message that comes after this amount of ms will be grouped separately
(def ^:private group-ms 60000)
(defn add-positional-metadata
"Reduce step which adds positional metadata to a message and conditionally
update the previous message with :first-in-group?."
[{:keys [stream last-outgoing-seen]}
{:keys [type message-type from datemark outgoing timestamp] :as message}]
(let [previous-message (peek stream)
; Was the previous message from a different author or this message
; comes after x ms
last-in-group? (or (= :system-message message-type)
(not= from (:from previous-message))
(> (- (:timestamp previous-message) timestamp) group-ms))
same-direction? (= outgoing (:outgoing previous-message))
; Have we seen an outgoing message already?
last-outgoing? (and (not last-outgoing-seen)
outgoing)
datemark? (= :datemark (:type message))
; If this is a datemark or this is the last-message of a group,
; then the previous message was the first
previous-first-in-group? (or datemark?
last-in-group?)
new-message (assoc message
:display-photo? (display-photo? message)
:same-direction? same-direction?
:last-in-group? last-in-group?
:last-outgoing? last-outgoing?)]
{:stream (cond-> stream
previous-first-in-group?
; update previuous message if necessary
set-previous-message-info
:always
(conj new-message))
; mark the last message sent by the user
:last-outgoing-seen (or last-outgoing-seen last-outgoing?)}))
(defn messages-stream
"Enhances the messages in message sequence interspersed with datemarks
with derived stream context information, like:
`:first-in-group?`, `last-in-group?`, `:same-direction?`, `:last?` and `:last-outgoing?` flags."
[ordered-messages]
(when (seq ordered-messages)
(let [initial-message (first ordered-messages)
message-with-metadata (assoc initial-message
:last-in-group? true
:last? true
:display-photo? (display-photo? initial-message)
:last-outgoing? (:outgoing initial-message))]
(->> (rest ordered-messages)
(reduce add-positional-metadata
{:stream [message-with-metadata]
:last-outgoing-seen (:last-outgoing? message-with-metadata)})
:stream))))
(def map->sorted-seq
(comp (partial map second) (partial sort-by first)))
(defn available-commands
[commands {:keys [input-text]}]
(->> commands
map->sorted-seq
(filter (fn [{:keys [type]}]
(when (commands.input/starts-as-command? input-text)
(string/includes? (commands/command-name type) input-text))))))

View File

@ -15,7 +15,7 @@
[status-im.chat.models.loading :as chat-loading]
[status-im.chat.models.message-content :as message-content]
[status-im.chat.commands.receiving :as commands-receiving]
[status-im.chat.subs :as chat-subs]
[status-im.chat.db :as chat.db]
[status-im.utils.clocks :as utils.clocks]
[status-im.utils.money :as money]
[status-im.utils.types :as types]
@ -93,7 +93,7 @@
(not (system-message? message)))))
(defn build-desktop-notification [{:keys [db] :as cofx} {:keys [chat-id timestamp content from] :as message}]
(let [chat-name' (chat-subs/chat-name (get-in db [:chats chat-id]) from)
(let [chat-name' (chat.db/chat-name (get-in db [:chats chat-id]) from)
contact-name' (if-let [contact-name (get-in db [:contacts/contacts from :name])]
contact-name
(:name (utils.contacts/public-key->new-contact from)))

View File

@ -1,71 +1,57 @@
(ns status-im.chat.subs
(:require [clojure.string :as string]
[re-frame.core :refer [reg-sub subscribe]]
[status-im.utils.config :as utils.config]
[status-im.chat.constants :as chat.constants]
[re-frame.core :as re-frame]
[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.chat.constants :as chat.constants]
[status-im.chat.db :as chat.db]
[status-im.models.transactions :as transactions]
[clojure.set :as clojure.set]))
[status-im.utils.platform :as platform]))
(reg-sub :get-chats :chats)
(re-frame/reg-sub :get-chats :chats)
(reg-sub :get-current-chat-id :current-chat-id)
(re-frame/reg-sub :get-current-chat-id :current-chat-id)
(reg-sub :chat-ui-props :chat-ui-props)
(re-frame/reg-sub :chat-ui-props :chat-ui-props)
(reg-sub :get-id->command :id->command)
(re-frame/reg-sub :get-id->command :id->command)
(reg-sub :get-access-scope->command-id :access-scope->command-id)
(re-frame/reg-sub :get-access-scope->command-id :access-scope->command-id)
(reg-sub
(re-frame/reg-sub
:get-current-chat-ui-props
:<- [:chat-ui-props]
:<- [:get-current-chat-id]
(fn [[chat-ui-props id]]
(get chat-ui-props id)))
(defn chat-name [{:keys [group-chat
chat-id
public?
name]}
{contact-name :name}]
(cond
public? (str "#" name)
group-chat name
:else (or contact-name
(gfycat/generate-gfy chat-id))))
(reg-sub
(re-frame/reg-sub
:get-current-chat-name
:<- [:get-current-chat-contact]
:<- [:get-current-chat]
(fn [[contact chat]]
(chat-name chat contact)))
(chat.db/chat-name chat contact)))
(reg-sub
(re-frame/reg-sub
:get-chat-name
:<- [:get-contacts]
:<- [:get-chats]
(fn [[contacts chats] [_ chat-id]]
(chat-name (get chats chat-id) (get contacts chat-id))))
(chat.db/chat-name (get chats chat-id) (get contacts chat-id))))
(reg-sub
(re-frame/reg-sub
:get-current-chat-ui-prop
:<- [:get-current-chat-ui-props]
(fn [ui-props [_ prop]]
(get ui-props prop)))
(reg-sub
(re-frame/reg-sub
:validation-messages
:<- [:get-current-chat-ui-props]
(fn [ui-props]
(some-> ui-props :validation-messages)))
(reg-sub
(re-frame/reg-sub
:chat-input-margin
:<- [:get :keyboard-height]
(fn [kb-height]
@ -74,189 +60,69 @@
platform/ios? kb-height
:default 0)))
(defn active-chats [[contacts chats {:keys [dev-mode?]}]]
(reduce (fn [acc [chat-id {:keys [group-chat public? is-active] :as chat}]]
(if (and is-active
;; not a group chat
(or (not (and group-chat (not public?)))
;; if it's a group chat
(utils.config/group-chats-enabled? dev-mode?)))
(assoc acc chat-id (if-let [contact (get contacts chat-id)]
(-> chat
(assoc :name (:name contact))
(assoc :random-name (gfycat/generate-gfy (:public-key contact)))
(update :tags clojure.set/union (:tags contact)))
chat))
acc))
{}
chats))
(reg-sub
(re-frame/reg-sub
:get-active-chats
:<- [:get-contacts]
:<- [:get-chats]
:<- [:account/account]
active-chats)
(fn [[contacts chats account]]
(chat.db/active-chats contacts chats account)))
(reg-sub
(re-frame/reg-sub
:get-chat
:<- [:get-active-chats]
(fn [chats [_ chat-id]]
(get chats chat-id)))
(reg-sub
(re-frame/reg-sub
:get-current-chat
:<- [:get-active-chats]
:<- [:get-current-chat-id]
(fn [[chats current-chat-id]]
(get chats current-chat-id)))
(reg-sub
(re-frame/reg-sub
:get-current-chat-message
:<- [:get-current-chat]
(fn [{:keys [messages]} [_ message-id]]
(get messages message-id)))
(reg-sub
(re-frame/reg-sub
:get-current-chat-messages
:<- [:get-current-chat]
(fn [{:keys [messages]}]
(or messages {})))
(reg-sub
(re-frame/reg-sub
:get-current-chat-message-groups
:<- [:get-current-chat]
(fn [{:keys [message-groups]}]
(or message-groups {})))
(reg-sub
(re-frame/reg-sub
:get-current-chat-message-statuses
:<- [:get-current-chat]
(fn [{:keys [message-statuses]}]
(or message-statuses {})))
(reg-sub
(re-frame/reg-sub
:get-current-chat-referenced-messages
:<- [:get-current-chat]
(fn [{:keys [referenced-messages]}]
(or referenced-messages {})))
(defn sort-message-groups
"Sorts message groups according to timestamp of first message in group"
[message-groups messages]
(sort-by
(comp unchecked-negate :timestamp (partial get messages) :message-id first second)
message-groups))
(defn quoted-message-data
"Selects certain data from quoted message which must be available in the view"
[message-id messages referenced-messages]
(when-let [{:keys [from content]} (get messages message-id
(get referenced-messages message-id))]
{:from from
:text (:text content)}))
(defn messages-with-datemarks-and-statuses
"Converts message groups into sequence of messages interspersed with datemarks,
with correct user statuses associated into message"
[message-groups messages message-statuses referenced-messages]
(mapcat (fn [[datemark message-references]]
(into (list {:value datemark
:type :datemark})
(map (fn [{:keys [message-id timestamp-str]}]
(let [{:keys [content] :as message} (get messages message-id)
quote (some-> (:response-to content)
(quoted-message-data messages referenced-messages))]
(cond-> (-> message
(update :content dissoc :response-to)
(assoc :datemark datemark
:timestamp-str timestamp-str
:user-statuses (get message-statuses message-id)))
quote ;; quoted message reference
(assoc-in [:content :response-to] quote)))))
message-references))
message-groups))
(defn- set-previous-message-info [stream]
(let [{:keys [display-photo? message-type] :as previous-message} (peek stream)]
(conj (pop stream) (assoc previous-message
:display-username? (and display-photo?
(not= :system-message message-type))
:first-in-group? true))))
(defn display-photo? [{:keys [outgoing message-type]}]
(or (= :system-message message-type)
(and (not outgoing)
(not (= :user-message message-type)))))
; any message that comes after this amount of ms will be grouped separately
(def ^:private group-ms 60000)
(defn add-positional-metadata
"Reduce step which adds positional metadata to a message and conditionally
update the previous message with :first-in-group?."
[{:keys [stream last-outgoing-seen]}
{:keys [type message-type from datemark outgoing timestamp] :as message}]
(let [previous-message (peek stream)
; Was the previous message from a different author or this message
; comes after x ms
last-in-group? (or (= :system-message message-type)
(not= from (:from previous-message))
(> (- (:timestamp previous-message) timestamp) group-ms))
same-direction? (= outgoing (:outgoing previous-message))
; Have we seen an outgoing message already?
last-outgoing? (and (not last-outgoing-seen)
outgoing)
datemark? (= :datemark (:type message))
; If this is a datemark or this is the last-message of a group,
; then the previous message was the first
previous-first-in-group? (or datemark?
last-in-group?)
new-message (assoc message
:display-photo? (display-photo? message)
:same-direction? same-direction?
:last-in-group? last-in-group?
:last-outgoing? last-outgoing?)]
{:stream (cond-> stream
previous-first-in-group?
; update previuous message if necessary
set-previous-message-info
:always
(conj new-message))
; mark the last message sent by the user
:last-outgoing-seen (or last-outgoing-seen last-outgoing?)}))
(defn messages-stream
"Enhances the messages in message sequence interspersed with datemarks
with derived stream context information, like:
`:first-in-group?`, `last-in-group?`, `:same-direction?`, `:last?` and `:last-outgoing?` flags."
[ordered-messages]
(when (seq ordered-messages)
(let [initial-message (first ordered-messages)
message-with-metadata (assoc initial-message
:last-in-group? true
:last? true
:display-photo? (display-photo? initial-message)
:last-outgoing? (:outgoing initial-message))]
(->> (rest ordered-messages)
(reduce add-positional-metadata
{:stream [message-with-metadata]
:last-outgoing-seen (:last-outgoing? message-with-metadata)})
:stream))))
(reg-sub
(re-frame/reg-sub
:get-current-chat-messages-stream
:<- [:get-current-chat-messages]
:<- [:get-current-chat-message-groups]
:<- [:get-current-chat-message-statuses]
:<- [:get-current-chat-referenced-messages]
(fn [[messages message-groups message-statuses referenced-messages]]
(-> (sort-message-groups message-groups messages)
(messages-with-datemarks-and-statuses messages message-statuses referenced-messages)
messages-stream)))
(-> (chat.db/sort-message-groups message-groups messages)
(chat.db/messages-with-datemarks-and-statuses messages message-statuses referenced-messages)
chat.db/messages-stream)))
(reg-sub
(re-frame/reg-sub
:get-commands-for-chat
:<- [:get-id->command]
:<- [:get-access-scope->command-id]
@ -264,28 +130,20 @@
(fn [[id->command access-scope->command-id chat]]
(commands/chat-commands id->command access-scope->command-id chat)))
(def ^:private map->sorted-seq (comp (partial map second) (partial sort-by first)))
(defn- available-commands [[commands {:keys [input-text]}]]
(->> commands
map->sorted-seq
(filter (fn [{:keys [type]}]
(when (commands.input/starts-as-command? input-text)
(string/includes? (commands/command-name type) input-text))))))
(reg-sub
(re-frame/reg-sub
:get-available-commands
:<- [:get-commands-for-chat]
:<- [:get-current-chat]
available-commands)
(fn [[commands chat]]
(chat.db/available-commands commands chat)))
(reg-sub
(re-frame/reg-sub
:get-all-available-commands
:<- [:get-commands-for-chat]
(fn [commands]
(map->sorted-seq commands)))
(chat.db/map->sorted-seq commands)))
(reg-sub
(re-frame/reg-sub
:selected-chat-command
:<- [:get-current-chat]
:<- [:get-current-chat-ui-prop :selection]
@ -293,7 +151,7 @@
(fn [[{:keys [input-text]} selection commands]]
(commands.input/selected-chat-command input-text selection commands)))
(reg-sub
(re-frame/reg-sub
:chat-input-placeholder
:<- [:get-current-chat]
:<- [:selected-chat-command]
@ -301,7 +159,7 @@
(when (string/ends-with? (or input-text "") chat.constants/spacing-char)
(get-in params [current-param-position :placeholder]))))
(reg-sub
(re-frame/reg-sub
:chat-parameter-box
:<- [:get-current-chat]
:<- [:selected-chat-command]
@ -309,7 +167,7 @@
(when (and params current-param-position)
(get-in params [current-param-position :suggestions]))))
(reg-sub
(re-frame/reg-sub
:show-parameter-box?
:<- [:chat-parameter-box]
:<- [:show-suggestions?]
@ -321,7 +179,7 @@
(not show-suggestions?)
(not (= :complete command-completion)))))
(reg-sub
(re-frame/reg-sub
:show-suggestions-view?
:<- [:get-current-chat-ui-prop :show-suggestions?]
:<- [:get-current-chat]
@ -331,21 +189,21 @@
(commands.input/starts-as-command? (string/trim (or input-text ""))))
(seq commands))))
(reg-sub
(re-frame/reg-sub
:show-suggestions?
:<- [:show-suggestions-view?]
:<- [:selected-chat-command]
(fn [[show-suggestions-box? selected-command]]
(and show-suggestions-box? (not selected-command))))
(reg-sub
(re-frame/reg-sub
:unviewed-messages-count
(fn [[_ chat-id]]
(subscribe [:get-chat chat-id]))
(re-frame/subscribe [:get-chat chat-id]))
(fn [{:keys [unviewed-messages]}]
(count unviewed-messages)))
(reg-sub
(re-frame/reg-sub
:get-photo-path
:<- [:get-contacts]
:<- [:account/account]
@ -354,48 +212,48 @@
(when (= id (:public-key account))
(:photo-path account)))))
(reg-sub
(re-frame/reg-sub
:get-last-message
(fn [[_ chat-id]]
(subscribe [:get-chat chat-id]))
(re-frame/subscribe [:get-chat chat-id]))
(fn [{:keys [messages message-groups]}]
(->> (sort-message-groups message-groups messages)
(->> (chat.db/sort-message-groups message-groups messages)
first
second
last
:message-id
(get messages))))
(reg-sub
(re-frame/reg-sub
:chat-animations
(fn [db [_ key type]]
(let [chat-id (subscribe [:get-current-chat-id])]
(let [chat-id (re-frame/subscribe [:get-current-chat-id])]
(get-in db [:animations :chats @chat-id key type]))))
(reg-sub
(re-frame/reg-sub
:get-chats-unread-messages-number
:<- [:get-active-chats]
(fn [chats _]
(apply + (map (comp count :unviewed-messages) (vals chats)))))
(reg-sub
(re-frame/reg-sub
:transaction-confirmed?
(fn [db [_ tx-hash]]
(-> (get-in db [:wallet :transactions tx-hash :confirmations] "0")
(js/parseInt)
(>= transactions/confirmations-count-threshold))))
(reg-sub
(re-frame/reg-sub
:wallet-transaction-exists?
(fn [db [_ tx-hash]]
(not (nil? (get-in db [:wallet :transactions tx-hash])))))
(reg-sub
(re-frame/reg-sub
:chat/cooldown-enabled?
(fn [db]
(:chat/cooldown-enabled? db)))
(reg-sub
(re-frame/reg-sub
:chat-cooldown-enabled?
:<- [:get-current-chat]
:<- [:chat/cooldown-enabled?]
@ -403,7 +261,7 @@
(and public?
cooldown-enabled?)))
(reg-sub
(re-frame/reg-sub
:get-reply-message
:<- [:get-current-chat]
(fn [{:keys [metadata messages]}]

View File

@ -1,6 +1,6 @@
(ns status-im.test.chat.subs
(ns status-im.test.chat.db
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.subs :as s]))
[status-im.chat.db :as s]))
(deftest chat-name
(testing "it prepends # if it's a public chat"
@ -129,4 +129,4 @@
(testing "it returns only chats with is-active"
(is (= {1 active-chat-1
2 active-chat-2}
(s/active-chats [{} chats {}]))))))
(s/active-chats {} chats {}))))))

View File

@ -26,7 +26,7 @@
[status-im.test.chat.models.loading]
[status-im.test.chat.models.message]
[status-im.test.chat.models.message-content]
[status-im.test.chat.subs]
[status-im.test.chat.db]
[status-im.test.chat.views.photos]
[status-im.test.chat.commands.core]
[status-im.test.chat.commands.input]
@ -68,7 +68,7 @@
(doo-tests
'status-im.test.utils.async
'status-im.test.chat.subs
'status-im.test.chat.db
'status-im.test.chat.models
'status-im.test.contacts.subs
'status-im.test.init.core